From 5a85e81685300e2299dabfeb25d513b99df471be Mon Sep 17 00:00:00 2001 From: PaweÅ‚ Redman Date: Fri, 6 Sep 2013 22:40:51 +0200 Subject: Initial commit --- src/AL/VERSION | 16 + src/AL/al.h | 506 ++ src/AL/alc.h | 166 + src/AL/alctypes.h | 143 + src/AL/altypes.h | 352 + src/AL/alut.h | 90 + src/SDL12/include/SDL.h | 101 + src/SDL12/include/SDL_active.h | 63 + src/SDL12/include/SDL_audio.h | 284 + src/SDL12/include/SDL_byteorder.h | 29 + src/SDL12/include/SDL_cdrom.h | 202 + src/SDL12/include/SDL_config.h | 45 + src/SDL12/include/SDL_config_amiga.h | 80 + src/SDL12/include/SDL_config_dreamcast.h | 106 + src/SDL12/include/SDL_config_macos.h | 112 + src/SDL12/include/SDL_config_macosx.h | 150 + src/SDL12/include/SDL_config_minimal.h | 62 + src/SDL12/include/SDL_config_nds.h | 115 + src/SDL12/include/SDL_config_os2.h | 141 + src/SDL12/include/SDL_config_symbian.h | 146 + src/SDL12/include/SDL_config_win32.h | 183 + src/SDL12/include/SDL_copying.h | 22 + src/SDL12/include/SDL_cpuinfo.h | 69 + src/SDL12/include/SDL_endian.h | 209 + src/SDL12/include/SDL_error.h | 72 + src/SDL12/include/SDL_events.h | 356 + src/SDL12/include/SDL_getenv.h | 28 + src/SDL12/include/SDL_joystick.h | 187 + src/SDL12/include/SDL_keyboard.h | 135 + src/SDL12/include/SDL_keysym.h | 326 + src/SDL12/include/SDL_loadso.h | 78 + src/SDL12/include/SDL_main.h | 106 + src/SDL12/include/SDL_mouse.h | 143 + src/SDL12/include/SDL_mutex.h | 177 + src/SDL12/include/SDL_name.h | 11 + src/SDL12/include/SDL_opengl.h | 6556 ++++++++++++++++++ src/SDL12/include/SDL_platform.h | 110 + src/SDL12/include/SDL_quit.h | 55 + src/SDL12/include/SDL_rwops.h | 155 + src/SDL12/include/SDL_stdinc.h | 620 ++ src/SDL12/include/SDL_syswm.h | 225 + src/SDL12/include/SDL_thread.h | 120 + src/SDL12/include/SDL_timer.h | 125 + src/SDL12/include/SDL_types.h | 28 + src/SDL12/include/SDL_version.h | 91 + src/SDL12/include/SDL_video.h | 951 +++ src/SDL12/include/begin_code.h | 191 + src/SDL12/include/close_code.h | 46 + src/asm/ftola.s | 160 + src/asm/matha.s | 54 + src/asm/qasm.h | 38 + src/asm/snapvectora.s | 103 + src/asm/snd_mixa.s | 217 + src/cgame/cg_animation.c | 113 + src/cgame/cg_animmapobj.c | 227 + src/cgame/cg_attachment.c | 404 ++ src/cgame/cg_buildable.c | 2290 +++++++ src/cgame/cg_consolecmds.c | 298 + src/cgame/cg_draw.c | 3813 +++++++++++ src/cgame/cg_drawtools.c | 439 ++ src/cgame/cg_ents.c | 1252 ++++ src/cgame/cg_event.c | 1116 ++++ src/cgame/cg_event.c.orig | 1086 +++ src/cgame/cg_local.h | 2177 ++++++ src/cgame/cg_main.c | 1985 ++++++ src/cgame/cg_main.c.orig | 1982 ++++++ src/cgame/cg_marks.c | 289 + src/cgame/cg_particles.c | 2599 ++++++++ src/cgame/cg_players.c | 2422 +++++++ src/cgame/cg_playerstate.c | 344 + src/cgame/cg_predict.c | 898 +++ src/cgame/cg_public.h | 262 + src/cgame/cg_scanner.c | 362 + src/cgame/cg_servercmds.c | 1339 ++++ src/cgame/cg_snapshot.c | 408 ++ src/cgame/cg_syscalls.asm | 121 + src/cgame/cg_syscalls.c | 592 ++ src/cgame/cg_trails.c | 1531 +++++ src/cgame/cg_tutorial.c | 864 +++ src/cgame/cg_view.c | 1500 +++++ src/cgame/cg_weapons.c | 2075 ++++++ src/client/cl_avi.c | 676 ++ src/client/cl_cgame.c | 1155 ++++ src/client/cl_cin.c | 1697 +++++ src/client/cl_console.c | 624 ++ src/client/cl_curl.c | 339 + src/client/cl_curl.h | 103 + src/client/cl_input.c | 1010 +++ src/client/cl_keys.c | 1490 +++++ src/client/cl_main.c | 4418 +++++++++++++ src/client/cl_net_chan.c | 168 + src/client/cl_parse.c | 910 +++ src/client/cl_scrn.c | 571 ++ src/client/cl_ui.c | 1140 ++++ src/client/client.h | 645 ++ src/client/keycodes.h | 281 + src/client/keys.h | 58 + src/client/libmumblelink.c | 189 + src/client/libmumblelink.h | 36 + src/client/qal.c | 352 + src/client/qal.h | 253 + src/client/snd_adpcm.c | 330 + src/client/snd_codec.c | 238 + src/client/snd_codec.h | 99 + src/client/snd_codec_ogg.c | 479 ++ src/client/snd_codec_wav.c | 295 + src/client/snd_dma.c | 1558 +++++ src/client/snd_local.h | 254 + src/client/snd_main.c | 562 ++ src/client/snd_mem.c | 270 + src/client/snd_mix.c | 742 +++ src/client/snd_openal.c | 2601 ++++++++ src/client/snd_public.h | 85 + src/client/snd_wavelet.c | 254 + src/game/bg_alloc.c | 241 + src/game/bg_lib.c | 2816 ++++++++ src/game/bg_lib.h | 137 + src/game/bg_local.h | 88 + src/game/bg_misc.c | 4491 +++++++++++++ src/game/bg_pmove.c | 3878 +++++++++++ src/game/bg_public.h | 1409 ++++ src/game/bg_slidemove.c | 408 ++ src/game/bg_voice.c | 652 ++ src/game/g_active.c | 1955 ++++++ src/game/g_active.c.orig | 1937 ++++++ src/game/g_admin.c | 3277 +++++++++ src/game/g_admin.h | 197 + src/game/g_buildable.c | 4548 +++++++++++++ src/game/g_client.c | 1800 +++++ src/game/g_client.c.orig | 1600 +++++ src/game/g_cmds.c | 3609 ++++++++++ src/game/g_cmds.c.orig | 3588 ++++++++++ src/game/g_combat.c | 1470 +++++ src/game/g_combat.c.orig | 1468 ++++ src/game/g_local.h | 1270 ++++ src/game/g_main.c | 2565 +++++++ src/game/g_main.c.orig | 2548 +++++++ src/game/g_maprotation.c | 1264 ++++ src/game/g_misc.c | 440 ++ src/game/g_missile.c | 913 +++ src/game/g_mover.c | 2485 +++++++ src/game/g_namelog.c | 127 + src/game/g_physics.c | 169 + src/game/g_public.h | 265 + src/game/g_session.c | 152 + src/game/g_spawn.c | 665 ++ src/game/g_svcmds.c | 639 ++ src/game/g_syscalls.asm | 68 + src/game/g_syscalls.c | 299 + src/game/g_target.c | 478 ++ src/game/g_team.c | 473 ++ src/game/g_trigger.c | 1148 ++++ src/game/g_utils.c | 1032 +++ src/game/g_weapon.c | 1647 +++++ src/game/tremulous.h | 718 ++ src/jpeg-8c/README | 326 + src/jpeg-8c/jaricom.c | 153 + src/jpeg-8c/jcapimin.c | 288 + src/jpeg-8c/jcapistd.c | 161 + src/jpeg-8c/jcarith.c | 934 +++ src/jpeg-8c/jccoefct.c | 453 ++ src/jpeg-8c/jccolor.c | 459 ++ src/jpeg-8c/jcdctmgr.c | 482 ++ src/jpeg-8c/jchuff.c | 1576 +++++ src/jpeg-8c/jcinit.c | 65 + src/jpeg-8c/jcmainct.c | 293 + src/jpeg-8c/jcmarker.c | 682 ++ src/jpeg-8c/jcmaster.c | 858 +++ src/jpeg-8c/jcomapi.c | 106 + src/jpeg-8c/jconfig.h | 56 + src/jpeg-8c/jcparam.c | 632 ++ src/jpeg-8c/jcprepct.c | 358 + src/jpeg-8c/jcsample.c | 545 ++ src/jpeg-8c/jctrans.c | 382 ++ src/jpeg-8c/jdapimin.c | 396 ++ src/jpeg-8c/jdapistd.c | 275 + src/jpeg-8c/jdarith.c | 772 +++ src/jpeg-8c/jdatadst.c | 267 + src/jpeg-8c/jdatasrc.c | 274 + src/jpeg-8c/jdcoefct.c | 736 +++ src/jpeg-8c/jdcolor.c | 396 ++ src/jpeg-8c/jdct.h | 393 ++ src/jpeg-8c/jddctmgr.c | 384 ++ src/jpeg-8c/jdhuff.c | 1541 +++++ src/jpeg-8c/jdinput.c | 661 ++ src/jpeg-8c/jdmainct.c | 512 ++ src/jpeg-8c/jdmarker.c | 1406 ++++ src/jpeg-8c/jdmaster.c | 533 ++ src/jpeg-8c/jdmerge.c | 400 ++ src/jpeg-8c/jdpostct.c | 290 + src/jpeg-8c/jdsample.c | 361 + src/jpeg-8c/jdtrans.c | 140 + src/jpeg-8c/jerror.c | 254 + src/jpeg-8c/jerror.h | 304 + src/jpeg-8c/jfdctflt.c | 174 + src/jpeg-8c/jfdctfst.c | 230 + src/jpeg-8c/jfdctint.c | 4348 ++++++++++++ src/jpeg-8c/jidctflt.c | 235 + src/jpeg-8c/jidctfst.c | 368 ++ src/jpeg-8c/jidctint.c | 5137 ++++++++++++++ src/jpeg-8c/jinclude.h | 91 + src/jpeg-8c/jmemmgr.c | 1118 ++++ src/jpeg-8c/jmemnobs.c | 109 + src/jpeg-8c/jmemsys.h | 198 + src/jpeg-8c/jmorecfg.h | 371 ++ src/jpeg-8c/jpegint.h | 407 ++ src/jpeg-8c/jpeglib.h | 1160 ++++ src/jpeg-8c/jquant1.c | 856 +++ src/jpeg-8c/jquant2.c | 1310 ++++ src/jpeg-8c/jutils.c | 231 + src/jpeg-8c/jversion.h | 14 + src/libcurl/curl/curl.h | 1563 +++++ src/libcurl/curl/curlver.h | 56 + src/libcurl/curl/easy.h | 81 + src/libcurl/curl/mprintf.h | 62 + src/libcurl/curl/multi.h | 344 + src/libcurl/curl/stdcheaders.h | 34 + src/libcurl/curl/types.h | 1 + src/libs/macosx/libSDL-1.2.0.dylib | Bin 0 -> 4436304 bytes src/libs/macosx/libSDLmain.a | Bin 0 -> 97816 bytes src/libs/win32/libSDL.dll.a | Bin 0 -> 123944 bytes src/libs/win32/libSDL.la | 41 + src/libs/win32/libSDLmain.a | Bin 0 -> 208386 bytes src/libs/win32/libcurl.a | Bin 0 -> 253484 bytes src/libspeex/_kiss_fft_guts.h | 160 + src/libspeex/arch.h | 239 + src/libspeex/bits.c | 372 ++ src/libspeex/buffer.c | 176 + src/libspeex/cb_search.c | 612 ++ src/libspeex/cb_search.h | 103 + src/libspeex/cb_search_arm4.h | 137 + src/libspeex/cb_search_bfin.h | 112 + src/libspeex/cb_search_sse.h | 84 + src/libspeex/config.h | 23 + src/libspeex/echo_diagnostic.m | 72 + src/libspeex/exc_10_16_table.c | 50 + src/libspeex/exc_10_32_table.c | 66 + src/libspeex/exc_20_32_table.c | 66 + src/libspeex/exc_5_256_table.c | 290 + src/libspeex/exc_5_64_table.c | 98 + src/libspeex/exc_8_128_table.c | 162 + src/libspeex/fftwrap.c | 288 + src/libspeex/fftwrap.h | 58 + src/libspeex/filterbank.c | 227 + src/libspeex/filterbank.h | 66 + src/libspeex/filters.c | 821 +++ src/libspeex/filters.h | 90 + src/libspeex/filters_arm4.h | 96 + src/libspeex/filters_bfin.h | 515 ++ src/libspeex/filters_sse.h | 336 + src/libspeex/fixed_arm4.h | 148 + src/libspeex/fixed_arm5e.h | 178 + src/libspeex/fixed_bfin.h | 173 + src/libspeex/fixed_debug.h | 487 ++ src/libspeex/fixed_generic.h | 106 + src/libspeex/gain_table.c | 160 + src/libspeex/gain_table_lbr.c | 64 + src/libspeex/hexc_10_32_table.c | 66 + src/libspeex/hexc_table.c | 162 + src/libspeex/high_lsp_tables.c | 163 + src/libspeex/include/speex/speex.h | 424 ++ src/libspeex/include/speex/speex_bits.h | 174 + src/libspeex/include/speex/speex_buffer.h | 68 + src/libspeex/include/speex/speex_callbacks.h | 134 + src/libspeex/include/speex/speex_config_types.h | 18 + src/libspeex/include/speex/speex_echo.h | 123 + src/libspeex/include/speex/speex_header.h | 94 + src/libspeex/include/speex/speex_jitter.h | 197 + src/libspeex/include/speex/speex_preprocess.h | 190 + src/libspeex/include/speex/speex_resampler.h | 340 + src/libspeex/include/speex/speex_stereo.h | 91 + src/libspeex/include/speex/speex_types.h | 126 + src/libspeex/jitter.c | 840 +++ src/libspeex/kiss_fft.c | 523 ++ src/libspeex/kiss_fft.h | 108 + src/libspeex/kiss_fftr.c | 297 + src/libspeex/kiss_fftr.h | 51 + src/libspeex/lpc.c | 201 + src/libspeex/lpc.h | 53 + src/libspeex/lpc_bfin.h | 131 + src/libspeex/lsp.c | 656 ++ src/libspeex/lsp.h | 64 + src/libspeex/lsp_bfin.h | 89 + src/libspeex/lsp_tables_nb.c | 360 + src/libspeex/ltp.c | 839 +++ src/libspeex/ltp.h | 141 + src/libspeex/ltp_arm4.h | 187 + src/libspeex/ltp_bfin.h | 419 ++ src/libspeex/ltp_sse.h | 92 + src/libspeex/math_approx.h | 332 + src/libspeex/mdf.c | 1177 ++++ src/libspeex/misc_bfin.h | 54 + src/libspeex/modes.c | 362 + src/libspeex/modes.h | 161 + src/libspeex/modes_wb.c | 300 + src/libspeex/nb_celp.c | 1903 ++++++ src/libspeex/nb_celp.h | 203 + src/libspeex/os_support.h | 169 + src/libspeex/preprocess.c | 1185 ++++ src/libspeex/pseudofloat.h | 379 ++ src/libspeex/quant_lsp.c | 385 ++ src/libspeex/quant_lsp.h | 74 + src/libspeex/quant_lsp_bfin.h | 165 + src/libspeex/resample.c | 1179 ++++ src/libspeex/sb_celp.c | 1488 +++++ src/libspeex/sb_celp.h | 155 + src/libspeex/smallft.c | 1261 ++++ src/libspeex/smallft.h | 46 + src/libspeex/speex.c | 250 + src/libspeex/speex_callbacks.c | 144 + src/libspeex/speex_header.c | 188 + src/libspeex/stack_alloc.h | 115 + src/libspeex/stereo.c | 296 + src/libspeex/testdenoise.c | 44 + src/libspeex/testecho.c | 53 + src/libspeex/testenc.c | 146 + src/libspeex/testenc_uwb.c | 137 + src/libspeex/testenc_wb.c | 140 + src/libspeex/vbr.c | 275 + src/libspeex/vbr.h | 70 + src/libspeex/vorbis_psy.h | 97 + src/libspeex/vq.c | 147 + src/libspeex/vq.h | 54 + src/libspeex/vq_arm4.h | 115 + src/libspeex/vq_bfin.h | 107 + src/libspeex/vq_sse.h | 120 + src/libspeex/window.c | 102 + src/master/Makefile | 64 + src/master/common.h | 91 + src/master/master.c | 877 +++ src/master/messages.c | 574 ++ src/master/messages.h | 35 + src/master/servers.c | 666 ++ src/master/servers.h | 106 + src/master/stats.c | 136 + src/null/mac_net.c | 55 + src/null/null_client.c | 93 + src/null/null_glimp.c | 57 + src/null/null_input.c | 38 + src/null/null_main.c | 117 + src/null/null_net.c | 54 + src/null/null_snddma.c | 61 + src/qcommon/cm_load.c | 1024 +++ src/qcommon/cm_local.h | 216 + src/qcommon/cm_patch.c | 1773 +++++ src/qcommon/cm_patch.h | 104 + src/qcommon/cm_polylib.c | 738 +++ src/qcommon/cm_polylib.h | 69 + src/qcommon/cm_public.h | 84 + src/qcommon/cm_test.c | 525 ++ src/qcommon/cm_trace.c | 1793 +++++ src/qcommon/cmd.c | 878 +++ src/qcommon/common.c | 3326 ++++++++++ src/qcommon/cvar.c | 1321 ++++ src/qcommon/files.c | 3525 ++++++++++ src/qcommon/huffman.c | 448 ++ src/qcommon/ioapi.c | 182 + src/qcommon/ioapi.h | 75 + src/qcommon/md4.c | 208 + src/qcommon/md5.c | 310 + src/qcommon/msg.c | 1759 +++++ src/qcommon/net_chan.c | 739 +++ src/qcommon/net_ip.c | 1727 +++++ src/qcommon/parse.c | 3727 +++++++++++ src/qcommon/puff.c | 758 +++ src/qcommon/puff.h | 43 + src/qcommon/q_math.c | 1295 ++++ src/qcommon/q_platform.h | 380 ++ src/qcommon/q_shared.c | 1522 +++++ src/qcommon/q_shared.h | 1424 ++++ src/qcommon/qcommon.h | 1194 ++++ src/qcommon/qfiles.h | 582 ++ src/qcommon/surfaceflags.h | 91 + src/qcommon/unzip.c | 1608 +++++ src/qcommon/unzip.h | 355 + src/qcommon/vm.c | 917 +++ src/qcommon/vm_interpreted.c | 909 +++ src/qcommon/vm_local.h | 189 + src/qcommon/vm_none.c | 10 + src/qcommon/vm_powerpc.c | 2170 ++++++ src/qcommon/vm_powerpc_asm.c | 1006 +++ src/qcommon/vm_powerpc_asm.h | 156 + src/qcommon/vm_sparc.c | 1648 +++++ src/qcommon/vm_sparc.h | 78 + src/qcommon/vm_x86.c | 1245 ++++ src/qcommon/vm_x86_64.c | 1099 +++ src/qcommon/vm_x86_64_assembler.c | 1447 ++++ src/renderer/qgl.h | 381 ++ src/renderer/tr_animation.c | 659 ++ src/renderer/tr_backend.c | 1210 ++++ src/renderer/tr_bsp.c | 1870 ++++++ src/renderer/tr_cmds.c | 651 ++ src/renderer/tr_curve.c | 627 ++ src/renderer/tr_flares.c | 530 ++ src/renderer/tr_font.c | 550 ++ src/renderer/tr_image.c | 1607 +++++ src/renderer/tr_image_bmp.c | 240 + src/renderer/tr_image_jpg.c | 438 ++ src/renderer/tr_image_pcx.c | 176 + src/renderer/tr_image_png.c | 2488 +++++++ src/renderer/tr_image_tga.c | 321 + src/renderer/tr_init.c | 1358 ++++ src/renderer/tr_light.c | 395 ++ src/renderer/tr_local.h | 1711 +++++ src/renderer/tr_main.c | 1403 ++++ src/renderer/tr_marks.c | 455 ++ src/renderer/tr_mesh.c | 419 ++ src/renderer/tr_model.c | 1149 ++++ src/renderer/tr_noise.c | 92 + src/renderer/tr_public.h | 173 + src/renderer/tr_scene.c | 419 ++ src/renderer/tr_shade.c | 1525 +++++ src/renderer/tr_shade_calc.c | 1232 ++++ src/renderer/tr_shader.c | 3078 +++++++++ src/renderer/tr_shadows.c | 344 + src/renderer/tr_sky.c | 846 +++ src/renderer/tr_surface.c | 1249 ++++ src/renderer/tr_types.h | 218 + src/renderer/tr_world.c | 670 ++ src/sdl/sdl_gamma.c | 92 + src/sdl/sdl_glimp.c | 1061 +++ src/sdl/sdl_icon.h | 138 + src/sdl/sdl_input.c | 1158 ++++ src/sdl/sdl_snd.c | 299 + src/server/server.h | 426 ++ src/server/sv_ccmds.c | 326 + src/server/sv_client.c | 1699 +++++ src/server/sv_game.c | 601 ++ src/server/sv_init.c | 754 +++ src/server/sv_main.c | 1147 ++++ src/server/sv_net_chan.c | 210 + src/server/sv_snapshot.c | 694 ++ src/server/sv_world.c | 692 ++ src/sys/con_log.c | 130 + src/sys/con_passive.c | 69 + src/sys/con_tty.c | 478 ++ src/sys/con_win32.c | 359 + src/sys/sys_loadlib.h | 50 + src/sys/sys_local.h | 61 + src/sys/sys_main.c | 605 ++ src/sys/sys_osx.m | 141 + src/sys/sys_unix.c | 818 +++ src/sys/sys_win32.c | 811 +++ src/sys/win_resource.h | 45 + src/sys/win_resource.rc | 72 + src/tools/asm/README.Id | 10 + src/tools/asm/cmdlib.c | 1141 ++++ src/tools/asm/cmdlib.h | 153 + src/tools/asm/lib.txt | 31 + src/tools/asm/mathlib.h | 95 + src/tools/asm/notes.txt | 16 + src/tools/asm/ops.txt | 132 + src/tools/asm/opstrings.h | 176 + src/tools/asm/q3asm.c | 1647 +++++ src/tools/lcc/COPYRIGHT | 61 + src/tools/lcc/LOG | 91 + src/tools/lcc/README | 21 + src/tools/lcc/README.id | 3 + src/tools/lcc/cpp/cpp.c | 327 + src/tools/lcc/cpp/cpp.h | 166 + src/tools/lcc/cpp/eval.c | 524 ++ src/tools/lcc/cpp/getopt.c | 53 + src/tools/lcc/cpp/hideset.c | 112 + src/tools/lcc/cpp/include.c | 154 + src/tools/lcc/cpp/lex.c | 580 ++ src/tools/lcc/cpp/macro.c | 515 ++ src/tools/lcc/cpp/nlist.c | 104 + src/tools/lcc/cpp/tokens.c | 371 ++ src/tools/lcc/cpp/unix.c | 128 + src/tools/lcc/doc/4.html | 754 +++ src/tools/lcc/doc/bprint.1 | 83 + src/tools/lcc/doc/bprint.pdf | Bin 0 -> 4963 bytes src/tools/lcc/doc/install.html | 796 +++ src/tools/lcc/doc/lcc.1 | 605 ++ src/tools/lcc/doc/lcc.pdf | Bin 0 -> 16421 bytes src/tools/lcc/etc/bytecode.c | 66 + src/tools/lcc/etc/lcc.c | 853 +++ src/tools/lcc/lburg/gram.c | 682 ++ src/tools/lcc/lburg/gram.y | 202 + src/tools/lcc/lburg/lburg.1 | 179 + src/tools/lcc/lburg/lburg.c | 671 ++ src/tools/lcc/lburg/lburg.h | 65 + src/tools/lcc/src/alloc.c | 94 + src/tools/lcc/src/bind.c | 8 + src/tools/lcc/src/bytecode.c | 367 + src/tools/lcc/src/c.h | 729 ++ src/tools/lcc/src/config.h | 102 + src/tools/lcc/src/dag.c | 736 +++ src/tools/lcc/src/dagcheck.md | 210 + src/tools/lcc/src/decl.c | 1162 ++++ src/tools/lcc/src/enode.c | 545 ++ src/tools/lcc/src/error.c | 137 + src/tools/lcc/src/event.c | 28 + src/tools/lcc/src/expr.c | 711 ++ src/tools/lcc/src/gen.c | 830 +++ src/tools/lcc/src/init.c | 318 + src/tools/lcc/src/inits.c | 7 + src/tools/lcc/src/input.c | 135 + src/tools/lcc/src/lex.c | 923 +++ src/tools/lcc/src/list.c | 56 + src/tools/lcc/src/main.c | 225 + src/tools/lcc/src/null.c | 74 + src/tools/lcc/src/output.c | 135 + src/tools/lcc/src/prof.c | 228 + src/tools/lcc/src/profio.c | 276 + src/tools/lcc/src/simp.c | 587 ++ src/tools/lcc/src/stmt.c | 696 ++ src/tools/lcc/src/string.c | 122 + src/tools/lcc/src/sym.c | 314 + src/tools/lcc/src/symbolic.c | 494 ++ src/tools/lcc/src/token.h | 133 + src/tools/lcc/src/trace.c | 181 + src/tools/lcc/src/tree.c | 223 + src/tools/lcc/src/types.c | 748 +++ src/ui/ui_atoms.c | 295 + src/ui/ui_gameinfo.c | 377 ++ src/ui/ui_local.h | 400 ++ src/ui/ui_main.c | 4653 +++++++++++++ src/ui/ui_public.h | 205 + src/ui/ui_shared.c | 8083 +++++++++++++++++++++++ src/ui/ui_shared.h | 559 ++ src/ui/ui_syscalls.asm | 102 + src/ui/ui_syscalls.c | 483 ++ src/zlib/adler32.c | 149 + src/zlib/crc32.c | 423 ++ src/zlib/crc32.h | 441 ++ src/zlib/inffast.c | 318 + src/zlib/inffast.h | 11 + src/zlib/inffixed.h | 94 + src/zlib/inflate.c | 1368 ++++ src/zlib/inflate.h | 115 + src/zlib/inftrees.c | 329 + src/zlib/inftrees.h | 55 + src/zlib/zconf.h | 332 + src/zlib/zlib.h | 1357 ++++ src/zlib/zutil.c | 318 + src/zlib/zutil.h | 269 + 537 files changed, 328695 insertions(+) create mode 100644 src/AL/VERSION create mode 100644 src/AL/al.h create mode 100644 src/AL/alc.h create mode 100644 src/AL/alctypes.h create mode 100644 src/AL/altypes.h create mode 100644 src/AL/alut.h create mode 100644 src/SDL12/include/SDL.h create mode 100644 src/SDL12/include/SDL_active.h create mode 100644 src/SDL12/include/SDL_audio.h create mode 100644 src/SDL12/include/SDL_byteorder.h create mode 100644 src/SDL12/include/SDL_cdrom.h create mode 100644 src/SDL12/include/SDL_config.h create mode 100644 src/SDL12/include/SDL_config_amiga.h create mode 100644 src/SDL12/include/SDL_config_dreamcast.h create mode 100644 src/SDL12/include/SDL_config_macos.h create mode 100644 src/SDL12/include/SDL_config_macosx.h create mode 100644 src/SDL12/include/SDL_config_minimal.h create mode 100644 src/SDL12/include/SDL_config_nds.h create mode 100644 src/SDL12/include/SDL_config_os2.h create mode 100644 src/SDL12/include/SDL_config_symbian.h create mode 100644 src/SDL12/include/SDL_config_win32.h create mode 100644 src/SDL12/include/SDL_copying.h create mode 100644 src/SDL12/include/SDL_cpuinfo.h create mode 100644 src/SDL12/include/SDL_endian.h create mode 100644 src/SDL12/include/SDL_error.h create mode 100644 src/SDL12/include/SDL_events.h create mode 100644 src/SDL12/include/SDL_getenv.h create mode 100644 src/SDL12/include/SDL_joystick.h create mode 100644 src/SDL12/include/SDL_keyboard.h create mode 100644 src/SDL12/include/SDL_keysym.h create mode 100644 src/SDL12/include/SDL_loadso.h create mode 100644 src/SDL12/include/SDL_main.h create mode 100644 src/SDL12/include/SDL_mouse.h create mode 100644 src/SDL12/include/SDL_mutex.h create mode 100644 src/SDL12/include/SDL_name.h create mode 100644 src/SDL12/include/SDL_opengl.h create mode 100644 src/SDL12/include/SDL_platform.h create mode 100644 src/SDL12/include/SDL_quit.h create mode 100644 src/SDL12/include/SDL_rwops.h create mode 100644 src/SDL12/include/SDL_stdinc.h create mode 100644 src/SDL12/include/SDL_syswm.h create mode 100644 src/SDL12/include/SDL_thread.h create mode 100644 src/SDL12/include/SDL_timer.h create mode 100644 src/SDL12/include/SDL_types.h create mode 100644 src/SDL12/include/SDL_version.h create mode 100644 src/SDL12/include/SDL_video.h create mode 100644 src/SDL12/include/begin_code.h create mode 100644 src/SDL12/include/close_code.h create mode 100644 src/asm/ftola.s create mode 100644 src/asm/matha.s create mode 100644 src/asm/qasm.h create mode 100644 src/asm/snapvectora.s create mode 100644 src/asm/snd_mixa.s create mode 100644 src/cgame/cg_animation.c create mode 100644 src/cgame/cg_animmapobj.c create mode 100644 src/cgame/cg_attachment.c create mode 100644 src/cgame/cg_buildable.c create mode 100644 src/cgame/cg_consolecmds.c create mode 100644 src/cgame/cg_draw.c create mode 100644 src/cgame/cg_drawtools.c create mode 100644 src/cgame/cg_ents.c create mode 100644 src/cgame/cg_event.c create mode 100644 src/cgame/cg_event.c.orig create mode 100644 src/cgame/cg_local.h create mode 100644 src/cgame/cg_main.c create mode 100644 src/cgame/cg_main.c.orig create mode 100644 src/cgame/cg_marks.c create mode 100644 src/cgame/cg_particles.c create mode 100644 src/cgame/cg_players.c create mode 100644 src/cgame/cg_playerstate.c create mode 100644 src/cgame/cg_predict.c create mode 100644 src/cgame/cg_public.h create mode 100644 src/cgame/cg_scanner.c create mode 100644 src/cgame/cg_servercmds.c create mode 100644 src/cgame/cg_snapshot.c create mode 100644 src/cgame/cg_syscalls.asm create mode 100644 src/cgame/cg_syscalls.c create mode 100644 src/cgame/cg_trails.c create mode 100644 src/cgame/cg_tutorial.c create mode 100644 src/cgame/cg_view.c create mode 100644 src/cgame/cg_weapons.c create mode 100644 src/client/cl_avi.c create mode 100644 src/client/cl_cgame.c create mode 100644 src/client/cl_cin.c create mode 100644 src/client/cl_console.c create mode 100644 src/client/cl_curl.c create mode 100644 src/client/cl_curl.h create mode 100644 src/client/cl_input.c create mode 100644 src/client/cl_keys.c create mode 100644 src/client/cl_main.c create mode 100644 src/client/cl_net_chan.c create mode 100644 src/client/cl_parse.c create mode 100644 src/client/cl_scrn.c create mode 100644 src/client/cl_ui.c create mode 100644 src/client/client.h create mode 100644 src/client/keycodes.h create mode 100644 src/client/keys.h create mode 100644 src/client/libmumblelink.c create mode 100644 src/client/libmumblelink.h create mode 100644 src/client/qal.c create mode 100644 src/client/qal.h create mode 100644 src/client/snd_adpcm.c create mode 100644 src/client/snd_codec.c create mode 100644 src/client/snd_codec.h create mode 100644 src/client/snd_codec_ogg.c create mode 100644 src/client/snd_codec_wav.c create mode 100644 src/client/snd_dma.c create mode 100644 src/client/snd_local.h create mode 100644 src/client/snd_main.c create mode 100644 src/client/snd_mem.c create mode 100644 src/client/snd_mix.c create mode 100644 src/client/snd_openal.c create mode 100644 src/client/snd_public.h create mode 100644 src/client/snd_wavelet.c create mode 100644 src/game/bg_alloc.c create mode 100644 src/game/bg_lib.c create mode 100644 src/game/bg_lib.h create mode 100644 src/game/bg_local.h create mode 100644 src/game/bg_misc.c create mode 100644 src/game/bg_pmove.c create mode 100644 src/game/bg_public.h create mode 100644 src/game/bg_slidemove.c create mode 100644 src/game/bg_voice.c create mode 100644 src/game/g_active.c create mode 100644 src/game/g_active.c.orig create mode 100644 src/game/g_admin.c create mode 100644 src/game/g_admin.h create mode 100644 src/game/g_buildable.c create mode 100644 src/game/g_client.c create mode 100644 src/game/g_client.c.orig create mode 100644 src/game/g_cmds.c create mode 100644 src/game/g_cmds.c.orig create mode 100644 src/game/g_combat.c create mode 100644 src/game/g_combat.c.orig create mode 100644 src/game/g_local.h create mode 100644 src/game/g_main.c create mode 100644 src/game/g_main.c.orig create mode 100644 src/game/g_maprotation.c create mode 100644 src/game/g_misc.c create mode 100644 src/game/g_missile.c create mode 100644 src/game/g_mover.c create mode 100644 src/game/g_namelog.c create mode 100644 src/game/g_physics.c create mode 100644 src/game/g_public.h create mode 100644 src/game/g_session.c create mode 100644 src/game/g_spawn.c create mode 100644 src/game/g_svcmds.c create mode 100644 src/game/g_syscalls.asm create mode 100644 src/game/g_syscalls.c create mode 100644 src/game/g_target.c create mode 100644 src/game/g_team.c create mode 100644 src/game/g_trigger.c create mode 100644 src/game/g_utils.c create mode 100644 src/game/g_weapon.c create mode 100644 src/game/tremulous.h create mode 100644 src/jpeg-8c/README create mode 100644 src/jpeg-8c/jaricom.c create mode 100644 src/jpeg-8c/jcapimin.c create mode 100644 src/jpeg-8c/jcapistd.c create mode 100644 src/jpeg-8c/jcarith.c create mode 100644 src/jpeg-8c/jccoefct.c create mode 100644 src/jpeg-8c/jccolor.c create mode 100644 src/jpeg-8c/jcdctmgr.c create mode 100644 src/jpeg-8c/jchuff.c create mode 100644 src/jpeg-8c/jcinit.c create mode 100644 src/jpeg-8c/jcmainct.c create mode 100644 src/jpeg-8c/jcmarker.c create mode 100644 src/jpeg-8c/jcmaster.c create mode 100644 src/jpeg-8c/jcomapi.c create mode 100644 src/jpeg-8c/jconfig.h create mode 100644 src/jpeg-8c/jcparam.c create mode 100644 src/jpeg-8c/jcprepct.c create mode 100644 src/jpeg-8c/jcsample.c create mode 100644 src/jpeg-8c/jctrans.c create mode 100644 src/jpeg-8c/jdapimin.c create mode 100644 src/jpeg-8c/jdapistd.c create mode 100644 src/jpeg-8c/jdarith.c create mode 100644 src/jpeg-8c/jdatadst.c create mode 100644 src/jpeg-8c/jdatasrc.c create mode 100644 src/jpeg-8c/jdcoefct.c create mode 100644 src/jpeg-8c/jdcolor.c create mode 100644 src/jpeg-8c/jdct.h create mode 100644 src/jpeg-8c/jddctmgr.c create mode 100644 src/jpeg-8c/jdhuff.c create mode 100644 src/jpeg-8c/jdinput.c create mode 100644 src/jpeg-8c/jdmainct.c create mode 100644 src/jpeg-8c/jdmarker.c create mode 100644 src/jpeg-8c/jdmaster.c create mode 100644 src/jpeg-8c/jdmerge.c create mode 100644 src/jpeg-8c/jdpostct.c create mode 100644 src/jpeg-8c/jdsample.c create mode 100644 src/jpeg-8c/jdtrans.c create mode 100644 src/jpeg-8c/jerror.c create mode 100644 src/jpeg-8c/jerror.h create mode 100644 src/jpeg-8c/jfdctflt.c create mode 100644 src/jpeg-8c/jfdctfst.c create mode 100644 src/jpeg-8c/jfdctint.c create mode 100644 src/jpeg-8c/jidctflt.c create mode 100644 src/jpeg-8c/jidctfst.c create mode 100644 src/jpeg-8c/jidctint.c create mode 100644 src/jpeg-8c/jinclude.h create mode 100644 src/jpeg-8c/jmemmgr.c create mode 100644 src/jpeg-8c/jmemnobs.c create mode 100644 src/jpeg-8c/jmemsys.h create mode 100644 src/jpeg-8c/jmorecfg.h create mode 100644 src/jpeg-8c/jpegint.h create mode 100644 src/jpeg-8c/jpeglib.h create mode 100644 src/jpeg-8c/jquant1.c create mode 100644 src/jpeg-8c/jquant2.c create mode 100644 src/jpeg-8c/jutils.c create mode 100644 src/jpeg-8c/jversion.h create mode 100644 src/libcurl/curl/curl.h create mode 100644 src/libcurl/curl/curlver.h create mode 100644 src/libcurl/curl/easy.h create mode 100644 src/libcurl/curl/mprintf.h create mode 100644 src/libcurl/curl/multi.h create mode 100644 src/libcurl/curl/stdcheaders.h create mode 100644 src/libcurl/curl/types.h create mode 100755 src/libs/macosx/libSDL-1.2.0.dylib create mode 100644 src/libs/macosx/libSDLmain.a create mode 100644 src/libs/win32/libSDL.dll.a create mode 100644 src/libs/win32/libSDL.la create mode 100644 src/libs/win32/libSDLmain.a create mode 100644 src/libs/win32/libcurl.a create mode 100644 src/libspeex/_kiss_fft_guts.h create mode 100644 src/libspeex/arch.h create mode 100644 src/libspeex/bits.c create mode 100644 src/libspeex/buffer.c create mode 100644 src/libspeex/cb_search.c create mode 100644 src/libspeex/cb_search.h create mode 100644 src/libspeex/cb_search_arm4.h create mode 100644 src/libspeex/cb_search_bfin.h create mode 100644 src/libspeex/cb_search_sse.h create mode 100644 src/libspeex/config.h create mode 100644 src/libspeex/echo_diagnostic.m create mode 100644 src/libspeex/exc_10_16_table.c create mode 100644 src/libspeex/exc_10_32_table.c create mode 100644 src/libspeex/exc_20_32_table.c create mode 100644 src/libspeex/exc_5_256_table.c create mode 100644 src/libspeex/exc_5_64_table.c create mode 100644 src/libspeex/exc_8_128_table.c create mode 100644 src/libspeex/fftwrap.c create mode 100644 src/libspeex/fftwrap.h create mode 100644 src/libspeex/filterbank.c create mode 100644 src/libspeex/filterbank.h create mode 100644 src/libspeex/filters.c create mode 100644 src/libspeex/filters.h create mode 100644 src/libspeex/filters_arm4.h create mode 100644 src/libspeex/filters_bfin.h create mode 100644 src/libspeex/filters_sse.h create mode 100644 src/libspeex/fixed_arm4.h create mode 100644 src/libspeex/fixed_arm5e.h create mode 100644 src/libspeex/fixed_bfin.h create mode 100644 src/libspeex/fixed_debug.h create mode 100644 src/libspeex/fixed_generic.h create mode 100644 src/libspeex/gain_table.c create mode 100644 src/libspeex/gain_table_lbr.c create mode 100644 src/libspeex/hexc_10_32_table.c create mode 100644 src/libspeex/hexc_table.c create mode 100644 src/libspeex/high_lsp_tables.c create mode 100644 src/libspeex/include/speex/speex.h create mode 100644 src/libspeex/include/speex/speex_bits.h create mode 100644 src/libspeex/include/speex/speex_buffer.h create mode 100644 src/libspeex/include/speex/speex_callbacks.h create mode 100644 src/libspeex/include/speex/speex_config_types.h create mode 100644 src/libspeex/include/speex/speex_echo.h create mode 100644 src/libspeex/include/speex/speex_header.h create mode 100644 src/libspeex/include/speex/speex_jitter.h create mode 100644 src/libspeex/include/speex/speex_preprocess.h create mode 100644 src/libspeex/include/speex/speex_resampler.h create mode 100644 src/libspeex/include/speex/speex_stereo.h create mode 100644 src/libspeex/include/speex/speex_types.h create mode 100644 src/libspeex/jitter.c create mode 100644 src/libspeex/kiss_fft.c create mode 100644 src/libspeex/kiss_fft.h create mode 100644 src/libspeex/kiss_fftr.c create mode 100644 src/libspeex/kiss_fftr.h create mode 100644 src/libspeex/lpc.c create mode 100644 src/libspeex/lpc.h create mode 100644 src/libspeex/lpc_bfin.h create mode 100644 src/libspeex/lsp.c create mode 100644 src/libspeex/lsp.h create mode 100644 src/libspeex/lsp_bfin.h create mode 100644 src/libspeex/lsp_tables_nb.c create mode 100644 src/libspeex/ltp.c create mode 100644 src/libspeex/ltp.h create mode 100644 src/libspeex/ltp_arm4.h create mode 100644 src/libspeex/ltp_bfin.h create mode 100644 src/libspeex/ltp_sse.h create mode 100644 src/libspeex/math_approx.h create mode 100644 src/libspeex/mdf.c create mode 100644 src/libspeex/misc_bfin.h create mode 100644 src/libspeex/modes.c create mode 100644 src/libspeex/modes.h create mode 100644 src/libspeex/modes_wb.c create mode 100644 src/libspeex/nb_celp.c create mode 100644 src/libspeex/nb_celp.h create mode 100644 src/libspeex/os_support.h create mode 100644 src/libspeex/preprocess.c create mode 100644 src/libspeex/pseudofloat.h create mode 100644 src/libspeex/quant_lsp.c create mode 100644 src/libspeex/quant_lsp.h create mode 100644 src/libspeex/quant_lsp_bfin.h create mode 100644 src/libspeex/resample.c create mode 100644 src/libspeex/sb_celp.c create mode 100644 src/libspeex/sb_celp.h create mode 100644 src/libspeex/smallft.c create mode 100644 src/libspeex/smallft.h create mode 100644 src/libspeex/speex.c create mode 100644 src/libspeex/speex_callbacks.c create mode 100644 src/libspeex/speex_header.c create mode 100644 src/libspeex/stack_alloc.h create mode 100644 src/libspeex/stereo.c create mode 100644 src/libspeex/testdenoise.c create mode 100644 src/libspeex/testecho.c create mode 100644 src/libspeex/testenc.c create mode 100644 src/libspeex/testenc_uwb.c create mode 100644 src/libspeex/testenc_wb.c create mode 100644 src/libspeex/vbr.c create mode 100644 src/libspeex/vbr.h create mode 100644 src/libspeex/vorbis_psy.h create mode 100644 src/libspeex/vq.c create mode 100644 src/libspeex/vq.h create mode 100644 src/libspeex/vq_arm4.h create mode 100644 src/libspeex/vq_bfin.h create mode 100644 src/libspeex/vq_sse.h create mode 100644 src/libspeex/window.c create mode 100644 src/master/Makefile create mode 100644 src/master/common.h create mode 100644 src/master/master.c create mode 100644 src/master/messages.c create mode 100644 src/master/messages.h create mode 100644 src/master/servers.c create mode 100644 src/master/servers.h create mode 100644 src/master/stats.c create mode 100644 src/null/mac_net.c create mode 100644 src/null/null_client.c create mode 100644 src/null/null_glimp.c create mode 100644 src/null/null_input.c create mode 100644 src/null/null_main.c create mode 100644 src/null/null_net.c create mode 100644 src/null/null_snddma.c create mode 100644 src/qcommon/cm_load.c create mode 100644 src/qcommon/cm_local.h create mode 100644 src/qcommon/cm_patch.c create mode 100644 src/qcommon/cm_patch.h create mode 100644 src/qcommon/cm_polylib.c create mode 100644 src/qcommon/cm_polylib.h create mode 100644 src/qcommon/cm_public.h create mode 100644 src/qcommon/cm_test.c create mode 100644 src/qcommon/cm_trace.c create mode 100644 src/qcommon/cmd.c create mode 100644 src/qcommon/common.c create mode 100644 src/qcommon/cvar.c create mode 100644 src/qcommon/files.c create mode 100644 src/qcommon/huffman.c create mode 100644 src/qcommon/ioapi.c create mode 100644 src/qcommon/ioapi.h create mode 100644 src/qcommon/md4.c create mode 100644 src/qcommon/md5.c create mode 100644 src/qcommon/msg.c create mode 100644 src/qcommon/net_chan.c create mode 100644 src/qcommon/net_ip.c create mode 100644 src/qcommon/parse.c create mode 100644 src/qcommon/puff.c create mode 100644 src/qcommon/puff.h create mode 100644 src/qcommon/q_math.c create mode 100644 src/qcommon/q_platform.h create mode 100644 src/qcommon/q_shared.c create mode 100644 src/qcommon/q_shared.h create mode 100644 src/qcommon/qcommon.h create mode 100644 src/qcommon/qfiles.h create mode 100644 src/qcommon/surfaceflags.h create mode 100644 src/qcommon/unzip.c create mode 100644 src/qcommon/unzip.h create mode 100644 src/qcommon/vm.c create mode 100644 src/qcommon/vm_interpreted.c create mode 100644 src/qcommon/vm_local.h create mode 100644 src/qcommon/vm_none.c create mode 100644 src/qcommon/vm_powerpc.c create mode 100644 src/qcommon/vm_powerpc_asm.c create mode 100644 src/qcommon/vm_powerpc_asm.h create mode 100644 src/qcommon/vm_sparc.c create mode 100644 src/qcommon/vm_sparc.h create mode 100644 src/qcommon/vm_x86.c create mode 100644 src/qcommon/vm_x86_64.c create mode 100644 src/qcommon/vm_x86_64_assembler.c create mode 100644 src/renderer/qgl.h create mode 100644 src/renderer/tr_animation.c create mode 100644 src/renderer/tr_backend.c create mode 100644 src/renderer/tr_bsp.c create mode 100644 src/renderer/tr_cmds.c create mode 100644 src/renderer/tr_curve.c create mode 100644 src/renderer/tr_flares.c create mode 100644 src/renderer/tr_font.c create mode 100644 src/renderer/tr_image.c create mode 100644 src/renderer/tr_image_bmp.c create mode 100644 src/renderer/tr_image_jpg.c create mode 100644 src/renderer/tr_image_pcx.c create mode 100644 src/renderer/tr_image_png.c create mode 100644 src/renderer/tr_image_tga.c create mode 100644 src/renderer/tr_init.c create mode 100644 src/renderer/tr_light.c create mode 100644 src/renderer/tr_local.h create mode 100644 src/renderer/tr_main.c create mode 100644 src/renderer/tr_marks.c create mode 100644 src/renderer/tr_mesh.c create mode 100644 src/renderer/tr_model.c create mode 100644 src/renderer/tr_noise.c create mode 100644 src/renderer/tr_public.h create mode 100644 src/renderer/tr_scene.c create mode 100644 src/renderer/tr_shade.c create mode 100644 src/renderer/tr_shade_calc.c create mode 100644 src/renderer/tr_shader.c create mode 100644 src/renderer/tr_shadows.c create mode 100644 src/renderer/tr_sky.c create mode 100644 src/renderer/tr_surface.c create mode 100644 src/renderer/tr_types.h create mode 100644 src/renderer/tr_world.c create mode 100644 src/sdl/sdl_gamma.c create mode 100644 src/sdl/sdl_glimp.c create mode 100644 src/sdl/sdl_icon.h create mode 100644 src/sdl/sdl_input.c create mode 100644 src/sdl/sdl_snd.c create mode 100644 src/server/server.h create mode 100644 src/server/sv_ccmds.c create mode 100644 src/server/sv_client.c create mode 100644 src/server/sv_game.c create mode 100644 src/server/sv_init.c create mode 100644 src/server/sv_main.c create mode 100644 src/server/sv_net_chan.c create mode 100644 src/server/sv_snapshot.c create mode 100644 src/server/sv_world.c create mode 100644 src/sys/con_log.c create mode 100644 src/sys/con_passive.c create mode 100644 src/sys/con_tty.c create mode 100644 src/sys/con_win32.c create mode 100644 src/sys/sys_loadlib.h create mode 100644 src/sys/sys_local.h create mode 100644 src/sys/sys_main.c create mode 100644 src/sys/sys_osx.m create mode 100644 src/sys/sys_unix.c create mode 100644 src/sys/sys_win32.c create mode 100644 src/sys/win_resource.h create mode 100644 src/sys/win_resource.rc create mode 100644 src/tools/asm/README.Id create mode 100644 src/tools/asm/cmdlib.c create mode 100644 src/tools/asm/cmdlib.h create mode 100644 src/tools/asm/lib.txt create mode 100644 src/tools/asm/mathlib.h create mode 100644 src/tools/asm/notes.txt create mode 100644 src/tools/asm/ops.txt create mode 100644 src/tools/asm/opstrings.h create mode 100644 src/tools/asm/q3asm.c create mode 100644 src/tools/lcc/COPYRIGHT create mode 100644 src/tools/lcc/LOG create mode 100644 src/tools/lcc/README create mode 100644 src/tools/lcc/README.id create mode 100644 src/tools/lcc/cpp/cpp.c create mode 100644 src/tools/lcc/cpp/cpp.h create mode 100644 src/tools/lcc/cpp/eval.c create mode 100644 src/tools/lcc/cpp/getopt.c create mode 100644 src/tools/lcc/cpp/hideset.c create mode 100644 src/tools/lcc/cpp/include.c create mode 100644 src/tools/lcc/cpp/lex.c create mode 100644 src/tools/lcc/cpp/macro.c create mode 100644 src/tools/lcc/cpp/nlist.c create mode 100644 src/tools/lcc/cpp/tokens.c create mode 100644 src/tools/lcc/cpp/unix.c create mode 100644 src/tools/lcc/doc/4.html create mode 100644 src/tools/lcc/doc/bprint.1 create mode 100644 src/tools/lcc/doc/bprint.pdf create mode 100644 src/tools/lcc/doc/install.html create mode 100644 src/tools/lcc/doc/lcc.1 create mode 100644 src/tools/lcc/doc/lcc.pdf create mode 100644 src/tools/lcc/etc/bytecode.c create mode 100644 src/tools/lcc/etc/lcc.c create mode 100644 src/tools/lcc/lburg/gram.c create mode 100644 src/tools/lcc/lburg/gram.y create mode 100644 src/tools/lcc/lburg/lburg.1 create mode 100644 src/tools/lcc/lburg/lburg.c create mode 100644 src/tools/lcc/lburg/lburg.h create mode 100644 src/tools/lcc/src/alloc.c create mode 100644 src/tools/lcc/src/bind.c create mode 100644 src/tools/lcc/src/bytecode.c create mode 100644 src/tools/lcc/src/c.h create mode 100644 src/tools/lcc/src/config.h create mode 100644 src/tools/lcc/src/dag.c create mode 100644 src/tools/lcc/src/dagcheck.md create mode 100644 src/tools/lcc/src/decl.c create mode 100644 src/tools/lcc/src/enode.c create mode 100644 src/tools/lcc/src/error.c create mode 100644 src/tools/lcc/src/event.c create mode 100644 src/tools/lcc/src/expr.c create mode 100644 src/tools/lcc/src/gen.c create mode 100644 src/tools/lcc/src/init.c create mode 100644 src/tools/lcc/src/inits.c create mode 100644 src/tools/lcc/src/input.c create mode 100644 src/tools/lcc/src/lex.c create mode 100644 src/tools/lcc/src/list.c create mode 100644 src/tools/lcc/src/main.c create mode 100644 src/tools/lcc/src/null.c create mode 100644 src/tools/lcc/src/output.c create mode 100644 src/tools/lcc/src/prof.c create mode 100644 src/tools/lcc/src/profio.c create mode 100644 src/tools/lcc/src/simp.c create mode 100644 src/tools/lcc/src/stmt.c create mode 100644 src/tools/lcc/src/string.c create mode 100644 src/tools/lcc/src/sym.c create mode 100644 src/tools/lcc/src/symbolic.c create mode 100644 src/tools/lcc/src/token.h create mode 100644 src/tools/lcc/src/trace.c create mode 100644 src/tools/lcc/src/tree.c create mode 100644 src/tools/lcc/src/types.c create mode 100644 src/ui/ui_atoms.c create mode 100644 src/ui/ui_gameinfo.c create mode 100644 src/ui/ui_local.h create mode 100644 src/ui/ui_main.c create mode 100644 src/ui/ui_public.h create mode 100644 src/ui/ui_shared.c create mode 100644 src/ui/ui_shared.h create mode 100644 src/ui/ui_syscalls.asm create mode 100644 src/ui/ui_syscalls.c create mode 100644 src/zlib/adler32.c create mode 100644 src/zlib/crc32.c create mode 100644 src/zlib/crc32.h create mode 100644 src/zlib/inffast.c create mode 100644 src/zlib/inffast.h create mode 100644 src/zlib/inffixed.h create mode 100644 src/zlib/inflate.c create mode 100644 src/zlib/inflate.h create mode 100644 src/zlib/inftrees.c create mode 100644 src/zlib/inftrees.h create mode 100644 src/zlib/zconf.h create mode 100644 src/zlib/zlib.h create mode 100644 src/zlib/zutil.c create mode 100644 src/zlib/zutil.h (limited to 'src') diff --git a/src/AL/VERSION b/src/AL/VERSION new file mode 100644 index 0000000..1af4e3f --- /dev/null +++ b/src/AL/VERSION @@ -0,0 +1,16 @@ +This file identifies the version of the AL headers in this directory. If you +change or update the AL headers in any way, please make sure you also update +this file. + +SVN revision >= 402 +------------------- +Headers are from OpenAL CVS 6th August 2005: +$ cvs -d:pserver:guest@opensource.creative.com:/usr/local/cvs-repository +login +(use password "guest") +$ cvs -d:pserver:guest@opensource.creative.com:/usr/local/cvs-repository +co -D "6 Aug 2005" openal + +SVN revision >= 374 +------------------- +Standard OpenAL 1.0 headers diff --git a/src/AL/al.h b/src/AL/al.h new file mode 100644 index 0000000..fd9a537 --- /dev/null +++ b/src/AL/al.h @@ -0,0 +1,506 @@ +#ifndef __al_h_ +#define __al_h_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ +#include "altypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* WIN32, not Xbox */ +#ifdef _WIN32 +#ifndef _XBOX +#ifdef _OPENAL32LIB +#define ALAPI __declspec(dllexport) +#else +#define ALAPI __declspec(dllimport) +#endif +#define ALAPIENTRY __cdecl +#define AL_CALLBACK +#endif +#endif + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export on +#endif +#endif + +#ifndef ALAPI +#define ALAPI +#endif + +#ifndef ALAPIENTRY +#define ALAPIENTRY +#endif + +#ifndef CALLBACK +#define AL_CALLBACK +#endif + +#define OPENAL + +#ifndef AL_NO_PROTOTYPES + +/* + * Renderer State management + */ +ALAPI void ALAPIENTRY alEnable( ALenum capability ); + +ALAPI void ALAPIENTRY alDisable( ALenum capability ); + +ALAPI ALboolean ALAPIENTRY alIsEnabled( ALenum capability ); + + +/* + * State retrieval + */ +ALAPI const ALchar* ALAPIENTRY alGetString( ALenum param ); + +ALAPI void ALAPIENTRY alGetBooleanv( ALenum param, ALboolean* data ); + +ALAPI void ALAPIENTRY alGetIntegerv( ALenum param, ALint* data ); + +ALAPI void ALAPIENTRY alGetFloatv( ALenum param, ALfloat* data ); + +ALAPI void ALAPIENTRY alGetDoublev( ALenum param, ALdouble* data ); + +ALAPI ALboolean ALAPIENTRY alGetBoolean( ALenum param ); + +ALAPI ALint ALAPIENTRY alGetInteger( ALenum param ); + +ALAPI ALfloat ALAPIENTRY alGetFloat( ALenum param ); + +ALAPI ALdouble ALAPIENTRY alGetDouble( ALenum param ); + + +/* + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +ALAPI ALenum ALAPIENTRY alGetError( ALvoid ); + + +/* + * Extension support. + * Query for the presence of an extension, and obtain any appropriate + * function pointers and enum values. + */ +ALAPI ALboolean ALAPIENTRY alIsExtensionPresent( const ALchar* extname ); + +ALAPI void* ALAPIENTRY alGetProcAddress( const ALchar* fname ); + +ALAPI ALenum ALAPIENTRY alGetEnumValue( const ALchar* ename ); + + +/* + * LISTENER + * Listener represents the location and orientation of the + * 'user' in 3D-space. + * + * Properties include: - + * + * Gain AL_GAIN ALfloat + * Position AL_POSITION ALfloat[3] + * Velocity AL_VELOCITY ALfloat[3] + * Orientation AL_ORIENTATION ALfloat[6] (Forward then Up vectors) +*/ + +/* + * Set Listener parameters + */ +ALAPI void ALAPIENTRY alListenerf( ALenum param, ALfloat value ); + +ALAPI void ALAPIENTRY alListener3f( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +ALAPI void ALAPIENTRY alListenerfv( ALenum param, const ALfloat* values ); + +ALAPI void ALAPIENTRY alListeneri( ALenum param, ALint value ); + +ALAPI void ALAPIENTRY alListener3i( ALenum param, ALint value1, ALint value2, ALint value3 ); + +ALAPI void ALAPIENTRY alListeneriv( ALenum param, const ALint* values ); + +/* + * Get Listener parameters + */ +ALAPI void ALAPIENTRY alGetListenerf( ALenum param, ALfloat* value ); + +ALAPI void ALAPIENTRY alGetListener3f( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); + +ALAPI void ALAPIENTRY alGetListenerfv( ALenum param, ALfloat* values ); + +ALAPI void ALAPIENTRY alGetListeneri( ALenum param, ALint* value ); + +ALAPI void ALAPIENTRY alGetListener3i( ALenum param, ALint *value1, ALint *value2, ALint *value3 ); + +ALAPI void ALAPIENTRY alGetListeneriv( ALenum param, ALint* values ); + + +/** + * SOURCE + * Sources represent individual sound objects in 3D-space. + * Sources take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial arrangement etc. + * + * Properties include: - + * + * Gain AL_GAIN ALfloat + * Min Gain AL_MIN_GAIN ALfloat + * Max Gain AL_MAX_GAIN ALfloat + * Position AL_POSITION ALfloat[3] + * Velocity AL_VELOCITY ALfloat[3] + * Direction AL_DIRECTION ALfloat[3] + * Head Relative Mode AL_SOURCE_RELATIVE ALint (AL_TRUE or AL_FALSE) + * Reference Distance AL_REFERENCE_DISTANCE ALfloat + * Max Distance AL_MAX_DISTANCE ALfloat + * RollOff Factor AL_ROLLOFF_FACTOR ALfloat + * Inner Angle AL_CONE_INNER_ANGLE ALint or ALfloat + * Outer Angle AL_CONE_OUTER_ANGLE ALint or ALfloat + * Cone Outer Gain AL_CONE_OUTER_GAIN ALint or ALfloat + * Pitch AL_PITCH ALfloat + * Looping AL_LOOPING ALint (AL_TRUE or AL_FALSE) + * MS Offset AL_MSEC_OFFSET ALint or ALfloat + * Byte Offset AL_BYTE_OFFSET ALint or ALfloat + * Sample Offset AL_SAMPLE_OFFSET ALint or ALfloat + * Attached Buffer AL_BUFFER ALint + * State (Query only) AL_SOURCE_STATE ALint + * Buffers Queued (Query only) AL_BUFFERS_QUEUED ALint + * Buffers Processed (Query only) AL_BUFFERS_PROCESSED ALint + */ + +/* Create Source objects */ +ALAPI void ALAPIENTRY alGenSources( ALsizei n, ALuint* sources ); + +/* Delete Source objects */ +ALAPI void ALAPIENTRY alDeleteSources( ALsizei n, const ALuint* sources ); + +/* Verify a handle is a valid Source */ +ALAPI ALboolean ALAPIENTRY alIsSource( ALuint sid ); + +/* + * Set Source parameters + */ +ALAPI void ALAPIENTRY alSourcef( ALuint sid, ALenum param, ALfloat value ); + +ALAPI void ALAPIENTRY alSource3f( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +ALAPI void ALAPIENTRY alSourcefv( ALuint sid, ALenum param, const ALfloat* values ); + +ALAPI void ALAPIENTRY alSourcei( ALuint sid, ALenum param, ALint value ); + +ALAPI void ALAPIENTRY alSource3i( ALuint sid, ALenum param, ALint value1, ALint value2, ALint value3 ); + +ALAPI void ALAPIENTRY alSourceiv( ALuint sid, ALenum param, const ALint* values ); + +/* + * Get Source parameters + */ +ALAPI void ALAPIENTRY alGetSourcef( ALuint sid, ALenum param, ALfloat* value ); + +ALAPI void ALAPIENTRY alGetSource3f( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); + +ALAPI void ALAPIENTRY alGetSourcefv( ALuint sid, ALenum param, ALfloat* values ); + +ALAPI void ALAPIENTRY alGetSourcei( ALuint sid, ALenum param, ALint* value ); + +ALAPI void ALAPIENTRY alGetSource3i( ALuint sid, ALenum param, ALint* value1, ALint* value2, ALint* value3); + +ALAPI void ALAPIENTRY alGetSourceiv( ALuint sid, ALenum param, ALint* values ); + + +/* + * Source vector based playback calls + */ + +/* Play, replay, or resume (if paused) a list of Sources */ +ALAPI void ALAPIENTRY alSourcePlayv( ALsizei ns, const ALuint *sids ); + +/* Stop a list of Sources */ +ALAPI void ALAPIENTRY alSourceStopv( ALsizei ns, const ALuint *sids ); + +/* Rewind a list of Sources */ +ALAPI void ALAPIENTRY alSourceRewindv( ALsizei ns, const ALuint *sids ); + +/* Pause a list of Sources */ +ALAPI void ALAPIENTRY alSourcePausev( ALsizei ns, const ALuint *sids ); + +/* + * Source based playback calls + */ + +/* Play, replay, or resume a Source */ +ALAPI void ALAPIENTRY alSourcePlay( ALuint sid ); + +/* Stop a Source */ +ALAPI void ALAPIENTRY alSourceStop( ALuint sid ); + +/* Rewind a Source (set playback postiton to beginning) */ +ALAPI void ALAPIENTRY alSourceRewind( ALuint sid ); + +/* Pause a Source */ +ALAPI void ALAPIENTRY alSourcePause( ALuint sid ); + +/* + * Source Queuing + */ +ALAPI void ALAPIENTRY alSourceQueueBuffers( ALuint sid, ALsizei numEntries, const ALuint *bids ); + +ALAPI void ALAPIENTRY alSourceUnqueueBuffers( ALuint sid, ALsizei numEntries, ALuint *bids ); + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. One Buffer can be used + * by multiple Sources. + * + * Properties include: - + * + * Frequency (Query only) AL_FREQUENCY ALint + * Size (Query only) AL_SIZE ALint + * Bits (Query only) AL_BITS ALint + * Channels (Query only) AL_CHANNELS ALint + */ + +/* Create Buffer objects */ +ALAPI void ALAPIENTRY alGenBuffers( ALsizei n, ALuint* buffers ); + +/* Delete Buffer objects */ +ALAPI void ALAPIENTRY alDeleteBuffers( ALsizei n, const ALuint* buffers ); + +/* Verify a handle is a valid Buffer */ +ALAPI ALboolean ALAPIENTRY alIsBuffer( ALuint bid ); + +/* Specify the data to be copied into a buffer */ +ALAPI void ALAPIENTRY alBufferData( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); + +/* + * Set Buffer parameters + */ +ALAPI void ALAPIENTRY alBufferf( ALuint bid, ALenum param, ALfloat value ); + +ALAPI void ALAPIENTRY alBuffer3f( ALuint bid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +ALAPI void ALAPIENTRY alBufferfv( ALuint bid, ALenum param, const ALfloat* values ); + +ALAPI void ALAPIENTRY alBufferi( ALuint bid, ALenum param, ALint value ); + +ALAPI void ALAPIENTRY alBuffer3i( ALuint bid, ALenum param, ALint value1, ALint value2, ALint value3 ); + +ALAPI void ALAPIENTRY alBufferiv( ALuint bid, ALenum param, const ALint* values ); + +/* + * Get Buffer parameters + */ +ALAPI void ALAPIENTRY alGetBufferf( ALuint bid, ALenum param, ALfloat* value ); + +ALAPI void ALAPIENTRY alGetBuffer3f( ALuint bid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); + +ALAPI void ALAPIENTRY alGetBufferfv( ALuint bid, ALenum param, ALfloat* values ); + +ALAPI void ALAPIENTRY alGetBufferi( ALuint bid, ALenum param, ALint* value ); + +ALAPI void ALAPIENTRY alGetBuffer3i( ALuint bid, ALenum param, ALint* value1, ALint* value2, ALint* value3); + +ALAPI void ALAPIENTRY alGetBufferiv( ALuint bid, ALenum param, ALint* values ); + + +/* + * Global Parameters + */ +ALAPI void ALAPIENTRY alDopplerFactor( ALfloat value ); + +ALAPI void ALAPIENTRY alDopplerVelocity( ALfloat value ); + +ALAPI void ALAPIENTRY alSpeedOfSound( ALfloat value ); + +ALAPI void ALAPIENTRY alDistanceModel( ALenum distanceModel ); + +#else /* AL_NO_PROTOTYPES */ + +/* +void (ALAPIENTRY *alEnable)( ALenum capability ); +void (ALAPIENTRY *alDisable)( ALenum capability ); +ALboolean (ALAPIENTRY *alIsEnabled)( ALenum capability ); +const ALchar* (ALAPIENTRY *alGetString)( ALenum param ); +void (ALAPIENTRY *alGetBooleanv)( ALenum param, ALboolean* data ); +void (ALAPIENTRY *alGetIntegerv)( ALenum param, ALint* data ); +void (ALAPIENTRY *alGetFloatv)( ALenum param, ALfloat* data ); +void (ALAPIENTRY *alGetDoublev)( ALenum param, ALdouble* data ); +ALboolean (ALAPIENTRY *alGetBoolean)( ALenum param ); +ALint (ALAPIENTRY *alGetInteger)( ALenum param ); +ALfloat (ALAPIENTRY *alGetFloat)( ALenum param ); +ALdouble (ALAPIENTRY *alGetDouble)( ALenum param ); +ALenum (ALAPIENTRY *alGetError)( ALvoid ); +ALboolean (ALAPIENTRY *alIsExtensionPresent)(const ALchar* extname ); +void* (ALAPIENTRY *alGetProcAddress)( const ALchar* fname ); +ALenum (ALAPIENTRY *alGetEnumValue)( const ALchar* ename ); +void (ALAPIENTRY *alListenerf)( ALenum param, ALfloat value ); +void (ALAPIENTRY *alListener3f)( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +void (ALAPIENTRY *alListenerfv)( ALenum param, const ALfloat* values ); +void (ALAPIENTRY *alListeneri)( ALenum param, ALint value ); +void (ALAPIENTRY *alListener3i)( ALenum param, ALint value1, ALint value2, ALint value3 ); +void (ALAPIENTRY *alListeneriv)( ALenum param, const ALint* values ); +void (ALAPIENTRY *alGetListenerf)( ALenum param, ALfloat* value ); +void (ALAPIENTRY *alGetListener3f)( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); +void (ALAPIENTRY *alGetListenerfv)( ALenum param, ALfloat* values ); +void (ALAPIENTRY *alGetListeneri)( ALenum param, ALint* value ); +void (ALAPIENTRY *alGetListener3i)( ALenum param, ALint *value1, ALint *value2, ALint *value3 ); +void (ALAPIENTRY *alGetListeneriv)( ALenum param, ALint* values ); +void (ALAPIENTRY *alGenSources)( ALsizei n, ALuint* sources ); +void (ALAPIENTRY *alDeleteSources)( ALsizei n, const ALuint* sources ); +ALboolean (ALAPIENTRY *alIsSource)( ALuint sid ); +void (ALAPIENTRY *alSourcef)( ALuint sid, ALenum param, ALfloat value); +void (ALAPIENTRY *alSource3f)( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +void (ALAPIENTRY *alSourcefv)( ALuint sid, ALenum param, const ALfloat* values ); +void (ALAPIENTRY *alSourcei)( ALuint sid, ALenum param, ALint value); +void (ALAPIENTRY *alSource3i)( ALuint sid, ALenum param, ALint value1, ALint value2, ALint value3 ); +void (ALAPIENTRY *alSourceiv)( ALuint sid, ALenum param, const ALint* values ); +void (ALAPIENTRY *alGetSourcef)( ALuint sid, ALenum param, ALfloat* value ); +void (ALAPIENTRY *alGetSource3f)( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +void (ALAPIENTRY *alGetSourcefv)( ALuint sid, ALenum param, ALfloat* values ); +void (ALAPIENTRY *alGetSourcei)( ALuint sid, ALenum param, ALint* value ); +void (ALAPIENTRY *alGetSource3i)( ALuint sid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +void (ALAPIENTRY *alGetSourceiv)( ALuint sid, ALenum param, ALint* values ); +void (ALAPIENTRY *alSourcePlayv)( ALsizei ns, const ALuint *sids ); +void (ALAPIENTRY *alSourceStopv)( ALsizei ns, const ALuint *sids ); +void (ALAPIENTRY *alSourceRewindv)( ALsizei ns, const ALuint *sids ); +void (ALAPIENTRY *alSourcePausev)( ALsizei ns, const ALuint *sids ); +void (ALAPIENTRY *alSourcePlay)( ALuint sid ); +void (ALAPIENTRY *alSourceStop)( ALuint sid ); +void (ALAPIENTRY *alSourceRewind)( ALuint sid ); +void (ALAPIENTRY *alSourcePause)( ALuint sid ); +void (ALAPIENTRY *alSourceQueueBuffers)( ALuint sid, ALsizei numEntries, const ALuint *bids ); +void (ALAPIENTRY *alSourceUnqueueBuffers)( ALuint sid, ALsizei numEntries, ALuint *bids ); +void (ALAPIENTRY *alGenBuffers)( ALsizei n, ALuint* buffers ); +void (ALAPIENTRY *alDeleteBuffers)( ALsizei n, const ALuint* buffers ); +ALboolean (ALAPIENTRY *alIsBuffer)( ALuint bid ); +void (ALAPIENTRY *alBufferData)( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); +void (ALAPIENTRY *alBufferf)( ALuint bid, ALenum param, ALfloat value); +void (ALAPIENTRY *alBuffer3f)( ALuint bid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +void (ALAPIENTRY *alBufferfv)( ALuint bid, ALenum param, const ALfloat* values ); +void (ALAPIENTRY *alBufferi)( ALuint bid, ALenum param, ALint value); +void (ALAPIENTRY *alBuffer3i)( ALuint bid, ALenum param, ALint value1, ALint value2, ALint value3 ); +void (ALAPIENTRY *alBufferiv)( ALuint bid, ALenum param, const ALint* values ); +void (ALAPIENTRY *alGetBufferf)( ALuint bid, ALenum param, ALfloat* value ); +void (ALAPIENTRY *alGetBuffer3f)( ALuint bid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +void (ALAPIENTRY *alGetBufferfv)( ALuint bid, ALenum param, ALfloat* values ); +void (ALAPIENTRY *alGetBufferi)( ALuint bid, ALenum param, ALint* value ); +void (ALAPIENTRY *alGetBuffer3i)( ALuint bid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +void (ALAPIENTRY *alGetBufferiv)( ALuint bid, ALenum param, ALint* values ); +void (ALAPIENTRY *alDopplerFactor)( ALfloat value ); +void (ALAPIENTRY *alDopplerVelocity)( ALfloat value ); +void (ALAPIENTRY *alSpeedOfSound)( ALfloat value ); +void (ALAPIENTRY *alDistanceModel)( ALenum distanceModel ); +*/ +/* Type Definitions */ + +typedef void (ALAPIENTRY *LPALENABLE)( ALenum capability ); +typedef void (ALAPIENTRY *LPALDISABLE)( ALenum capability ); +typedef ALboolean (ALAPIENTRY *LPALISENABLED)( ALenum capability ); +typedef const ALchar* (ALAPIENTRY *LPALGETSTRING)( ALenum param ); +typedef void (ALAPIENTRY *LPALGETBOOLEANV)( ALenum param, ALboolean* data ); +typedef void (ALAPIENTRY *LPALGETINTEGERV)( ALenum param, ALint* data ); +typedef void (ALAPIENTRY *LPALGETFLOATV)( ALenum param, ALfloat* data ); +typedef void (ALAPIENTRY *LPALGETDOUBLEV)( ALenum param, ALdouble* data ); +typedef ALboolean (ALAPIENTRY *LPALGETBOOLEAN)( ALenum param ); +typedef ALint (ALAPIENTRY *LPALGETINTEGER)( ALenum param ); +typedef ALfloat (ALAPIENTRY *LPALGETFLOAT)( ALenum param ); +typedef ALdouble (ALAPIENTRY *LPALGETDOUBLE)( ALenum param ); +typedef ALenum (ALAPIENTRY *LPALGETERROR)( ALvoid ); +typedef ALboolean (ALAPIENTRY *LPALISEXTENSIONPRESENT)(const ALchar* extname ); +typedef void* (ALAPIENTRY *LPALGETPROCADDRESS)( const ALchar* fname ); +typedef ALenum (ALAPIENTRY *LPALGETENUMVALUE)( const ALchar* ename ); +typedef void (ALAPIENTRY *LPALLISTENERF)( ALenum param, ALfloat value ); +typedef void (ALAPIENTRY *LPALLISTENER3F)( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (ALAPIENTRY *LPALLISTENERFV)( ALenum param, const ALfloat* values ); +typedef void (ALAPIENTRY *LPALLISTENERI)( ALenum param, ALint value ); +typedef void (ALAPIENTRY *LPALLISTENER3I)( ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (ALAPIENTRY *LPALLISTENERIV)( ALenum param, const ALint* values ); +typedef void (ALAPIENTRY *LPALGETLISTENERF)( ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETLISTENER3F)( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); +typedef void (ALAPIENTRY *LPALGETLISTENERFV)( ALenum param, ALfloat* values ); +typedef void (ALAPIENTRY *LPALGETLISTENERI)( ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALGETLISTENER3I)( ALenum param, ALint *value1, ALint *value2, ALint *value3 ); +typedef void (ALAPIENTRY *LPALGETLISTENERIV)( ALenum param, ALint* values ); +typedef void (ALAPIENTRY *LPALGENSOURCES)( ALsizei n, ALuint* sources ); +typedef void (ALAPIENTRY *LPALDELETESOURCES)( ALsizei n, const ALuint* sources ); +typedef ALboolean (ALAPIENTRY *LPALISSOURCE)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEF)( ALuint sid, ALenum param, ALfloat value); +typedef void (ALAPIENTRY *LPALSOURCE3F)( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (ALAPIENTRY *LPALSOURCEFV)( ALuint sid, ALenum param, const ALfloat* values ); +typedef void (ALAPIENTRY *LPALSOURCEI)( ALuint sid, ALenum param, ALint value); +typedef void (ALAPIENTRY *LPALSOURCE3I)( ALuint sid, ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (ALAPIENTRY *LPALSOURCEIV)( ALuint sid, ALenum param, const ALint* values ); +typedef void (ALAPIENTRY *LPALGETSOURCEF)( ALuint sid, ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETSOURCE3F)( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +typedef void (ALAPIENTRY *LPALGETSOURCEFV)( ALuint sid, ALenum param, ALfloat* values ); +typedef void (ALAPIENTRY *LPALGETSOURCEI)( ALuint sid, ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALGETSOURCE3I)( ALuint sid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +typedef void (ALAPIENTRY *LPALGETSOURCEIV)( ALuint sid, ALenum param, ALint* values ); +typedef void (ALAPIENTRY *LPALSOURCEPLAYV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCESTOPV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEREWINDV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEPAUSEV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEPLAY)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCESTOP)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEREWIND)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEPAUSE)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, const ALuint *bids ); +typedef void (ALAPIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, ALuint *bids ); +typedef void (ALAPIENTRY *LPALGENBUFFERS)( ALsizei n, ALuint* buffers ); +typedef void (ALAPIENTRY *LPALDELETEBUFFERS)( ALsizei n, const ALuint* buffers ); +typedef ALboolean (ALAPIENTRY *LPALISBUFFER)( ALuint bid ); +typedef void (ALAPIENTRY *LPALBUFFERDATA)( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); +typedef void (ALAPIENTRY *LPALBUFFERF)( ALuint bid, ALenum param, ALfloat value); +typedef void (ALAPIENTRY *LPALBUFFER3F)( ALuint bid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (ALAPIENTRY *LPALBUFFERFV)( ALuint bid, ALenum param, const ALfloat* values ); +typedef void (ALAPIENTRY *LPALBUFFERI)( ALuint bid, ALenum param, ALint value); +typedef void (ALAPIENTRY *LPALBUFFER3I)( ALuint bid, ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (ALAPIENTRY *LPALBUFFERIV)( ALuint bid, ALenum param, const ALint* values ); +typedef void (ALAPIENTRY *LPALGETBUFFERF)( ALuint bid, ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETBUFFER3F)( ALuint bid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +typedef void (ALAPIENTRY *LPALGETBUFFERFV)( ALuint bid, ALenum param, ALfloat* values ); +typedef void (ALAPIENTRY *LPALGETBUFFERI)( ALuint bid, ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALGETBUFFER3I)( ALuint bid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +typedef void (ALAPIENTRY *LPALGETBUFFERIV)( ALuint bid, ALenum param, ALint* values ); +typedef void (ALAPIENTRY *LPALDOPPLERFACTOR)( ALfloat value ); +typedef void (ALAPIENTRY *LPALDOPPLERVELOCITY)( ALfloat value ); +typedef void (ALAPIENTRY *LPALSPEEDOFSOUND)( ALfloat value ); +typedef void (ALAPIENTRY *LPALDISTANCEMODEL)( ALenum distanceModel ); + +#endif /* AL_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export off +#endif /* TARGET_OS_MAC */ +#endif /* TARGET_OS_MAC */ + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __al_h_ */ diff --git a/src/AL/alc.h b/src/AL/alc.h new file mode 100644 index 0000000..f3a41bc --- /dev/null +++ b/src/AL/alc.h @@ -0,0 +1,166 @@ +#ifndef ALC_CONTEXT_H_ +#define ALC_CONTEXT_H_ + +#include "altypes.h" +#include "alctypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ALC_VERSION_0_1 1 + +#ifdef _WIN32 + typedef struct ALCdevice_struct ALCdevice; + typedef struct ALCcontext_struct ALCcontext; + #ifndef _XBOX + #ifdef _OPENAL32LIB + #define ALCAPI __declspec(dllexport) + #else + #define ALCAPI __declspec(dllimport) + #endif + #define ALCAPIENTRY __cdecl + #endif +#endif + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif +#endif + +#ifndef ALCAPI + #define ALCAPI +#endif + +#ifndef ALCAPIENTRY + #define ALCAPIENTRY +#endif + + +#ifndef ALC_NO_PROTOTYPES + +/* + * Context Management + */ +ALCAPI ALCcontext * ALCAPIENTRY alcCreateContext( ALCdevice *device, const ALCint* attrlist ); + +ALCAPI ALCboolean ALCAPIENTRY alcMakeContextCurrent( ALCcontext *context ); + +ALCAPI void ALCAPIENTRY alcProcessContext( ALCcontext *context ); + +ALCAPI void ALCAPIENTRY alcSuspendContext( ALCcontext *context ); + +ALCAPI void ALCAPIENTRY alcDestroyContext( ALCcontext *context ); + +ALCAPI ALCcontext * ALCAPIENTRY alcGetCurrentContext( ALCvoid ); + +ALCAPI ALCdevice* ALCAPIENTRY alcGetContextsDevice( ALCcontext *context ); + + +/* + * Device Management + */ +ALCAPI ALCdevice * ALCAPIENTRY alcOpenDevice( const ALchar *devicename ); + +ALCAPI ALCboolean ALCAPIENTRY alcCloseDevice( ALCdevice *device ); + + +/* + * Error support. + * Obtain the most recent Context error + */ +ALCAPI ALCenum ALCAPIENTRY alcGetError( ALCdevice *device ); + + +/* + * Extension support. + * Query for the presence of an extension, and obtain any appropriate + * function pointers and enum values. + */ +ALCAPI ALCboolean ALCAPIENTRY alcIsExtensionPresent( ALCdevice *device, const ALCchar *extname ); + +ALCAPI void * ALCAPIENTRY alcGetProcAddress( ALCdevice *device, const ALCchar *funcname ); + +ALCAPI ALCenum ALCAPIENTRY alcGetEnumValue( ALCdevice *device, const ALCchar *enumname ); + + +/* + * Query functions + */ +ALCAPI const ALCchar * ALCAPIENTRY alcGetString( ALCdevice *device, ALCenum param ); + +ALCAPI void ALCAPIENTRY alcGetIntegerv( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *data ); + + +/* + * Capture functions + */ +ALCAPI ALCdevice* ALCAPIENTRY alcCaptureOpenDevice( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); + +ALCAPI ALCboolean ALCAPIENTRY alcCaptureCloseDevice( ALCdevice *device ); + +ALCAPI void ALCAPIENTRY alcCaptureStart( ALCdevice *device ); + +ALCAPI void ALCAPIENTRY alcCaptureStop( ALCdevice *device ); + +ALCAPI void ALCAPIENTRY alcCaptureSamples( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); + +#else /* ALC_NO_PROTOTYPES */ +/* +ALCAPI ALCcontext * (ALCAPIENTRY *alcCreateContext)( ALCdevice *device, const ALCint* attrlist ); +ALCAPI ALCboolean (ALCAPIENTRY *alcMakeContextCurrent)( ALCcontext *context ); +ALCAPI void (ALCAPIENTRY *alcProcessContext)( ALCcontext *context ); +ALCAPI void (ALCAPIENTRY *alcSuspendContext)( ALCcontext *context ); +ALCAPI void (ALCAPIENTRY *alcDestroyContext)( ALCcontext *context ); +ALCAPI ALCcontext * (ALCAPIENTRY *alcGetCurrentContext)( ALCvoid ); +ALCAPI ALCdevice * (ALCAPIENTRY *alcGetContextsDevice)( ALCcontext *context ); +ALCAPI ALCdevice * (ALCAPIENTRY *alcOpenDevice)( const ALCchar *devicename ); +ALCAPI ALCboolean (ALCAPIENTRY *alcCloseDevice)( ALCdevice *device ); +ALCAPI ALCenum (ALCAPIENTRY *alcGetError)( ALCdevice *device ); +ALCAPI ALCboolean (ALCAPIENTRY *alcIsExtensionPresent)( ALCdevice *device, const ALCchar *extname ); +ALCAPI void * (ALCAPIENTRY *alcGetProcAddress)( ALCdevice *device, const ALCchar *funcname ); +ALCAPI ALCenum (ALCAPIENTRY *alcGetEnumValue)( ALCdevice *device, const ALCchar *enumname ); +ALCAPI const ALCchar* (ALCAPIENTRY *alcGetString)( ALCdevice *device, ALCenum param ); +ALCAPI void (ALCAPIENTRY *alcGetIntegerv)( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *dest ); +ALCAPI ALCdevice * (ALCAPIENTRY *alcCaptureOpenDevice)( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); +ALCAPI ALCboolean (ALCAPIENTRY *alcCaptureCloseDevice)( ALCdevice *device ); +ALCAPI void (ALCAPIENTRY *alcCaptureStart)( ALCdevice *device ); +ALCAPI void (ALCAPIENTRY *alcCaptureStop)( ALCdevice *device ); +ALCAPI void (ALCAPIENTRY *alcCaptureSamples)( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); +*/ +/* Type definitions */ +typedef ALCcontext * (ALCAPIENTRY *LPALCCREATECONTEXT) (ALCdevice *device, const ALCint *attrlist); +typedef ALCboolean (ALCAPIENTRY *LPALCMAKECONTEXTCURRENT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCPROCESSCONTEXT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCSUSPENDCONTEXT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCDESTROYCONTEXT)( ALCcontext *context ); +typedef ALCcontext * (ALCAPIENTRY *LPALCGETCURRENTCONTEXT)( ALCvoid ); +typedef ALCdevice * (ALCAPIENTRY *LPALCGETCONTEXTSDEVICE)( ALCcontext *context ); +typedef ALCdevice * (ALCAPIENTRY *LPALCOPENDEVICE)( const ALCchar *devicename ); +typedef ALCboolean (ALCAPIENTRY *LPALCCLOSEDEVICE)( ALCdevice *device ); +typedef ALCenum (ALCAPIENTRY *LPALCGETERROR)( ALCdevice *device ); +typedef ALCboolean (ALCAPIENTRY *LPALCISEXTENSIONPRESENT)( ALCdevice *device, const ALCchar *extname ); +typedef void * (ALCAPIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname ); +typedef ALCenum (ALCAPIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname ); +typedef const ALCchar* (ALCAPIENTRY *LPALCGETSTRING)( ALCdevice *device, ALCenum param ); +typedef void (ALCAPIENTRY *LPALCGETINTEGERV)( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *dest ); +typedef ALCdevice * (ALCAPIENTRY *LPALCCAPTUREOPENDEVICE)( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); +typedef ALCboolean (ALCAPIENTRY *LPALCCAPTURECLOSEDEVICE)( ALCdevice *device ); +typedef void (ALCAPIENTRY *LPALCCAPTURESTART)( ALCdevice *device ); +typedef void (ALCAPIENTRY *LPALCCAPTURESTOP)( ALCdevice *device ); +typedef void (ALCAPIENTRY *LPALCCAPTURESAMPLES)( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); + +#endif /* ALC_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export off +#endif /* TARGET_OS_MAC */ +#endif /* TARGET_OS_MAC */ + +#ifdef __cplusplus +} +#endif + +#endif /* ALC_CONTEXT_H_ */ diff --git a/src/AL/alctypes.h b/src/AL/alctypes.h new file mode 100644 index 0000000..a7c7858 --- /dev/null +++ b/src/AL/alctypes.h @@ -0,0 +1,143 @@ +#ifndef _ALCTYPES_H_ +#define _ALCTYPES_H_ + +#if !defined(_WIN32) +struct _AL_device; +typedef struct _AL_device ALCdevice; + +typedef void ALCcontext; +#endif /* _WIN32 */ + +typedef int ALCenum; + +/** ALC boolean type. */ +typedef char ALCboolean; + +/** ALC 8bit signed byte. */ +typedef char ALCbyte; + +/** ALC 8bit unsigned byte. */ +typedef unsigned char ALCubyte; + +/** OpenAL 8bit char */ +typedef char ALCchar; + +/** ALC 16bit signed short integer type. */ +typedef short ALCshort; + +/** ALC 16bit unsigned short integer type. */ +typedef unsigned short ALCushort; + +/** ALC 32bit unsigned integer type. */ +typedef unsigned ALCuint; + +/** ALC 32bit signed integer type. */ +typedef int ALCint; + +/** ALC 32bit floating point type. */ +typedef float ALCfloat; + +/** ALC 64bit double point type. */ +typedef double ALCdouble; + +/** ALC 32bit type. */ +typedef int ALCsizei; + +/** ALC void type */ +typedef void ALCvoid; + +/* Enumerant values begin at column 50. No tabs. */ + +/* bad value */ +#define ALC_INVALID 0 + +/* Boolean False. */ +#define ALC_FALSE 0 + +/* Boolean True. */ +#define ALC_TRUE 1 + +/** + * followed by Hz + */ +#define ALC_FREQUENCY 0x1007 + +/** + * followed by Hz + */ +#define ALC_REFRESH 0x1008 + +/** + * followed by AL_TRUE, AL_FALSE + */ +#define ALC_SYNC 0x1009 + +/** + * followed by Num of requested Mono (3D) Sources + */ +#define ALC_MONO_SOURCES 0x1010 + +/** + * followed by Num of requested Stereo Sources + */ +#define ALC_STEREO_SOURCES 0x1011 + +/** + * errors + */ + +/** + * No error + */ +#define ALC_NO_ERROR ALC_FALSE + +/** + * No device + */ +#define ALC_INVALID_DEVICE 0xA001 + +/** + * invalid context ID + */ +#define ALC_INVALID_CONTEXT 0xA002 + +/** + * bad enum + */ +#define ALC_INVALID_ENUM 0xA003 + +/** + * bad value + */ +#define ALC_INVALID_VALUE 0xA004 + +/** + * Out of memory. + */ +#define ALC_OUT_OF_MEMORY 0xA005 + + + +/** + * The Specifier string for default device + */ +#define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 +#define ALC_DEVICE_SPECIFIER 0x1005 +#define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#define ALC_EXTENSIONS 0x1006 + +#define ALC_MAJOR_VERSION 0x1000 +#define ALC_MINOR_VERSION 0x1001 + +#define ALC_ATTRIBUTES_SIZE 0x1002 +#define ALC_ALL_ATTRIBUTES 0x1003 + +/** + * Capture extension + */ +#define ALC_CAPTURE_DEVICE_SPECIFIER 0x310 +#define ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER 0x311 +#define ALC_CAPTURE_SAMPLES 0x312 + + +#endif /* _ALCTYPES_H */ diff --git a/src/AL/altypes.h b/src/AL/altypes.h new file mode 100644 index 0000000..3ec959f --- /dev/null +++ b/src/AL/altypes.h @@ -0,0 +1,352 @@ +#ifndef _AL_TYPES_H_ +#define _AL_TYPES_H_ + +/* define platform type */ +#if !defined(MACINTOSH_AL) && !defined(LINUX_AL) && !defined(WINDOWS_AL) + #ifdef __APPLE__ + #define MACINTOSH_AL + #else + #ifdef _WIN32 + #define WINDOWS_AL + #else + #define LINUX_AL + #endif + #endif +#endif + +/** OpenAL bool type. */ +typedef char ALboolean; + +/** OpenAL 8bit signed byte. */ +typedef char ALbyte; + +/** OpenAL 8bit unsigned byte. */ +typedef unsigned char ALubyte; + +/** OpenAL 8bit char */ +typedef char ALchar; + +/** OpenAL 16bit signed short integer type. */ +typedef short ALshort; + +/** OpenAL 16bit unsigned short integer type. */ +typedef unsigned short ALushort; + +/** OpenAL 32bit unsigned integer type. */ +typedef unsigned int ALuint; + +/** OpenAL 32bit signed integer type. */ +typedef int ALint; + +/** OpenAL 32bit floating point type. */ +typedef float ALfloat; + +/** OpenAL 64bit double point type. */ +typedef double ALdouble; + +/** OpenAL 32bit type. */ +typedef int ALsizei; + +/** OpenAL void type (for params, not returns). */ +typedef void ALvoid; + +/** OpenAL enumerations. */ +typedef int ALenum; + +/** OpenAL bitfields. */ +typedef unsigned int ALbitfield; + +/** OpenAL clamped float. */ +typedef ALfloat ALclampf; + +/** Openal clamped double. */ +typedef ALdouble ALclampd; + +/* Enumerant values begin at column 50. No tabs. */ + +/* bad value */ +#define AL_INVALID -1 + +#define AL_NONE 0 + +/* Boolean False. */ +#define AL_FALSE 0 + +/** Boolean True. */ +#define AL_TRUE 1 + +/** Indicate Source has relative coordinates. */ +#define AL_SOURCE_RELATIVE 0x202 + + + +/** + * Directional source, inner cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_INNER_ANGLE 0x1001 + +/** + * Directional source, outer cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_OUTER_ANGLE 0x1002 + +/** + * Specify the pitch to be applied, either at source, + * or on mixer results, at listener. + * Range: [0.5-2.0] + * Default: 1.0 + */ +#define AL_PITCH 0x1003 + +/** + * Specify the current location in three dimensional space. + * OpenAL, like OpenGL, uses a right handed coordinate system, + * where in a frontal default view X (thumb) points right, + * Y points up (index finger), and Z points towards the + * viewer/camera (middle finger). + * To switch from a left handed coordinate system, flip the + * sign on the Z coordinate. + * Listener position is always in the world coordinate system. + */ +#define AL_POSITION 0x1004 + +/** Specify the current direction. */ +#define AL_DIRECTION 0x1005 + +/** Specify the current velocity in three dimensional space. */ +#define AL_VELOCITY 0x1006 + +/** + * Indicate whether source is looping. + * Type: ALboolean? + * Range: [AL_TRUE, AL_FALSE] + * Default: FALSE. + */ +#define AL_LOOPING 0x1007 + +/** + * Indicate the buffer to provide sound samples. + * Type: ALuint. + * Range: any valid Buffer id. + */ +#define AL_BUFFER 0x1009 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_GAIN 0x100A + +/* + * Indicate minimum source attenuation + * Type: ALfloat + * Range: [0.0 - 1.0] + * + * Logarthmic + */ +#define AL_MIN_GAIN 0x100D + +/** + * Indicate maximum source attenuation + * Type: ALfloat + * Range: [0.0 - 1.0] + * + * Logarthmic + */ +#define AL_MAX_GAIN 0x100E + +/** + * Indicate listener orientation. + * + * at/up + */ +#define AL_ORIENTATION 0x100F + +/** + * Specify the channel mask. (Creative) + * Type: ALuint + * Range: [0 - 255] + */ +#define AL_CHANNEL_MASK 0x3000 + + +/** + * Source state information. + */ +#define AL_SOURCE_STATE 0x1010 +#define AL_INITIAL 0x1011 +#define AL_PLAYING 0x1012 +#define AL_PAUSED 0x1013 +#define AL_STOPPED 0x1014 + +/** + * Buffer Queue params + */ +#define AL_BUFFERS_QUEUED 0x1015 +#define AL_BUFFERS_PROCESSED 0x1016 + +/** + * Source buffer position information + */ +#define AL_SEC_OFFSET 0x1024 +#define AL_SAMPLE_OFFSET 0x1025 +#define AL_BYTE_OFFSET 0x1026 + +/* + * Source type (Static, Streaming or undetermined) + * Source is Static if a Buffer has been attached using AL_BUFFER + * Source is Streaming if one or more Buffers have been attached using alSourceQueueBuffers + * Source is undetermined when it has the NULL buffer attached + */ +#define AL_SOURCE_TYPE 0x1027 +#define AL_STATIC 0x1028 +#define AL_STREAMING 0x1029 +#define AL_UNDETERMINED 0x1030 + +/** Sound samples: format specifier. */ +#define AL_FORMAT_MONO8 0x1100 +#define AL_FORMAT_MONO16 0x1101 +#define AL_FORMAT_STEREO8 0x1102 +#define AL_FORMAT_STEREO16 0x1103 + +/** + * source specific reference distance + * Type: ALfloat + * Range: 0.0 - +inf + * + * At 0.0, no distance attenuation occurs. Default is + * 1.0. + */ +#define AL_REFERENCE_DISTANCE 0x1020 + +/** + * source specific rolloff factor + * Type: ALfloat + * Range: 0.0 - +inf + * + */ +#define AL_ROLLOFF_FACTOR 0x1021 + +/** + * Directional source, outer cone gain. + * + * Default: 0.0 + * Range: [0.0 - 1.0] + * Logarithmic + */ +#define AL_CONE_OUTER_GAIN 0x1022 + +/** + * Indicate distance above which sources are not + * attenuated using the inverse clamped distance model. + * + * Default: +inf + * Type: ALfloat + * Range: 0.0 - +inf + */ +#define AL_MAX_DISTANCE 0x1023 + +/** + * Sound samples: frequency, in units of Hertz [Hz]. + * This is the number of samples per second. Half of the + * sample frequency marks the maximum significant + * frequency component. + */ +#define AL_FREQUENCY 0x2001 +#define AL_BITS 0x2002 +#define AL_CHANNELS 0x2003 +#define AL_SIZE 0x2004 +#define AL_DATA 0x2005 + +/** + * Buffer state. + * + * Not supported for public use (yet). + */ +#define AL_UNUSED 0x2010 +#define AL_PENDING 0x2011 +#define AL_PROCESSED 0x2012 + + +/** Errors: No Error. */ +#define AL_NO_ERROR AL_FALSE + +/** + * Invalid Name paramater passed to AL call. + */ +#define AL_INVALID_NAME 0xA001 + +/** + * Invalid parameter passed to AL call. + */ +#define AL_ILLEGAL_ENUM 0xA002 +#define AL_INVALID_ENUM 0xA002 + +/** + * Invalid enum parameter value. + */ +#define AL_INVALID_VALUE 0xA003 + +/** + * Illegal call. + */ +#define AL_ILLEGAL_COMMAND 0xA004 +#define AL_INVALID_OPERATION 0xA004 + + +/** + * No mojo. + */ +#define AL_OUT_OF_MEMORY 0xA005 + + +/** Context strings: Vendor Name. */ +#define AL_VENDOR 0xB001 +#define AL_VERSION 0xB002 +#define AL_RENDERER 0xB003 +#define AL_EXTENSIONS 0xB004 + +/** Global tweakage. */ + +/** + * Doppler scale. Default 1.0 + */ +#define AL_DOPPLER_FACTOR 0xC000 + +/** + * Tweaks speed of propagation. + */ +#define AL_DOPPLER_VELOCITY 0xC001 + +/** + * Speed of Sound in units per second + */ +#define AL_SPEED_OF_SOUND 0xC003 + +/** + * Distance models + * + * used in conjunction with DistanceModel + * + * implicit: NONE, which disances distance attenuation. + */ +#define AL_DISTANCE_MODEL 0xD000 +#define AL_INVERSE_DISTANCE 0xD001 +#define AL_INVERSE_DISTANCE_CLAMPED 0xD002 +#define AL_LINEAR_DISTANCE 0xD003 +#define AL_LINEAR_DISTANCE_CLAMPED 0xD004 +#define AL_EXPONENT_DISTANCE 0xD005 +#define AL_EXPONENT_DISTANCE_CLAMPED 0xD006 + +#endif diff --git a/src/AL/alut.h b/src/AL/alut.h new file mode 100644 index 0000000..e29ae58 --- /dev/null +++ b/src/AL/alut.h @@ -0,0 +1,90 @@ +#ifndef _ALUT_H_ +#define _ALUT_H_ + +/* define platform type */ +#if !defined(MACINTOSH_AL) && !defined(LINUX_AL) && !defined(WINDOWS_AL) + #ifdef __APPLE__ + #define MACINTOSH_AL + #else + #ifdef _WIN32 + #define WINDOWS_AL + #else + #define LINUX_AL + #endif + #endif +#endif + +#include "altypes.h" + +#ifdef _WIN32 +#define ALUTAPI +#define ALUTAPIENTRY __cdecl +#define AL_CALLBACK +#else /* _WIN32 */ + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export on +#endif /* TARGET_OS_MAC */ +#endif /* TARGET_OS_MAC */ + +#ifndef ALUTAPI +#define ALUTAPI +#endif + +#ifndef ALUTAPIENTRY +#define ALUTAPIENTRY +#endif + +#ifndef AL_CALLBACK +#define AL_CALLBACK +#endif + +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ALUT_NO_PROTOTYPES + +ALUTAPI void ALUTAPIENTRY alutInit(int *argc, char *argv[]); +ALUTAPI void ALUTAPIENTRY alutExit(ALvoid); + +#ifndef MACINTOSH_AL +/* Windows and Linux versions have a loop parameter, Macintosh doesn't */ +ALUTAPI void ALUTAPIENTRY alutLoadWAVFile(ALbyte *file, ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq, ALboolean *loop); +ALUTAPI void ALUTAPIENTRY alutLoadWAVMemory(ALbyte *memory, ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq, ALboolean *loop); +#else +ALUTAPI void ALUTAPIENTRY alutLoadWAVFile(ALbyte *file, ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq); +ALUTAPI void ALUTAPIENTRY alutLoadWAVMemory(ALbyte *memory, ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq); +#endif + +ALUTAPI void ALUTAPIENTRY alutUnloadWAV(ALenum format, ALvoid *data, ALsizei size, ALsizei freq); + +#else /* ALUT_NO_PROTOTYPES */ + + void (ALUTAPIENTRY *alutInit)( int *argc, char *argv[] ); + void (ALUTAPIENTRY *alutExit)( ALvoid ); +#ifndef MACINTOSH_AL + void (ALUTAPIENTRY *alutLoadWAVFile)( ALbyte *file,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq,ALboolean *loop ); + void (ALUTAPIENTRY *alutLoadWAVMemory)( ALbyte *memory,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq,ALboolean *loop ); +#else + void (ALUTAPIENTRY *alutLoadWAVFile( ALbyte *file,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq ); + void (ALUTAPIENTRY *alutLoadWAVMemory)( ALbyte *memory,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq ); +#endif + void (ALUTAPIENTRY *alutUnloadWAV)( ALenum format,ALvoid *data,ALsizei size,ALsizei freq ); + +#endif /* ALUT_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export off +#endif /* TARGET_OS_MAC */ +#endif /* TARGET_OS_MAC */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/SDL12/include/SDL.h b/src/SDL12/include/SDL.h new file mode 100644 index 0000000..119ed7f --- /dev/null +++ b/src/SDL12/include/SDL.h @@ -0,0 +1,101 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL.h + * Main include header for the SDL library + */ + +#ifndef _SDL_H +#define _SDL_H + +#include "SDL_main.h" +#include "SDL_stdinc.h" +#include "SDL_audio.h" +#include "SDL_cdrom.h" +#include "SDL_cpuinfo.h" +#include "SDL_endian.h" +#include "SDL_error.h" +#include "SDL_events.h" +#include "SDL_loadso.h" +#include "SDL_mutex.h" +#include "SDL_rwops.h" +#include "SDL_thread.h" +#include "SDL_timer.h" +#include "SDL_video.h" +#include "SDL_version.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @file SDL.h + * @note As of version 0.5, SDL is loaded dynamically into the application + */ + +/** @name SDL_INIT Flags + * These are the flags which may be passed to SDL_Init() -- you should + * specify the subsystems which you will be using in your application. + */ +/*@{*/ +#define SDL_INIT_TIMER 0x00000001 +#define SDL_INIT_AUDIO 0x00000010 +#define SDL_INIT_VIDEO 0x00000020 +#define SDL_INIT_CDROM 0x00000100 +#define SDL_INIT_JOYSTICK 0x00000200 +#define SDL_INIT_NOPARACHUTE 0x00100000 /**< Don't catch fatal signals */ +#define SDL_INIT_EVENTTHREAD 0x01000000 /**< Not supported on all OS's */ +#define SDL_INIT_EVERYTHING 0x0000FFFF +/*@}*/ + +/** This function loads the SDL dynamically linked library and initializes + * the subsystems specified by 'flags' (and those satisfying dependencies) + * Unless the SDL_INIT_NOPARACHUTE flag is set, it will install cleanup + * signal handlers for some commonly ignored fatal signals (like SIGSEGV) + */ +extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags); + +/** This function initializes specific SDL subsystems */ +extern DECLSPEC int SDLCALL SDL_InitSubSystem(Uint32 flags); + +/** This function cleans up specific SDL subsystems */ +extern DECLSPEC void SDLCALL SDL_QuitSubSystem(Uint32 flags); + +/** This function returns mask of the specified subsystems which have + * been initialized. + * If 'flags' is 0, it returns a mask of all initialized subsystems. + */ +extern DECLSPEC Uint32 SDLCALL SDL_WasInit(Uint32 flags); + +/** This function cleans up all initialized subsystems and unloads the + * dynamically linked library. You should call it upon all exit conditions. + */ +extern DECLSPEC void SDLCALL SDL_Quit(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_H */ diff --git a/src/SDL12/include/SDL_active.h b/src/SDL12/include/SDL_active.h new file mode 100644 index 0000000..0ae92f2 --- /dev/null +++ b/src/SDL12/include/SDL_active.h @@ -0,0 +1,63 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_active.h + * Include file for SDL application focus event handling + */ + +#ifndef _SDL_active_h +#define _SDL_active_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @name The available application states */ +/*@{*/ +#define SDL_APPMOUSEFOCUS 0x01 /**< The app has mouse coverage */ +#define SDL_APPINPUTFOCUS 0x02 /**< The app has input focus */ +#define SDL_APPACTIVE 0x04 /**< The application is active */ +/*@}*/ + +/* Function prototypes */ +/** + * This function returns the current state of the application, which is a + * bitwise combination of SDL_APPMOUSEFOCUS, SDL_APPINPUTFOCUS, and + * SDL_APPACTIVE. If SDL_APPACTIVE is set, then the user is able to + * see your application, otherwise it has been iconified or disabled. + */ +extern DECLSPEC Uint8 SDLCALL SDL_GetAppState(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_active_h */ diff --git a/src/SDL12/include/SDL_audio.h b/src/SDL12/include/SDL_audio.h new file mode 100644 index 0000000..3a8e7fa --- /dev/null +++ b/src/SDL12/include/SDL_audio.h @@ -0,0 +1,284 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_audio.h + * Access to the raw audio mixing buffer for the SDL library + */ + +#ifndef _SDL_audio_h +#define _SDL_audio_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_endian.h" +#include "SDL_mutex.h" +#include "SDL_thread.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * When filling in the desired audio spec structure, + * - 'desired->freq' should be the desired audio frequency in samples-per-second. + * - 'desired->format' should be the desired audio format. + * - 'desired->samples' is the desired size of the audio buffer, in samples. + * This number should be a power of two, and may be adjusted by the audio + * driver to a value more suitable for the hardware. Good values seem to + * range between 512 and 8096 inclusive, depending on the application and + * CPU speed. Smaller values yield faster response time, but can lead + * to underflow if the application is doing heavy processing and cannot + * fill the audio buffer in time. A stereo sample consists of both right + * and left channels in LR ordering. + * Note that the number of samples is directly related to time by the + * following formula: ms = (samples*1000)/freq + * - 'desired->size' is the size in bytes of the audio buffer, and is + * calculated by SDL_OpenAudio(). + * - 'desired->silence' is the value used to set the buffer to silence, + * and is calculated by SDL_OpenAudio(). + * - 'desired->callback' should be set to a function that will be called + * when the audio device is ready for more data. It is passed a pointer + * to the audio buffer, and the length in bytes of the audio buffer. + * This function usually runs in a separate thread, and so you should + * protect data structures that it accesses by calling SDL_LockAudio() + * and SDL_UnlockAudio() in your code. + * - 'desired->userdata' is passed as the first parameter to your callback + * function. + * + * @note The calculated values in this structure are calculated by SDL_OpenAudio() + * + */ +typedef struct SDL_AudioSpec { + int freq; /**< DSP frequency -- samples per second */ + Uint16 format; /**< Audio data format */ + Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */ + Uint8 silence; /**< Audio buffer silence value (calculated) */ + Uint16 samples; /**< Audio buffer size in samples (power of 2) */ + Uint16 padding; /**< Necessary for some compile environments */ + Uint32 size; /**< Audio buffer size in bytes (calculated) */ + /** + * This function is called when the audio device needs more data. + * + * @param[out] stream A pointer to the audio data buffer + * @param[in] len The length of the audio buffer in bytes. + * + * Once the callback returns, the buffer will no longer be valid. + * Stereo samples are stored in a LRLRLR ordering. + */ + void (SDLCALL *callback)(void *userdata, Uint8 *stream, int len); + void *userdata; +} SDL_AudioSpec; + +/** + * @name Audio format flags + * defaults to LSB byte order + */ +/*@{*/ +#define AUDIO_U8 0x0008 /**< Unsigned 8-bit samples */ +#define AUDIO_S8 0x8008 /**< Signed 8-bit samples */ +#define AUDIO_U16LSB 0x0010 /**< Unsigned 16-bit samples */ +#define AUDIO_S16LSB 0x8010 /**< Signed 16-bit samples */ +#define AUDIO_U16MSB 0x1010 /**< As above, but big-endian byte order */ +#define AUDIO_S16MSB 0x9010 /**< As above, but big-endian byte order */ +#define AUDIO_U16 AUDIO_U16LSB +#define AUDIO_S16 AUDIO_S16LSB + +/** + * @name Native audio byte ordering + */ +/*@{*/ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define AUDIO_U16SYS AUDIO_U16LSB +#define AUDIO_S16SYS AUDIO_S16LSB +#else +#define AUDIO_U16SYS AUDIO_U16MSB +#define AUDIO_S16SYS AUDIO_S16MSB +#endif +/*@}*/ + +/*@}*/ + + +/** A structure to hold a set of audio conversion filters and buffers */ +typedef struct SDL_AudioCVT { + int needed; /**< Set to 1 if conversion possible */ + Uint16 src_format; /**< Source audio format */ + Uint16 dst_format; /**< Target audio format */ + double rate_incr; /**< Rate conversion increment */ + Uint8 *buf; /**< Buffer to hold entire audio data */ + int len; /**< Length of original audio buffer */ + int len_cvt; /**< Length of converted audio buffer */ + int len_mult; /**< buffer must be len*len_mult big */ + double len_ratio; /**< Given len, final size is len*len_ratio */ + void (SDLCALL *filters[10])(struct SDL_AudioCVT *cvt, Uint16 format); + int filter_index; /**< Current audio conversion function */ +} SDL_AudioCVT; + + +/* Function prototypes */ + +/** + * @name Audio Init and Quit + * These functions are used internally, and should not be used unless you + * have a specific need to specify the audio driver you want to use. + * You should normally use SDL_Init() or SDL_InitSubSystem(). + */ +/*@{*/ +extern DECLSPEC int SDLCALL SDL_AudioInit(const char *driver_name); +extern DECLSPEC void SDLCALL SDL_AudioQuit(void); +/*@}*/ + +/** + * This function fills the given character buffer with the name of the + * current audio driver, and returns a pointer to it if the audio driver has + * been initialized. It returns NULL if no driver has been initialized. + */ +extern DECLSPEC char * SDLCALL SDL_AudioDriverName(char *namebuf, int maxlen); + +/** + * This function opens the audio device with the desired parameters, and + * returns 0 if successful, placing the actual hardware parameters in the + * structure pointed to by 'obtained'. If 'obtained' is NULL, the audio + * data passed to the callback function will be guaranteed to be in the + * requested format, and will be automatically converted to the hardware + * audio format if necessary. This function returns -1 if it failed + * to open the audio device, or couldn't set up the audio thread. + * + * The audio device starts out playing silence when it's opened, and should + * be enabled for playing by calling SDL_PauseAudio(0) when you are ready + * for your audio callback function to be called. Since the audio driver + * may modify the requested size of the audio buffer, you should allocate + * any local mixing buffers after you open the audio device. + * + * @sa SDL_AudioSpec + */ +extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained); + +typedef enum { + SDL_AUDIO_STOPPED = 0, + SDL_AUDIO_PLAYING, + SDL_AUDIO_PAUSED +} SDL_audiostatus; + +/** Get the current audio state */ +extern DECLSPEC SDL_audiostatus SDLCALL SDL_GetAudioStatus(void); + +/** + * This function pauses and unpauses the audio callback processing. + * It should be called with a parameter of 0 after opening the audio + * device to start playing sound. This is so you can safely initialize + * data for your callback function after opening the audio device. + * Silence will be written to the audio device during the pause. + */ +extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on); + +/** + * This function loads a WAVE from the data source, automatically freeing + * that source if 'freesrc' is non-zero. For example, to load a WAVE file, + * you could do: + * @code SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, ...); @endcode + * + * If this function succeeds, it returns the given SDL_AudioSpec, + * filled with the audio data format of the wave data, and sets + * 'audio_buf' to a malloc()'d buffer containing the audio data, + * and sets 'audio_len' to the length of that audio buffer, in bytes. + * You need to free the audio buffer with SDL_FreeWAV() when you are + * done with it. + * + * This function returns NULL and sets the SDL error message if the + * wave file cannot be opened, uses an unknown data format, or is + * corrupt. Currently raw and MS-ADPCM WAVE files are supported. + */ +extern DECLSPEC SDL_AudioSpec * SDLCALL SDL_LoadWAV_RW(SDL_RWops *src, int freesrc, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len); + +/** Compatibility convenience function -- loads a WAV from a file */ +#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \ + SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len) + +/** + * This function frees data previously allocated with SDL_LoadWAV_RW() + */ +extern DECLSPEC void SDLCALL SDL_FreeWAV(Uint8 *audio_buf); + +/** + * This function takes a source format and rate and a destination format + * and rate, and initializes the 'cvt' structure with information needed + * by SDL_ConvertAudio() to convert a buffer of audio data from one format + * to the other. + * + * @return This function returns 0, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_BuildAudioCVT(SDL_AudioCVT *cvt, + Uint16 src_format, Uint8 src_channels, int src_rate, + Uint16 dst_format, Uint8 dst_channels, int dst_rate); + +/** + * Once you have initialized the 'cvt' structure using SDL_BuildAudioCVT(), + * created an audio buffer cvt->buf, and filled it with cvt->len bytes of + * audio data in the source format, this function will convert it in-place + * to the desired format. + * The data conversion may expand the size of the audio data, so the buffer + * cvt->buf should be allocated after the cvt structure is initialized by + * SDL_BuildAudioCVT(), and should be cvt->len*cvt->len_mult bytes long. + */ +extern DECLSPEC int SDLCALL SDL_ConvertAudio(SDL_AudioCVT *cvt); + + +#define SDL_MIX_MAXVOLUME 128 +/** + * This takes two audio buffers of the playing audio format and mixes + * them, performing addition, volume adjustment, and overflow clipping. + * The volume ranges from 0 - 128, and should be set to SDL_MIX_MAXVOLUME + * for full audio volume. Note this does not change hardware volume. + * This is provided for convenience -- you can mix your own audio data. + */ +extern DECLSPEC void SDLCALL SDL_MixAudio(Uint8 *dst, const Uint8 *src, Uint32 len, int volume); + +/** + * @name Audio Locks + * The lock manipulated by these functions protects the callback function. + * During a LockAudio/UnlockAudio pair, you can be guaranteed that the + * callback function is not running. Do not call these from the callback + * function or you will cause deadlock. + */ +/*@{*/ +extern DECLSPEC void SDLCALL SDL_LockAudio(void); +extern DECLSPEC void SDLCALL SDL_UnlockAudio(void); +/*@}*/ + +/** + * This function shuts down audio processing and closes the audio device. + */ +extern DECLSPEC void SDLCALL SDL_CloseAudio(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_audio_h */ diff --git a/src/SDL12/include/SDL_byteorder.h b/src/SDL12/include/SDL_byteorder.h new file mode 100644 index 0000000..9b93cd6 --- /dev/null +++ b/src/SDL12/include/SDL_byteorder.h @@ -0,0 +1,29 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_byteorder.h + * @deprecated Use SDL_endian.h instead + */ + +/* DEPRECATED */ +#include "SDL_endian.h" diff --git a/src/SDL12/include/SDL_cdrom.h b/src/SDL12/include/SDL_cdrom.h new file mode 100644 index 0000000..fff5cfa --- /dev/null +++ b/src/SDL12/include/SDL_cdrom.h @@ -0,0 +1,202 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_cdrom.h + * This is the CD-audio control API for Simple DirectMedia Layer + */ + +#ifndef _SDL_cdrom_h +#define _SDL_cdrom_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file SDL_cdrom.h + * In order to use these functions, SDL_Init() must have been called + * with the SDL_INIT_CDROM flag. This causes SDL to scan the system + * for CD-ROM drives, and load appropriate drivers. + */ + +/** The maximum number of CD-ROM tracks on a disk */ +#define SDL_MAX_TRACKS 99 + +/** @name Track Types + * The types of CD-ROM track possible + */ +/*@{*/ +#define SDL_AUDIO_TRACK 0x00 +#define SDL_DATA_TRACK 0x04 +/*@}*/ + +/** The possible states which a CD-ROM drive can be in. */ +typedef enum { + CD_TRAYEMPTY, + CD_STOPPED, + CD_PLAYING, + CD_PAUSED, + CD_ERROR = -1 +} CDstatus; + +/** Given a status, returns true if there's a disk in the drive */ +#define CD_INDRIVE(status) ((int)(status) > 0) + +typedef struct SDL_CDtrack { + Uint8 id; /**< Track number */ + Uint8 type; /**< Data or audio track */ + Uint16 unused; + Uint32 length; /**< Length, in frames, of this track */ + Uint32 offset; /**< Offset, in frames, from start of disk */ +} SDL_CDtrack; + +/** This structure is only current as of the last call to SDL_CDStatus() */ +typedef struct SDL_CD { + int id; /**< Private drive identifier */ + CDstatus status; /**< Current drive status */ + + /** The rest of this structure is only valid if there's a CD in drive */ + /*@{*/ + int numtracks; /**< Number of tracks on disk */ + int cur_track; /**< Current track position */ + int cur_frame; /**< Current frame offset within current track */ + SDL_CDtrack track[SDL_MAX_TRACKS+1]; + /*@}*/ +} SDL_CD; + +/** @name Frames / MSF Conversion Functions + * Conversion functions from frames to Minute/Second/Frames and vice versa + */ +/*@{*/ +#define CD_FPS 75 +#define FRAMES_TO_MSF(f, M,S,F) { \ + int value = f; \ + *(F) = value%CD_FPS; \ + value /= CD_FPS; \ + *(S) = value%60; \ + value /= 60; \ + *(M) = value; \ +} +#define MSF_TO_FRAMES(M, S, F) ((M)*60*CD_FPS+(S)*CD_FPS+(F)) +/*@}*/ + +/* CD-audio API functions: */ + +/** + * Returns the number of CD-ROM drives on the system, or -1 if + * SDL_Init() has not been called with the SDL_INIT_CDROM flag. + */ +extern DECLSPEC int SDLCALL SDL_CDNumDrives(void); + +/** + * Returns a human-readable, system-dependent identifier for the CD-ROM. + * Example: + * - "/dev/cdrom" + * - "E:" + * - "/dev/disk/ide/1/master" + */ +extern DECLSPEC const char * SDLCALL SDL_CDName(int drive); + +/** + * Opens a CD-ROM drive for access. It returns a drive handle on success, + * or NULL if the drive was invalid or busy. This newly opened CD-ROM + * becomes the default CD used when other CD functions are passed a NULL + * CD-ROM handle. + * Drives are numbered starting with 0. Drive 0 is the system default CD-ROM. + */ +extern DECLSPEC SDL_CD * SDLCALL SDL_CDOpen(int drive); + +/** + * This function returns the current status of the given drive. + * If the drive has a CD in it, the table of contents of the CD and current + * play position of the CD will be stored in the SDL_CD structure. + */ +extern DECLSPEC CDstatus SDLCALL SDL_CDStatus(SDL_CD *cdrom); + +/** + * Play the given CD starting at 'start_track' and 'start_frame' for 'ntracks' + * tracks and 'nframes' frames. If both 'ntrack' and 'nframe' are 0, play + * until the end of the CD. This function will skip data tracks. + * This function should only be called after calling SDL_CDStatus() to + * get track information about the CD. + * For example: + * @code + * // Play entire CD: + * if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) + * SDL_CDPlayTracks(cdrom, 0, 0, 0, 0); + * // Play last track: + * if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) { + * SDL_CDPlayTracks(cdrom, cdrom->numtracks-1, 0, 0, 0); + * } + * // Play first and second track and 10 seconds of third track: + * if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) + * SDL_CDPlayTracks(cdrom, 0, 0, 2, 10); + * @endcode + * + * @return This function returns 0, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_CDPlayTracks(SDL_CD *cdrom, + int start_track, int start_frame, int ntracks, int nframes); + +/** + * Play the given CD starting at 'start' frame for 'length' frames. + * @return It returns 0, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_CDPlay(SDL_CD *cdrom, int start, int length); + +/** Pause play + * @return returns 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CDPause(SDL_CD *cdrom); + +/** Resume play + * @return returns 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CDResume(SDL_CD *cdrom); + +/** Stop play + * @return returns 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CDStop(SDL_CD *cdrom); + +/** Eject CD-ROM + * @return returns 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CDEject(SDL_CD *cdrom); + +/** Closes the handle for the CD-ROM drive */ +extern DECLSPEC void SDLCALL SDL_CDClose(SDL_CD *cdrom); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_video_h */ diff --git a/src/SDL12/include/SDL_config.h b/src/SDL12/include/SDL_config.h new file mode 100644 index 0000000..a508101 --- /dev/null +++ b/src/SDL12/include/SDL_config.h @@ -0,0 +1,45 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_h +#define _SDL_config_h + +#include "SDL_platform.h" + +/* Add any platform that doesn't build using the configure system */ +#if defined(__DREAMCAST__) +#include "SDL_config_dreamcast.h" +#elif defined(__MACOS__) +#include "SDL_config_macos.h" +#elif defined(__MACOSX__) +#include "SDL_config_macosx.h" +#elif defined(__SYMBIAN32__) +#include "SDL_config_symbian.h" /* must be before win32! */ +#elif defined(__WIN32__) +#include "SDL_config_win32.h" +#elif defined(__OS2__) +#include "SDL_config_os2.h" +#else +#include "SDL_config_minimal.h" +#endif /* platform config */ + +#endif /* _SDL_config_h */ diff --git a/src/SDL12/include/SDL_config_amiga.h b/src/SDL12/include/SDL_config_amiga.h new file mode 100644 index 0000000..23e0861 --- /dev/null +++ b/src/SDL12/include/SDL_config_amiga.h @@ -0,0 +1,80 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2006 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_amiga_h +#define _SDL_config_amiga_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +#define SDL_HAS_64BIT_TYPE 1 + +/* Useful headers */ +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_AHI 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#define SDL_CDROM_DUMMY 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_AMIGA 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_DUMMY 1 + +/* Enable various threading systems */ +#define SDL_THREAD_AMIGA 1 + +/* Enable various timer systems */ +#define SDL_TIMER_AMIGA 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_CYBERGRAPHICS 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 + +/* Enable OpenGL support */ +#define SDL_VIDEO_OPENGL 1 + +#endif /* _SDL_config_amiga_h */ diff --git a/src/SDL12/include/SDL_config_dreamcast.h b/src/SDL12/include/SDL_config_dreamcast.h new file mode 100644 index 0000000..07c2f08 --- /dev/null +++ b/src/SDL12/include/SDL_config_dreamcast.h @@ -0,0 +1,106 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_dreamcast_h +#define _SDL_config_dreamcast_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; +typedef unsigned long uintptr_t; +#define SDL_HAS_64BIT_TYPE 1 + +/* Useful headers */ +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRDUP 1 +#define HAVE_INDEX 1 +#define HAVE_RINDEX 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRICMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_DC 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#define SDL_CDROM_DC 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_DC 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_DUMMY 1 + +/* Enable various threading systems */ +#define SDL_THREAD_DC 1 + +/* Enable various timer systems */ +#define SDL_TIMER_DC 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DC 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 + +#endif /* _SDL_config_dreamcast_h */ diff --git a/src/SDL12/include/SDL_config_macos.h b/src/SDL12/include/SDL_config_macos.h new file mode 100644 index 0000000..4ba5c22 --- /dev/null +++ b/src/SDL12/include/SDL_config_macos.h @@ -0,0 +1,112 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_macos_h +#define _SDL_config_macos_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +#include + +typedef SInt8 int8_t; +typedef UInt8 uint8_t; +typedef SInt16 int16_t; +typedef UInt16 uint16_t; +typedef SInt32 int32_t; +typedef UInt32 uint32_t; +typedef SInt64 int64_t; +typedef UInt64 uint64_t; +typedef unsigned long uintptr_t; + +#define SDL_HAS_64BIT_TYPE 1 + +/* Useful headers */ +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_ABS 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_SSCANF 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_SNDMGR 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#if TARGET_API_MAC_CARBON +#define SDL_CDROM_DUMMY 1 +#else +#define SDL_CDROM_MACOS 1 +#endif + +/* Enable various input drivers */ +#if TARGET_API_MAC_CARBON +#define SDL_JOYSTICK_DUMMY 1 +#else +#define SDL_JOYSTICK_MACOS 1 +#endif + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_MACOS 1 + +/* Enable various threading systems */ +#define SDL_THREADS_DISABLED 1 + +/* Enable various timer systems */ +#define SDL_TIMER_MACOS 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_DRAWSPROCKET 1 +#define SDL_VIDEO_DRIVER_TOOLBOX 1 + +/* Enable OpenGL support */ +#define SDL_VIDEO_OPENGL 1 + +#endif /* _SDL_config_macos_h */ diff --git a/src/SDL12/include/SDL_config_macosx.h b/src/SDL12/include/SDL_config_macosx.h new file mode 100644 index 0000000..295b872 --- /dev/null +++ b/src/SDL12/include/SDL_config_macosx.h @@ -0,0 +1,150 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_macosx_h +#define _SDL_config_macosx_h + +#include "SDL_platform.h" + +/* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */ +#include + +/* This is a set of defines to configure the SDL features */ + +#define SDL_HAS_64BIT_TYPE 1 + +/* Useful headers */ +/* If we specified an SDK or have a post-PowerPC chip, then alloca.h exists. */ +#if ( (MAC_OS_X_VERSION_MIN_REQUIRED >= 1030) || (!defined(__POWERPC__)) ) +#define HAVE_ALLOCA_H 1 +#endif +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_COREAUDIO 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#define SDL_CDROM_MACOSX 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_IOKIT 1 + +/* Enable various shared object loading systems */ +#ifdef __ppc__ +/* For Mac OS X 10.2 compatibility */ +#define SDL_LOADSO_DLCOMPAT 1 +#else +#define SDL_LOADSO_DLOPEN 1 +#endif + +/* Enable various threading systems */ +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 + +/* Enable various timer systems */ +#define SDL_TIMER_UNIX 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#if ((defined TARGET_API_MAC_CARBON) && (TARGET_API_MAC_CARBON)) +#define SDL_VIDEO_DRIVER_TOOLBOX 1 +#else +#define SDL_VIDEO_DRIVER_QUARTZ 1 +#endif +#define SDL_VIDEO_DRIVER_DGA 1 +#define SDL_VIDEO_DRIVER_X11 1 +#define SDL_VIDEO_DRIVER_X11_DGAMOUSE 1 +#define SDL_VIDEO_DRIVER_X11_DYNAMIC "/usr/X11R6/lib/libX11.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT "/usr/X11R6/lib/libXext.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "/usr/X11R6/lib/libXrandr.2.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRENDER "/usr/X11R6/lib/libXrender.1.dylib" +#define SDL_VIDEO_DRIVER_X11_VIDMODE 1 +#define SDL_VIDEO_DRIVER_X11_XINERAMA 1 +#define SDL_VIDEO_DRIVER_X11_XME 1 +#define SDL_VIDEO_DRIVER_X11_XRANDR 1 +#define SDL_VIDEO_DRIVER_X11_XV 1 + +/* Enable OpenGL support */ +#define SDL_VIDEO_OPENGL 1 +#define SDL_VIDEO_OPENGL_GLX 1 + +/* Disable screensaver */ +#define SDL_VIDEO_DISABLE_SCREENSAVER 1 + +/* Enable assembly routines */ +#define SDL_ASSEMBLY_ROUTINES 1 +#ifdef __ppc__ +#define SDL_ALTIVEC_BLITTERS 1 +#endif + +#endif /* _SDL_config_macosx_h */ diff --git a/src/SDL12/include/SDL_config_minimal.h b/src/SDL12/include/SDL_config_minimal.h new file mode 100644 index 0000000..002c56e --- /dev/null +++ b/src/SDL12/include/SDL_config_minimal.h @@ -0,0 +1,62 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_minimal_h +#define _SDL_config_minimal_h + +#include "SDL_platform.h" + +/* This is the minimal configuration that can be used to build SDL */ + +#include + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef unsigned int size_t; +typedef unsigned long uintptr_t; + +/* Enable the dummy audio driver (src/audio/dummy/\*.c) */ +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable the stub cdrom driver (src/cdrom/dummy/\*.c) */ +#define SDL_CDROM_DISABLED 1 + +/* Enable the stub joystick driver (src/joystick/dummy/\*.c) */ +#define SDL_JOYSTICK_DISABLED 1 + +/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */ +#define SDL_LOADSO_DISABLED 1 + +/* Enable the stub thread support (src/thread/generic/\*.c) */ +#define SDL_THREADS_DISABLED 1 + +/* Enable the stub timer support (src/timer/dummy/\*.c) */ +#define SDL_TIMERS_DISABLED 1 + +/* Enable the dummy video driver (src/video/dummy/\*.c) */ +#define SDL_VIDEO_DRIVER_DUMMY 1 + +#endif /* _SDL_config_minimal_h */ diff --git a/src/SDL12/include/SDL_config_nds.h b/src/SDL12/include/SDL_config_nds.h new file mode 100644 index 0000000..4ac60a5 --- /dev/null +++ b/src/SDL12/include/SDL_config_nds.h @@ -0,0 +1,115 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_nds_h +#define _SDL_config_nds_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +/* General platform specific identifiers */ +#include "SDL_platform.h" + +/* C datatypes */ +#define SDL_HAS_64BIT_TYPE 1 + +/* Endianness */ +#define SDL_BYTEORDER 1234 + +/* Useful headers */ +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_ICONV_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_SETJMP 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_NDS 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable the stub cdrom driver (src/cdrom/dummy/\*.c) */ +#define SDL_CDROM_DISABLED 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_NDS 1 + +/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */ +#define SDL_LOADSO_DISABLED 1 + +/* Enable the stub thread support (src/thread/generic/\*.c) */ +#define SDL_THREADS_DISABLED 1 + +/* Enable various timer systems */ +#define SDL_TIMER_NDS 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_NDS 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 + +#endif /* _SDL_config_nds_h */ diff --git a/src/SDL12/include/SDL_config_os2.h b/src/SDL12/include/SDL_config_os2.h new file mode 100644 index 0000000..bb40df0 --- /dev/null +++ b/src/SDL12/include/SDL_config_os2.h @@ -0,0 +1,141 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_os2_h +#define _SDL_config_os2_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef unsigned int size_t; +typedef unsigned long uintptr_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; + +#define SDL_HAS_64BIT_TYPE 1 + +/* Use Watcom's LIBC */ +#define HAVE_LIBC 1 + +/* Useful headers */ +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE__STRREV 1 +#define HAVE__STRUPR 1 +#define HAVE__STRLWR 1 +#define HAVE_INDEX 1 +#define HAVE_RINDEX 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE__LTOA 1 +#define HAVE__UITOA 1 +#define HAVE__ULTOA 1 +#define HAVE_STRTOL 1 +#define HAVE__I64TOA 1 +#define HAVE__UI64TOA 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRICMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_SETJMP 1 +#define HAVE_CLOCK_GETTIME 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_DART 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#define SDL_CDROM_OS2 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_OS2 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_OS2 1 + +/* Enable various threading systems */ +#define SDL_THREAD_OS2 1 + +/* Enable various timer systems */ +#define SDL_TIMER_OS2 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_OS2FS 1 + +/* Enable OpenGL support */ +/* Nothing here yet for OS/2... :( */ + +/* Enable assembly routines where available */ +#define SDL_ASSEMBLY_ROUTINES 1 + +#endif /* _SDL_config_os2_h */ diff --git a/src/SDL12/include/SDL_config_symbian.h b/src/SDL12/include/SDL_config_symbian.h new file mode 100644 index 0000000..53527b2 --- /dev/null +++ b/src/SDL12/include/SDL_config_symbian.h @@ -0,0 +1,146 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/* + +Symbian version Markus Mertama + +*/ + + +#ifndef _SDL_CONFIG_SYMBIAN_H +#define _SDL_CONFIG_SYMBIAN_H + +#include "SDL_platform.h" + +/* This is the minimal configuration that can be used to build SDL */ + + +#include +#include + + +#ifdef __GCCE__ +#define SYMBIAN32_GCCE +#endif + +#ifndef _SIZE_T_DEFINED +typedef unsigned int size_t; +#endif + +#ifndef _INTPTR_T_DECLARED +typedef unsigned int uintptr_t; +#endif + +#ifndef _INT8_T_DECLARED +typedef signed char int8_t; +#endif + +#ifndef _UINT8_T_DECLARED +typedef unsigned char uint8_t; +#endif + +#ifndef _INT16_T_DECLARED +typedef signed short int16_t; +#endif + +#ifndef _UINT16_T_DECLARED +typedef unsigned short uint16_t; +#endif + +#ifndef _INT32_T_DECLARED +typedef signed int int32_t; +#endif + +#ifndef _UINT32_T_DECLARED +typedef unsigned int uint32_t; +#endif + +#ifndef _INT64_T_DECLARED +typedef signed long long int64_t; +#endif + +#ifndef _UINT64_T_DECLARED +typedef unsigned long long uint64_t; +#endif + +#define SDL_AUDIO_DRIVER_EPOCAUDIO 1 + + +/* Enable the stub cdrom driver (src/cdrom/dummy/\*.c) */ +#define SDL_CDROM_DISABLED 1 + +/* Enable the stub joystick driver (src/joystick/dummy/\*.c) */ +#define SDL_JOYSTICK_DISABLED 1 + +/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */ +#define SDL_LOADSO_DISABLED 1 + +#define SDL_THREAD_SYMBIAN 1 + +#define SDL_VIDEO_DRIVER_EPOC 1 + +#define SDL_VIDEO_OPENGL 0 + +#define SDL_HAS_64BIT_TYPE 1 + +#define HAVE_LIBC 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 + +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +/*#define HAVE_ALLOCA 1*/ +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE__STRUPR 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +/*#define HAVE__STRICMP 1*/ +#define HAVE__STRNICMP 1 +#define HAVE_SSCANF 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 + + + +#endif /* _SDL_CONFIG_SYMBIAN_H */ diff --git a/src/SDL12/include/SDL_config_win32.h b/src/SDL12/include/SDL_config_win32.h new file mode 100644 index 0000000..6d019a8 --- /dev/null +++ b/src/SDL12/include/SDL_config_win32.h @@ -0,0 +1,183 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_win32_h +#define _SDL_config_win32_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +#if defined(__GNUC__) || defined(__DMC__) +#define HAVE_STDINT_H 1 +#elif defined(_MSC_VER) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifndef _UINTPTR_T_DEFINED +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +typedef unsigned int uintptr_t; +#endif +#define _UINTPTR_T_DEFINED +#endif +/* Older Visual C++ headers don't have the Win64-compatible typedefs... */ +#if ((_MSC_VER <= 1200) && (!defined(DWORD_PTR))) +#define DWORD_PTR DWORD +#endif +#if ((_MSC_VER <= 1200) && (!defined(LONG_PTR))) +#define LONG_PTR LONG +#endif +#else /* !__GNUC__ && !_MSC_VER */ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; +#ifndef _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED_ +typedef unsigned int size_t; +#endif +typedef unsigned int uintptr_t; +#endif /* __GNUC__ || _MSC_VER */ +#define SDL_HAS_64BIT_TYPE 1 + +/* Enabled for SDL 1.2 (binary compatibility) */ +#define HAVE_LIBC 1 +#ifdef HAVE_LIBC +/* Useful headers */ +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#ifndef _WIN32_WCE +#define HAVE_SIGNAL_H 1 +#endif + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE__STRREV 1 +#define HAVE__STRUPR 1 +#define HAVE__STRLWR 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE__LTOA 1 +#define HAVE__ULTOA 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE__STRICMP 1 +#define HAVE__STRNICMP 1 +#define HAVE_SSCANF 1 +#else +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#endif + +/* Enable various audio drivers */ +#ifndef _WIN32_WCE +#define SDL_AUDIO_DRIVER_DSOUND 1 +#endif +#define SDL_AUDIO_DRIVER_WAVEOUT 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#ifdef _WIN32_WCE +#define SDL_CDROM_DISABLED 1 +#else +#define SDL_CDROM_WIN32 1 +#endif + +/* Enable various input drivers */ +#ifdef _WIN32_WCE +#define SDL_JOYSTICK_DISABLED 1 +#else +#define SDL_JOYSTICK_WINMM 1 +#endif + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_WIN32 1 + +/* Enable various threading systems */ +#define SDL_THREAD_WIN32 1 + +/* Enable various timer systems */ +#ifdef _WIN32_WCE +#define SDL_TIMER_WINCE 1 +#else +#define SDL_TIMER_WIN32 1 +#endif + +/* Enable various video drivers */ +#ifdef _WIN32_WCE +#define SDL_VIDEO_DRIVER_GAPI 1 +#endif +#ifndef _WIN32_WCE +#define SDL_VIDEO_DRIVER_DDRAW 1 +#endif +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_WINDIB 1 + +/* Enable OpenGL support */ +#ifndef _WIN32_WCE +#define SDL_VIDEO_OPENGL 1 +#define SDL_VIDEO_OPENGL_WGL 1 +#endif + +/* Disable screensaver */ +#define SDL_VIDEO_DISABLE_SCREENSAVER 1 + +/* Enable assembly routines (Win64 doesn't have inline asm) */ +#ifndef _WIN64 +#define SDL_ASSEMBLY_ROUTINES 1 +#endif + +#endif /* _SDL_config_win32_h */ diff --git a/src/SDL12/include/SDL_copying.h b/src/SDL12/include/SDL_copying.h new file mode 100644 index 0000000..1bd6b84 --- /dev/null +++ b/src/SDL12/include/SDL_copying.h @@ -0,0 +1,22 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + diff --git a/src/SDL12/include/SDL_cpuinfo.h b/src/SDL12/include/SDL_cpuinfo.h new file mode 100644 index 0000000..f4be8e0 --- /dev/null +++ b/src/SDL12/include/SDL_cpuinfo.h @@ -0,0 +1,69 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_cpuinfo.h + * CPU feature detection for SDL + */ + +#ifndef _SDL_cpuinfo_h +#define _SDL_cpuinfo_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** This function returns true if the CPU has the RDTSC instruction */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasRDTSC(void); + +/** This function returns true if the CPU has MMX features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasMMX(void); + +/** This function returns true if the CPU has MMX Ext. features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasMMXExt(void); + +/** This function returns true if the CPU has 3DNow features */ +extern DECLSPEC SDL_bool SDLCALL SDL_Has3DNow(void); + +/** This function returns true if the CPU has 3DNow! Ext. features */ +extern DECLSPEC SDL_bool SDLCALL SDL_Has3DNowExt(void); + +/** This function returns true if the CPU has SSE features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE(void); + +/** This function returns true if the CPU has SSE2 features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE2(void); + +/** This function returns true if the CPU has AltiVec features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasAltiVec(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_cpuinfo_h */ diff --git a/src/SDL12/include/SDL_endian.h b/src/SDL12/include/SDL_endian.h new file mode 100644 index 0000000..f7a2e2f --- /dev/null +++ b/src/SDL12/include/SDL_endian.h @@ -0,0 +1,209 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_endian.h + * Functions for reading and writing endian-specific values + */ + +#ifndef _SDL_endian_h +#define _SDL_endian_h + +#include "SDL_stdinc.h" + +/** @name SDL_ENDIANs + * The two types of endianness + */ +/*@{*/ +#define SDL_LIL_ENDIAN 1234 +#define SDL_BIG_ENDIAN 4321 +/*@}*/ + +#ifndef SDL_BYTEORDER /* Not defined in SDL_config.h? */ +#if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MISPEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) +#define SDL_BYTEORDER SDL_BIG_ENDIAN +#else +#define SDL_BYTEORDER SDL_LIL_ENDIAN +#endif +#endif /* !SDL_BYTEORDER */ + + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name SDL_Swap Functions + * Use inline functions for compilers that support them, and static + * functions for those that do not. Because these functions become + * static for compilers that do not support inline functions, this + * header should only be included in files that actually use them. + */ +/*@{*/ +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */) +static __inline__ Uint16 SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0" : "=q" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +static __inline__ Uint16 SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0" : "=Q" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +static __inline__ Uint16 SDL_Swap16(Uint16 x) +{ + Uint16 result; + + __asm__("rlwimi %0,%2,8,16,23" : "=&r" (result) : "0" (x >> 8), "r" (x)); + return result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) +static __inline__ Uint16 SDL_Swap16(Uint16 x) +{ + __asm__("rorw #8,%0" : "=d" (x) : "0" (x) : "cc"); + return x; +} +#else +static __inline__ Uint16 SDL_Swap16(Uint16 x) { + return((x<<8)|(x>>8)); +} +#endif + +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */) +static __inline__ Uint32 SDL_Swap32(Uint32 x) +{ + __asm__("bswap %0" : "=r" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +static __inline__ Uint32 SDL_Swap32(Uint32 x) +{ + __asm__("bswapl %0" : "=r" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +static __inline__ Uint32 SDL_Swap32(Uint32 x) +{ + Uint32 result; + + __asm__("rlwimi %0,%2,24,16,23" : "=&r" (result) : "0" (x>>24), "r" (x)); + __asm__("rlwimi %0,%2,8,8,15" : "=&r" (result) : "0" (result), "r" (x)); + __asm__("rlwimi %0,%2,24,0,7" : "=&r" (result) : "0" (result), "r" (x)); + return result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) +static __inline__ Uint32 SDL_Swap32(Uint32 x) +{ + __asm__("rorw #8,%0\n\tswap %0\n\trorw #8,%0" : "=d" (x) : "0" (x) : "cc"); + return x; +} +#else +static __inline__ Uint32 SDL_Swap32(Uint32 x) { + return((x<<24)|((x<<8)&0x00FF0000)|((x>>8)&0x0000FF00)|(x>>24)); +} +#endif + +#ifdef SDL_HAS_64BIT_TYPE +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */) +static __inline__ Uint64 SDL_Swap64(Uint64 x) +{ + union { + struct { Uint32 a,b; } s; + Uint64 u; + } v; + v.u = x; + __asm__("bswapl %0 ; bswapl %1 ; xchgl %0,%1" + : "=r" (v.s.a), "=r" (v.s.b) + : "0" (v.s.a), "1" (v.s.b)); + return v.u; +} +#elif defined(__GNUC__) && defined(__x86_64__) +static __inline__ Uint64 SDL_Swap64(Uint64 x) +{ + __asm__("bswapq %0" : "=r" (x) : "0" (x)); + return x; +} +#else +static __inline__ Uint64 SDL_Swap64(Uint64 x) +{ + Uint32 hi, lo; + + /* Separate into high and low 32-bit values and swap them */ + lo = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x >>= 32; + hi = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x = SDL_Swap32(lo); + x <<= 32; + x |= SDL_Swap32(hi); + return(x); +} +#endif +#else +/* This is mainly to keep compilers from complaining in SDL code. + * If there is no real 64-bit datatype, then compilers will complain about + * the fake 64-bit datatype that SDL provides when it compiles user code. + */ +#define SDL_Swap64(X) (X) +#endif /* SDL_HAS_64BIT_TYPE */ +/*@}*/ + +/** + * @name SDL_SwapLE and SDL_SwapBE Functions + * Byteswap item from the specified endianness to the native endianness + */ +/*@{*/ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define SDL_SwapLE16(X) (X) +#define SDL_SwapLE32(X) (X) +#define SDL_SwapLE64(X) (X) +#define SDL_SwapBE16(X) SDL_Swap16(X) +#define SDL_SwapBE32(X) SDL_Swap32(X) +#define SDL_SwapBE64(X) SDL_Swap64(X) +#else +#define SDL_SwapLE16(X) SDL_Swap16(X) +#define SDL_SwapLE32(X) SDL_Swap32(X) +#define SDL_SwapLE64(X) SDL_Swap64(X) +#define SDL_SwapBE16(X) (X) +#define SDL_SwapBE32(X) (X) +#define SDL_SwapBE64(X) (X) +#endif +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_endian_h */ diff --git a/src/SDL12/include/SDL_error.h b/src/SDL12/include/SDL_error.h new file mode 100644 index 0000000..b103703 --- /dev/null +++ b/src/SDL12/include/SDL_error.h @@ -0,0 +1,72 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_error.h + * Simple error message routines for SDL + */ + +#ifndef _SDL_error_h +#define _SDL_error_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Public functions + */ +/*@{*/ +extern DECLSPEC void SDLCALL SDL_SetError(const char *fmt, ...); +extern DECLSPEC char * SDLCALL SDL_GetError(void); +extern DECLSPEC void SDLCALL SDL_ClearError(void); +/*@}*/ + +/** + * @name Private functions + * @internal Private error message function - used internally + */ +/*@{*/ +#define SDL_OutOfMemory() SDL_Error(SDL_ENOMEM) +#define SDL_Unsupported() SDL_Error(SDL_UNSUPPORTED) +typedef enum { + SDL_ENOMEM, + SDL_EFREAD, + SDL_EFWRITE, + SDL_EFSEEK, + SDL_UNSUPPORTED, + SDL_LASTERROR +} SDL_errorcode; +extern DECLSPEC void SDLCALL SDL_Error(SDL_errorcode code); +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_error_h */ diff --git a/src/SDL12/include/SDL_events.h b/src/SDL12/include/SDL_events.h new file mode 100644 index 0000000..c94a30c --- /dev/null +++ b/src/SDL12/include/SDL_events.h @@ -0,0 +1,356 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_events.h + * Include file for SDL event handling + */ + +#ifndef _SDL_events_h +#define _SDL_events_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_active.h" +#include "SDL_keyboard.h" +#include "SDL_mouse.h" +#include "SDL_joystick.h" +#include "SDL_quit.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @name General keyboard/mouse state definitions */ +/*@{*/ +#define SDL_RELEASED 0 +#define SDL_PRESSED 1 +/*@}*/ + +/** Event enumerations */ +typedef enum { + SDL_NOEVENT = 0, /**< Unused (do not remove) */ + SDL_ACTIVEEVENT, /**< Application loses/gains visibility */ + SDL_KEYDOWN, /**< Keys pressed */ + SDL_KEYUP, /**< Keys released */ + SDL_MOUSEMOTION, /**< Mouse moved */ + SDL_MOUSEBUTTONDOWN, /**< Mouse button pressed */ + SDL_MOUSEBUTTONUP, /**< Mouse button released */ + SDL_JOYAXISMOTION, /**< Joystick axis motion */ + SDL_JOYBALLMOTION, /**< Joystick trackball motion */ + SDL_JOYHATMOTION, /**< Joystick hat position change */ + SDL_JOYBUTTONDOWN, /**< Joystick button pressed */ + SDL_JOYBUTTONUP, /**< Joystick button released */ + SDL_QUIT, /**< User-requested quit */ + SDL_SYSWMEVENT, /**< System specific event */ + SDL_EVENT_RESERVEDA, /**< Reserved for future use.. */ + SDL_EVENT_RESERVEDB, /**< Reserved for future use.. */ + SDL_VIDEORESIZE, /**< User resized video mode */ + SDL_VIDEOEXPOSE, /**< Screen needs to be redrawn */ + SDL_EVENT_RESERVED2, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED3, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED4, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED5, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED6, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED7, /**< Reserved for future use.. */ + /** Events SDL_USEREVENT through SDL_MAXEVENTS-1 are for your use */ + SDL_USEREVENT = 24, + /** This last event is only for bounding internal arrays + * It is the number of bits in the event mask datatype -- Uint32 + */ + SDL_NUMEVENTS = 32 +} SDL_EventType; + +/** @name Predefined event masks */ +/*@{*/ +#define SDL_EVENTMASK(X) (1<<(X)) +typedef enum { + SDL_ACTIVEEVENTMASK = SDL_EVENTMASK(SDL_ACTIVEEVENT), + SDL_KEYDOWNMASK = SDL_EVENTMASK(SDL_KEYDOWN), + SDL_KEYUPMASK = SDL_EVENTMASK(SDL_KEYUP), + SDL_KEYEVENTMASK = SDL_EVENTMASK(SDL_KEYDOWN)| + SDL_EVENTMASK(SDL_KEYUP), + SDL_MOUSEMOTIONMASK = SDL_EVENTMASK(SDL_MOUSEMOTION), + SDL_MOUSEBUTTONDOWNMASK = SDL_EVENTMASK(SDL_MOUSEBUTTONDOWN), + SDL_MOUSEBUTTONUPMASK = SDL_EVENTMASK(SDL_MOUSEBUTTONUP), + SDL_MOUSEEVENTMASK = SDL_EVENTMASK(SDL_MOUSEMOTION)| + SDL_EVENTMASK(SDL_MOUSEBUTTONDOWN)| + SDL_EVENTMASK(SDL_MOUSEBUTTONUP), + SDL_JOYAXISMOTIONMASK = SDL_EVENTMASK(SDL_JOYAXISMOTION), + SDL_JOYBALLMOTIONMASK = SDL_EVENTMASK(SDL_JOYBALLMOTION), + SDL_JOYHATMOTIONMASK = SDL_EVENTMASK(SDL_JOYHATMOTION), + SDL_JOYBUTTONDOWNMASK = SDL_EVENTMASK(SDL_JOYBUTTONDOWN), + SDL_JOYBUTTONUPMASK = SDL_EVENTMASK(SDL_JOYBUTTONUP), + SDL_JOYEVENTMASK = SDL_EVENTMASK(SDL_JOYAXISMOTION)| + SDL_EVENTMASK(SDL_JOYBALLMOTION)| + SDL_EVENTMASK(SDL_JOYHATMOTION)| + SDL_EVENTMASK(SDL_JOYBUTTONDOWN)| + SDL_EVENTMASK(SDL_JOYBUTTONUP), + SDL_VIDEORESIZEMASK = SDL_EVENTMASK(SDL_VIDEORESIZE), + SDL_VIDEOEXPOSEMASK = SDL_EVENTMASK(SDL_VIDEOEXPOSE), + SDL_QUITMASK = SDL_EVENTMASK(SDL_QUIT), + SDL_SYSWMEVENTMASK = SDL_EVENTMASK(SDL_SYSWMEVENT) +} SDL_EventMask ; +#define SDL_ALLEVENTS 0xFFFFFFFF +/*@}*/ + +/** Application visibility event structure */ +typedef struct SDL_ActiveEvent { + Uint8 type; /**< SDL_ACTIVEEVENT */ + Uint8 gain; /**< Whether given states were gained or lost (1/0) */ + Uint8 state; /**< A mask of the focus states */ +} SDL_ActiveEvent; + +/** Keyboard event structure */ +typedef struct SDL_KeyboardEvent { + Uint8 type; /**< SDL_KEYDOWN or SDL_KEYUP */ + Uint8 which; /**< The keyboard device index */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ + SDL_keysym keysym; +} SDL_KeyboardEvent; + +/** Mouse motion event structure */ +typedef struct SDL_MouseMotionEvent { + Uint8 type; /**< SDL_MOUSEMOTION */ + Uint8 which; /**< The mouse device index */ + Uint8 state; /**< The current button state */ + Uint16 x, y; /**< The X/Y coordinates of the mouse */ + Sint16 xrel; /**< The relative motion in the X direction */ + Sint16 yrel; /**< The relative motion in the Y direction */ +} SDL_MouseMotionEvent; + +/** Mouse button event structure */ +typedef struct SDL_MouseButtonEvent { + Uint8 type; /**< SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP */ + Uint8 which; /**< The mouse device index */ + Uint8 button; /**< The mouse button index */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ + Uint16 x, y; /**< The X/Y coordinates of the mouse at press time */ +} SDL_MouseButtonEvent; + +/** Joystick axis motion event structure */ +typedef struct SDL_JoyAxisEvent { + Uint8 type; /**< SDL_JOYAXISMOTION */ + Uint8 which; /**< The joystick device index */ + Uint8 axis; /**< The joystick axis index */ + Sint16 value; /**< The axis value (range: -32768 to 32767) */ +} SDL_JoyAxisEvent; + +/** Joystick trackball motion event structure */ +typedef struct SDL_JoyBallEvent { + Uint8 type; /**< SDL_JOYBALLMOTION */ + Uint8 which; /**< The joystick device index */ + Uint8 ball; /**< The joystick trackball index */ + Sint16 xrel; /**< The relative motion in the X direction */ + Sint16 yrel; /**< The relative motion in the Y direction */ +} SDL_JoyBallEvent; + +/** Joystick hat position change event structure */ +typedef struct SDL_JoyHatEvent { + Uint8 type; /**< SDL_JOYHATMOTION */ + Uint8 which; /**< The joystick device index */ + Uint8 hat; /**< The joystick hat index */ + Uint8 value; /**< The hat position value: + * SDL_HAT_LEFTUP SDL_HAT_UP SDL_HAT_RIGHTUP + * SDL_HAT_LEFT SDL_HAT_CENTERED SDL_HAT_RIGHT + * SDL_HAT_LEFTDOWN SDL_HAT_DOWN SDL_HAT_RIGHTDOWN + * Note that zero means the POV is centered. + */ +} SDL_JoyHatEvent; + +/** Joystick button event structure */ +typedef struct SDL_JoyButtonEvent { + Uint8 type; /**< SDL_JOYBUTTONDOWN or SDL_JOYBUTTONUP */ + Uint8 which; /**< The joystick device index */ + Uint8 button; /**< The joystick button index */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ +} SDL_JoyButtonEvent; + +/** The "window resized" event + * When you get this event, you are responsible for setting a new video + * mode with the new width and height. + */ +typedef struct SDL_ResizeEvent { + Uint8 type; /**< SDL_VIDEORESIZE */ + int w; /**< New width */ + int h; /**< New height */ +} SDL_ResizeEvent; + +/** The "screen redraw" event */ +typedef struct SDL_ExposeEvent { + Uint8 type; /**< SDL_VIDEOEXPOSE */ +} SDL_ExposeEvent; + +/** The "quit requested" event */ +typedef struct SDL_QuitEvent { + Uint8 type; /**< SDL_QUIT */ +} SDL_QuitEvent; + +/** A user-defined event type */ +typedef struct SDL_UserEvent { + Uint8 type; /**< SDL_USEREVENT through SDL_NUMEVENTS-1 */ + int code; /**< User defined event code */ + void *data1; /**< User defined data pointer */ + void *data2; /**< User defined data pointer */ +} SDL_UserEvent; + +/** If you want to use this event, you should include SDL_syswm.h */ +struct SDL_SysWMmsg; +typedef struct SDL_SysWMmsg SDL_SysWMmsg; +typedef struct SDL_SysWMEvent { + Uint8 type; + SDL_SysWMmsg *msg; +} SDL_SysWMEvent; + +/** General event structure */ +typedef union SDL_Event { + Uint8 type; + SDL_ActiveEvent active; + SDL_KeyboardEvent key; + SDL_MouseMotionEvent motion; + SDL_MouseButtonEvent button; + SDL_JoyAxisEvent jaxis; + SDL_JoyBallEvent jball; + SDL_JoyHatEvent jhat; + SDL_JoyButtonEvent jbutton; + SDL_ResizeEvent resize; + SDL_ExposeEvent expose; + SDL_QuitEvent quit; + SDL_UserEvent user; + SDL_SysWMEvent syswm; +} SDL_Event; + + +/* Function prototypes */ + +/** Pumps the event loop, gathering events from the input devices. + * This function updates the event queue and internal input device state. + * This should only be run in the thread that sets the video mode. + */ +extern DECLSPEC void SDLCALL SDL_PumpEvents(void); + +typedef enum { + SDL_ADDEVENT, + SDL_PEEKEVENT, + SDL_GETEVENT +} SDL_eventaction; + +/** + * Checks the event queue for messages and optionally returns them. + * + * If 'action' is SDL_ADDEVENT, up to 'numevents' events will be added to + * the back of the event queue. + * If 'action' is SDL_PEEKEVENT, up to 'numevents' events at the front + * of the event queue, matching 'mask', will be returned and will not + * be removed from the queue. + * If 'action' is SDL_GETEVENT, up to 'numevents' events at the front + * of the event queue, matching 'mask', will be returned and will be + * removed from the queue. + * + * @return + * This function returns the number of events actually stored, or -1 + * if there was an error. + * + * This function is thread-safe. + */ +extern DECLSPEC int SDLCALL SDL_PeepEvents(SDL_Event *events, int numevents, + SDL_eventaction action, Uint32 mask); + +/** Polls for currently pending events, and returns 1 if there are any pending + * events, or 0 if there are none available. If 'event' is not NULL, the next + * event is removed from the queue and stored in that area. + */ +extern DECLSPEC int SDLCALL SDL_PollEvent(SDL_Event *event); + +/** Waits indefinitely for the next available event, returning 1, or 0 if there + * was an error while waiting for events. If 'event' is not NULL, the next + * event is removed from the queue and stored in that area. + */ +extern DECLSPEC int SDLCALL SDL_WaitEvent(SDL_Event *event); + +/** Add an event to the event queue. + * This function returns 0 on success, or -1 if the event queue was full + * or there was some other error. + */ +extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event *event); + +/** @name Event Filtering */ +/*@{*/ +typedef int (SDLCALL *SDL_EventFilter)(const SDL_Event *event); +/** + * This function sets up a filter to process all events before they + * change internal state and are posted to the internal event queue. + * + * The filter is protypted as: + * @code typedef int (SDLCALL *SDL_EventFilter)(const SDL_Event *event); @endcode + * + * If the filter returns 1, then the event will be added to the internal queue. + * If it returns 0, then the event will be dropped from the queue, but the + * internal state will still be updated. This allows selective filtering of + * dynamically arriving events. + * + * @warning Be very careful of what you do in the event filter function, as + * it may run in a different thread! + * + * There is one caveat when dealing with the SDL_QUITEVENT event type. The + * event filter is only called when the window manager desires to close the + * application window. If the event filter returns 1, then the window will + * be closed, otherwise the window will remain open if possible. + * If the quit event is generated by an interrupt signal, it will bypass the + * internal queue and be delivered to the application at the next event poll. + */ +extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter); + +/** + * Return the current event filter - can be used to "chain" filters. + * If there is no event filter set, this function returns NULL. + */ +extern DECLSPEC SDL_EventFilter SDLCALL SDL_GetEventFilter(void); +/*@}*/ + +/** @name Event State */ +/*@{*/ +#define SDL_QUERY -1 +#define SDL_IGNORE 0 +#define SDL_DISABLE 0 +#define SDL_ENABLE 1 +/*@}*/ + +/** +* This function allows you to set the state of processing certain events. +* If 'state' is set to SDL_IGNORE, that event will be automatically dropped +* from the event queue and will not event be filtered. +* If 'state' is set to SDL_ENABLE, that event will be processed normally. +* If 'state' is set to SDL_QUERY, SDL_EventState() will return the +* current processing state of the specified event. +*/ +extern DECLSPEC Uint8 SDLCALL SDL_EventState(Uint8 type, int state); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_events_h */ diff --git a/src/SDL12/include/SDL_getenv.h b/src/SDL12/include/SDL_getenv.h new file mode 100644 index 0000000..253ad88 --- /dev/null +++ b/src/SDL12/include/SDL_getenv.h @@ -0,0 +1,28 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_getenv.h + * @deprecated Use SDL_stdinc.h instead + */ + +/* DEPRECATED */ +#include "SDL_stdinc.h" diff --git a/src/SDL12/include/SDL_joystick.h b/src/SDL12/include/SDL_joystick.h new file mode 100644 index 0000000..d5135c3 --- /dev/null +++ b/src/SDL12/include/SDL_joystick.h @@ -0,0 +1,187 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_joystick.h + * Include file for SDL joystick event handling + */ + +#ifndef _SDL_joystick_h +#define _SDL_joystick_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @file SDL_joystick.h + * @note In order to use these functions, SDL_Init() must have been called + * with the SDL_INIT_JOYSTICK flag. This causes SDL to scan the system + * for joysticks, and load appropriate drivers. + */ + +/** The joystick structure used to identify an SDL joystick */ +struct _SDL_Joystick; +typedef struct _SDL_Joystick SDL_Joystick; + +/* Function prototypes */ +/** + * Count the number of joysticks attached to the system + */ +extern DECLSPEC int SDLCALL SDL_NumJoysticks(void); + +/** + * Get the implementation dependent name of a joystick. + * + * This can be called before any joysticks are opened. + * If no name can be found, this function returns NULL. + */ +extern DECLSPEC const char * SDLCALL SDL_JoystickName(int device_index); + +/** + * Open a joystick for use. + * + * @param[in] device_index + * The index passed as an argument refers to + * the N'th joystick on the system. This index is the value which will + * identify this joystick in future joystick events. + * + * @return This function returns a joystick identifier, or NULL if an error occurred. + */ +extern DECLSPEC SDL_Joystick * SDLCALL SDL_JoystickOpen(int device_index); + +/** + * Returns 1 if the joystick has been opened, or 0 if it has not. + */ +extern DECLSPEC int SDLCALL SDL_JoystickOpened(int device_index); + +/** + * Get the device index of an opened joystick. + */ +extern DECLSPEC int SDLCALL SDL_JoystickIndex(SDL_Joystick *joystick); + +/** + * Get the number of general axis controls on a joystick + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumAxes(SDL_Joystick *joystick); + +/** + * Get the number of trackballs on a joystick + * + * Joystick trackballs have only relative motion events associated + * with them and their state cannot be polled. + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumBalls(SDL_Joystick *joystick); + +/** + * Get the number of POV hats on a joystick + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumHats(SDL_Joystick *joystick); + +/** + * Get the number of buttons on a joystick + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumButtons(SDL_Joystick *joystick); + +/** + * Update the current state of the open joysticks. + * + * This is called automatically by the event loop if any joystick + * events are enabled. + */ +extern DECLSPEC void SDLCALL SDL_JoystickUpdate(void); + +/** + * Enable/disable joystick event polling. + * + * If joystick events are disabled, you must call SDL_JoystickUpdate() + * yourself and check the state of the joystick when you want joystick + * information. + * + * @param[in] state The state can be one of SDL_QUERY, SDL_ENABLE or SDL_IGNORE. + */ +extern DECLSPEC int SDLCALL SDL_JoystickEventState(int state); + +/** + * Get the current state of an axis control on a joystick + * + * @param[in] axis The axis indices start at index 0. + * + * @return The state is a value ranging from -32768 to 32767. + */ +extern DECLSPEC Sint16 SDLCALL SDL_JoystickGetAxis(SDL_Joystick *joystick, int axis); + +/** + * @name Hat Positions + * The return value of SDL_JoystickGetHat() is one of the following positions: + */ +/*@{*/ +#define SDL_HAT_CENTERED 0x00 +#define SDL_HAT_UP 0x01 +#define SDL_HAT_RIGHT 0x02 +#define SDL_HAT_DOWN 0x04 +#define SDL_HAT_LEFT 0x08 +#define SDL_HAT_RIGHTUP (SDL_HAT_RIGHT|SDL_HAT_UP) +#define SDL_HAT_RIGHTDOWN (SDL_HAT_RIGHT|SDL_HAT_DOWN) +#define SDL_HAT_LEFTUP (SDL_HAT_LEFT|SDL_HAT_UP) +#define SDL_HAT_LEFTDOWN (SDL_HAT_LEFT|SDL_HAT_DOWN) +/*@}*/ + +/** + * Get the current state of a POV hat on a joystick + * + * @param[in] hat The hat indices start at index 0. + */ +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetHat(SDL_Joystick *joystick, int hat); + +/** + * Get the ball axis change since the last poll + * + * @param[in] ball The ball indices start at index 0. + * + * @return This returns 0, or -1 if you passed it invalid parameters. + */ +extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick *joystick, int ball, int *dx, int *dy); + +/** + * Get the current state of a button on a joystick + * + * @param[in] button The button indices start at index 0. + */ +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick *joystick, int button); + +/** + * Close a joystick previously opened with SDL_JoystickOpen() + */ +extern DECLSPEC void SDLCALL SDL_JoystickClose(SDL_Joystick *joystick); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_joystick_h */ diff --git a/src/SDL12/include/SDL_keyboard.h b/src/SDL12/include/SDL_keyboard.h new file mode 100644 index 0000000..7b59d24 --- /dev/null +++ b/src/SDL12/include/SDL_keyboard.h @@ -0,0 +1,135 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_keyboard.h + * Include file for SDL keyboard event handling + */ + +#ifndef _SDL_keyboard_h +#define _SDL_keyboard_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_keysym.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** Keysym structure + * + * - The scancode is hardware dependent, and should not be used by general + * applications. If no hardware scancode is available, it will be 0. + * + * - The 'unicode' translated character is only available when character + * translation is enabled by the SDL_EnableUNICODE() API. If non-zero, + * this is a UNICODE character corresponding to the keypress. If the + * high 9 bits of the character are 0, then this maps to the equivalent + * ASCII character: + * @code + * char ch; + * if ( (keysym.unicode & 0xFF80) == 0 ) { + * ch = keysym.unicode & 0x7F; + * } else { + * An international character.. + * } + * @endcode + */ +typedef struct SDL_keysym { + Uint8 scancode; /**< hardware specific scancode */ + SDLKey sym; /**< SDL virtual keysym */ + SDLMod mod; /**< current key modifiers */ + Uint16 unicode; /**< translated character */ +} SDL_keysym; + +/** This is the mask which refers to all hotkey bindings */ +#define SDL_ALL_HOTKEYS 0xFFFFFFFF + +/* Function prototypes */ +/** + * Enable/Disable UNICODE translation of keyboard input. + * + * This translation has some overhead, so translation defaults off. + * + * @param[in] enable + * If 'enable' is 1, translation is enabled. + * If 'enable' is 0, translation is disabled. + * If 'enable' is -1, the translation state is not changed. + * + * @return It returns the previous state of keyboard translation. + */ +extern DECLSPEC int SDLCALL SDL_EnableUNICODE(int enable); + +#define SDL_DEFAULT_REPEAT_DELAY 500 +#define SDL_DEFAULT_REPEAT_INTERVAL 30 +/** + * Enable/Disable keyboard repeat. Keyboard repeat defaults to off. + * + * @param[in] delay + * 'delay' is the initial delay in ms between the time when a key is + * pressed, and keyboard repeat begins. + * + * @param[in] interval + * 'interval' is the time in ms between keyboard repeat events. + * + * If 'delay' is set to 0, keyboard repeat is disabled. + */ +extern DECLSPEC int SDLCALL SDL_EnableKeyRepeat(int delay, int interval); +extern DECLSPEC void SDLCALL SDL_GetKeyRepeat(int *delay, int *interval); + +/** + * Get a snapshot of the current state of the keyboard. + * Returns an array of keystates, indexed by the SDLK_* syms. + * Usage: + * @code + * Uint8 *keystate = SDL_GetKeyState(NULL); + * if ( keystate[SDLK_RETURN] ) //... \ is pressed. + * @endcode + */ +extern DECLSPEC Uint8 * SDLCALL SDL_GetKeyState(int *numkeys); + +/** + * Get the current key modifier state + */ +extern DECLSPEC SDLMod SDLCALL SDL_GetModState(void); + +/** + * Set the current key modifier state. + * This does not change the keyboard state, only the key modifier flags. + */ +extern DECLSPEC void SDLCALL SDL_SetModState(SDLMod modstate); + +/** + * Get the name of an SDL virtual keysym + */ +extern DECLSPEC char * SDLCALL SDL_GetKeyName(SDLKey key); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_keyboard_h */ diff --git a/src/SDL12/include/SDL_keysym.h b/src/SDL12/include/SDL_keysym.h new file mode 100644 index 0000000..9010128 --- /dev/null +++ b/src/SDL12/include/SDL_keysym.h @@ -0,0 +1,326 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_keysym_h +#define _SDL_keysym_h + +/** What we really want is a mapping of every raw key on the keyboard. + * To support international keyboards, we use the range 0xA1 - 0xFF + * as international virtual keycodes. We'll follow in the footsteps of X11... + * @brief The names of the keys + */ +typedef enum { + /** @name ASCII mapped keysyms + * The keyboard syms have been cleverly chosen to map to ASCII + */ + /*@{*/ + SDLK_UNKNOWN = 0, + SDLK_FIRST = 0, + SDLK_BACKSPACE = 8, + SDLK_TAB = 9, + SDLK_CLEAR = 12, + SDLK_RETURN = 13, + SDLK_PAUSE = 19, + SDLK_ESCAPE = 27, + SDLK_SPACE = 32, + SDLK_EXCLAIM = 33, + SDLK_QUOTEDBL = 34, + SDLK_HASH = 35, + SDLK_DOLLAR = 36, + SDLK_AMPERSAND = 38, + SDLK_QUOTE = 39, + SDLK_LEFTPAREN = 40, + SDLK_RIGHTPAREN = 41, + SDLK_ASTERISK = 42, + SDLK_PLUS = 43, + SDLK_COMMA = 44, + SDLK_MINUS = 45, + SDLK_PERIOD = 46, + SDLK_SLASH = 47, + SDLK_0 = 48, + SDLK_1 = 49, + SDLK_2 = 50, + SDLK_3 = 51, + SDLK_4 = 52, + SDLK_5 = 53, + SDLK_6 = 54, + SDLK_7 = 55, + SDLK_8 = 56, + SDLK_9 = 57, + SDLK_COLON = 58, + SDLK_SEMICOLON = 59, + SDLK_LESS = 60, + SDLK_EQUALS = 61, + SDLK_GREATER = 62, + SDLK_QUESTION = 63, + SDLK_AT = 64, + /* + Skip uppercase letters + */ + SDLK_LEFTBRACKET = 91, + SDLK_BACKSLASH = 92, + SDLK_RIGHTBRACKET = 93, + SDLK_CARET = 94, + SDLK_UNDERSCORE = 95, + SDLK_BACKQUOTE = 96, + SDLK_a = 97, + SDLK_b = 98, + SDLK_c = 99, + SDLK_d = 100, + SDLK_e = 101, + SDLK_f = 102, + SDLK_g = 103, + SDLK_h = 104, + SDLK_i = 105, + SDLK_j = 106, + SDLK_k = 107, + SDLK_l = 108, + SDLK_m = 109, + SDLK_n = 110, + SDLK_o = 111, + SDLK_p = 112, + SDLK_q = 113, + SDLK_r = 114, + SDLK_s = 115, + SDLK_t = 116, + SDLK_u = 117, + SDLK_v = 118, + SDLK_w = 119, + SDLK_x = 120, + SDLK_y = 121, + SDLK_z = 122, + SDLK_DELETE = 127, + /* End of ASCII mapped keysyms */ + /*@}*/ + + /** @name International keyboard syms */ + /*@{*/ + SDLK_WORLD_0 = 160, /* 0xA0 */ + SDLK_WORLD_1 = 161, + SDLK_WORLD_2 = 162, + SDLK_WORLD_3 = 163, + SDLK_WORLD_4 = 164, + SDLK_WORLD_5 = 165, + SDLK_WORLD_6 = 166, + SDLK_WORLD_7 = 167, + SDLK_WORLD_8 = 168, + SDLK_WORLD_9 = 169, + SDLK_WORLD_10 = 170, + SDLK_WORLD_11 = 171, + SDLK_WORLD_12 = 172, + SDLK_WORLD_13 = 173, + SDLK_WORLD_14 = 174, + SDLK_WORLD_15 = 175, + SDLK_WORLD_16 = 176, + SDLK_WORLD_17 = 177, + SDLK_WORLD_18 = 178, + SDLK_WORLD_19 = 179, + SDLK_WORLD_20 = 180, + SDLK_WORLD_21 = 181, + SDLK_WORLD_22 = 182, + SDLK_WORLD_23 = 183, + SDLK_WORLD_24 = 184, + SDLK_WORLD_25 = 185, + SDLK_WORLD_26 = 186, + SDLK_WORLD_27 = 187, + SDLK_WORLD_28 = 188, + SDLK_WORLD_29 = 189, + SDLK_WORLD_30 = 190, + SDLK_WORLD_31 = 191, + SDLK_WORLD_32 = 192, + SDLK_WORLD_33 = 193, + SDLK_WORLD_34 = 194, + SDLK_WORLD_35 = 195, + SDLK_WORLD_36 = 196, + SDLK_WORLD_37 = 197, + SDLK_WORLD_38 = 198, + SDLK_WORLD_39 = 199, + SDLK_WORLD_40 = 200, + SDLK_WORLD_41 = 201, + SDLK_WORLD_42 = 202, + SDLK_WORLD_43 = 203, + SDLK_WORLD_44 = 204, + SDLK_WORLD_45 = 205, + SDLK_WORLD_46 = 206, + SDLK_WORLD_47 = 207, + SDLK_WORLD_48 = 208, + SDLK_WORLD_49 = 209, + SDLK_WORLD_50 = 210, + SDLK_WORLD_51 = 211, + SDLK_WORLD_52 = 212, + SDLK_WORLD_53 = 213, + SDLK_WORLD_54 = 214, + SDLK_WORLD_55 = 215, + SDLK_WORLD_56 = 216, + SDLK_WORLD_57 = 217, + SDLK_WORLD_58 = 218, + SDLK_WORLD_59 = 219, + SDLK_WORLD_60 = 220, + SDLK_WORLD_61 = 221, + SDLK_WORLD_62 = 222, + SDLK_WORLD_63 = 223, + SDLK_WORLD_64 = 224, + SDLK_WORLD_65 = 225, + SDLK_WORLD_66 = 226, + SDLK_WORLD_67 = 227, + SDLK_WORLD_68 = 228, + SDLK_WORLD_69 = 229, + SDLK_WORLD_70 = 230, + SDLK_WORLD_71 = 231, + SDLK_WORLD_72 = 232, + SDLK_WORLD_73 = 233, + SDLK_WORLD_74 = 234, + SDLK_WORLD_75 = 235, + SDLK_WORLD_76 = 236, + SDLK_WORLD_77 = 237, + SDLK_WORLD_78 = 238, + SDLK_WORLD_79 = 239, + SDLK_WORLD_80 = 240, + SDLK_WORLD_81 = 241, + SDLK_WORLD_82 = 242, + SDLK_WORLD_83 = 243, + SDLK_WORLD_84 = 244, + SDLK_WORLD_85 = 245, + SDLK_WORLD_86 = 246, + SDLK_WORLD_87 = 247, + SDLK_WORLD_88 = 248, + SDLK_WORLD_89 = 249, + SDLK_WORLD_90 = 250, + SDLK_WORLD_91 = 251, + SDLK_WORLD_92 = 252, + SDLK_WORLD_93 = 253, + SDLK_WORLD_94 = 254, + SDLK_WORLD_95 = 255, /* 0xFF */ + /*@}*/ + + /** @name Numeric keypad */ + /*@{*/ + SDLK_KP0 = 256, + SDLK_KP1 = 257, + SDLK_KP2 = 258, + SDLK_KP3 = 259, + SDLK_KP4 = 260, + SDLK_KP5 = 261, + SDLK_KP6 = 262, + SDLK_KP7 = 263, + SDLK_KP8 = 264, + SDLK_KP9 = 265, + SDLK_KP_PERIOD = 266, + SDLK_KP_DIVIDE = 267, + SDLK_KP_MULTIPLY = 268, + SDLK_KP_MINUS = 269, + SDLK_KP_PLUS = 270, + SDLK_KP_ENTER = 271, + SDLK_KP_EQUALS = 272, + /*@}*/ + + /** @name Arrows + Home/End pad */ + /*@{*/ + SDLK_UP = 273, + SDLK_DOWN = 274, + SDLK_RIGHT = 275, + SDLK_LEFT = 276, + SDLK_INSERT = 277, + SDLK_HOME = 278, + SDLK_END = 279, + SDLK_PAGEUP = 280, + SDLK_PAGEDOWN = 281, + /*@}*/ + + /** @name Function keys */ + /*@{*/ + SDLK_F1 = 282, + SDLK_F2 = 283, + SDLK_F3 = 284, + SDLK_F4 = 285, + SDLK_F5 = 286, + SDLK_F6 = 287, + SDLK_F7 = 288, + SDLK_F8 = 289, + SDLK_F9 = 290, + SDLK_F10 = 291, + SDLK_F11 = 292, + SDLK_F12 = 293, + SDLK_F13 = 294, + SDLK_F14 = 295, + SDLK_F15 = 296, + /*@}*/ + + /** @name Key state modifier keys */ + /*@{*/ + SDLK_NUMLOCK = 300, + SDLK_CAPSLOCK = 301, + SDLK_SCROLLOCK = 302, + SDLK_RSHIFT = 303, + SDLK_LSHIFT = 304, + SDLK_RCTRL = 305, + SDLK_LCTRL = 306, + SDLK_RALT = 307, + SDLK_LALT = 308, + SDLK_RMETA = 309, + SDLK_LMETA = 310, + SDLK_LSUPER = 311, /**< Left "Windows" key */ + SDLK_RSUPER = 312, /**< Right "Windows" key */ + SDLK_MODE = 313, /**< "Alt Gr" key */ + SDLK_COMPOSE = 314, /**< Multi-key compose key */ + /*@}*/ + + /** @name Miscellaneous function keys */ + /*@{*/ + SDLK_HELP = 315, + SDLK_PRINT = 316, + SDLK_SYSREQ = 317, + SDLK_BREAK = 318, + SDLK_MENU = 319, + SDLK_POWER = 320, /**< Power Macintosh power key */ + SDLK_EURO = 321, /**< Some european keyboards */ + SDLK_UNDO = 322, /**< Atari keyboard has Undo */ + /*@}*/ + + /* Add any other keys here */ + + SDLK_LAST +} SDLKey; + +/** Enumeration of valid key mods (possibly OR'd together) */ +typedef enum { + KMOD_NONE = 0x0000, + KMOD_LSHIFT= 0x0001, + KMOD_RSHIFT= 0x0002, + KMOD_LCTRL = 0x0040, + KMOD_RCTRL = 0x0080, + KMOD_LALT = 0x0100, + KMOD_RALT = 0x0200, + KMOD_LMETA = 0x0400, + KMOD_RMETA = 0x0800, + KMOD_NUM = 0x1000, + KMOD_CAPS = 0x2000, + KMOD_MODE = 0x4000, + KMOD_RESERVED = 0x8000 +} SDLMod; + +#define KMOD_CTRL (KMOD_LCTRL|KMOD_RCTRL) +#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT) +#define KMOD_ALT (KMOD_LALT|KMOD_RALT) +#define KMOD_META (KMOD_LMETA|KMOD_RMETA) + +#endif /* _SDL_keysym_h */ diff --git a/src/SDL12/include/SDL_loadso.h b/src/SDL12/include/SDL_loadso.h new file mode 100644 index 0000000..45a17f9 --- /dev/null +++ b/src/SDL12/include/SDL_loadso.h @@ -0,0 +1,78 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_loadso.h + * System dependent library loading routines + */ + +/** @file SDL_loadso.h + * Some things to keep in mind: + * - These functions only work on C function names. Other languages may + * have name mangling and intrinsic language support that varies from + * compiler to compiler. + * - Make sure you declare your function pointers with the same calling + * convention as the actual library function. Your code will crash + * mysteriously if you do not do this. + * - Avoid namespace collisions. If you load a symbol from the library, + * it is not defined whether or not it goes into the global symbol + * namespace for the application. If it does and it conflicts with + * symbols in your code or other shared libraries, you will not get + * the results you expect. :) + */ + + +#ifndef _SDL_loadso_h +#define _SDL_loadso_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This function dynamically loads a shared object and returns a pointer + * to the object handle (or NULL if there was an error). + * The 'sofile' parameter is a system dependent name of the object file. + */ +extern DECLSPEC void * SDLCALL SDL_LoadObject(const char *sofile); + +/** + * Given an object handle, this function looks up the address of the + * named function in the shared object and returns it. This address + * is no longer valid after calling SDL_UnloadObject(). + */ +extern DECLSPEC void * SDLCALL SDL_LoadFunction(void *handle, const char *name); + +/** Unload a shared object from memory */ +extern DECLSPEC void SDLCALL SDL_UnloadObject(void *handle); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_loadso_h */ diff --git a/src/SDL12/include/SDL_main.h b/src/SDL12/include/SDL_main.h new file mode 100644 index 0000000..b7f6b2c --- /dev/null +++ b/src/SDL12/include/SDL_main.h @@ -0,0 +1,106 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_main_h +#define _SDL_main_h + +#include "SDL_stdinc.h" + +/** @file SDL_main.h + * Redefine main() on Win32 and MacOS so that it is called by winmain.c + */ + +#if defined(__WIN32__) || \ + (defined(__MWERKS__) && !defined(__BEOS__)) || \ + defined(__MACOS__) || defined(__MACOSX__) || \ + defined(__SYMBIAN32__) || defined(QWS) + +#ifdef __cplusplus +#define C_LINKAGE "C" +#else +#define C_LINKAGE +#endif /* __cplusplus */ + +/** The application's main() function must be called with C linkage, + * and should be declared like this: + * @code + * #ifdef __cplusplus + * extern "C" + * #endif + * int main(int argc, char *argv[]) + * { + * } + * @endcode + */ +#define main SDL_main + +/** The prototype for the application's main() function */ +extern C_LINKAGE int SDL_main(int argc, char *argv[]); + + +/** @name From the SDL library code -- needed for registering the app on Win32 */ +/*@{*/ +#ifdef __WIN32__ + +#include "begin_code.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** This should be called from your WinMain() function, if any */ +extern DECLSPEC void SDLCALL SDL_SetModuleHandle(void *hInst); +/** This can also be called, but is no longer necessary */ +extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style, void *hInst); +/** This can also be called, but is no longer necessary (SDL_Quit calls it) */ +extern DECLSPEC void SDLCALL SDL_UnregisterApp(void); +#ifdef __cplusplus +} +#endif +#include "close_code.h" +#endif +/*@}*/ + +/** @name From the SDL library code -- needed for registering QuickDraw on MacOS */ +/*@{*/ +#if defined(__MACOS__) + +#include "begin_code.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** Forward declaration so we don't need to include QuickDraw.h */ +struct QDGlobals; + +/** This should be called from your main() function, if any */ +extern DECLSPEC void SDLCALL SDL_InitQuickDraw(struct QDGlobals *the_qd); + +#ifdef __cplusplus +} +#endif +#include "close_code.h" +#endif +/*@}*/ + +#endif /* Need to redefine main()? */ + +#endif /* _SDL_main_h */ diff --git a/src/SDL12/include/SDL_mouse.h b/src/SDL12/include/SDL_mouse.h new file mode 100644 index 0000000..a573f04 --- /dev/null +++ b/src/SDL12/include/SDL_mouse.h @@ -0,0 +1,143 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_mouse.h + * Include file for SDL mouse event handling + */ + +#ifndef _SDL_mouse_h +#define _SDL_mouse_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct WMcursor WMcursor; /**< Implementation dependent */ +typedef struct SDL_Cursor { + SDL_Rect area; /**< The area of the mouse cursor */ + Sint16 hot_x, hot_y; /**< The "tip" of the cursor */ + Uint8 *data; /**< B/W cursor data */ + Uint8 *mask; /**< B/W cursor mask */ + Uint8 *save[2]; /**< Place to save cursor area */ + WMcursor *wm_cursor; /**< Window-manager cursor */ +} SDL_Cursor; + +/* Function prototypes */ +/** + * Retrieve the current state of the mouse. + * The current button state is returned as a button bitmask, which can + * be tested using the SDL_BUTTON(X) macros, and x and y are set to the + * current mouse cursor position. You can pass NULL for either x or y. + */ +extern DECLSPEC Uint8 SDLCALL SDL_GetMouseState(int *x, int *y); + +/** + * Retrieve the current state of the mouse. + * The current button state is returned as a button bitmask, which can + * be tested using the SDL_BUTTON(X) macros, and x and y are set to the + * mouse deltas since the last call to SDL_GetRelativeMouseState(). + */ +extern DECLSPEC Uint8 SDLCALL SDL_GetRelativeMouseState(int *x, int *y); + +/** + * Set the position of the mouse cursor (generates a mouse motion event) + */ +extern DECLSPEC void SDLCALL SDL_WarpMouse(Uint16 x, Uint16 y); + +/** + * Create a cursor using the specified data and mask (in MSB format). + * The cursor width must be a multiple of 8 bits. + * + * The cursor is created in black and white according to the following: + * data mask resulting pixel on screen + * 0 1 White + * 1 1 Black + * 0 0 Transparent + * 1 0 Inverted color if possible, black if not. + * + * Cursors created with this function must be freed with SDL_FreeCursor(). + */ +extern DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor + (Uint8 *data, Uint8 *mask, int w, int h, int hot_x, int hot_y); + +/** + * Set the currently active cursor to the specified one. + * If the cursor is currently visible, the change will be immediately + * represented on the display. + */ +extern DECLSPEC void SDLCALL SDL_SetCursor(SDL_Cursor *cursor); + +/** + * Returns the currently active cursor. + */ +extern DECLSPEC SDL_Cursor * SDLCALL SDL_GetCursor(void); + +/** + * Deallocates a cursor created with SDL_CreateCursor(). + */ +extern DECLSPEC void SDLCALL SDL_FreeCursor(SDL_Cursor *cursor); + +/** + * Toggle whether or not the cursor is shown on the screen. + * The cursor start off displayed, but can be turned off. + * SDL_ShowCursor() returns 1 if the cursor was being displayed + * before the call, or 0 if it was not. You can query the current + * state by passing a 'toggle' value of -1. + */ +extern DECLSPEC int SDLCALL SDL_ShowCursor(int toggle); + +/*@{*/ +/** Used as a mask when testing buttons in buttonstate + * Button 1: Left mouse button + * Button 2: Middle mouse button + * Button 3: Right mouse button + * Button 4: Mouse wheel up (may also be a real button) + * Button 5: Mouse wheel down (may also be a real button) + */ +#define SDL_BUTTON(X) (1 << ((X)-1)) +#define SDL_BUTTON_LEFT 1 +#define SDL_BUTTON_MIDDLE 2 +#define SDL_BUTTON_RIGHT 3 +#define SDL_BUTTON_WHEELUP 4 +#define SDL_BUTTON_WHEELDOWN 5 +#define SDL_BUTTON_X1 6 +#define SDL_BUTTON_X2 7 +#define SDL_BUTTON_LMASK SDL_BUTTON(SDL_BUTTON_LEFT) +#define SDL_BUTTON_MMASK SDL_BUTTON(SDL_BUTTON_MIDDLE) +#define SDL_BUTTON_RMASK SDL_BUTTON(SDL_BUTTON_RIGHT) +#define SDL_BUTTON_X1MASK SDL_BUTTON(SDL_BUTTON_X1) +#define SDL_BUTTON_X2MASK SDL_BUTTON(SDL_BUTTON_X2) +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_mouse_h */ diff --git a/src/SDL12/include/SDL_mutex.h b/src/SDL12/include/SDL_mutex.h new file mode 100644 index 0000000..920971d --- /dev/null +++ b/src/SDL12/include/SDL_mutex.h @@ -0,0 +1,177 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_mutex_h +#define _SDL_mutex_h + +/** @file SDL_mutex.h + * Functions to provide thread synchronization primitives + * + * @note These are independent of the other SDL routines. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** Synchronization functions which can time out return this value + * if they time out. + */ +#define SDL_MUTEX_TIMEDOUT 1 + +/** This is the timeout value which corresponds to never time out */ +#define SDL_MUTEX_MAXWAIT (~(Uint32)0) + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name Mutex functions */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** The SDL mutex structure, defined in SDL_mutex.c */ +struct SDL_mutex; +typedef struct SDL_mutex SDL_mutex; + +/** Create a mutex, initialized unlocked */ +extern DECLSPEC SDL_mutex * SDLCALL SDL_CreateMutex(void); + +#define SDL_LockMutex(m) SDL_mutexP(m) +/** Lock the mutex + * @return 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_mutexP(SDL_mutex *mutex); + +#define SDL_UnlockMutex(m) SDL_mutexV(m) +/** Unlock the mutex + * @return 0, or -1 on error + * + * It is an error to unlock a mutex that has not been locked by + * the current thread, and doing so results in undefined behavior. + */ +extern DECLSPEC int SDLCALL SDL_mutexV(SDL_mutex *mutex); + +/** Destroy a mutex */ +extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex *mutex); + +/*@}*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name Semaphore functions */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** The SDL semaphore structure, defined in SDL_sem.c */ +struct SDL_semaphore; +typedef struct SDL_semaphore SDL_sem; + +/** Create a semaphore, initialized with value, returns NULL on failure. */ +extern DECLSPEC SDL_sem * SDLCALL SDL_CreateSemaphore(Uint32 initial_value); + +/** Destroy a semaphore */ +extern DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_sem *sem); + +/** + * This function suspends the calling thread until the semaphore pointed + * to by sem has a positive count. It then atomically decreases the semaphore + * count. + */ +extern DECLSPEC int SDLCALL SDL_SemWait(SDL_sem *sem); + +/** Non-blocking variant of SDL_SemWait(). + * @return 0 if the wait succeeds, + * SDL_MUTEX_TIMEDOUT if the wait would block, and -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_SemTryWait(SDL_sem *sem); + +/** Variant of SDL_SemWait() with a timeout in milliseconds, returns 0 if + * the wait succeeds, SDL_MUTEX_TIMEDOUT if the wait does not succeed in + * the allotted time, and -1 on error. + * + * On some platforms this function is implemented by looping with a delay + * of 1 ms, and so should be avoided if possible. + */ +extern DECLSPEC int SDLCALL SDL_SemWaitTimeout(SDL_sem *sem, Uint32 ms); + +/** Atomically increases the semaphore's count (not blocking). + * @return 0, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_SemPost(SDL_sem *sem); + +/** Returns the current count of the semaphore */ +extern DECLSPEC Uint32 SDLCALL SDL_SemValue(SDL_sem *sem); + +/*@}*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name Condition_variable_functions */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*@{*/ +/** The SDL condition variable structure, defined in SDL_cond.c */ +struct SDL_cond; +typedef struct SDL_cond SDL_cond; +/*@}*/ + +/** Create a condition variable */ +extern DECLSPEC SDL_cond * SDLCALL SDL_CreateCond(void); + +/** Destroy a condition variable */ +extern DECLSPEC void SDLCALL SDL_DestroyCond(SDL_cond *cond); + +/** Restart one of the threads that are waiting on the condition variable, + * @return 0 or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondSignal(SDL_cond *cond); + +/** Restart all threads that are waiting on the condition variable, + * @return 0 or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondBroadcast(SDL_cond *cond); + +/** Wait on the condition variable, unlocking the provided mutex. + * The mutex must be locked before entering this function! + * The mutex is re-locked once the condition variable is signaled. + * @return 0 when it is signaled, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondWait(SDL_cond *cond, SDL_mutex *mut); + +/** Waits for at most 'ms' milliseconds, and returns 0 if the condition + * variable is signaled, SDL_MUTEX_TIMEDOUT if the condition is not + * signaled in the allotted time, and -1 on error. + * On some platforms this function is implemented by looping with a delay + * of 1 ms, and so should be avoided if possible. + */ +extern DECLSPEC int SDLCALL SDL_CondWaitTimeout(SDL_cond *cond, SDL_mutex *mutex, Uint32 ms); + +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_mutex_h */ + diff --git a/src/SDL12/include/SDL_name.h b/src/SDL12/include/SDL_name.h new file mode 100644 index 0000000..511619a --- /dev/null +++ b/src/SDL12/include/SDL_name.h @@ -0,0 +1,11 @@ + +#ifndef _SDLname_h_ +#define _SDLname_h_ + +#if defined(__STDC__) || defined(__cplusplus) +#define NeedFunctionPrototypes 1 +#endif + +#define SDL_NAME(X) SDL_##X + +#endif /* _SDLname_h_ */ diff --git a/src/SDL12/include/SDL_opengl.h b/src/SDL12/include/SDL_opengl.h new file mode 100644 index 0000000..c479a3a --- /dev/null +++ b/src/SDL12/include/SDL_opengl.h @@ -0,0 +1,6556 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_opengl.h + * This is a simple file to encapsulate the OpenGL API headers + */ + +#include "SDL_config.h" + +#ifdef __WIN32__ +#define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX /* Don't defined min() and max() */ +#endif +#include +#endif +#ifndef NO_SDL_GLEXT +#define __glext_h_ /* Don't let gl.h include glext.h */ +#endif +#if defined(__MACOSX__) +#include /* Header File For The OpenGL Library */ +#include /* Header File For The GLU Library */ +#elif defined(__MACOS__) +#include /* Header File For The OpenGL Library */ +#include /* Header File For The GLU Library */ +#else +#include /* Header File For The OpenGL Library */ +#include /* Header File For The GLU Library */ +#endif +#ifndef NO_SDL_GLEXT +#undef __glext_h_ +#endif + +/** @name GLext.h + * This file taken from "GLext.h" from the Jeff Molofee OpenGL tutorials. + * It is included here because glext.h is not available on some systems. + * If you don't want this version included, simply define "NO_SDL_GLEXT" + */ +/*@{*/ +#ifndef NO_SDL_GLEXT +#if !defined(__glext_h_) && !defined(GL_GLEXT_LEGACY) +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2004 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +/* Header file version number, required by OpenGL ABI for Linux */ +/* glext.h last updated 2005/06/20 */ +/* Current version at http://oss.sgi.com/projects/ogl-sample/registry/ */ +#define GL_GLEXT_VERSION 29 + +#ifndef GL_VERSION_1_2 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#endif + +#ifndef GL_ARB_imaging +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +#endif + +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#endif + +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#endif + +#ifndef GL_VERSION_1_5 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_FOG_COORD_SRC GL_FOG_COORDINATE_SOURCE +#define GL_FOG_COORD GL_FOG_COORDINATE +#define GL_CURRENT_FOG_COORD GL_CURRENT_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY_TYPE GL_FOG_COORDINATE_ARRAY_TYPE +#define GL_FOG_COORD_ARRAY_STRIDE GL_FOG_COORDINATE_ARRAY_STRIDE +#define GL_FOG_COORD_ARRAY_POINTER GL_FOG_COORDINATE_ARRAY_POINTER +#define GL_FOG_COORD_ARRAY GL_FOG_COORDINATE_ARRAY +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING +#define GL_SRC0_RGB GL_SOURCE0_RGB +#define GL_SRC1_RGB GL_SOURCE1_RGB +#define GL_SRC2_RGB GL_SOURCE2_RGB +#define GL_SRC0_ALPHA GL_SOURCE0_ALPHA +#define GL_SRC1_ALPHA GL_SOURCE1_ALPHA +#define GL_SRC2_ALPHA GL_SOURCE2_ALPHA +#endif + +#ifndef GL_VERSION_2_0 +#define GL_BLEND_EQUATION_RGB GL_BLEND_EQUATION +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_env_add +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif + +#ifndef GL_ARB_point_parameters +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +#ifndef GL_ARB_shadow +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif + +#ifndef GL_ARB_window_pos +#endif + +#ifndef GL_ARB_vertex_program +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#endif + +#ifndef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +#endif + +#ifndef GL_ARB_shader_objects +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#endif + +#ifndef GL_ARB_point_sprite +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#endif + +#ifndef GL_ARB_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +#endif + +#ifndef GL_ARB_texture_rectangle +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif + +#ifndef GL_ARB_color_buffer_float +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_HALF_FLOAT_ARB 0x140B +#endif + +#ifndef GL_ARB_texture_float +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif + +#ifndef GL_ARB_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_EXT_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_FfdMaskSGIX +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_SGIX_impact_pixel_texture +#define GL_PIXEL_TEX_GEN_Q_CEILING_SGIX 0x8184 +#define GL_PIXEL_TEX_GEN_Q_ROUND_SGIX 0x8185 +#define GL_PIXEL_TEX_GEN_Q_FLOOR_SGIX 0x8186 +#define GL_PIXEL_TEX_GEN_ALPHA_REPLACE_SGIX 0x8187 +#define GL_PIXEL_TEX_GEN_ALPHA_NO_REPLACE_SGIX 0x8188 +#define GL_PIXEL_TEX_GEN_ALPHA_LS_SGIX 0x8189 +#define GL_PIXEL_TEX_GEN_ALPHA_MS_SGIX 0x818A +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_SGIX_async +#define GL_ASYNC_MARKER_SGIX 0x8329 +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#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_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#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_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif + +#ifndef GL_NV_fence +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +#endif + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif + +#ifndef GL_NV_evaluators +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +#endif + +#ifndef GL_NV_texture_compression_vtc +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif + +#ifndef GL_NV_texture_shader +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV GL_OFFSET_TEXTURE_MATRIX_NV +#define GL_OFFSET_TEXTURE_2D_SCALE_NV GL_OFFSET_TEXTURE_SCALE_NV +#define GL_OFFSET_TEXTURE_2D_BIAS_NV GL_OFFSET_TEXTURE_BIAS_NV +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif + +#ifndef GL_NV_vertex_program +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif + +#ifndef GL_OML_interlace +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif + +#ifndef GL_OML_subsample +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif + +#ifndef GL_OML_resample +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +#endif + +#ifndef GL_ATI_element_array +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +#endif + +#ifndef GL_SUN_mesh_array +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_DEPTH_CLAMP_NV 0x864F +#endif + +#ifndef GL_NV_occlusion_query +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +#endif + +#ifndef GL_NV_point_sprite +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif + +#ifndef GL_NV_vertex_program1_1 +#endif + +#ifndef GL_EXT_shadow_funcs +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif + +#ifndef GL_APPLE_element_array +#define GL_ELEMENT_ARRAY_APPLE 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x876A +#endif + +#ifndef GL_APPLE_fence +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_YCBCR_422_APPLE 0x85B9 +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#endif + +#ifndef GL_S3_s3tc +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +#endif + +#ifndef GL_ATI_pixel_format_float +#define GL_TYPE_RGBA_FLOAT_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif + +#ifndef GL_ATI_texture_float +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif + +#ifndef GL_NV_float_buffer +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif + +#ifndef GL_NV_fragment_program +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +#endif + +#ifndef GL_NV_half_float +#define GL_HALF_FLOAT_NV 0x140B +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +#endif + +#ifndef GL_NV_primitive_restart +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif + +#ifndef GL_NV_vertex_program2 +#endif + +#ifndef GL_ATI_map_object_buffer +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#endif + +#ifndef GL_OES_read_format +#define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_BLEND_EQUATION_RGB_EXT GL_BLEND_EQUATION +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +#endif + +#ifndef GL_MESA_pack_invert +#define GL_PACK_INVERT_MESA 0x8758 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif + +#ifndef GL_EXT_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF +#endif + +#ifndef GL_NV_fragment_program_option +#endif + +#ifndef GL_NV_fragment_program2 +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 +#endif + +#ifndef GL_NV_vertex_program2_option +/* reuse GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV */ +/* reuse GL_MAX_PROGRAM_CALL_DEPTH_NV */ +#endif + +#ifndef GL_NV_vertex_program3 +/* reuse GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB */ +#endif + +#ifndef GL_EXT_framebuffer_object +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT 0x8CD8 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +#endif + +#ifndef GL_GREMEDY_string_marker +#endif + + +/*************************************************************/ + +#include +#ifndef GL_VERSION_2_0 +/* GL type for program/shader text */ +typedef char GLchar; /* native character */ +#endif + +#ifndef GL_VERSION_1_5 +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptr; +typedef ptrdiff_t GLsizeiptr; +#endif + +#ifndef GL_ARB_vertex_buffer_object +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptrARB; +typedef ptrdiff_t GLsizeiptrARB; +#endif + +#ifndef GL_ARB_shader_objects +/* GL types for handling shader object handles and program/shader text */ +typedef char GLcharARB; /* native character */ +typedef unsigned int GLhandleARB; /* shader object handle */ +#endif + +/* GL types for "half" precision (s10e5) float data in host memory */ +#ifndef GL_ARB_half_float_pixel +typedef unsigned short GLhalfARB; +#endif + +#ifndef GL_NV_half_float +typedef unsigned short GLhalfNV; +#endif + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColor (GLclampf, GLclampf, GLclampf, GLclampf); +GLAPI void APIENTRY glBlendEquation (GLenum); +GLAPI void APIENTRY glDrawRangeElements (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTable (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTableParameterfv (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glColorTableParameteriv (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyColorTable (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glGetColorTable (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glColorSubTable (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyColorSubTable (GLenum, GLsizei, GLint, GLint, GLsizei); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionParameterf (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glConvolutionParameteri (GLenum, GLenum, GLint); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetSeparableFilter (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +GLAPI void APIENTRY glSeparableFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +GLAPI void APIENTRY glGetHistogram (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMinmax (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glHistogram (GLenum, GLsizei, GLenum, GLboolean); +GLAPI void APIENTRY glMinmax (GLenum, GLenum, GLboolean); +GLAPI void APIENTRY glResetHistogram (GLenum); +GLAPI void APIENTRY glResetMinmax (GLenum); +GLAPI void APIENTRY glTexImage3D (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum); +GLAPI void APIENTRY glClientActiveTexture (GLenum); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum, GLdouble); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum, GLfloat); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum, GLint); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum, GLshort); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum, const GLshort *); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *); +GLAPI void APIENTRY glSampleCoverage (GLclampf, GLboolean); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum, GLint, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glFogCoordf (GLfloat); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *); +GLAPI void APIENTRY glFogCoordd (GLdouble); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *); +GLAPI void APIENTRY glFogCoordPointer (GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glMultiDrawArrays (GLenum, GLint *, GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawElements (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +GLAPI void APIENTRY glPointParameterf (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfv (GLenum, const GLfloat *); +GLAPI void APIENTRY glPointParameteri (GLenum, GLint); +GLAPI void APIENTRY glPointParameteriv (GLenum, const GLint *); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *); +GLAPI void APIENTRY glSecondaryColor3i (GLint, GLint, GLint); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *); +GLAPI void APIENTRY glSecondaryColor3s (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *); +GLAPI void APIENTRY glSecondaryColor3us (GLushort, GLushort, GLushort); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *); +GLAPI void APIENTRY glSecondaryColorPointer (GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glWindowPos2d (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *); +GLAPI void APIENTRY glWindowPos2f (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *); +GLAPI void APIENTRY glWindowPos2i (GLint, GLint); +GLAPI void APIENTRY glWindowPos2iv (const GLint *); +GLAPI void APIENTRY glWindowPos2s (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *); +GLAPI void APIENTRY glWindowPos3d (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *); +GLAPI void APIENTRY glWindowPos3f (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *); +GLAPI void APIENTRY glWindowPos3i (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3iv (const GLint *); +GLAPI void APIENTRY glWindowPos3s (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +#endif + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteQueries (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsQuery (GLuint); +GLAPI void APIENTRY glBeginQuery (GLenum, GLuint); +GLAPI void APIENTRY glEndQuery (GLenum); +GLAPI void APIENTRY glGetQueryiv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint, GLenum, GLuint *); +GLAPI void APIENTRY glBindBuffer (GLenum, GLuint); +GLAPI void APIENTRY glDeleteBuffers (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenBuffers (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint); +GLAPI void APIENTRY glBufferData (GLenum, GLsizeiptr, const GLvoid *, GLenum); +GLAPI void APIENTRY glBufferSubData (GLenum, GLintptr, GLsizeiptr, const GLvoid *); +GLAPI void APIENTRY glGetBufferSubData (GLenum, GLintptr, GLsizeiptr, GLvoid *); +GLAPI GLvoid* APIENTRY glMapBuffer (GLenum, GLenum); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetBufferPointerv (GLenum, GLenum, GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum, GLenum); +GLAPI void APIENTRY glDrawBuffers (GLsizei, const GLenum *); +GLAPI void APIENTRY glStencilOpSeparate (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glStencilFuncSeparate (GLenum, GLenum, GLint, GLuint); +GLAPI void APIENTRY glStencilMaskSeparate (GLenum, GLuint); +GLAPI void APIENTRY glAttachShader (GLuint, GLuint); +GLAPI void APIENTRY glBindAttribLocation (GLuint, GLuint, const GLchar *); +GLAPI void APIENTRY glCompileShader (GLuint); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum); +GLAPI void APIENTRY glDeleteProgram (GLuint); +GLAPI void APIENTRY glDeleteShader (GLuint); +GLAPI void APIENTRY glDetachShader (GLuint, GLuint); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint); +GLAPI void APIENTRY glGetActiveAttrib (GLuint, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLchar *); +GLAPI void APIENTRY glGetActiveUniform (GLuint, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLchar *); +GLAPI void APIENTRY glGetAttachedShaders (GLuint, GLsizei, GLsizei *, GLuint *); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint, const GLchar *); +GLAPI void APIENTRY glGetProgramiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint, GLsizei, GLsizei *, GLchar *); +GLAPI void APIENTRY glGetShaderiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint, GLsizei, GLsizei *, GLchar *); +GLAPI void APIENTRY glGetShaderSource (GLuint, GLsizei, GLsizei *, GLchar *); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint, const GLchar *); +GLAPI void APIENTRY glGetUniformfv (GLuint, GLint, GLfloat *); +GLAPI void APIENTRY glGetUniformiv (GLuint, GLint, GLint *); +GLAPI void APIENTRY glGetVertexAttribdv (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfv (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgram (GLuint); +GLAPI GLboolean APIENTRY glIsShader (GLuint); +GLAPI void APIENTRY glLinkProgram (GLuint); +GLAPI void APIENTRY glShaderSource (GLuint, GLsizei, const GLchar* *, const GLint *); +GLAPI void APIENTRY glUseProgram (GLuint); +GLAPI void APIENTRY glUniform1f (GLint, GLfloat); +GLAPI void APIENTRY glUniform2f (GLint, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform3f (GLint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform4f (GLint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform1i (GLint, GLint); +GLAPI void APIENTRY glUniform2i (GLint, GLint, GLint); +GLAPI void APIENTRY glUniform3i (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform4i (GLint, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform1fv (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform2fv (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform3fv (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform4fv (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform1iv (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform2iv (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform3iv (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform4iv (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniformMatrix2fv (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix3fv (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix4fv (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glValidateProgram (GLuint); +GLAPI void APIENTRY glVertexAttrib1d (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dv (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1f (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fv (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1s (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1sv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2d (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dv (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2f (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fv (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2s (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2sv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3d (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dv (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3f (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fv (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3s (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3sv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4Nbv (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4Niv (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4Nsv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4Nub (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4Nubv (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4Nuiv (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4Nusv (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttrib4bv (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4d (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dv (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4f (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fv (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4iv (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4s (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4sv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubv (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4uiv (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4usv (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttribPointer (GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *obj); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum, GLdouble); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum, GLfloat); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum, GLint); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum, GLshort); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum, const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLclampf, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum, GLint, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#endif + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvARB (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint, const GLbyte *); +GLAPI void APIENTRY glWeightsvARB (GLint, const GLshort *); +GLAPI void APIENTRY glWeightivARB (GLint, const GLint *); +GLAPI void APIENTRY glWeightfvARB (GLint, const GLfloat *); +GLAPI void APIENTRY glWeightdvARB (GLint, const GLdouble *); +GLAPI void APIENTRY glWeightubvARB (GLint, const GLubyte *); +GLAPI void APIENTRY glWeightusvARB (GLint, const GLushort *); +GLAPI void APIENTRY glWeightuivARB (GLint, const GLuint *); +GLAPI void APIENTRY glWeightPointerARB (GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexBlendARB (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint, const GLubyte *); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint, const GLushort *); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint, const GLuint *); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#endif + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#endif + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *); +GLAPI void APIENTRY glWindowPos2iARB (GLint, GLint); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *); +GLAPI void APIENTRY glWindowPos2sARB (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *); +GLAPI void APIENTRY glWindowPos3iARB (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *); +GLAPI void APIENTRY glWindowPos3sARB (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#endif + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid *); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint); +GLAPI void APIENTRY glProgramStringARB (GLenum, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glBindProgramARB (GLenum, GLuint); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenProgramsARB (GLsizei, GLuint *); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum, GLuint, GLdouble *); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum, GLuint, GLfloat *); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum, GLuint, GLdouble *); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum, GLuint, GLfloat *); +GLAPI void APIENTRY glGetProgramivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramStringARB (GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, GLvoid *string); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#endif + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +/* All ARB_fragment_program entry points are shared with ARB_vertex_program. */ +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum, GLuint); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenBuffersARB (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint); +GLAPI void APIENTRY glBufferDataARB (GLenum, GLsizeiptrARB, const GLvoid *, GLenum); +GLAPI void APIENTRY glBufferSubDataARB (GLenum, GLintptrARB, GLsizeiptrARB, const GLvoid *); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum, GLintptrARB, GLsizeiptrARB, GLvoid *); +GLAPI GLvoid* APIENTRY glMapBufferARB (GLenum, GLenum); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum, GLenum, GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint); +GLAPI void APIENTRY glBeginQueryARB (GLenum, GLuint); +GLAPI void APIENTRY glEndQueryARB (GLenum); +GLAPI void APIENTRY glGetQueryivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint, GLenum, GLuint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB, GLhandleARB); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB, GLsizei, const GLcharARB* *, const GLint *); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB, GLhandleARB); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB); +GLAPI void APIENTRY glUniform1fARB (GLint, GLfloat); +GLAPI void APIENTRY glUniform2fARB (GLint, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform3fARB (GLint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform4fARB (GLint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform1iARB (GLint, GLint); +GLAPI void APIENTRY glUniform2iARB (GLint, GLint, GLint); +GLAPI void APIENTRY glUniform3iARB (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform4iARB (GLint, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform1fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform2fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform3fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform4fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform1ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform2ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform3ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform4ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB, GLenum, GLfloat *); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB, GLenum, GLint *); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB, GLsizei, GLsizei *, GLcharARB *); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB, GLsizei, GLsizei *, GLhandleARB *); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB, const GLcharARB *); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB, GLint, GLfloat *); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB, GLint, GLint *); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB, GLsizei, GLsizei *, GLcharARB *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB* *string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB, GLuint, const GLcharARB *); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB, const GLcharARB *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif + +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersARB (GLsizei, const GLenum *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ARB_texture_rectangle +#define GL_ARB_texture_rectangle 1 +#endif + +#ifndef GL_ARB_color_buffer_float +#define GL_ARB_color_buffer_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClampColorARB (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel 1 +#endif + +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float 1 +#endif + +#ifndef GL_ARB_pixel_buffer_object +#define GL_ARB_pixel_buffer_object 1 +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLclampf, GLclampf, GLclampf, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum, GLenum, GLsizei, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glHistogramEXT (GLenum, GLsizei, GLenum, GLboolean); +GLAPI void APIENTRY glMinmaxEXT (GLenum, GLenum, GLboolean); +GLAPI void APIENTRY glResetHistogramEXT (GLenum); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum, GLenum, GLint); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glGetColorTableSGI (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum, GLint); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum, const GLint *); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum, GLfloat); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum, const GLfloat *); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum, GLint *); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei, const GLuint *, GLboolean *); +GLAPI void APIENTRY glBindTextureEXT (GLenum, GLuint); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei, const GLuint *, const GLclampf *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf, GLboolean); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint); +GLAPI void APIENTRY glColorPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glDrawArraysEXT (GLenum, GLint, GLsizei); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei, GLsizei, const GLboolean *); +GLAPI void APIENTRY glGetPointervEXT (GLenum, GLvoid* *); +GLAPI void APIENTRY glIndexPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glNormalPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum, GLfloat); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum, const GLfloat *); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum, GLint); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei, GLint *); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, const GLdouble *); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, const GLfloat *); +GLAPI void APIENTRY glDeformSGIX (GLbitfield); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum, GLenum, GLint); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glGetColorTableEXT (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glListParameterfSGIX (GLuint, GLenum, GLfloat); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint, GLenum, const GLfloat *); +GLAPI void APIENTRY glListParameteriSGIX (GLuint, GLenum, GLint); +GLAPI void APIENTRY glListParameterivSGIX (GLuint, GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint, GLsizei); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum, GLdouble *); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum, GLenum); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum, GLenum, GLint); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum, GLfloat); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum, GLint); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum, const GLint *); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum, GLenum, GLint); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glLightEnviSGIX (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum); +GLAPI void APIENTRY glTextureLightEXT (GLenum); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint, GLsizei); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint, GLenum, const GLvoid* *); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum, const GLvoid* *); +GLAPI void APIENTRY glColorPointervINTEL (GLint, GLenum, const GLvoid* *); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint, GLenum, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum, GLenum, GLint); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum, GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort, GLushort, GLushort); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum, GLint *, GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *); +GLAPI void APIENTRY glTangent3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *); +GLAPI void APIENTRY glTangent3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *); +GLAPI void APIENTRY glTangent3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *); +GLAPI void APIENTRY glTangent3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *); +GLAPI void APIENTRY glBinormal3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *); +GLAPI void APIENTRY glBinormal3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *); +GLAPI void APIENTRY glTangentPointerEXT (GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum, GLsizei, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const GLvoid *pointer); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum, const GLfloat *); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum, GLfloat); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum, const GLint *); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum, GLint); +GLAPI void APIENTRY glCombinerInputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glCombinerOutputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum, GLenum, GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum, GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum, GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos2iMESA (GLint, GLint); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos3iMESA (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos4iMESA (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *, const GLint *, const GLsizei *, GLsizei, GLint); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *, const GLsizei *, GLenum, const GLvoid* const *, GLsizei, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* const *indices, GLsizei primcount, GLint modestride); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint, const GLboolean* *, GLint); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glVertexPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf, GLboolean); +GLAPI void APIENTRY glSamplePatternEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean, GLboolean, GLboolean, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const GLvoid *params); +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#endif + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenFencesNV (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint); +GLAPI void APIENTRY glGetFenceivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glFinishFenceNV (GLuint); +GLAPI void APIENTRY glSetFenceNV (GLuint, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#endif + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum, GLuint, GLenum, GLsizei, GLsizei, GLint, GLint, GLboolean, const GLvoid *); +GLAPI void APIENTRY glMapParameterivNV (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glMapParameterfvNV (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum, GLuint, GLenum, GLsizei, GLsizei, GLboolean, GLvoid *); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum, GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum, GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glEvalMapsNV (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const GLvoid *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, GLvoid *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#endif + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#endif + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei, const GLuint *, GLboolean *); +GLAPI void APIENTRY glBindProgramNV (GLenum, GLuint); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glExecuteProgramNV (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glGenProgramsNV (GLsizei, GLuint *); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum, GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum, GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetProgramivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramStringNV (GLuint, GLenum, GLubyte *); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum, GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint); +GLAPI void APIENTRY glLoadProgramNV (GLenum, GLuint, GLsizei, const GLubyte *); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum, GLuint, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum, GLuint, GLuint, const GLfloat *); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glTrackMatrixNV (GLenum, GLuint, GLenum, GLenum); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint, GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint, GLsizei, const GLubyte *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLuint count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLuint count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#endif + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#endif + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#endif + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum, const GLint *); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum, GLint *); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint, GLuint, GLenum); +GLAPI void APIENTRY glSampleMapATI (GLuint, GLuint, GLenum); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum, GLint); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei, const GLvoid *, GLenum); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint, GLuint, GLsizei, const GLvoid *, GLenum); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint); +GLAPI void APIENTRY glArrayObjectATI (GLenum, GLint, GLenum, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint, GLenum, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const GLvoid *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const GLvoid *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint); +GLAPI void APIENTRY glShaderOp1EXT (GLenum, GLuint, GLuint); +GLAPI void APIENTRY glShaderOp2EXT (GLenum, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glShaderOp3EXT (GLenum, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSwizzleEXT (GLuint, GLuint, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glWriteMaskEXT (GLuint, GLuint, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glInsertComponentEXT (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glExtractComponentEXT (GLuint, GLuint, GLuint); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum, GLenum, GLenum, GLuint); +GLAPI void APIENTRY glSetInvariantEXT (GLuint, GLenum, const GLvoid *); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint, GLenum, const GLvoid *); +GLAPI void APIENTRY glVariantbvEXT (GLuint, const GLbyte *); +GLAPI void APIENTRY glVariantsvEXT (GLuint, const GLshort *); +GLAPI void APIENTRY glVariantivEXT (GLuint, const GLint *); +GLAPI void APIENTRY glVariantfvEXT (GLuint, const GLfloat *); +GLAPI void APIENTRY glVariantdvEXT (GLuint, const GLdouble *); +GLAPI void APIENTRY glVariantubvEXT (GLuint, const GLubyte *); +GLAPI void APIENTRY glVariantusvEXT (GLuint, const GLushort *); +GLAPI void APIENTRY glVariantuivEXT (GLuint, const GLuint *); +GLAPI void APIENTRY glVariantPointerEXT (GLuint, GLenum, GLuint, const GLvoid *); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum, GLenum, GLenum); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint, GLenum); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint, GLenum, GLvoid* *); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const GLvoid *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, GLvoid* *data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum, GLshort); +GLAPI void APIENTRY glVertexStream1svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream1iATI (GLenum, GLint); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream1fATI (GLenum, GLfloat); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream1dATI (GLenum, GLdouble); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream2sATI (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream2svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream2iATI (GLenum, GLint, GLint); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream2fATI (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream2dATI (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream3sATI (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream3svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream3iATI (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream3fATI (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream3dATI (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream4sATI (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream4svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream4iATI (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream4fATI (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream4dATI (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glNormalStream3bATI (GLenum, GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum, const GLbyte *); +GLAPI void APIENTRY glNormalStream3sATI (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glNormalStream3svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glNormalStream3iATI (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glNormalStream3fATI (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glNormalStream3dATI (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum, GLint); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum, const GLvoid *); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum, GLsizei); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum, GLuint, GLuint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#endif + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint, GLenum, GLuint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum, GLint); +GLAPI void APIENTRY glPointParameterivNV (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#endif + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#endif + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum, const GLvoid *); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum, GLint, GLsizei); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum, GLuint, GLuint, GLint, GLsizei); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum, const GLint *, const GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum, GLuint, GLuint, const GLint *, const GLsizei *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei, const GLuint *); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum, GLuint); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei, GLvoid *); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei, GLvoid *); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#endif + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei, const GLenum *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ATI_pixel_format_float +#define GL_ATI_pixel_format_float 1 +/* This is really a WGL extension, but defines some associated GL enums. + * ATI does not export "GL_ATI_pixel_format_float" in the GL_EXTENSIONS string. + */ +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#endif + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#endif + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#endif + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +/* Some NV_fragment_program entry points are shared with ARB_vertex_program. */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint, GLsizei, const GLubyte *, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint, GLsizei, const GLubyte *, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint, GLsizei, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint, GLsizei, const GLubyte *, const GLdouble *); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint, GLsizei, const GLubyte *, GLfloat *); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint, GLsizei, const GLubyte *, GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glColor3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glColor4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint, GLsizei, const GLhalfNV *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum, GLsizei, GLvoid *); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#endif + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#endif + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvoid* APIENTRY glMapObjectBufferATI (GLuint); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLvoid* (APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum, GLenum, GLint, GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint, GLint, GLenum, GLboolean, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#endif + +#ifndef GL_OES_read_format +#define GL_OES_read_format 1 +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd, GLclampd); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#endif + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#endif + +#ifndef GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_buffer_object 1 +#endif + +#ifndef GL_NV_fragment_program_option +#define GL_NV_fragment_program_option 1 +#endif + +#ifndef GL_NV_fragment_program2 +#define GL_NV_fragment_program2 1 +#endif + +#ifndef GL_NV_vertex_program2_option +#define GL_NV_vertex_program2_option 1 +#endif + +#ifndef GL_NV_vertex_program3 +#define GL_NV_vertex_program3 1 +#endif + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbufferEXT (GLuint); +GLAPI void APIENTRY glBindRenderbufferEXT (GLenum, GLuint); +GLAPI void APIENTRY glDeleteRenderbuffersEXT (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenRenderbuffersEXT (GLsizei, GLuint *); +GLAPI void APIENTRY glRenderbufferStorageEXT (GLenum, GLenum, GLsizei, GLsizei); +GLAPI void APIENTRY glGetRenderbufferParameterivEXT (GLenum, GLenum, GLint *); +GLAPI GLboolean APIENTRY glIsFramebufferEXT (GLuint); +GLAPI void APIENTRY glBindFramebufferEXT (GLenum, GLuint); +GLAPI void APIENTRY glDeleteFramebuffersEXT (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenFramebuffersEXT (GLsizei, GLuint *); +GLAPI GLenum APIENTRY glCheckFramebufferStatusEXT (GLenum); +GLAPI void APIENTRY glFramebufferTexture1DEXT (GLenum, GLenum, GLenum, GLuint, GLint); +GLAPI void APIENTRY glFramebufferTexture2DEXT (GLenum, GLenum, GLenum, GLuint, GLint); +GLAPI void APIENTRY glFramebufferTexture3DEXT (GLenum, GLenum, GLenum, GLuint, GLint, GLint); +GLAPI void APIENTRY glFramebufferRenderbufferEXT (GLenum, GLenum, GLenum, GLuint); +GLAPI void APIENTRY glGetFramebufferAttachmentParameterivEXT (GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGenerateMipmapEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); +#endif + +#ifndef GL_GREMEDY_string_marker +#define GL_GREMEDY_string_marker 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const GLvoid *string); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif +#endif /* NO_SDL_GLEXT */ +/*@}*/ diff --git a/src/SDL12/include/SDL_platform.h b/src/SDL12/include/SDL_platform.h new file mode 100644 index 0000000..11d8673 --- /dev/null +++ b/src/SDL12/include/SDL_platform.h @@ -0,0 +1,110 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_platform.h + * Try to get a standard set of platform defines + */ + +#ifndef _SDL_platform_h +#define _SDL_platform_h + +#if defined(_AIX) +#undef __AIX__ +#define __AIX__ 1 +#endif +#if defined(__BEOS__) +#undef __BEOS__ +#define __BEOS__ 1 +#endif +#if defined(__HAIKU__) +#undef __HAIKU__ +#define __HAIKU__ 1 +#endif +#if defined(bsdi) || defined(__bsdi) || defined(__bsdi__) +#undef __BSDI__ +#define __BSDI__ 1 +#endif +#if defined(_arch_dreamcast) +#undef __DREAMCAST__ +#define __DREAMCAST__ 1 +#endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) +#undef __FREEBSD__ +#define __FREEBSD__ 1 +#endif +#if defined(__HAIKU__) +#undef __HAIKU__ +#define __HAIKU__ 1 +#endif +#if defined(hpux) || defined(__hpux) || defined(__hpux__) +#undef __HPUX__ +#define __HPUX__ 1 +#endif +#if defined(sgi) || defined(__sgi) || defined(__sgi__) || defined(_SGI_SOURCE) +#undef __IRIX__ +#define __IRIX__ 1 +#endif +#if defined(linux) || defined(__linux) || defined(__linux__) +#undef __LINUX__ +#define __LINUX__ 1 +#endif +#if defined(__APPLE__) +#undef __MACOSX__ +#define __MACOSX__ 1 +#elif defined(macintosh) +#undef __MACOS__ +#define __MACOS__ 1 +#endif +#if defined(__NetBSD__) +#undef __NETBSD__ +#define __NETBSD__ 1 +#endif +#if defined(__OpenBSD__) +#undef __OPENBSD__ +#define __OPENBSD__ 1 +#endif +#if defined(__OS2__) +#undef __OS2__ +#define __OS2__ 1 +#endif +#if defined(osf) || defined(__osf) || defined(__osf__) || defined(_OSF_SOURCE) +#undef __OSF__ +#define __OSF__ 1 +#endif +#if defined(__QNXNTO__) +#undef __QNXNTO__ +#define __QNXNTO__ 1 +#endif +#if defined(riscos) || defined(__riscos) || defined(__riscos__) +#undef __RISCOS__ +#define __RISCOS__ 1 +#endif +#if defined(__SVR4) +#undef __SOLARIS__ +#define __SOLARIS__ 1 +#endif +#if defined(WIN32) || defined(_WIN32) +#undef __WIN32__ +#define __WIN32__ 1 +#endif + +#endif /* _SDL_platform_h */ diff --git a/src/SDL12/include/SDL_quit.h b/src/SDL12/include/SDL_quit.h new file mode 100644 index 0000000..6d82e7e --- /dev/null +++ b/src/SDL12/include/SDL_quit.h @@ -0,0 +1,55 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_quit.h + * Include file for SDL quit event handling + */ + +#ifndef _SDL_quit_h +#define _SDL_quit_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/** @file SDL_quit.h + * An SDL_QUITEVENT is generated when the user tries to close the application + * window. If it is ignored or filtered out, the window will remain open. + * If it is not ignored or filtered, it is queued normally and the window + * is allowed to close. When the window is closed, screen updates will + * complete, but have no effect. + * + * SDL_Init() installs signal handlers for SIGINT (keyboard interrupt) + * and SIGTERM (system termination request), if handlers do not already + * exist, that generate SDL_QUITEVENT events as well. There is no way + * to determine the cause of an SDL_QUITEVENT, but setting a signal + * handler in your application will override the default generation of + * quit events for that signal. + */ + +/** @file SDL_quit.h + * There are no functions directly affecting the quit event + */ + +#define SDL_QuitRequested() \ + (SDL_PumpEvents(), SDL_PeepEvents(NULL,0,SDL_PEEKEVENT,SDL_QUITMASK)) + +#endif /* _SDL_quit_h */ diff --git a/src/SDL12/include/SDL_rwops.h b/src/SDL12/include/SDL_rwops.h new file mode 100644 index 0000000..a450119 --- /dev/null +++ b/src/SDL12/include/SDL_rwops.h @@ -0,0 +1,155 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_rwops.h + * This file provides a general interface for SDL to read and write + * data sources. It can easily be extended to files, memory, etc. + */ + +#ifndef _SDL_rwops_h +#define _SDL_rwops_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** This is the read/write operation structure -- very basic */ + +typedef struct SDL_RWops { + /** Seek to 'offset' relative to whence, one of stdio's whence values: + * SEEK_SET, SEEK_CUR, SEEK_END + * Returns the final offset in the data source. + */ + int (SDLCALL *seek)(struct SDL_RWops *context, int offset, int whence); + + /** Read up to 'maxnum' objects each of size 'size' from the data + * source to the area pointed at by 'ptr'. + * Returns the number of objects read, or -1 if the read failed. + */ + int (SDLCALL *read)(struct SDL_RWops *context, void *ptr, int size, int maxnum); + + /** Write exactly 'num' objects each of size 'objsize' from the area + * pointed at by 'ptr' to data source. + * Returns 'num', or -1 if the write failed. + */ + int (SDLCALL *write)(struct SDL_RWops *context, const void *ptr, int size, int num); + + /** Close and free an allocated SDL_FSops structure */ + int (SDLCALL *close)(struct SDL_RWops *context); + + Uint32 type; + union { +#if defined(__WIN32__) && !defined(__SYMBIAN32__) + struct { + int append; + void *h; + struct { + void *data; + int size; + int left; + } buffer; + } win32io; +#endif +#ifdef HAVE_STDIO_H + struct { + int autoclose; + FILE *fp; + } stdio; +#endif + struct { + Uint8 *base; + Uint8 *here; + Uint8 *stop; + } mem; + struct { + void *data1; + } unknown; + } hidden; + +} SDL_RWops; + + +/** @name Functions to create SDL_RWops structures from various data sources */ +/*@{*/ + +extern DECLSPEC SDL_RWops * SDLCALL SDL_RWFromFile(const char *file, const char *mode); + +#ifdef HAVE_STDIO_H +extern DECLSPEC SDL_RWops * SDLCALL SDL_RWFromFP(FILE *fp, int autoclose); +#endif + +extern DECLSPEC SDL_RWops * SDLCALL SDL_RWFromMem(void *mem, int size); +extern DECLSPEC SDL_RWops * SDLCALL SDL_RWFromConstMem(const void *mem, int size); + +extern DECLSPEC SDL_RWops * SDLCALL SDL_AllocRW(void); +extern DECLSPEC void SDLCALL SDL_FreeRW(SDL_RWops *area); + +/*@}*/ + +/** @name Seek Reference Points */ +/*@{*/ +#define RW_SEEK_SET 0 /**< Seek from the beginning of data */ +#define RW_SEEK_CUR 1 /**< Seek relative to current read point */ +#define RW_SEEK_END 2 /**< Seek relative to the end of data */ +/*@}*/ + +/** @name Macros to easily read and write from an SDL_RWops structure */ +/*@{*/ +#define SDL_RWseek(ctx, offset, whence) (ctx)->seek(ctx, offset, whence) +#define SDL_RWtell(ctx) (ctx)->seek(ctx, 0, RW_SEEK_CUR) +#define SDL_RWread(ctx, ptr, size, n) (ctx)->read(ctx, ptr, size, n) +#define SDL_RWwrite(ctx, ptr, size, n) (ctx)->write(ctx, ptr, size, n) +#define SDL_RWclose(ctx) (ctx)->close(ctx) +/*@}*/ + +/** @name Read an item of the specified endianness and return in native format */ +/*@{*/ +extern DECLSPEC Uint16 SDLCALL SDL_ReadLE16(SDL_RWops *src); +extern DECLSPEC Uint16 SDLCALL SDL_ReadBE16(SDL_RWops *src); +extern DECLSPEC Uint32 SDLCALL SDL_ReadLE32(SDL_RWops *src); +extern DECLSPEC Uint32 SDLCALL SDL_ReadBE32(SDL_RWops *src); +extern DECLSPEC Uint64 SDLCALL SDL_ReadLE64(SDL_RWops *src); +extern DECLSPEC Uint64 SDLCALL SDL_ReadBE64(SDL_RWops *src); +/*@}*/ + +/** @name Write an item of native format to the specified endianness */ +/*@{*/ +extern DECLSPEC int SDLCALL SDL_WriteLE16(SDL_RWops *dst, Uint16 value); +extern DECLSPEC int SDLCALL SDL_WriteBE16(SDL_RWops *dst, Uint16 value); +extern DECLSPEC int SDLCALL SDL_WriteLE32(SDL_RWops *dst, Uint32 value); +extern DECLSPEC int SDLCALL SDL_WriteBE32(SDL_RWops *dst, Uint32 value); +extern DECLSPEC int SDLCALL SDL_WriteLE64(SDL_RWops *dst, Uint64 value); +extern DECLSPEC int SDLCALL SDL_WriteBE64(SDL_RWops *dst, Uint64 value); +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_rwops_h */ diff --git a/src/SDL12/include/SDL_stdinc.h b/src/SDL12/include/SDL_stdinc.h new file mode 100644 index 0000000..e1f85fb --- /dev/null +++ b/src/SDL12/include/SDL_stdinc.h @@ -0,0 +1,620 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_stdinc.h + * This is a general header that includes C language support + */ + +#ifndef _SDL_stdinc_h +#define _SDL_stdinc_h + +#include "SDL_config.h" + + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#if defined(STDC_HEADERS) +# include +# include +# include +#else +# if defined(HAVE_STDLIB_H) +# include +# elif defined(HAVE_MALLOC_H) +# include +# endif +# if defined(HAVE_STDDEF_H) +# include +# endif +# if defined(HAVE_STDARG_H) +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#if defined(HAVE_INTTYPES_H) +# include +#elif defined(HAVE_STDINT_H) +# include +#endif +#ifdef HAVE_CTYPE_H +# include +#endif +#if defined(HAVE_ICONV) && defined(HAVE_ICONV_H) +# include +#endif + +/** The number of elements in an array */ +#define SDL_arraysize(array) (sizeof(array)/sizeof(array[0])) +#define SDL_TABLESIZE(table) SDL_arraysize(table) + +/* Use proper C++ casts when compiled as C++ to be compatible with the option + -Wold-style-cast of GCC (and -Werror=old-style-cast in GCC 4.2 and above. */ +#ifdef __cplusplus +#define SDL_reinterpret_cast(type, expression) reinterpret_cast(expression) +#define SDL_static_cast(type, expression) static_cast(expression) +#else +#define SDL_reinterpret_cast(type, expression) ((type)(expression)) +#define SDL_static_cast(type, expression) ((type)(expression)) +#endif + +/** @name Basic data types */ +/*@{*/ +typedef enum { + SDL_FALSE = 0, + SDL_TRUE = 1 +} SDL_bool; + +typedef int8_t Sint8; +typedef uint8_t Uint8; +typedef int16_t Sint16; +typedef uint16_t Uint16; +typedef int32_t Sint32; +typedef uint32_t Uint32; + +#ifdef SDL_HAS_64BIT_TYPE +typedef int64_t Sint64; +#ifndef SYMBIAN32_GCCE +typedef uint64_t Uint64; +#endif +#else +/* This is really just a hack to prevent the compiler from complaining */ +typedef struct { + Uint32 hi; + Uint32 lo; +} Uint64, Sint64; +#endif + +/*@}*/ + +/** @name Make sure the types really have the right sizes */ +/*@{*/ +#define SDL_COMPILE_TIME_ASSERT(name, x) \ + typedef int SDL_dummy_ ## name[(x) * 2 - 1] + +SDL_COMPILE_TIME_ASSERT(uint8, sizeof(Uint8) == 1); +SDL_COMPILE_TIME_ASSERT(sint8, sizeof(Sint8) == 1); +SDL_COMPILE_TIME_ASSERT(uint16, sizeof(Uint16) == 2); +SDL_COMPILE_TIME_ASSERT(sint16, sizeof(Sint16) == 2); +SDL_COMPILE_TIME_ASSERT(uint32, sizeof(Uint32) == 4); +SDL_COMPILE_TIME_ASSERT(sint32, sizeof(Sint32) == 4); +SDL_COMPILE_TIME_ASSERT(uint64, sizeof(Uint64) == 8); +SDL_COMPILE_TIME_ASSERT(sint64, sizeof(Sint64) == 8); +/*@}*/ + +/** @name Enum Size Check + * Check to make sure enums are the size of ints, for structure packing. + * For both Watcom C/C++ and Borland C/C++ the compiler option that makes + * enums having the size of an int must be enabled. + * This is "-b" for Borland C/C++ and "-ei" for Watcom C/C++ (v11). + */ +/* Enable enums always int in CodeWarrior (for MPW use "-enum int") */ +#ifdef __MWERKS__ +#pragma enumsalwaysint on +#endif + +typedef enum { + DUMMY_ENUM_VALUE +} SDL_DUMMY_ENUM; + +#ifndef __NDS__ +SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int)); +#endif +/*@}*/ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HAVE_MALLOC +#define SDL_malloc malloc +#else +extern DECLSPEC void * SDLCALL SDL_malloc(size_t size); +#endif + +#ifdef HAVE_CALLOC +#define SDL_calloc calloc +#else +extern DECLSPEC void * SDLCALL SDL_calloc(size_t nmemb, size_t size); +#endif + +#ifdef HAVE_REALLOC +#define SDL_realloc realloc +#else +extern DECLSPEC void * SDLCALL SDL_realloc(void *mem, size_t size); +#endif + +#ifdef HAVE_FREE +#define SDL_free free +#else +extern DECLSPEC void SDLCALL SDL_free(void *mem); +#endif + +#if defined(HAVE_ALLOCA) && !defined(alloca) +# if defined(HAVE_ALLOCA_H) +# include +# elif defined(__GNUC__) +# define alloca __builtin_alloca +# elif defined(_MSC_VER) +# include +# define alloca _alloca +# elif defined(__WATCOMC__) +# include +# elif defined(__BORLANDC__) +# include +# elif defined(__DMC__) +# include +# elif defined(__AIX__) + #pragma alloca +# elif defined(__MRC__) + void *alloca (unsigned); +# else + char *alloca (); +# endif +#endif +#ifdef HAVE_ALLOCA +#define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count)) +#define SDL_stack_free(data) +#else +#define SDL_stack_alloc(type, count) (type*)SDL_malloc(sizeof(type)*(count)) +#define SDL_stack_free(data) SDL_free(data) +#endif + +#ifdef HAVE_GETENV +#define SDL_getenv getenv +#else +extern DECLSPEC char * SDLCALL SDL_getenv(const char *name); +#endif + +#ifdef HAVE_PUTENV +#define SDL_putenv putenv +#else +extern DECLSPEC int SDLCALL SDL_putenv(const char *variable); +#endif + +#ifdef HAVE_QSORT +#define SDL_qsort qsort +#else +extern DECLSPEC void SDLCALL SDL_qsort(void *base, size_t nmemb, size_t size, + int (*compare)(const void *, const void *)); +#endif + +#ifdef HAVE_ABS +#define SDL_abs abs +#else +#define SDL_abs(X) ((X) < 0 ? -(X) : (X)) +#endif + +#define SDL_min(x, y) (((x) < (y)) ? (x) : (y)) +#define SDL_max(x, y) (((x) > (y)) ? (x) : (y)) + +#ifdef HAVE_CTYPE_H +#define SDL_isdigit(X) isdigit(X) +#define SDL_isspace(X) isspace(X) +#define SDL_toupper(X) toupper(X) +#define SDL_tolower(X) tolower(X) +#else +#define SDL_isdigit(X) (((X) >= '0') && ((X) <= '9')) +#define SDL_isspace(X) (((X) == ' ') || ((X) == '\t') || ((X) == '\r') || ((X) == '\n')) +#define SDL_toupper(X) (((X) >= 'a') && ((X) <= 'z') ? ('A'+((X)-'a')) : (X)) +#define SDL_tolower(X) (((X) >= 'A') && ((X) <= 'Z') ? ('a'+((X)-'A')) : (X)) +#endif + +#ifdef HAVE_MEMSET +#define SDL_memset memset +#else +extern DECLSPEC void * SDLCALL SDL_memset(void *dst, int c, size_t len); +#endif + +#if defined(__GNUC__) && defined(i386) +#define SDL_memset4(dst, val, len) \ +do { \ + int u0, u1, u2; \ + __asm__ __volatile__ ( \ + "cld\n\t" \ + "rep ; stosl\n\t" \ + : "=&D" (u0), "=&a" (u1), "=&c" (u2) \ + : "0" (dst), "1" (val), "2" (SDL_static_cast(Uint32, len)) \ + : "memory" ); \ +} while(0) +#endif +#ifndef SDL_memset4 +#define SDL_memset4(dst, val, len) \ +do { \ + unsigned _count = (len); \ + unsigned _n = (_count + 3) / 4; \ + Uint32 *_p = SDL_static_cast(Uint32 *, dst); \ + Uint32 _val = (val); \ + if (len == 0) break; \ + switch (_count % 4) { \ + case 0: do { *_p++ = _val; \ + case 3: *_p++ = _val; \ + case 2: *_p++ = _val; \ + case 1: *_p++ = _val; \ + } while ( --_n ); \ + } \ +} while(0) +#endif + +/* We can count on memcpy existing on Mac OS X and being well-tuned. */ +#if defined(__MACH__) && defined(__APPLE__) +#define SDL_memcpy(dst, src, len) memcpy(dst, src, len) +#elif defined(__GNUC__) && defined(i386) +#define SDL_memcpy(dst, src, len) \ +do { \ + int u0, u1, u2; \ + __asm__ __volatile__ ( \ + "cld\n\t" \ + "rep ; movsl\n\t" \ + "testb $2,%b4\n\t" \ + "je 1f\n\t" \ + "movsw\n" \ + "1:\ttestb $1,%b4\n\t" \ + "je 2f\n\t" \ + "movsb\n" \ + "2:" \ + : "=&c" (u0), "=&D" (u1), "=&S" (u2) \ + : "0" (SDL_static_cast(unsigned, len)/4), "q" (len), "1" (dst),"2" (src) \ + : "memory" ); \ +} while(0) +#endif +#ifndef SDL_memcpy +#ifdef HAVE_MEMCPY +#define SDL_memcpy memcpy +#elif defined(HAVE_BCOPY) +#define SDL_memcpy(d, s, n) bcopy((s), (d), (n)) +#else +extern DECLSPEC void * SDLCALL SDL_memcpy(void *dst, const void *src, size_t len); +#endif +#endif + +/* We can count on memcpy existing on Mac OS X and being well-tuned. */ +#if defined(__MACH__) && defined(__APPLE__) +#define SDL_memcpy4(dst, src, len) memcpy(dst, src, (len)*4) +#elif defined(__GNUC__) && defined(i386) +#define SDL_memcpy4(dst, src, len) \ +do { \ + int ecx, edi, esi; \ + __asm__ __volatile__ ( \ + "cld\n\t" \ + "rep ; movsl" \ + : "=&c" (ecx), "=&D" (edi), "=&S" (esi) \ + : "0" (SDL_static_cast(unsigned, len)), "1" (dst), "2" (src) \ + : "memory" ); \ +} while(0) +#endif +#ifndef SDL_memcpy4 +#define SDL_memcpy4(dst, src, len) SDL_memcpy(dst, src, (len) << 2) +#endif + +#if defined(__GNUC__) && defined(i386) +#define SDL_revcpy(dst, src, len) \ +do { \ + int u0, u1, u2; \ + char *dstp = SDL_static_cast(char *, dst); \ + char *srcp = SDL_static_cast(char *, src); \ + int n = (len); \ + if ( n >= 4 ) { \ + __asm__ __volatile__ ( \ + "std\n\t" \ + "rep ; movsl\n\t" \ + "cld\n\t" \ + : "=&c" (u0), "=&D" (u1), "=&S" (u2) \ + : "0" (n >> 2), \ + "1" (dstp+(n-4)), "2" (srcp+(n-4)) \ + : "memory" ); \ + } \ + switch (n & 3) { \ + case 3: dstp[2] = srcp[2]; \ + case 2: dstp[1] = srcp[1]; \ + case 1: dstp[0] = srcp[0]; \ + break; \ + default: \ + break; \ + } \ +} while(0) +#endif +#ifndef SDL_revcpy +extern DECLSPEC void * SDLCALL SDL_revcpy(void *dst, const void *src, size_t len); +#endif + +#ifdef HAVE_MEMMOVE +#define SDL_memmove memmove +#elif defined(HAVE_BCOPY) +#define SDL_memmove(d, s, n) bcopy((s), (d), (n)) +#else +#define SDL_memmove(dst, src, len) \ +do { \ + if ( dst < src ) { \ + SDL_memcpy(dst, src, len); \ + } else { \ + SDL_revcpy(dst, src, len); \ + } \ +} while(0) +#endif + +#ifdef HAVE_MEMCMP +#define SDL_memcmp memcmp +#else +extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len); +#endif + +#ifdef HAVE_STRLEN +#define SDL_strlen strlen +#else +extern DECLSPEC size_t SDLCALL SDL_strlen(const char *string); +#endif + +#ifdef HAVE_STRLCPY +#define SDL_strlcpy strlcpy +#else +extern DECLSPEC size_t SDLCALL SDL_strlcpy(char *dst, const char *src, size_t maxlen); +#endif + +#ifdef HAVE_STRLCAT +#define SDL_strlcat strlcat +#else +extern DECLSPEC size_t SDLCALL SDL_strlcat(char *dst, const char *src, size_t maxlen); +#endif + +#ifdef HAVE_STRDUP +#define SDL_strdup strdup +#else +extern DECLSPEC char * SDLCALL SDL_strdup(const char *string); +#endif + +#ifdef HAVE__STRREV +#define SDL_strrev _strrev +#else +extern DECLSPEC char * SDLCALL SDL_strrev(char *string); +#endif + +#ifdef HAVE__STRUPR +#define SDL_strupr _strupr +#else +extern DECLSPEC char * SDLCALL SDL_strupr(char *string); +#endif + +#ifdef HAVE__STRLWR +#define SDL_strlwr _strlwr +#else +extern DECLSPEC char * SDLCALL SDL_strlwr(char *string); +#endif + +#ifdef HAVE_STRCHR +#define SDL_strchr strchr +#elif defined(HAVE_INDEX) +#define SDL_strchr index +#else +extern DECLSPEC char * SDLCALL SDL_strchr(const char *string, int c); +#endif + +#ifdef HAVE_STRRCHR +#define SDL_strrchr strrchr +#elif defined(HAVE_RINDEX) +#define SDL_strrchr rindex +#else +extern DECLSPEC char * SDLCALL SDL_strrchr(const char *string, int c); +#endif + +#ifdef HAVE_STRSTR +#define SDL_strstr strstr +#else +extern DECLSPEC char * SDLCALL SDL_strstr(const char *haystack, const char *needle); +#endif + +#ifdef HAVE_ITOA +#define SDL_itoa itoa +#else +#define SDL_itoa(value, string, radix) SDL_ltoa((long)value, string, radix) +#endif + +#ifdef HAVE__LTOA +#define SDL_ltoa _ltoa +#else +extern DECLSPEC char * SDLCALL SDL_ltoa(long value, char *string, int radix); +#endif + +#ifdef HAVE__UITOA +#define SDL_uitoa _uitoa +#else +#define SDL_uitoa(value, string, radix) SDL_ultoa((long)value, string, radix) +#endif + +#ifdef HAVE__ULTOA +#define SDL_ultoa _ultoa +#else +extern DECLSPEC char * SDLCALL SDL_ultoa(unsigned long value, char *string, int radix); +#endif + +#ifdef HAVE_STRTOL +#define SDL_strtol strtol +#else +extern DECLSPEC long SDLCALL SDL_strtol(const char *string, char **endp, int base); +#endif + +#ifdef HAVE_STRTOUL +#define SDL_strtoul strtoul +#else +extern DECLSPEC unsigned long SDLCALL SDL_strtoul(const char *string, char **endp, int base); +#endif + +#ifdef SDL_HAS_64BIT_TYPE + +#ifdef HAVE__I64TOA +#define SDL_lltoa _i64toa +#else +extern DECLSPEC char* SDLCALL SDL_lltoa(Sint64 value, char *string, int radix); +#endif + +#ifdef HAVE__UI64TOA +#define SDL_ulltoa _ui64toa +#else +extern DECLSPEC char* SDLCALL SDL_ulltoa(Uint64 value, char *string, int radix); +#endif + +#ifdef HAVE_STRTOLL +#define SDL_strtoll strtoll +#else +extern DECLSPEC Sint64 SDLCALL SDL_strtoll(const char *string, char **endp, int base); +#endif + +#ifdef HAVE_STRTOULL +#define SDL_strtoull strtoull +#else +extern DECLSPEC Uint64 SDLCALL SDL_strtoull(const char *string, char **endp, int base); +#endif + +#endif /* SDL_HAS_64BIT_TYPE */ + +#ifdef HAVE_STRTOD +#define SDL_strtod strtod +#else +extern DECLSPEC double SDLCALL SDL_strtod(const char *string, char **endp); +#endif + +#ifdef HAVE_ATOI +#define SDL_atoi atoi +#else +#define SDL_atoi(X) SDL_strtol(X, NULL, 0) +#endif + +#ifdef HAVE_ATOF +#define SDL_atof atof +#else +#define SDL_atof(X) SDL_strtod(X, NULL) +#endif + +#ifdef HAVE_STRCMP +#define SDL_strcmp strcmp +#else +extern DECLSPEC int SDLCALL SDL_strcmp(const char *str1, const char *str2); +#endif + +#ifdef HAVE_STRNCMP +#define SDL_strncmp strncmp +#else +extern DECLSPEC int SDLCALL SDL_strncmp(const char *str1, const char *str2, size_t maxlen); +#endif + +#ifdef HAVE_STRCASECMP +#define SDL_strcasecmp strcasecmp +#elif defined(HAVE__STRICMP) +#define SDL_strcasecmp _stricmp +#else +extern DECLSPEC int SDLCALL SDL_strcasecmp(const char *str1, const char *str2); +#endif + +#ifdef HAVE_STRNCASECMP +#define SDL_strncasecmp strncasecmp +#elif defined(HAVE__STRNICMP) +#define SDL_strncasecmp _strnicmp +#else +extern DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *str2, size_t maxlen); +#endif + +#ifdef HAVE_SSCANF +#define SDL_sscanf sscanf +#else +extern DECLSPEC int SDLCALL SDL_sscanf(const char *text, const char *fmt, ...); +#endif + +#ifdef HAVE_SNPRINTF +#define SDL_snprintf snprintf +#else +extern DECLSPEC int SDLCALL SDL_snprintf(char *text, size_t maxlen, const char *fmt, ...); +#endif + +#ifdef HAVE_VSNPRINTF +#define SDL_vsnprintf vsnprintf +#else +extern DECLSPEC int SDLCALL SDL_vsnprintf(char *text, size_t maxlen, const char *fmt, va_list ap); +#endif + +/** @name SDL_ICONV Error Codes + * The SDL implementation of iconv() returns these error codes + */ +/*@{*/ +#define SDL_ICONV_ERROR (size_t)-1 +#define SDL_ICONV_E2BIG (size_t)-2 +#define SDL_ICONV_EILSEQ (size_t)-3 +#define SDL_ICONV_EINVAL (size_t)-4 +/*@}*/ + +#if defined(HAVE_ICONV) && defined(HAVE_ICONV_H) +#define SDL_iconv_t iconv_t +#define SDL_iconv_open iconv_open +#define SDL_iconv_close iconv_close +#else +typedef struct _SDL_iconv_t *SDL_iconv_t; +extern DECLSPEC SDL_iconv_t SDLCALL SDL_iconv_open(const char *tocode, const char *fromcode); +extern DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); +#endif +extern DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft); +/** This function converts a string between encodings in one pass, returning a + * string that must be freed with SDL_free() or NULL on error. + */ +extern DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, const char *fromcode, const char *inbuf, size_t inbytesleft); +#define SDL_iconv_utf8_locale(S) SDL_iconv_string("", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs2(S) (Uint16 *)SDL_iconv_string("UCS-2", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs4(S) (Uint32 *)SDL_iconv_string("UCS-4", "UTF-8", S, SDL_strlen(S)+1) + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_stdinc_h */ diff --git a/src/SDL12/include/SDL_syswm.h b/src/SDL12/include/SDL_syswm.h new file mode 100644 index 0000000..716dddc --- /dev/null +++ b/src/SDL12/include/SDL_syswm.h @@ -0,0 +1,225 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_syswm.h + * Include file for SDL custom system window manager hooks + */ + +#ifndef _SDL_syswm_h +#define _SDL_syswm_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_version.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @file SDL_syswm.h + * Your application has access to a special type of event 'SDL_SYSWMEVENT', + * which contains window-manager specific information and arrives whenever + * an unhandled window event occurs. This event is ignored by default, but + * you can enable it with SDL_EventState() + */ +#ifdef SDL_PROTOTYPES_ONLY +struct SDL_SysWMinfo; +typedef struct SDL_SysWMinfo SDL_SysWMinfo; +#else + +/* This is the structure for custom window manager events */ +#if defined(SDL_VIDEO_DRIVER_X11) +#if defined(__APPLE__) && defined(__MACH__) +/* conflicts with Quickdraw.h */ +#define Cursor X11Cursor +#endif + +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +/* matches the re-define above */ +#undef Cursor +#endif + +/** These are the various supported subsystems under UNIX */ +typedef enum { + SDL_SYSWM_X11 +} SDL_SYSWM_TYPE; + +/** The UNIX custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union { + XEvent xevent; + } event; +}; + +/** The UNIX custom window manager information structure. + * When this structure is returned, it holds information about which + * low level system it is using, and will be one of SDL_SYSWM_TYPE. + */ +typedef struct SDL_SysWMinfo { + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union { + struct { + Display *display; /**< The X11 display */ + Window window; /**< The X11 display window */ + /** These locking functions should be called around + * any X11 functions using the display variable, + * but not the gfxdisplay variable. + * They lock the event thread, so should not be + * called around event functions or from event filters. + */ + /*@{*/ + void (*lock_func)(void); + void (*unlock_func)(void); + /*@}*/ + + /** @name Introduced in SDL 1.0.2 */ + /*@{*/ + Window fswindow; /**< The X11 fullscreen window */ + Window wmwindow; /**< The X11 managed input window */ + /*@}*/ + + /** @name Introduced in SDL 1.2.12 */ + /*@{*/ + Display *gfxdisplay; /**< The X11 display to which rendering is done */ + /*@}*/ + } x11; + } info; +} SDL_SysWMinfo; + +#elif defined(SDL_VIDEO_DRIVER_NANOX) +#include + +/** The generic custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + int data; +}; + +/** The windows custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version ; + GR_WINDOW_ID window ; /* The display window */ +} SDL_SysWMinfo; + +#elif defined(SDL_VIDEO_DRIVER_WINDIB) || defined(SDL_VIDEO_DRIVER_DDRAW) || defined(SDL_VIDEO_DRIVER_GAPI) +#define WIN32_LEAN_AND_MEAN +#include + +/** The windows custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + HWND hwnd; /**< The window for the message */ + UINT msg; /**< The type of message */ + WPARAM wParam; /**< WORD message parameter */ + LPARAM lParam; /**< LONG message parameter */ +}; + +/** The windows custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version; + HWND window; /**< The Win32 display window */ + HGLRC hglrc; /**< The OpenGL context, if any */ +} SDL_SysWMinfo; + +#elif defined(SDL_VIDEO_DRIVER_RISCOS) + +/** RISC OS custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + int eventCode; /**< The window for the message */ + int pollBlock[64]; +}; + +/** The RISC OS custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version; + int wimpVersion; /**< Wimp version running under */ + int taskHandle; /**< The RISC OS task handle */ + int window; /**< The RISC OS display window */ +} SDL_SysWMinfo; + +#elif defined(SDL_VIDEO_DRIVER_PHOTON) +#include +#include + +/** The QNX custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + int data; +}; + +/** The QNX custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version; + int data; +} SDL_SysWMinfo; + +#else + +/** The generic custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + int data; +}; + +/** The generic custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version; + int data; +} SDL_SysWMinfo; + +#endif /* video driver type */ + +#endif /* SDL_PROTOTYPES_ONLY */ + +/* Function prototypes */ +/** + * This function gives you custom hooks into the window manager information. + * It fills the structure pointed to by 'info' with custom information and + * returns 1 if the function is implemented. If it's not implemented, or + * the version member of the 'info' structure is invalid, it returns 0. + * + * You typically use this function like this: + * @code + * SDL_SysWMInfo info; + * SDL_VERSION(&info.version); + * if ( SDL_GetWMInfo(&info) ) { ... } + * @endcode + */ +extern DECLSPEC int SDLCALL SDL_GetWMInfo(SDL_SysWMinfo *info); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_syswm_h */ diff --git a/src/SDL12/include/SDL_thread.h b/src/SDL12/include/SDL_thread.h new file mode 100644 index 0000000..1ca9a1b --- /dev/null +++ b/src/SDL12/include/SDL_thread.h @@ -0,0 +1,120 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_thread_h +#define _SDL_thread_h + +/** @file SDL_thread.h + * Header for the SDL thread management routines + * + * @note These are independent of the other SDL routines. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/* Thread synchronization primitives */ +#include "SDL_mutex.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** The SDL thread structure, defined in SDL_thread.c */ +struct SDL_Thread; +typedef struct SDL_Thread SDL_Thread; + +/** Create a thread */ +#if ((defined(__WIN32__) && !defined(HAVE_LIBC)) || defined(__OS2__)) && !defined(__SYMBIAN32__) +/** + * We compile SDL into a DLL on OS/2. This means, that it's the DLL which + * creates a new thread for the calling process with the SDL_CreateThread() + * API. There is a problem with this, that only the RTL of the SDL.DLL will + * be initialized for those threads, and not the RTL of the calling application! + * To solve this, we make a little hack here. + * We'll always use the caller's _beginthread() and _endthread() APIs to + * start a new thread. This way, if it's the SDL.DLL which uses this API, + * then the RTL of SDL.DLL will be used to create the new thread, and if it's + * the application, then the RTL of the application will be used. + * So, in short: + * Always use the _beginthread() and _endthread() of the calling runtime library! + */ +#define SDL_PASSED_BEGINTHREAD_ENDTHREAD +#ifndef _WIN32_WCE +#include /* This has _beginthread() and _endthread() defined! */ +#endif + +#ifdef __OS2__ +typedef int (*pfnSDL_CurrentBeginThread)(void (*func)(void *), void *, unsigned, void *arg); +typedef void (*pfnSDL_CurrentEndThread)(void); +#elif __GNUC__ +typedef unsigned long (__cdecl *pfnSDL_CurrentBeginThread) (void *, unsigned, + unsigned (__stdcall *func)(void *), void *arg, + unsigned, unsigned *threadID); +typedef void (__cdecl *pfnSDL_CurrentEndThread)(unsigned code); +#else +typedef uintptr_t (__cdecl *pfnSDL_CurrentBeginThread) (void *, unsigned, + unsigned (__stdcall *func)(void *), void *arg, + unsigned, unsigned *threadID); +typedef void (__cdecl *pfnSDL_CurrentEndThread)(unsigned code); +#endif + +extern DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(int (SDLCALL *fn)(void *), void *data, pfnSDL_CurrentBeginThread pfnBeginThread, pfnSDL_CurrentEndThread pfnEndThread); + +#ifdef __OS2__ +#define SDL_CreateThread(fn, data) SDL_CreateThread(fn, data, _beginthread, _endthread) +#elif defined(_WIN32_WCE) +#define SDL_CreateThread(fn, data) SDL_CreateThread(fn, data, NULL, NULL) +#else +#define SDL_CreateThread(fn, data) SDL_CreateThread(fn, data, _beginthreadex, _endthreadex) +#endif +#else +extern DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(int (SDLCALL *fn)(void *), void *data); +#endif + +/** Get the 32-bit thread identifier for the current thread */ +extern DECLSPEC Uint32 SDLCALL SDL_ThreadID(void); + +/** Get the 32-bit thread identifier for the specified thread, + * equivalent to SDL_ThreadID() if the specified thread is NULL. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetThreadID(SDL_Thread *thread); + +/** Wait for a thread to finish. + * The return code for the thread function is placed in the area + * pointed to by 'status', if 'status' is not NULL. + */ +extern DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread *thread, int *status); + +/** Forcefully kill a thread without worrying about its state */ +extern DECLSPEC void SDLCALL SDL_KillThread(SDL_Thread *thread); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_thread_h */ diff --git a/src/SDL12/include/SDL_timer.h b/src/SDL12/include/SDL_timer.h new file mode 100644 index 0000000..d7cd024 --- /dev/null +++ b/src/SDL12/include/SDL_timer.h @@ -0,0 +1,125 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_timer_h +#define _SDL_timer_h + +/** @file SDL_timer.h + * Header for the SDL time management routines + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** This is the OS scheduler timeslice, in milliseconds */ +#define SDL_TIMESLICE 10 + +/** This is the maximum resolution of the SDL timer on all platforms */ +#define TIMER_RESOLUTION 10 /**< Experimentally determined */ + +/** + * Get the number of milliseconds since the SDL library initialization. + * Note that this value wraps if the program runs for more than ~49 days. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetTicks(void); + +/** Wait a specified number of milliseconds before returning */ +extern DECLSPEC void SDLCALL SDL_Delay(Uint32 ms); + +/** Function prototype for the timer callback function */ +typedef Uint32 (SDLCALL *SDL_TimerCallback)(Uint32 interval); + +/** + * Set a callback to run after the specified number of milliseconds has + * elapsed. The callback function is passed the current timer interval + * and returns the next timer interval. If the returned value is the + * same as the one passed in, the periodic alarm continues, otherwise a + * new alarm is scheduled. If the callback returns 0, the periodic alarm + * is cancelled. + * + * To cancel a currently running timer, call SDL_SetTimer(0, NULL); + * + * The timer callback function may run in a different thread than your + * main code, and so shouldn't call any functions from within itself. + * + * The maximum resolution of this timer is 10 ms, which means that if + * you request a 16 ms timer, your callback will run approximately 20 ms + * later on an unloaded system. If you wanted to set a flag signaling + * a frame update at 30 frames per second (every 33 ms), you might set a + * timer for 30 ms: + * @code SDL_SetTimer((33/10)*10, flag_update); @endcode + * + * If you use this function, you need to pass SDL_INIT_TIMER to SDL_Init(). + * + * Under UNIX, you should not use raise or use SIGALRM and this function + * in the same program, as it is implemented using setitimer(). You also + * should not use this function in multi-threaded applications as signals + * to multi-threaded apps have undefined behavior in some implementations. + * + * This function returns 0 if successful, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_SetTimer(Uint32 interval, SDL_TimerCallback callback); + +/** @name New timer API + * New timer API, supports multiple timers + * Written by Stephane Peter + */ +/*@{*/ + +/** + * Function prototype for the new timer callback function. + * The callback function is passed the current timer interval and returns + * the next timer interval. If the returned value is the same as the one + * passed in, the periodic alarm continues, otherwise a new alarm is + * scheduled. If the callback returns 0, the periodic alarm is cancelled. + */ +typedef Uint32 (SDLCALL *SDL_NewTimerCallback)(Uint32 interval, void *param); + +/** Definition of the timer ID type */ +typedef struct _SDL_TimerID *SDL_TimerID; + +/** Add a new timer to the pool of timers already running. + * Returns a timer ID, or NULL when an error occurs. + */ +extern DECLSPEC SDL_TimerID SDLCALL SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param); + +/** + * Remove one of the multiple timers knowing its ID. + * Returns a boolean value indicating success. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_RemoveTimer(SDL_TimerID t); + +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_timer_h */ diff --git a/src/SDL12/include/SDL_types.h b/src/SDL12/include/SDL_types.h new file mode 100644 index 0000000..cfa3523 --- /dev/null +++ b/src/SDL12/include/SDL_types.h @@ -0,0 +1,28 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_types.h + * @deprecated Use SDL_stdinc.h instead. + */ + +/* DEPRECATED */ +#include "SDL_stdinc.h" diff --git a/src/SDL12/include/SDL_version.h b/src/SDL12/include/SDL_version.h new file mode 100644 index 0000000..fa02c3f --- /dev/null +++ b/src/SDL12/include/SDL_version.h @@ -0,0 +1,91 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_version.h + * This header defines the current SDL version + */ + +#ifndef _SDL_version_h +#define _SDL_version_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @name Version Number + * Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL + */ +/*@{*/ +#define SDL_MAJOR_VERSION 1 +#define SDL_MINOR_VERSION 2 +#define SDL_PATCHLEVEL 14 +/*@}*/ + +typedef struct SDL_version { + Uint8 major; + Uint8 minor; + Uint8 patch; +} SDL_version; + +/** + * This macro can be used to fill a version structure with the compile-time + * version of the SDL library. + */ +#define SDL_VERSION(X) \ +{ \ + (X)->major = SDL_MAJOR_VERSION; \ + (X)->minor = SDL_MINOR_VERSION; \ + (X)->patch = SDL_PATCHLEVEL; \ +} + +/** This macro turns the version numbers into a numeric value: + * (1,2,3) -> (1203) + * This assumes that there will never be more than 100 patchlevels + */ +#define SDL_VERSIONNUM(X, Y, Z) \ + ((X)*1000 + (Y)*100 + (Z)) + +/** This is the version number macro for the current SDL version */ +#define SDL_COMPILEDVERSION \ + SDL_VERSIONNUM(SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL) + +/** This macro will evaluate to true if compiled with SDL at least X.Y.Z */ +#define SDL_VERSION_ATLEAST(X, Y, Z) \ + (SDL_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z)) + +/** This function gets the version of the dynamically linked SDL library. + * it should NOT be used to fill a version structure, instead you should + * use the SDL_Version() macro. + */ +extern DECLSPEC const SDL_version * SDLCALL SDL_Linked_Version(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_version_h */ diff --git a/src/SDL12/include/SDL_video.h b/src/SDL12/include/SDL_video.h new file mode 100644 index 0000000..8f7f305 --- /dev/null +++ b/src/SDL12/include/SDL_video.h @@ -0,0 +1,951 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_video.h + * Header file for access to the SDL raw framebuffer window + */ + +#ifndef _SDL_video_h +#define _SDL_video_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @name Transparency definitions + * These define alpha as the opacity of a surface + */ +/*@{*/ +#define SDL_ALPHA_OPAQUE 255 +#define SDL_ALPHA_TRANSPARENT 0 +/*@}*/ + +/** @name Useful data types */ +/*@{*/ +typedef struct SDL_Rect { + Sint16 x, y; + Uint16 w, h; +} SDL_Rect; + +typedef struct SDL_Color { + Uint8 r; + Uint8 g; + Uint8 b; + Uint8 unused; +} SDL_Color; +#define SDL_Colour SDL_Color + +typedef struct SDL_Palette { + int ncolors; + SDL_Color *colors; +} SDL_Palette; +/*@}*/ + +/** Everything in the pixel format structure is read-only */ +typedef struct SDL_PixelFormat { + SDL_Palette *palette; + Uint8 BitsPerPixel; + Uint8 BytesPerPixel; + Uint8 Rloss; + Uint8 Gloss; + Uint8 Bloss; + Uint8 Aloss; + Uint8 Rshift; + Uint8 Gshift; + Uint8 Bshift; + Uint8 Ashift; + Uint32 Rmask; + Uint32 Gmask; + Uint32 Bmask; + Uint32 Amask; + + /** RGB color key information */ + Uint32 colorkey; + /** Alpha value information (per-surface alpha) */ + Uint8 alpha; +} SDL_PixelFormat; + +/** This structure should be treated as read-only, except for 'pixels', + * which, if not NULL, contains the raw pixel data for the surface. + */ +typedef struct SDL_Surface { + Uint32 flags; /**< Read-only */ + SDL_PixelFormat *format; /**< Read-only */ + int w, h; /**< Read-only */ + Uint16 pitch; /**< Read-only */ + void *pixels; /**< Read-write */ + int offset; /**< Private */ + + /** Hardware-specific surface info */ + struct private_hwdata *hwdata; + + /** clipping information */ + SDL_Rect clip_rect; /**< Read-only */ + Uint32 unused1; /**< for binary compatibility */ + + /** Allow recursive locks */ + Uint32 locked; /**< Private */ + + /** info for fast blit mapping to other surfaces */ + struct SDL_BlitMap *map; /**< Private */ + + /** format version, bumped at every change to invalidate blit maps */ + unsigned int format_version; /**< Private */ + + /** Reference count -- used when freeing surface */ + int refcount; /**< Read-mostly */ +} SDL_Surface; + +/** @name SDL_Surface Flags + * These are the currently supported flags for the SDL_surface + */ +/*@{*/ + +/** Available for SDL_CreateRGBSurface() or SDL_SetVideoMode() */ +/*@{*/ +#define SDL_SWSURFACE 0x00000000 /**< Surface is in system memory */ +#define SDL_HWSURFACE 0x00000001 /**< Surface is in video memory */ +#define SDL_ASYNCBLIT 0x00000004 /**< Use asynchronous blits if possible */ +/*@}*/ + +/** Available for SDL_SetVideoMode() */ +/*@{*/ +#define SDL_ANYFORMAT 0x10000000 /**< Allow any video depth/pixel-format */ +#define SDL_HWPALETTE 0x20000000 /**< Surface has exclusive palette */ +#define SDL_DOUBLEBUF 0x40000000 /**< Set up double-buffered video mode */ +#define SDL_FULLSCREEN 0x80000000 /**< Surface is a full screen display */ +#define SDL_OPENGL 0x00000002 /**< Create an OpenGL rendering context */ +#define SDL_OPENGLBLIT 0x0000000A /**< Create an OpenGL rendering context and use it for blitting */ +#define SDL_RESIZABLE 0x00000010 /**< This video mode may be resized */ +#define SDL_NOFRAME 0x00000020 /**< No window caption or edge frame */ +/*@}*/ + +/** Used internally (read-only) */ +/*@{*/ +#define SDL_HWACCEL 0x00000100 /**< Blit uses hardware acceleration */ +#define SDL_SRCCOLORKEY 0x00001000 /**< Blit uses a source color key */ +#define SDL_RLEACCELOK 0x00002000 /**< Private flag */ +#define SDL_RLEACCEL 0x00004000 /**< Surface is RLE encoded */ +#define SDL_SRCALPHA 0x00010000 /**< Blit uses source alpha blending */ +#define SDL_PREALLOC 0x01000000 /**< Surface uses preallocated memory */ +/*@}*/ + +/*@}*/ + +/** Evaluates to true if the surface needs to be locked before access */ +#define SDL_MUSTLOCK(surface) \ + (surface->offset || \ + ((surface->flags & (SDL_HWSURFACE|SDL_ASYNCBLIT|SDL_RLEACCEL)) != 0)) + +/** typedef for private surface blitting functions */ +typedef int (*SDL_blit)(struct SDL_Surface *src, SDL_Rect *srcrect, + struct SDL_Surface *dst, SDL_Rect *dstrect); + + +/** Useful for determining the video hardware capabilities */ +typedef struct SDL_VideoInfo { + Uint32 hw_available :1; /**< Flag: Can you create hardware surfaces? */ + Uint32 wm_available :1; /**< Flag: Can you talk to a window manager? */ + Uint32 UnusedBits1 :6; + Uint32 UnusedBits2 :1; + Uint32 blit_hw :1; /**< Flag: Accelerated blits HW --> HW */ + Uint32 blit_hw_CC :1; /**< Flag: Accelerated blits with Colorkey */ + Uint32 blit_hw_A :1; /**< Flag: Accelerated blits with Alpha */ + Uint32 blit_sw :1; /**< Flag: Accelerated blits SW --> HW */ + Uint32 blit_sw_CC :1; /**< Flag: Accelerated blits with Colorkey */ + Uint32 blit_sw_A :1; /**< Flag: Accelerated blits with Alpha */ + Uint32 blit_fill :1; /**< Flag: Accelerated color fill */ + Uint32 UnusedBits3 :16; + Uint32 video_mem; /**< The total amount of video memory (in K) */ + SDL_PixelFormat *vfmt; /**< Value: The format of the video surface */ + int current_w; /**< Value: The current video mode width */ + int current_h; /**< Value: The current video mode height */ +} SDL_VideoInfo; + + +/** @name Overlay Formats + * The most common video overlay formats. + * For an explanation of these pixel formats, see: + * http://www.webartz.com/fourcc/indexyuv.htm + * + * For information on the relationship between color spaces, see: + * http://www.neuro.sfc.keio.ac.jp/~aly/polygon/info/color-space-faq.html + */ +/*@{*/ +#define SDL_YV12_OVERLAY 0x32315659 /**< Planar mode: Y + V + U (3 planes) */ +#define SDL_IYUV_OVERLAY 0x56555949 /**< Planar mode: Y + U + V (3 planes) */ +#define SDL_YUY2_OVERLAY 0x32595559 /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */ +#define SDL_UYVY_OVERLAY 0x59565955 /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */ +#define SDL_YVYU_OVERLAY 0x55595659 /**< Packed mode: Y0+V0+Y1+U0 (1 plane) */ +/*@}*/ + +/** The YUV hardware video overlay */ +typedef struct SDL_Overlay { + Uint32 format; /**< Read-only */ + int w, h; /**< Read-only */ + int planes; /**< Read-only */ + Uint16 *pitches; /**< Read-only */ + Uint8 **pixels; /**< Read-write */ + + /** @name Hardware-specific surface info */ + /*@{*/ + struct private_yuvhwfuncs *hwfuncs; + struct private_yuvhwdata *hwdata; + /*@{*/ + + /** @name Special flags */ + /*@{*/ + Uint32 hw_overlay :1; /**< Flag: This overlay hardware accelerated? */ + Uint32 UnusedBits :31; + /*@}*/ +} SDL_Overlay; + + +/** Public enumeration for setting the OpenGL window attributes. */ +typedef enum { + SDL_GL_RED_SIZE, + SDL_GL_GREEN_SIZE, + SDL_GL_BLUE_SIZE, + SDL_GL_ALPHA_SIZE, + SDL_GL_BUFFER_SIZE, + SDL_GL_DOUBLEBUFFER, + SDL_GL_DEPTH_SIZE, + SDL_GL_STENCIL_SIZE, + SDL_GL_ACCUM_RED_SIZE, + SDL_GL_ACCUM_GREEN_SIZE, + SDL_GL_ACCUM_BLUE_SIZE, + SDL_GL_ACCUM_ALPHA_SIZE, + SDL_GL_STEREO, + SDL_GL_MULTISAMPLEBUFFERS, + SDL_GL_MULTISAMPLESAMPLES, + SDL_GL_ACCELERATED_VISUAL, + SDL_GL_SWAP_CONTROL +} SDL_GLattr; + +/** @name flags for SDL_SetPalette() */ +/*@{*/ +#define SDL_LOGPAL 0x01 +#define SDL_PHYSPAL 0x02 +/*@}*/ + +/* Function prototypes */ + +/** + * @name Video Init and Quit + * These functions are used internally, and should not be used unless you + * have a specific need to specify the video driver you want to use. + * You should normally use SDL_Init() or SDL_InitSubSystem(). + */ +/*@{*/ +/** + * Initializes the video subsystem. Sets up a connection + * to the window manager, etc, and determines the current video mode and + * pixel format, but does not initialize a window or graphics mode. + * Note that event handling is activated by this routine. + * + * If you use both sound and video in your application, you need to call + * SDL_Init() before opening the sound device, otherwise under Win32 DirectX, + * you won't be able to set full-screen display modes. + */ +extern DECLSPEC int SDLCALL SDL_VideoInit(const char *driver_name, Uint32 flags); +extern DECLSPEC void SDLCALL SDL_VideoQuit(void); +/*@}*/ + +/** + * This function fills the given character buffer with the name of the + * video driver, and returns a pointer to it if the video driver has + * been initialized. It returns NULL if no driver has been initialized. + */ +extern DECLSPEC char * SDLCALL SDL_VideoDriverName(char *namebuf, int maxlen); + +/** + * This function returns a pointer to the current display surface. + * If SDL is doing format conversion on the display surface, this + * function returns the publicly visible surface, not the real video + * surface. + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_GetVideoSurface(void); + +/** + * This function returns a read-only pointer to information about the + * video hardware. If this is called before SDL_SetVideoMode(), the 'vfmt' + * member of the returned structure will contain the pixel format of the + * "best" video mode. + */ +extern DECLSPEC const SDL_VideoInfo * SDLCALL SDL_GetVideoInfo(void); + +/** + * Check to see if a particular video mode is supported. + * It returns 0 if the requested mode is not supported under any bit depth, + * or returns the bits-per-pixel of the closest available mode with the + * given width and height. If this bits-per-pixel is different from the + * one used when setting the video mode, SDL_SetVideoMode() will succeed, + * but will emulate the requested bits-per-pixel with a shadow surface. + * + * The arguments to SDL_VideoModeOK() are the same ones you would pass to + * SDL_SetVideoMode() + */ +extern DECLSPEC int SDLCALL SDL_VideoModeOK(int width, int height, int bpp, Uint32 flags); + +/** + * Return a pointer to an array of available screen dimensions for the + * given format and video flags, sorted largest to smallest. Returns + * NULL if there are no dimensions available for a particular format, + * or (SDL_Rect **)-1 if any dimension is okay for the given format. + * + * If 'format' is NULL, the mode list will be for the format given + * by SDL_GetVideoInfo()->vfmt + */ +extern DECLSPEC SDL_Rect ** SDLCALL SDL_ListModes(SDL_PixelFormat *format, Uint32 flags); + +/** + * Set up a video mode with the specified width, height and bits-per-pixel. + * + * If 'bpp' is 0, it is treated as the current display bits per pixel. + * + * If SDL_ANYFORMAT is set in 'flags', the SDL library will try to set the + * requested bits-per-pixel, but will return whatever video pixel format is + * available. The default is to emulate the requested pixel format if it + * is not natively available. + * + * If SDL_HWSURFACE is set in 'flags', the video surface will be placed in + * video memory, if possible, and you may have to call SDL_LockSurface() + * in order to access the raw framebuffer. Otherwise, the video surface + * will be created in system memory. + * + * If SDL_ASYNCBLIT is set in 'flags', SDL will try to perform rectangle + * updates asynchronously, but you must always lock before accessing pixels. + * SDL will wait for updates to complete before returning from the lock. + * + * If SDL_HWPALETTE is set in 'flags', the SDL library will guarantee + * that the colors set by SDL_SetColors() will be the colors you get. + * Otherwise, in 8-bit mode, SDL_SetColors() may not be able to set all + * of the colors exactly the way they are requested, and you should look + * at the video surface structure to determine the actual palette. + * If SDL cannot guarantee that the colors you request can be set, + * i.e. if the colormap is shared, then the video surface may be created + * under emulation in system memory, overriding the SDL_HWSURFACE flag. + * + * If SDL_FULLSCREEN is set in 'flags', the SDL library will try to set + * a fullscreen video mode. The default is to create a windowed mode + * if the current graphics system has a window manager. + * If the SDL library is able to set a fullscreen video mode, this flag + * will be set in the surface that is returned. + * + * If SDL_DOUBLEBUF is set in 'flags', the SDL library will try to set up + * two surfaces in video memory and swap between them when you call + * SDL_Flip(). This is usually slower than the normal single-buffering + * scheme, but prevents "tearing" artifacts caused by modifying video + * memory while the monitor is refreshing. It should only be used by + * applications that redraw the entire screen on every update. + * + * If SDL_RESIZABLE is set in 'flags', the SDL library will allow the + * window manager, if any, to resize the window at runtime. When this + * occurs, SDL will send a SDL_VIDEORESIZE event to you application, + * and you must respond to the event by re-calling SDL_SetVideoMode() + * with the requested size (or another size that suits the application). + * + * If SDL_NOFRAME is set in 'flags', the SDL library will create a window + * without any title bar or frame decoration. Fullscreen video modes have + * this flag set automatically. + * + * This function returns the video framebuffer surface, or NULL if it fails. + * + * If you rely on functionality provided by certain video flags, check the + * flags of the returned surface to make sure that functionality is available. + * SDL will fall back to reduced functionality if the exact flags you wanted + * are not available. + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_SetVideoMode + (int width, int height, int bpp, Uint32 flags); + +/** @name SDL_Update Functions + * These functions should not be called while 'screen' is locked. + */ +/*@{*/ +/** + * Makes sure the given list of rectangles is updated on the given screen. + */ +extern DECLSPEC void SDLCALL SDL_UpdateRects + (SDL_Surface *screen, int numrects, SDL_Rect *rects); +/** + * If 'x', 'y', 'w' and 'h' are all 0, SDL_UpdateRect will update the entire + * screen. + */ +extern DECLSPEC void SDLCALL SDL_UpdateRect + (SDL_Surface *screen, Sint32 x, Sint32 y, Uint32 w, Uint32 h); +/*@}*/ + +/** + * On hardware that supports double-buffering, this function sets up a flip + * and returns. The hardware will wait for vertical retrace, and then swap + * video buffers before the next video surface blit or lock will return. + * On hardware that doesn not support double-buffering, this is equivalent + * to calling SDL_UpdateRect(screen, 0, 0, 0, 0); + * The SDL_DOUBLEBUF flag must have been passed to SDL_SetVideoMode() when + * setting the video mode for this function to perform hardware flipping. + * This function returns 0 if successful, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_Flip(SDL_Surface *screen); + +/** + * Set the gamma correction for each of the color channels. + * The gamma values range (approximately) between 0.1 and 10.0 + * + * If this function isn't supported directly by the hardware, it will + * be emulated using gamma ramps, if available. If successful, this + * function returns 0, otherwise it returns -1. + */ +extern DECLSPEC int SDLCALL SDL_SetGamma(float red, float green, float blue); + +/** + * Set the gamma translation table for the red, green, and blue channels + * of the video hardware. Each table is an array of 256 16-bit quantities, + * representing a mapping between the input and output for that channel. + * The input is the index into the array, and the output is the 16-bit + * gamma value at that index, scaled to the output color precision. + * + * You may pass NULL for any of the channels to leave it unchanged. + * If the call succeeds, it will return 0. If the display driver or + * hardware does not support gamma translation, or otherwise fails, + * this function will return -1. + */ +extern DECLSPEC int SDLCALL SDL_SetGammaRamp(const Uint16 *red, const Uint16 *green, const Uint16 *blue); + +/** + * Retrieve the current values of the gamma translation tables. + * + * You must pass in valid pointers to arrays of 256 16-bit quantities. + * Any of the pointers may be NULL to ignore that channel. + * If the call succeeds, it will return 0. If the display driver or + * hardware does not support gamma translation, or otherwise fails, + * this function will return -1. + */ +extern DECLSPEC int SDLCALL SDL_GetGammaRamp(Uint16 *red, Uint16 *green, Uint16 *blue); + +/** + * Sets a portion of the colormap for the given 8-bit surface. If 'surface' + * is not a palettized surface, this function does nothing, returning 0. + * If all of the colors were set as passed to SDL_SetColors(), it will + * return 1. If not all the color entries were set exactly as given, + * it will return 0, and you should look at the surface palette to + * determine the actual color palette. + * + * When 'surface' is the surface associated with the current display, the + * display colormap will be updated with the requested colors. If + * SDL_HWPALETTE was set in SDL_SetVideoMode() flags, SDL_SetColors() + * will always return 1, and the palette is guaranteed to be set the way + * you desire, even if the window colormap has to be warped or run under + * emulation. + */ +extern DECLSPEC int SDLCALL SDL_SetColors(SDL_Surface *surface, + SDL_Color *colors, int firstcolor, int ncolors); + +/** + * Sets a portion of the colormap for a given 8-bit surface. + * 'flags' is one or both of: + * SDL_LOGPAL -- set logical palette, which controls how blits are mapped + * to/from the surface, + * SDL_PHYSPAL -- set physical palette, which controls how pixels look on + * the screen + * Only screens have physical palettes. Separate change of physical/logical + * palettes is only possible if the screen has SDL_HWPALETTE set. + * + * The return value is 1 if all colours could be set as requested, and 0 + * otherwise. + * + * SDL_SetColors() is equivalent to calling this function with + * flags = (SDL_LOGPAL|SDL_PHYSPAL). + */ +extern DECLSPEC int SDLCALL SDL_SetPalette(SDL_Surface *surface, int flags, + SDL_Color *colors, int firstcolor, + int ncolors); + +/** + * Maps an RGB triple to an opaque pixel value for a given pixel format + */ +extern DECLSPEC Uint32 SDLCALL SDL_MapRGB +(const SDL_PixelFormat * const format, + const Uint8 r, const Uint8 g, const Uint8 b); + +/** + * Maps an RGBA quadruple to a pixel value for a given pixel format + */ +extern DECLSPEC Uint32 SDLCALL SDL_MapRGBA +(const SDL_PixelFormat * const format, + const Uint8 r, const Uint8 g, const Uint8 b, const Uint8 a); + +/** + * Maps a pixel value into the RGB components for a given pixel format + */ +extern DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, + const SDL_PixelFormat * const fmt, + Uint8 *r, Uint8 *g, Uint8 *b); + +/** + * Maps a pixel value into the RGBA components for a given pixel format + */ +extern DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixel, + const SDL_PixelFormat * const fmt, + Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); + +/** @sa SDL_CreateRGBSurface */ +#define SDL_AllocSurface SDL_CreateRGBSurface +/** + * Allocate and free an RGB surface (must be called after SDL_SetVideoMode) + * If the depth is 4 or 8 bits, an empty palette is allocated for the surface. + * If the depth is greater than 8 bits, the pixel format is set using the + * flags '[RGB]mask'. + * If the function runs out of memory, it will return NULL. + * + * The 'flags' tell what kind of surface to create. + * SDL_SWSURFACE means that the surface should be created in system memory. + * SDL_HWSURFACE means that the surface should be created in video memory, + * with the same format as the display surface. This is useful for surfaces + * that will not change much, to take advantage of hardware acceleration + * when being blitted to the display surface. + * SDL_ASYNCBLIT means that SDL will try to perform asynchronous blits with + * this surface, but you must always lock it before accessing the pixels. + * SDL will wait for current blits to finish before returning from the lock. + * SDL_SRCCOLORKEY indicates that the surface will be used for colorkey blits. + * If the hardware supports acceleration of colorkey blits between + * two surfaces in video memory, SDL will try to place the surface in + * video memory. If this isn't possible or if there is no hardware + * acceleration available, the surface will be placed in system memory. + * SDL_SRCALPHA means that the surface will be used for alpha blits and + * if the hardware supports hardware acceleration of alpha blits between + * two surfaces in video memory, to place the surface in video memory + * if possible, otherwise it will be placed in system memory. + * If the surface is created in video memory, blits will be _much_ faster, + * but the surface format must be identical to the video surface format, + * and the only way to access the pixels member of the surface is to use + * the SDL_LockSurface() and SDL_UnlockSurface() calls. + * If the requested surface actually resides in video memory, SDL_HWSURFACE + * will be set in the flags member of the returned surface. If for some + * reason the surface could not be placed in video memory, it will not have + * the SDL_HWSURFACE flag set, and will be created in system memory instead. + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_CreateRGBSurface + (Uint32 flags, int width, int height, int depth, + Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); +/** @sa SDL_CreateRGBSurface */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_CreateRGBSurfaceFrom(void *pixels, + int width, int height, int depth, int pitch, + Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); +extern DECLSPEC void SDLCALL SDL_FreeSurface(SDL_Surface *surface); + +/** + * SDL_LockSurface() sets up a surface for directly accessing the pixels. + * Between calls to SDL_LockSurface()/SDL_UnlockSurface(), you can write + * to and read from 'surface->pixels', using the pixel format stored in + * 'surface->format'. Once you are done accessing the surface, you should + * use SDL_UnlockSurface() to release it. + * + * Not all surfaces require locking. If SDL_MUSTLOCK(surface) evaluates + * to 0, then you can read and write to the surface at any time, and the + * pixel format of the surface will not change. In particular, if the + * SDL_HWSURFACE flag is not given when calling SDL_SetVideoMode(), you + * will not need to lock the display surface before accessing it. + * + * No operating system or library calls should be made between lock/unlock + * pairs, as critical system locks may be held during this time. + * + * SDL_LockSurface() returns 0, or -1 if the surface couldn't be locked. + */ +extern DECLSPEC int SDLCALL SDL_LockSurface(SDL_Surface *surface); +extern DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface *surface); + +/** + * Load a surface from a seekable SDL data source (memory or file.) + * If 'freesrc' is non-zero, the source will be closed after being read. + * Returns the new surface, or NULL if there was an error. + * The new surface should be freed with SDL_FreeSurface(). + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_LoadBMP_RW(SDL_RWops *src, int freesrc); + +/** Convenience macro -- load a surface from a file */ +#define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1) + +/** + * Save a surface to a seekable SDL data source (memory or file.) + * If 'freedst' is non-zero, the source will be closed after being written. + * Returns 0 if successful or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_SaveBMP_RW + (SDL_Surface *surface, SDL_RWops *dst, int freedst); + +/** Convenience macro -- save a surface to a file */ +#define SDL_SaveBMP(surface, file) \ + SDL_SaveBMP_RW(surface, SDL_RWFromFile(file, "wb"), 1) + +/** + * Sets the color key (transparent pixel) in a blittable surface. + * If 'flag' is SDL_SRCCOLORKEY (optionally OR'd with SDL_RLEACCEL), + * 'key' will be the transparent pixel in the source image of a blit. + * SDL_RLEACCEL requests RLE acceleration for the surface if present, + * and removes RLE acceleration if absent. + * If 'flag' is 0, this function clears any current color key. + * This function returns 0, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_SetColorKey + (SDL_Surface *surface, Uint32 flag, Uint32 key); + +/** + * This function sets the alpha value for the entire surface, as opposed to + * using the alpha component of each pixel. This value measures the range + * of transparency of the surface, 0 being completely transparent to 255 + * being completely opaque. An 'alpha' value of 255 causes blits to be + * opaque, the source pixels copied to the destination (the default). Note + * that per-surface alpha can be combined with colorkey transparency. + * + * If 'flag' is 0, alpha blending is disabled for the surface. + * If 'flag' is SDL_SRCALPHA, alpha blending is enabled for the surface. + * OR:ing the flag with SDL_RLEACCEL requests RLE acceleration for the + * surface; if SDL_RLEACCEL is not specified, the RLE accel will be removed. + * + * The 'alpha' parameter is ignored for surfaces that have an alpha channel. + */ +extern DECLSPEC int SDLCALL SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha); + +/** + * Sets the clipping rectangle for the destination surface in a blit. + * + * If the clip rectangle is NULL, clipping will be disabled. + * If the clip rectangle doesn't intersect the surface, the function will + * return SDL_FALSE and blits will be completely clipped. Otherwise the + * function returns SDL_TRUE and blits to the surface will be clipped to + * the intersection of the surface area and the clipping rectangle. + * + * Note that blits are automatically clipped to the edges of the source + * and destination surfaces. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_SetClipRect(SDL_Surface *surface, const SDL_Rect *rect); + +/** + * Gets the clipping rectangle for the destination surface in a blit. + * 'rect' must be a pointer to a valid rectangle which will be filled + * with the correct values. + */ +extern DECLSPEC void SDLCALL SDL_GetClipRect(SDL_Surface *surface, SDL_Rect *rect); + +/** + * Creates a new surface of the specified format, and then copies and maps + * the given surface to it so the blit of the converted surface will be as + * fast as possible. If this function fails, it returns NULL. + * + * The 'flags' parameter is passed to SDL_CreateRGBSurface() and has those + * semantics. You can also pass SDL_RLEACCEL in the flags parameter and + * SDL will try to RLE accelerate colorkey and alpha blits in the resulting + * surface. + * + * This function is used internally by SDL_DisplayFormat(). + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_ConvertSurface + (SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags); + +/** + * This performs a fast blit from the source surface to the destination + * surface. It assumes that the source and destination rectangles are + * the same size. If either 'srcrect' or 'dstrect' are NULL, the entire + * surface (src or dst) is copied. The final blit rectangles are saved + * in 'srcrect' and 'dstrect' after all clipping is performed. + * If the blit is successful, it returns 0, otherwise it returns -1. + * + * The blit function should not be called on a locked surface. + * + * The blit semantics for surfaces with and without alpha and colorkey + * are defined as follows: + * + * RGBA->RGB: + * SDL_SRCALPHA set: + * alpha-blend (using alpha-channel). + * SDL_SRCCOLORKEY ignored. + * SDL_SRCALPHA not set: + * copy RGB. + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * RGB values of the source colour key, ignoring alpha in the + * comparison. + * + * RGB->RGBA: + * SDL_SRCALPHA set: + * alpha-blend (using the source per-surface alpha value); + * set destination alpha to opaque. + * SDL_SRCALPHA not set: + * copy RGB, set destination alpha to source per-surface alpha value. + * both: + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * source colour key. + * + * RGBA->RGBA: + * SDL_SRCALPHA set: + * alpha-blend (using the source alpha channel) the RGB values; + * leave destination alpha untouched. [Note: is this correct?] + * SDL_SRCCOLORKEY ignored. + * SDL_SRCALPHA not set: + * copy all of RGBA to the destination. + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * RGB values of the source colour key, ignoring alpha in the + * comparison. + * + * RGB->RGB: + * SDL_SRCALPHA set: + * alpha-blend (using the source per-surface alpha value). + * SDL_SRCALPHA not set: + * copy RGB. + * both: + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * source colour key. + * + * If either of the surfaces were in video memory, and the blit returns -2, + * the video memory was lost, so it should be reloaded with artwork and + * re-blitted: + * @code + * while ( SDL_BlitSurface(image, imgrect, screen, dstrect) == -2 ) { + * while ( SDL_LockSurface(image) < 0 ) + * Sleep(10); + * -- Write image pixels to image->pixels -- + * SDL_UnlockSurface(image); + * } + * @endcode + * + * This happens under DirectX 5.0 when the system switches away from your + * fullscreen application. The lock will also fail until you have access + * to the video memory again. + * + * You should call SDL_BlitSurface() unless you know exactly how SDL + * blitting works internally and how to use the other blit functions. + */ +#define SDL_BlitSurface SDL_UpperBlit + +/** This is the public blit function, SDL_BlitSurface(), and it performs + * rectangle validation and clipping before passing it to SDL_LowerBlit() + */ +extern DECLSPEC int SDLCALL SDL_UpperBlit + (SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect); +/** This is a semi-private blit function and it performs low-level surface + * blitting only. + */ +extern DECLSPEC int SDLCALL SDL_LowerBlit + (SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect); + +/** + * This function performs a fast fill of the given rectangle with 'color' + * The given rectangle is clipped to the destination surface clip area + * and the final fill rectangle is saved in the passed in pointer. + * If 'dstrect' is NULL, the whole surface will be filled with 'color' + * The color should be a pixel of the format used by the surface, and + * can be generated by the SDL_MapRGB() function. + * This function returns 0 on success, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_FillRect + (SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color); + +/** + * This function takes a surface and copies it to a new surface of the + * pixel format and colors of the video framebuffer, suitable for fast + * blitting onto the display surface. It calls SDL_ConvertSurface() + * + * If you want to take advantage of hardware colorkey or alpha blit + * acceleration, you should set the colorkey and alpha value before + * calling this function. + * + * If the conversion fails or runs out of memory, it returns NULL + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_DisplayFormat(SDL_Surface *surface); + +/** + * This function takes a surface and copies it to a new surface of the + * pixel format and colors of the video framebuffer (if possible), + * suitable for fast alpha blitting onto the display surface. + * The new surface will always have an alpha channel. + * + * If you want to take advantage of hardware colorkey or alpha blit + * acceleration, you should set the colorkey and alpha value before + * calling this function. + * + * If the conversion fails or runs out of memory, it returns NULL + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_DisplayFormatAlpha(SDL_Surface *surface); + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name YUV video surface overlay functions */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** This function creates a video output overlay + * Calling the returned surface an overlay is something of a misnomer because + * the contents of the display surface underneath the area where the overlay + * is shown is undefined - it may be overwritten with the converted YUV data. + */ +extern DECLSPEC SDL_Overlay * SDLCALL SDL_CreateYUVOverlay(int width, int height, + Uint32 format, SDL_Surface *display); + +/** Lock an overlay for direct access, and unlock it when you are done */ +extern DECLSPEC int SDLCALL SDL_LockYUVOverlay(SDL_Overlay *overlay); +extern DECLSPEC void SDLCALL SDL_UnlockYUVOverlay(SDL_Overlay *overlay); + +/** Blit a video overlay to the display surface. + * The contents of the video surface underneath the blit destination are + * not defined. + * The width and height of the destination rectangle may be different from + * that of the overlay, but currently only 2x scaling is supported. + */ +extern DECLSPEC int SDLCALL SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect); + +/** Free a video overlay */ +extern DECLSPEC void SDLCALL SDL_FreeYUVOverlay(SDL_Overlay *overlay); + +/*@}*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name OpenGL support functions. */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** + * Dynamically load an OpenGL library, or the default one if path is NULL + * + * If you do this, you need to retrieve all of the GL functions used in + * your program from the dynamic library using SDL_GL_GetProcAddress(). + */ +extern DECLSPEC int SDLCALL SDL_GL_LoadLibrary(const char *path); + +/** + * Get the address of a GL function + */ +extern DECLSPEC void * SDLCALL SDL_GL_GetProcAddress(const char* proc); + +/** + * Set an attribute of the OpenGL subsystem before intialization. + */ +extern DECLSPEC int SDLCALL SDL_GL_SetAttribute(SDL_GLattr attr, int value); + +/** + * Get an attribute of the OpenGL subsystem from the windowing + * interface, such as glX. This is of course different from getting + * the values from SDL's internal OpenGL subsystem, which only + * stores the values you request before initialization. + * + * Developers should track the values they pass into SDL_GL_SetAttribute + * themselves if they want to retrieve these values. + */ +extern DECLSPEC int SDLCALL SDL_GL_GetAttribute(SDL_GLattr attr, int* value); + +/** + * Swap the OpenGL buffers, if double-buffering is supported. + */ +extern DECLSPEC void SDLCALL SDL_GL_SwapBuffers(void); + +/** @name OpenGL Internal Functions + * Internal functions that should not be called unless you have read + * and understood the source code for these functions. + */ +/*@{*/ +extern DECLSPEC void SDLCALL SDL_GL_UpdateRects(int numrects, SDL_Rect* rects); +extern DECLSPEC void SDLCALL SDL_GL_Lock(void); +extern DECLSPEC void SDLCALL SDL_GL_Unlock(void); +/*@}*/ + +/*@}*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name Window Manager Functions */ +/** These functions allow interaction with the window manager, if any. */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** + * Sets the title and icon text of the display window (UTF-8 encoded) + */ +extern DECLSPEC void SDLCALL SDL_WM_SetCaption(const char *title, const char *icon); +/** + * Gets the title and icon text of the display window (UTF-8 encoded) + */ +extern DECLSPEC void SDLCALL SDL_WM_GetCaption(char **title, char **icon); + +/** + * Sets the icon for the display window. + * This function must be called before the first call to SDL_SetVideoMode(). + * It takes an icon surface, and a mask in MSB format. + * If 'mask' is NULL, the entire icon surface will be used as the icon. + */ +extern DECLSPEC void SDLCALL SDL_WM_SetIcon(SDL_Surface *icon, Uint8 *mask); + +/** + * This function iconifies the window, and returns 1 if it succeeded. + * If the function succeeds, it generates an SDL_APPACTIVE loss event. + * This function is a noop and returns 0 in non-windowed environments. + */ +extern DECLSPEC int SDLCALL SDL_WM_IconifyWindow(void); + +/** + * Toggle fullscreen mode without changing the contents of the screen. + * If the display surface does not require locking before accessing + * the pixel information, then the memory pointers will not change. + * + * If this function was able to toggle fullscreen mode (change from + * running in a window to fullscreen, or vice-versa), it will return 1. + * If it is not implemented, or fails, it returns 0. + * + * The next call to SDL_SetVideoMode() will set the mode fullscreen + * attribute based on the flags parameter - if SDL_FULLSCREEN is not + * set, then the display will be windowed by default where supported. + * + * This is currently only implemented in the X11 video driver. + */ +extern DECLSPEC int SDLCALL SDL_WM_ToggleFullScreen(SDL_Surface *surface); + +typedef enum { + SDL_GRAB_QUERY = -1, + SDL_GRAB_OFF = 0, + SDL_GRAB_ON = 1, + SDL_GRAB_FULLSCREEN /**< Used internally */ +} SDL_GrabMode; +/** + * This function allows you to set and query the input grab state of + * the application. It returns the new input grab state. + * + * Grabbing means that the mouse is confined to the application window, + * and nearly all keyboard input is passed directly to the application, + * and not interpreted by a window manager, if any. + */ +extern DECLSPEC SDL_GrabMode SDLCALL SDL_WM_GrabInput(SDL_GrabMode mode); + +/*@}*/ + +/** @internal Not in public API at the moment - do not use! */ +extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_video_h */ diff --git a/src/SDL12/include/begin_code.h b/src/SDL12/include/begin_code.h new file mode 100644 index 0000000..2274809 --- /dev/null +++ b/src/SDL12/include/begin_code.h @@ -0,0 +1,191 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file begin_code.h + * This file sets things up for C dynamic library function definitions, + * static inlined functions, and structures aligned at 4-byte alignment. + * If you don't like ugly C preprocessor code, don't look at this file. :) + */ + +/** + * @file begin_code.h + * This shouldn't be nested -- included it around code only. + */ +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +/** + * @def DECLSPEC + * Some compilers use a special export keyword + */ +#ifndef DECLSPEC +# if defined(__BEOS__) || defined(__HAIKU__) +# if defined(__GNUC__) +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC __declspec(export) +# endif +# elif defined(__WIN32__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# elif defined(__OS2__) +# ifdef __WATCOMC__ +# ifdef BUILD_SDL +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# elif defined (__GNUC__) && __GNUC__ < 4 +# /* Added support for GCC-EMX = 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# else +# define DECLSPEC +# endif +# endif +#endif + +/** + * @def SDLCALL + * By default SDL uses the C calling convention + */ +#ifndef SDLCALL +# if defined(__WIN32__) && !defined(__GNUC__) +# define SDLCALL __cdecl +# elif defined(__OS2__) +# if defined (__GNUC__) && __GNUC__ < 4 +# /* Added support for GCC-EMX + +#include "qasm.h" + +#if id386 + +.data + +temp: .single 0.0 +fpucw: .long 0 + +// Precision Control Field , 2 bits / 0x0300 +// PC24 0x0000 Single precision (24 bits). +// PC53 0x0200 Double precision (53 bits). +// PC64 0x0300 Extended precision (64 bits). + +// Rounding Control Field, 2 bits / 0x0C00 +// RCN 0x0000 Rounding to nearest (even). +// RCD 0x0400 Rounding down (directed, minus). +// RCU 0x0800 Rounding up (directed plus). +// RC0 0x0C00 Rounding towards zero (chop mode). + + +// rounding towards nearest (even) +cw027F: .long 0x027F +cw037F: .long 0x037F + +// rounding towards zero (chop mode) +cw0E7F: .long 0x0E7F +cw0F7F: .long 0x0F7F + + +.text + +// +// int qftol( void ) - default control word +// + +.globl C(qftol) + +C(qftol): + fistpl temp + movl temp,%eax + ret + + +// +// int qftol027F( void ) - DirectX FPU +// + +.globl C(qftol027F) + +C(qftol027F): + fnstcw fpucw + fldcw cw027F + fistpl temp + fldcw fpucw + movl temp,%eax + ret + +// +// int qftol037F( void ) - Linux FPU +// + +.globl C(qftol037F) + +C(qftol037F): + fnstcw fpucw + fldcw cw037F + fistpl temp + fldcw fpucw + movl temp,%eax + ret + + +// +// int qftol0F7F( void ) - ANSI +// + +.globl C(qftol0F7F) + +C(qftol0F7F): + fnstcw fpucw + fldcw cw0F7F + fistpl temp + fldcw fpucw + movl temp,%eax + ret + +// +// int qftol0E7F( void ) +// + +.globl C(qftol0E7F) + +C(qftol0E7F): + fnstcw fpucw + fldcw cw0E7F + fistpl temp + fldcw fpucw + movl temp,%eax + ret + + + +// +// long Q_ftol( float q ) +// + +.globl C(Q_ftol) + +C(Q_ftol): + flds 4(%esp) + fistpl temp + movl temp,%eax + ret + + +// +// long qftol0F7F( float q ) - Linux FPU +// + +.globl C(Q_ftol0F7F) + +C(Q_ftol0F7F): + fnstcw fpucw + flds 4(%esp) + fldcw cw0F7F + fistpl temp + fldcw fpucw + movl temp,%eax + ret +#endif diff --git a/src/asm/matha.s b/src/asm/matha.s new file mode 100644 index 0000000..e4770d3 --- /dev/null +++ b/src/asm/matha.s @@ -0,0 +1,54 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// math.s +// x86 assembly-language math routines. + +#include "qasm.h" + + +#if id386 + + .text + +// TODO: rounding needed? +// stack parameter offset +#define val 4 + +.globl C(Invert24To16) +C(Invert24To16): + + movl val(%esp),%ecx + movl $0x100,%edx // 0x10000000000 as dividend + cmpl %edx,%ecx + jle LOutOfRange + + subl %eax,%eax + divl %ecx + + ret + +LOutOfRange: + movl $0xFFFFFFFF,%eax + ret + +#endif // id386 diff --git a/src/asm/qasm.h b/src/asm/qasm.h new file mode 100644 index 0000000..d4b1674 --- /dev/null +++ b/src/asm/qasm.h @@ -0,0 +1,38 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#ifndef __ASM_I386__ +#define __ASM_I386__ + +#include "../qcommon/q_platform.h" + +#ifdef __ELF__ +.section .note.GNU-stack,"",@progbits +#endif + +#ifdef __ELF__ +#define C(label) label +#else +#define C(label) _##label +#endif + +#endif diff --git a/src/asm/snapvectora.s b/src/asm/snapvectora.s new file mode 100644 index 0000000..11294a0 --- /dev/null +++ b/src/asm/snapvectora.s @@ -0,0 +1,103 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// +// Sys_SnapVector NASM code (Andrew Henderson) +// See win32/win_shared.c for the Win32 equivalent +// This code is provided to ensure that the +// rounding behavior (and, if necessary, the +// precision) of DLL and QVM code are identical +// e.g. for network-visible operations. +// See ftol.nasm for operations on a single float, +// as used in compiled VM and DLL code that does +// not use this system trap. +// + +// 23/09/05 Ported to gas by intel2gas, best supporting actor Tim Angus +// + +#include "qasm.h" + +#if id386 +.data + +fpucw: .long 0 +cw037F: .long 0x037F + +.text + +// void Sys_SnapVector( float *v ) +.globl C(Sys_SnapVector) +C(Sys_SnapVector): + pushl %eax + pushl %ebp + movl %esp,%ebp + + fnstcw fpucw + movl 12(%ebp),%eax + fldcw cw037F + flds (%eax) + fistpl (%eax) + fildl (%eax) + fstps (%eax) + flds 4(%eax) + fistpl 4(%eax) + fildl 4(%eax) + fstps 4(%eax) + flds 8(%eax) + fistpl 8(%eax) + fildl 8(%eax) + fstps 8(%eax) + fldcw fpucw + + popl %ebp + popl %eax + ret + +// void Sys_SnapVectorCW( float *v, unsigned short int cw ) +.globl C(Sys_SnapVectorCW) +C(Sys_SnapVectorCW): + pushl %eax + pushl %ebp + movl %esp,%ebp + + fnstcw fpucw + movl 12(%ebp),%eax + fldcw 16(%ebp) + flds (%eax) + fistpl (%eax) + fildl (%eax) + fstps (%eax) + flds 4(%eax) + fistpl 4(%eax) + fildl 4(%eax) + fstps 4(%eax) + flds 8(%eax) + fistpl 8(%eax) + fildl 8(%eax) + fstps 8(%eax) + fldcw fpucw + + popl %ebp + popl %eax + ret +#endif diff --git a/src/asm/snd_mixa.s b/src/asm/snd_mixa.s new file mode 100644 index 0000000..4c6be5b --- /dev/null +++ b/src/asm/snd_mixa.s @@ -0,0 +1,217 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// snd_mixa.s +// x86 assembly-language sound code +// + +#include "qasm.h" + +#if id386 + + .text + +#if 0 +//---------------------------------------------------------------------- +// 8-bit sound-mixing code +//---------------------------------------------------------------------- + +#define ch 4+16 +#define sc 8+16 +#define count 12+16 + +.globl C(S_PaintChannelFrom8) +C(S_PaintChannelFrom8): + pushl %esi // preserve register variables + pushl %edi + pushl %ebx + pushl %ebp + +// int data; +// short *lscale, *rscale; +// unsigned char *sfx; +// int i; + + movl ch(%esp),%ebx + movl sc(%esp),%esi + +// if (ch->leftvol > 255) +// ch->leftvol = 255; +// if (ch->rightvol > 255) +// ch->rightvol = 255; + movl ch_leftvol(%ebx),%eax + movl ch_rightvol(%ebx),%edx + cmpl $255,%eax + jna LLeftSet + movl $255,%eax +LLeftSet: + cmpl $255,%edx + jna LRightSet + movl $255,%edx +LRightSet: + +// lscale = snd_scaletable[ch->leftvol >> 3]; +// rscale = snd_scaletable[ch->rightvol >> 3]; +// sfx = (signed char *)sc->data + ch->pos; +// ch->pos += count; + andl $0xF8,%eax + addl $20,%esi + movl (%esi),%esi + andl $0xF8,%edx + movl ch_pos(%ebx),%edi + movl count(%esp),%ecx + addl %edi,%esi + shll $7,%eax + addl %ecx,%edi + shll $7,%edx + movl %edi,ch_pos(%ebx) + addl $(C(snd_scaletable)),%eax + addl $(C(snd_scaletable)),%edx + subl %ebx,%ebx + movb -1(%esi,%ecx,1),%bl + + testl $1,%ecx + jz LMix8Loop + + movl (%eax,%ebx,4),%edi + movl (%edx,%ebx,4),%ebp + addl C(paintbuffer)+psp_left-psp_size(,%ecx,psp_size),%edi + addl C(paintbuffer)+psp_right-psp_size(,%ecx,psp_size),%ebp + movl %edi,C(paintbuffer)+psp_left-psp_size(,%ecx,psp_size) + movl %ebp,C(paintbuffer)+psp_right-psp_size(,%ecx,psp_size) + movb -2(%esi,%ecx,1),%bl + + decl %ecx + jz LDone + +// for (i=0 ; i>8; +// if (val > 0x7fff) +// snd_out[i] = 0x7fff; +// else if (val < (short)0x8000) +// snd_out[i] = (short)0x8000; +// else +// snd_out[i] = val; + movl -8(%ebx,%ecx,4),%eax + sarl $8,%eax + cmpl $0x7FFF,%eax + jg LClampHigh + cmpl $0xFFFF8000,%eax + jnl LClampDone + movl $0xFFFF8000,%eax + jmp LClampDone +LClampHigh: + movl $0x7FFF,%eax +LClampDone: + +// val = (snd_p[i+1]*snd_vol)>>8; +// if (val > 0x7fff) +// snd_out[i+1] = 0x7fff; +// else if (val < (short)0x8000) +// snd_out[i+1] = (short)0x8000; +// else +// snd_out[i+1] = val; + movl -4(%ebx,%ecx,4),%edx + sarl $8,%edx + cmpl $0x7FFF,%edx + jg LClampHigh2 + cmpl $0xFFFF8000,%edx + jnl LClampDone2 + movl $0xFFFF8000,%edx + jmp LClampDone2 +LClampHigh2: + movl $0x7FFF,%edx +LClampDone2: + shll $16,%edx + andl $0xFFFF,%eax + orl %eax,%edx + movl %edx,-4(%edi,%ecx,2) + +// } + subl $2,%ecx + jnz LWLBLoopTop + +// snd_p += snd_linear_count; + + popl %ebx + popl %edi + + ret + +#endif // id386 + diff --git a/src/cgame/cg_animation.c b/src/cgame/cg_animation.c new file mode 100644 index 0000000..e92a6e5 --- /dev/null +++ b/src/cgame/cg_animation.c @@ -0,0 +1,113 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "cg_local.h" + +/* +=============== +CG_RunLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +void CG_RunLerpFrame( lerpFrame_t *lf, float scale ) +{ + int f, numFrames; + animation_t *anim; + + // debugging tool to get no animations + if( cg_animSpeed.integer == 0 ) + { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return; + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if( cg.time >= lf->frameTime ) + { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if( !anim->frameLerp ) + return; // shouldn't happen + + if( cg.time < lf->animationTime ) + lf->frameTime = lf->animationTime; // initial lerp + else + lf->frameTime = lf->oldFrameTime + anim->frameLerp; + + f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + f *= scale; + numFrames = anim->numFrames; + + if( anim->flipflop ) + numFrames *= 2; + + if( f >= numFrames ) + { + f -= numFrames; + if( anim->loopFrames ) + { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } + else + { + f = numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = cg.time; + } + } + + if( anim->reversed ) + lf->frame = anim->firstFrame + anim->numFrames - 1 - f; + else if( anim->flipflop && f >= anim->numFrames ) + lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames ); + else + lf->frame = anim->firstFrame + f; + + if( cg.time > lf->frameTime ) + { + lf->frameTime = cg.time; + if( cg_debugAnim.integer ) + CG_Printf( "Clamp lf->frameTime\n" ); + } + } + + if( lf->frameTime > cg.time + 200 ) + lf->frameTime = cg.time; + + if( lf->oldFrameTime > cg.time ) + lf->oldFrameTime = cg.time; + + // calculate current lerp value + if( lf->frameTime == lf->oldFrameTime ) + lf->backlerp = 0; + else + lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); +} diff --git a/src/cgame/cg_animmapobj.c b/src/cgame/cg_animmapobj.c new file mode 100644 index 0000000..72464f7 --- /dev/null +++ b/src/cgame/cg_animmapobj.c @@ -0,0 +1,227 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "cg_local.h" + + +/* +=============== +CG_DoorAnimation +=============== +*/ +static void CG_DoorAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ + CG_RunLerpFrame( ¢->lerpFrame, 1.0f ); + + *old = cent->lerpFrame.oldFrame; + *now = cent->lerpFrame.frame; + *backLerp = cent->lerpFrame.backlerp; +} + + +/* +=============== +CG_ModelDoor +=============== +*/ +void CG_ModelDoor( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *es; + animation_t anim; + lerpFrame_t *lf = ¢->lerpFrame; + + es = ¢->currentState; + + if( !es->modelindex ) + return; + + //create the render entity + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + //add the door model + ent.skinNum = 0; + ent.hModel = cgs.gameModels[ es->modelindex ]; + + //scale the door + VectorScale( ent.axis[ 0 ], es->origin2[ 0 ], ent.axis[ 0 ] ); + VectorScale( ent.axis[ 1 ], es->origin2[ 1 ], ent.axis[ 1 ] ); + VectorScale( ent.axis[ 2 ], es->origin2[ 2 ], ent.axis[ 2 ] ); + ent.nonNormalizedAxes = qtrue; + + //setup animation + anim.firstFrame = es->misc; + anim.numFrames = es->weapon; + anim.reversed = !es->legsAnim; + anim.flipflop = qfalse; + anim.loopFrames = 0; + anim.frameLerp = 1000 / es->torsoAnim; + anim.initialLerp = 1000 / es->torsoAnim; + + //door changed state + if( es->legsAnim != cent->doorState ) + { + lf->animationTime = lf->frameTime + anim.initialLerp; + cent->doorState = es->legsAnim; + } + + lf->animation = &anim; + + //run animation + CG_DoorAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); + + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_AMOAnimation +=============== +*/ +static void CG_AMOAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ + if( !( cent->currentState.eFlags & EF_MOVER_STOP ) || cent->animPlaying ) + { + int delta = cg.time - cent->miscTime; + + //hack to prevent "pausing" mucking up the lerping + if( delta > 900 ) + { + cent->lerpFrame.oldFrameTime += delta; + cent->lerpFrame.frameTime += delta; + } + + CG_RunLerpFrame( ¢->lerpFrame, 1.0f ); + cent->miscTime = cg.time; + } + + *old = cent->lerpFrame.oldFrame; + *now = cent->lerpFrame.frame; + *backLerp = cent->lerpFrame.backlerp; +} + + +/* +================== +CG_animMapObj +================== +*/ +void CG_AnimMapObj( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *es; + float scale; + animation_t anim; + + es = ¢->currentState; + + // if set to invisible, skip + if( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) + return; + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( es->angles, cent->lerpAngles ); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.hModel = cgs.gameModels[ es->modelindex ]; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + //scale the model + if( es->angles2[ 0 ] ) + { + scale = es->angles2[ 0 ]; + VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] ); + VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] ); + VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] ); + ent.nonNormalizedAxes = qtrue; + } + + //setup animation + anim.firstFrame = es->misc; + anim.numFrames = es->weapon; + anim.reversed = qfalse; + anim.flipflop = qfalse; + + // if numFrames is negative the animation is reversed + if( anim.numFrames < 0 ) + { + anim.numFrames = -anim.numFrames; + anim.reversed = qtrue; + } + + anim.loopFrames = es->torsoAnim; + + if( !es->legsAnim ) + { + anim.frameLerp = 1000; + anim.initialLerp = 1000; + } + else + { + anim.frameLerp = 1000 / es->legsAnim; + anim.initialLerp = 1000 / es->legsAnim; + } + + cent->lerpFrame.animation = &anim; + + if( !anim.loopFrames ) + { + // add one frame to allow the animation to play the last frame + // add another to get it to stop playing at the first frame + anim.numFrames += 2; + + if( !cent->animInit ) + { + cent->animInit = qtrue; + cent->animPlaying = !( cent->currentState.eFlags & EF_MOVER_STOP ); + } + else + { + if( cent->animLastState != + !( cent->currentState.eFlags & EF_MOVER_STOP ) ) + { + cent->animPlaying = qtrue; + cent->lerpFrame.animationTime = cg.time; + cent->lerpFrame.frameTime = cg.time; + } + } + cent->animLastState = !( cent->currentState.eFlags & EF_MOVER_STOP ); + } + + //run animation + CG_AMOAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} diff --git a/src/cgame/cg_attachment.c b/src/cgame/cg_attachment.c new file mode 100644 index 0000000..da094a5 --- /dev/null +++ b/src/cgame/cg_attachment.c @@ -0,0 +1,404 @@ +/* +=========================================================================== +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_attachment.c -- an abstract attachment system + +#include "cg_local.h" + +/* +=============== +CG_AttachmentPoint + +Return the attachment point +=============== +*/ +qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v ) +{ + centity_t *cent; + + if( !a ) + return qfalse; + + // if it all breaks, then use the last point we know was correct + VectorCopy( a->lastValidAttachmentPoint, v ); + + switch( a->type ) + { + case AT_STATIC: + if( !a->staticValid ) + return qfalse; + + VectorCopy( a->origin, v ); + break; + + case AT_TAG: + if( !a->tagValid ) + return qfalse; + + AxisCopy( axisDefault, a->re.axis ); + CG_PositionRotatedEntityOnTag( &a->re, &a->parent, + a->model, a->tagName ); + VectorCopy( a->re.origin, v ); + break; + + case AT_CENT: + if( !a->centValid ) + return qfalse; + + if( a->centNum == cg.predictedPlayerState.clientNum ) + { + // this is smoother if it's the local client + VectorCopy( cg.predictedPlayerState.origin, v ); + } + else + { + cent = &cg_entities[ a->centNum ]; + VectorCopy( cent->lerpOrigin, v ); + } + break; + + case AT_PARTICLE: + if( !a->particleValid ) + return qfalse; + + if( !a->particle->valid ) + { + a->particleValid = qfalse; + return qfalse; + } + else + VectorCopy( a->particle->origin, v ); + break; + + default: + CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" ); + break; + } + + if( a->hasOffset ) + VectorAdd( v, a->offset, v ); + + VectorCopy( v, a->lastValidAttachmentPoint ); + + return qtrue; +} + +/* +=============== +CG_AttachmentDir + +Return the attachment direction +=============== +*/ +qboolean CG_AttachmentDir( attachment_t *a, vec3_t v ) +{ + vec3_t forward; + centity_t *cent; + + if( !a ) + return qfalse; + + switch( a->type ) + { + case AT_STATIC: + return qfalse; + break; + + case AT_TAG: + if( !a->tagValid ) + return qfalse; + + VectorCopy( a->re.axis[ 0 ], v ); + break; + + case AT_CENT: + if( !a->centValid ) + return qfalse; + + cent = &cg_entities[ a->centNum ]; + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + VectorCopy( forward, v ); + break; + + case AT_PARTICLE: + if( !a->particleValid ) + return qfalse; + + if( !a->particle->valid ) + { + a->particleValid = qfalse; + return qfalse; + } + else + VectorCopy( a->particle->velocity, v ); + break; + + default: + CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" ); + break; + } + + VectorNormalize( v ); + return qtrue; +} + +/* +=============== +CG_AttachmentAxis + +Return the attachment axis +=============== +*/ +qboolean CG_AttachmentAxis( attachment_t *a, vec3_t axis[ 3 ] ) +{ + centity_t *cent; + + if( !a ) + return qfalse; + + switch( a->type ) + { + case AT_STATIC: + return qfalse; + break; + + case AT_TAG: + if( !a->tagValid ) + return qfalse; + + AxisCopy( a->re.axis, axis ); + break; + + case AT_CENT: + if( !a->centValid ) + return qfalse; + + cent = &cg_entities[ a->centNum ]; + AnglesToAxis( cent->lerpAngles, axis ); + break; + + case AT_PARTICLE: + return qfalse; + break; + + default: + CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" ); + break; + } + + return qtrue; +} + +/* +=============== +CG_AttachmentVelocity + +If the attachment can have velocity, return it +=============== +*/ +qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v ) +{ + if( !a ) + return qfalse; + + if( a->particleValid && a->particle->valid ) + { + VectorCopy( a->particle->velocity, v ); + return qtrue; + } + else if( a->centValid ) + { + centity_t *cent = &cg_entities[ a->centNum ]; + + VectorCopy( cent->currentState.pos.trDelta, v ); + return qtrue; + } + + return qfalse; +} + +/* +=============== +CG_AttachmentCentNum + +If the attachment has a centNum, return it +=============== +*/ +int CG_AttachmentCentNum( attachment_t *a ) +{ + if( !a || !a->centValid ) + return -1; + + return a->centNum; +} + +/* +=============== +CG_Attached + +If the attachment is valid, return qtrue +=============== +*/ +qboolean CG_Attached( attachment_t *a ) +{ + if( !a ) + return qfalse; + + return a->attached; +} + +/* +=============== +CG_AttachToPoint + +Attach to a point in space +=============== +*/ +void CG_AttachToPoint( attachment_t *a ) +{ + if( !a || !a->staticValid ) + return; + + a->type = AT_STATIC; + a->attached = qtrue; +} + +/* +=============== +CG_AttachToCent + +Attach to a centity_t +=============== +*/ +void CG_AttachToCent( attachment_t *a ) +{ + if( !a || !a->centValid ) + return; + + a->type = AT_CENT; + a->attached = qtrue; +} + +/* +=============== +CG_AttachToTag + +Attach to a model tag +=============== +*/ +void CG_AttachToTag( attachment_t *a ) +{ + if( !a || !a->tagValid ) + return; + + a->type = AT_TAG; + a->attached = qtrue; +} + +/* +=============== +CG_AttachToParticle + +Attach to a particle +=============== +*/ +void CG_AttachToParticle( attachment_t *a ) +{ + if( !a || !a->particleValid ) + return; + + a->type = AT_PARTICLE; + a->attached = qtrue; +} + +/* +=============== +CG_SetAttachmentPoint +=============== +*/ +void CG_SetAttachmentPoint( attachment_t *a, vec3_t v ) +{ + if( !a ) + return; + + VectorCopy( v, a->origin ); + a->staticValid = qtrue; +} + +/* +=============== +CG_SetAttachmentCent +=============== +*/ +void CG_SetAttachmentCent( attachment_t *a, centity_t *cent ) +{ + if( !a || !cent ) + return; + + a->centNum = cent->currentState.number; + a->centValid = qtrue; +} + +/* +=============== +CG_SetAttachmentTag +=============== +*/ +void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent, + qhandle_t model, char *tagName ) +{ + if( !a ) + return; + + a->parent = parent; + a->model = model; + strncpy( a->tagName, tagName, MAX_STRING_CHARS ); + a->tagValid = qtrue; +} + +/* +=============== +CG_SetAttachmentParticle +=============== +*/ +void CG_SetAttachmentParticle( attachment_t *a, particle_t *p ) +{ + if( !a ) + return; + + a->particle = p; + a->particleValid = qtrue; +} + +/* +=============== +CG_SetAttachmentOffset +=============== +*/ +void CG_SetAttachmentOffset( attachment_t *a, vec3_t v ) +{ + if( !a ) + return; + + VectorCopy( v, a->offset ); + a->hasOffset = qtrue; +} diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c new file mode 100644 index 0000000..7b5b85d --- /dev/null +++ b/src/cgame/cg_buildable.c @@ -0,0 +1,2290 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#include "cg_local.h" + +char *cg_buildableSoundNames[ MAX_BUILDABLE_ANIMATIONS ] = +{ + "construct1.wav", + "construct2.wav", + "idle1.wav", + "idle2.wav", + "idle3.wav", + "attack1.wav", + "attack2.wav", + "spawn1.wav", + "spawn2.wav", + "pain1.wav", + "pain2.wav", + "destroy1.wav", + "destroy2.wav", + "destroyed.wav" +}; + +static sfxHandle_t defaultAlienSounds[ MAX_BUILDABLE_ANIMATIONS ]; +static sfxHandle_t defaultHumanSounds[ MAX_BUILDABLE_ANIMATIONS ]; + + +/* +====================== +CG_DrawCuboid + +cg_drawbbox for cuboids +====================== +*/ +static void CG_DrawCuboidFace( vec3_t a, vec3_t b, vec3_t c, vec3_t d, qhandle_t shader ) +{ + polyVert_t verts[ 4 ]; + vec4_t color = { 255.0f, 255.0f, 255.0f, 255.0f }; + VectorCopy( d, verts[ 0 ].xyz ); + verts[ 0 ].st[ 0 ] = 1; + verts[ 0 ].st[ 1 ] = 1; + Vector4Copy( color, verts[ 0 ].modulate ); + VectorCopy( c, verts[ 1 ].xyz ); + verts[ 1 ].st[ 0 ] = 1; + verts[ 1 ].st[ 1 ] = 0; + Vector4Copy( color, verts[ 1 ].modulate ); + VectorCopy( b, verts[ 2 ].xyz ); + verts[ 2 ].st[ 0 ] = 0; + verts[ 2 ].st[ 1 ] = 0; + Vector4Copy( color, verts[ 2 ].modulate ); + VectorCopy( a, verts[ 3 ].xyz ); + verts[ 3 ].st[ 0 ] = 0; + verts[ 3 ].st[ 1 ] = 1; + Vector4Copy( color, verts[ 3 ].modulate ); + trap_R_AddPolyToScene( (shader ? shader : cgs.media.outlineShader ), 4, verts ); +} + +void CG_DrawCuboid( vec3_t origin, vec3_t dims, qhandle_t shader, int margin ) +{ + vec3_t mins, maxs; + vec3_t ppp, mpp, mmp, pmp; + vec3_t mmm, pmm, ppm, mpm; + int i; + BG_CuboidBBox(dims,mins,maxs); + for(i=0;i<3&&margin;i++) + { + mins[i]-=margin; + maxs[i]+=margin; + } + ppp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + ppp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + ppp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + mpp[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mpp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + mpp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + mmp[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; + mmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + pmp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + pmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; + pmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + ppm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + ppm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + ppm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + mpm[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mpm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + mpm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + mmm[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; + mmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + pmm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + pmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; + pmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + CG_DrawCuboidFace( ppp, mpp, mmp, pmp, shader ); + CG_DrawCuboidFace( ppp, pmp, pmm, ppm, shader ); + CG_DrawCuboidFace( mpp, ppp, ppm, mpm, shader ); + CG_DrawCuboidFace( mmp, mpp, mpm, mmm, shader ); + CG_DrawCuboidFace( pmp, mmp, mmm, pmm, shader ); + CG_DrawCuboidFace( mmm, mpm, ppm, pmm, shader ); +} + +void CG_DrawCuboidAxis(vec3_t cuboidOrigin, vec3_t size, int axis, qhandle_t shader) +{ + vec3_t origin, localZ, localX, localY, start, end, quad[2][4]; + float len, width; + polyVert_t poly[4]; + + VectorCopy(cuboidOrigin,origin); + origin[2]+=size[2]/2.0f; + VectorClear(localZ); + VectorClear(localX); + switch(axis) + { + case 0: + localZ[0]=1.0f; + localX[1]=1.0f; + len=size[0]; + break; + case 1: + localZ[1]=1.0f; + localX[2]=1.0f; + len=size[1]; + break; + default: + localZ[2]=1.0f; + localX[0]=1.0f; + len=size[2]; + break; + } + CrossProduct(localZ,localX,localY); + VectorMA(origin,-MAX(len/2.0f,32),localZ,start); + VectorMA(origin,MAX(len/2.0f,32),localZ,end); + VectorMA(start,4.0f,localX,quad[0][0]); + VectorMA(start,-4.0f,localX,quad[0][1]); + VectorMA(end,-4.0f,localX,quad[0][2]); + VectorMA(end,4.0f,localX,quad[0][3]); + VectorMA(start,4.0f,localY,quad[1][0]); + VectorMA(start,-4.0f,localY,quad[1][1]); + VectorMA(end,-4.0f,localY,quad[1][2]); + VectorMA(end,4.0f,localY,quad[1][3]); + CG_DrawCuboidFace(quad[0][0],quad[0][1],quad[0][2],quad[0][3],shader); + CG_DrawCuboidFace(quad[1][0],quad[1][1],quad[1][2],quad[1][3],shader); +} + +/* +=================== +CG_InitCuboidExplosions +CG_CuboidExplosion +CG_DrawCuboidParticles + +Simple particle system based on cg_particles.c +=================== +*/ + +#define MAX_CPARTICLES 4096 +#define MAX_CPARTICLES_PER_EXPLOSION 128 + +typedef struct +{ + qboolean use; + vec3_t origin; + vec3_t velocity; + float rot,rotspeed; + float size; + qhandle_t shader; + int lastphysics; + int spawntime; + qboolean solid; +} cuboidParticle_t; + +cuboidParticle_t cuboidParticles[MAX_CPARTICLES]; +int cuboidParticlesCount=0; +qboolean cuboidParticles_init=qfalse; + +void CG_InitCuboidExplosions(void) +{ + memset(cuboidParticles,0,sizeof(cuboidParticles)); + cuboidParticles_init=qtrue; + return; +} + +void CG_CuboidExplosion(buildable_t buildable, vec3_t origin, vec3_t dims) +{ + int i,p,m,q; + float v,s; + cuboidParticle_t *pc; + vec3_t sdims; + const cuboidInfo_t *info; + + info=&cg_cuboids[buildable-CUBOID_FIRST]; + v=dims[0]*dims[1]*dims[2]; + for(i=0;idestroySoundCount;i++) + if(v*5.5306341e-5>=info->destroySoundThresholds[i]) + { + trap_S_StartSound(origin,ENTITYNUM_WORLD,CHAN_AUTO,info->destroySounds[i]); + break; + } + + if(!info->fragmentCount) + return; + + BG_CuboidSortSize(dims,sdims); + + q=cg_cuboidPSQuality.integer; + + if(q<=0) + p=2; + else if(q==1) + p=ceil(sqrt(v/pow(sdims[2],3)))*2; + else if(q==2) + p=ceil(pow(v/pow(sdims[2],3),0.75f))*3; + else if(q>=3) + p=ceil(pow(v/pow(sdims[2],3),0.85f))*6; + p=MIN(p,MAX_CPARTICLES_PER_EXPLOSION); + + s=ceil(pow(v/p,1.0f/3.0f))*1.2f; + + for(i=0;iuse) + continue; + pc->solid=qfalse; + pc->origin[0]=origin[0]+crandom()*dims[0]*0.5f; + pc->origin[1]=origin[1]+crandom()*dims[1]*0.5f; + pc->origin[2]=origin[2]+dims[2]/2.0f+crandom()*dims[2]*0.5f; + VectorSubtract(pc->origin,origin,pc->velocity); + pc->size=s; + pc->rot=crandom()*360.0f; + pc->rotspeed=crandom()*450.0f; + pc->shader=info->fragments[(int)(rand()%info->fragmentCount)]; + pc->lastphysics=pc->spawntime=cg.time; + pc->use=qtrue; + cuboidParticlesCount++; + p--; + } +} + +void CG_CuboidParticlePhysics(cuboidParticle_t *pc) +{ + float t; + int c; + vec3_t newOrigin; + t=(cg.time-pc->lastphysics)/1e3; + pc->lastphysics=cg.time; + if(pc->spawntime+3000use=qfalse; + cuboidParticlesCount--; + return; + } + if(pc->solid) + return; + pc->rot+=pc->rotspeed*t; + pc->velocity[2]-=t*DEFAULT_GRAVITY; + VectorMA(pc->origin,t,pc->velocity,newOrigin); + c=trap_CM_PointContents(newOrigin,0); + if(c&CONTENTS_NODROP) + { + pc->use=qfalse; + cuboidParticlesCount--; + return; + } + if(c&(CONTENTS_SOLID|CONTENTS_BODY)) + { + pc->solid=qtrue; + return; + } + VectorCopy(newOrigin,pc->origin); +} + +void CG_DrawCuboidParticles(void) +{ + int i,j,drawn=0,c; + float t; + vec3_t alight, dlight, lightdir, newOrigin; + cuboidParticle_t *pc; + refEntity_t re; + + if(!cuboidParticles_init) + CG_InitCuboidExplosions(); + + for(i=0;iuse) + continue; + CG_CuboidParticlePhysics(pc); + if(!pc->use) //particle physics can delete a particle + continue; + memset(&re,0,sizeof(refEntity_t)); + re.reType=RT_SPRITE; + VectorCopy(pc->origin,re.origin); + //trap_R_LightForPoint(pc->origin,alight,dlight,lightdir); + //for(j=0;j<3;j++) + // re.shaderRGBA[j]=(byte)alight[j]; + for(j=0;j<3;j++) + re.shaderRGBA[j]=255; + re.customShader=pc->shader; + re.radius=pc->size; + re.rotation=pc->rot; + trap_R_AddRefEntityToScene(&re); + drawn++; + } +} + + +/* +=================== +CG_AlienBuildableExplosion + +Generated a bunch of gibs launching out from a location +=================== +*/ +void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir ) +{ + particleSystem_t *ps; + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.alienBuildableExplosion ); + + //particle system + ps = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDestroyedPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToPoint( &ps->attachment ); + } +} + +/* +================= +CG_HumanBuildableExplosion + +Called for human buildables as they are destroyed +================= +*/ +void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ) +{ + particleSystem_t *ps; + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.humanBuildableExplosion ); + + //particle system + ps = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDestroyedPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToPoint( &ps->attachment ); + } +} + + +#define CREEP_SIZE 64.0f +#define CREEP_DISTANCE 64.0f + +/* +================== +CG_Creep +================== +*/ +static void CG_Creep( centity_t *cent ) +{ + int msec; + float size, frac; + trace_t tr; + vec3_t temp, origin; + int scaleUpTime = BG_Buildable( cent->currentState.modelindex, cent->currentState.angles )->buildTime; + int time; + + if(BG_Buildable(cent->currentState.modelindex,NULL)->cuboid) + return; + + time = cent->currentState.time; + + //should the creep be growing or receding? + if( time >= 0 ) + { + msec = cg.time - time; + if( msec >= 0 && msec < scaleUpTime ) + frac = (float)msec / scaleUpTime; + else + frac = 1.0f; + } + else if( time < 0 ) + { + msec = cg.time + time; + if( msec >= 0 && msec < CREEP_SCALEDOWN_TIME ) + frac = 1.0f - ( (float)msec / CREEP_SCALEDOWN_TIME ); + else + frac = 0.0f; + } + + VectorCopy( cent->currentState.origin2, temp ); + VectorScale( temp, -CREEP_DISTANCE, temp ); + VectorAdd( temp, cent->lerpOrigin, temp ); + + CG_Trace( &tr, cent->lerpOrigin, NULL, NULL, temp, cent->currentState.number, MASK_PLAYERSOLID ); + + if(tr.entityNum!=ENTITYNUM_WORLD) + return; + + VectorCopy( tr.endpos, origin ); + + size = CREEP_SIZE * frac; + + if( size > 0.0f && tr.fraction < 1.0f ) + CG_ImpactMark( cgs.media.creepShader, origin, cent->currentState.origin2, + 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue ); +} + +/* +====================== +CG_ParseBuildableAnimationFile + +Read a configuration file containing animation counts and rates +models/buildables/hivemind/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseBuildableAnimationFile( const char *filename, buildable_t buildable ) +{ + char *text_p; + int len; + int i; + char *token; + float fps; + char text[ 20000 ]; + fileHandle_t f; + animation_t *animations; + + if(BG_Buildable(buildable,NULL)->cuboid) + return qtrue; + + animations = cg_buildables[ buildable ].animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read information for each frame + for( i = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ ) + { + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + animations[ i ].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + animations[ i ].numFrames = atoi( token ); + animations[ i ].reversed = qfalse; + animations[ i ].flipflop = qfalse; + + // if numFrames is negative the animation is reversed + if( animations[ i ].numFrames < 0 ) + { + animations[ i ].numFrames = -animations[ i ].numFrames; + animations[ i ].reversed = qtrue; + } + + token = COM_Parse( &text_p ); + if ( !*token ) + break; + + animations[i].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + fps = atof( token ); + if( fps == 0 ) + fps = 1; + + animations[ i ].frameLerp = 1000 / fps; + animations[ i ].initialLerp = 1000 / fps; + } + + if( i != MAX_BUILDABLE_ANIMATIONS ) + { + CG_Printf( "Error parsing animation file: %s\n", filename ); + return qfalse; + } + + return qtrue; +} + +/* +====================== +CG_ParseBuildableSoundFile + +Read a configuration file containing sound properties +sound/buildables/hivemind/sound.cfg, etc +====================== +*/ +static qboolean CG_ParseBuildableSoundFile( const char *filename, buildable_t buildable ) +{ + char *text_p; + int len; + int i; + char *token; + char text[ 20000 ]; + fileHandle_t f; + sound_t *sounds; + + if(BG_Buildable(buildable,NULL)->cuboid) + return qtrue; + + sounds = cg_buildables[ buildable ].sounds; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len < 0 ) + return qfalse; + + if ( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read information for each frame + for( i = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ ) + { + + token = COM_Parse( &text_p ); + if ( !*token ) + break; + + sounds[ i ].enabled = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !*token ) + break; + + sounds[ i ].looped = atoi( token ); + + } + + if( i != MAX_BUILDABLE_ANIMATIONS ) + { + CG_Printf( "Error parsing sound file: %s\n", filename ); + return qfalse; + } + + return qtrue; +} +/* +=============== +CG_InitBuildables + +Initialises the animation db +=============== +*/ +void CG_InitBuildables( void ) +{ + char filename[ MAX_QPATH ]; + char soundfile[ MAX_QPATH ]; + char *buildableName; + const char *cuboidName; + char *modelFile; + int i; + int j; + fileHandle_t f; + pc_token_t token; + cuboidInfo_t *cuboid; + const char *s; + float n; + + memset( cg_buildables, 0, sizeof( cg_buildables ) ); + + //default sounds + for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ ) + { + strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] ); + + Com_sprintf( filename, sizeof( filename ), "sound/buildables/alien/%s", soundfile ); + defaultAlienSounds[ j ] = trap_S_RegisterSound( filename, qfalse ); + + Com_sprintf( filename, sizeof( filename ), "sound/buildables/human/%s", soundfile ); + defaultHumanSounds[ j ] = trap_S_RegisterSound( filename, qfalse ); + } + + cg.buildablesFraction = 0.0f; + + for( i = BA_NONE + 1; i < CUBOID_FIRST; i++ ) + { + buildableName = BG_Buildable( i, NULL )->name; + + //animation.cfg + Com_sprintf( filename, sizeof( filename ), "models/buildables/%s/animation.cfg", buildableName ); + if ( !CG_ParseBuildableAnimationFile( filename, i ) ) + Com_Printf( S_COLOR_YELLOW "WARNING: failed to load animation file %s\n", filename ); + + //sound.cfg + Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/sound.cfg", buildableName ); + if ( !CG_ParseBuildableSoundFile( filename, i ) ) + Com_Printf( S_COLOR_YELLOW "WARNING: failed to load sound file %s\n", filename ); + + //models + for( j = 0; j <= 3; j++ ) + { + modelFile = BG_BuildableConfig( i )->models[ j ]; + if( strlen( modelFile ) > 0 ) + cg_buildables[ i ].models[ j ] = trap_R_RegisterModel( modelFile ); + } + + //sounds + for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ ) + { + strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] ); + Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/%s", buildableName, soundfile ); + + if( cg_buildables[ i ].sounds[ j ].enabled ) + { + if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 ) + { + //file exists so close it + trap_FS_FCloseFile( f ); + + cg_buildables[ i ].sounds[ j ].sound = trap_S_RegisterSound( filename, qfalse ); + } + else + { + //file doesn't exist - use default + if( BG_Buildable( i, NULL )->team == TEAM_ALIENS ) + cg_buildables[ i ].sounds[ j ].sound = defaultAlienSounds[ j ]; + else + cg_buildables[ i ].sounds[ j ].sound = defaultHumanSounds[ j ]; + } + } + } + + cg.buildablesFraction = (float)i / (float)( BA_NUM_BUILDABLES - 1 ); + trap_UpdateScreen( ); + } + + cgs.media.teslaZapTS = CG_RegisterTrailSystem( "models/buildables/tesla/zap" ); + + memset( cg_cuboids, 0, sizeof( cg_cuboids ) ); + + for( i = CUBOID_FIRST; i <= CUBOID_LAST; i++ ) + { + cuboid = &cg_cuboids[ i - CUBOID_FIRST ]; + cuboidName = BG_CuboidAttributes( i )->file; + + Com_sprintf( filename, sizeof( filename ), "configs/cuboids/%s.cfg", cuboidName ); + if( !( f = trap_Parse_LoadSource( filename ) ) ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: failed to load cuboid config %s\n", filename ); + continue; + } + while( 1 ) + { + if( !trap_Parse_ReadToken( f, &token ) ) + break; + else if( !Q_stricmp( token.string, "texture" ) ) + { + if( cuboid->textureCount == MAX_CUBOID_TEXTURES ) + { + Com_Printf( S_COLOR_RED "ERROR: too many cuboid textures in %s\n", filename ); + break; + } + if( trap_Parse_ReadToken( f, &token ) ) + { + cuboid->textureThresholds[ cuboid->textureCount++ ] = atof( token.string ); + if( trap_Parse_ReadToken( f, &token ) ) + cuboid->textures[ cuboid->textureCount - 1 ] = trap_R_RegisterShader( token.string ); + else + { + Com_Printf( S_COLOR_YELLOW "WARNING: incomplete cuboid texture definition in %s\n", filename ); + cuboid->textureCount--; + } + } + } + else if( !Q_stricmp( token.string, "useCracks" ) ) + cuboid->useCracks=qtrue; + else if( !Q_stricmp( token.string, "fragment") ) + { + if( cuboid->fragmentCount == MAX_CUBOID_FRAGMENTS ) + { + Com_Printf( S_COLOR_RED "ERROR: too many cuboid fragments in %s\n", filename ); + break; + } + if( trap_Parse_ReadToken( f, &token ) ) + cuboid->fragments[ cuboid->fragmentCount++ ] = trap_R_RegisterShader( token.string ); + } + else if( !Q_stricmp( token.string, "painSound" ) ) + { + if( cuboid->painSoundCount == MAX_CUBOID_SOUNDS ) + { + Com_Printf( S_COLOR_RED "ERROR: too many cuboid pain sounds in %s\n", filename ); + break; + } + if( trap_Parse_ReadToken( f, &token ) ) + cuboid->painSounds[ cuboid->painSoundCount++ ] = trap_S_RegisterSound( token.string, qfalse ); + } + else if( !Q_stricmp( token.string, "destroySound" ) ) + { + if( cuboid->destroySoundCount == MAX_CUBOID_SOUNDS ) + { + Com_Printf( S_COLOR_RED "ERROR: too many cuboid destroy sounds in %s\n", filename ); + break; + } + if( trap_Parse_ReadToken( f, &token ) ) + { + cuboid->destroySoundThresholds[ cuboid->destroySoundCount++ ] = atof( token.string ); + if( trap_Parse_ReadToken( f, &token ) ) + cuboid->destroySounds[ cuboid->destroySoundCount - 1 ] = trap_S_RegisterSound( token.string, qfalse ); + else + { + Com_Printf( S_COLOR_YELLOW "WARNING: incomplete cuboid destroy sound definition in %s\n", filename ); + cuboid->destroySoundCount--; + } + } + } + else + Com_Printf( S_COLOR_YELLOW "WARNING: invalid token %s in %s\n", token.string, filename ); + } + trap_Parse_FreeSource( f ); + } +} + +/* +=============== +CG_SetBuildableLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetBuildableLerpFrameAnimation( buildable_t buildable, lerpFrame_t *lf, int newAnimation ) +{ + animation_t *anim; + + lf->animationNumber = newAnimation; + + if( newAnimation < 0 || newAnimation >= MAX_BUILDABLE_ANIMATIONS ) + CG_Error( "Bad animation number: %i", newAnimation ); + + anim = &cg_buildables[ buildable ].animations[ newAnimation ]; + + //this item has just spawned so lf->frameTime will be zero + if( !lf->animation ) + lf->frameTime = cg.time + 1000; //1 sec delay before starting the spawn anim + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if( cg_debugAnim.integer ) + CG_Printf( "Anim: %i\n", newAnimation ); +} + +/* +=============== +CG_RunBuildableLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunBuildableLerpFrame( centity_t *cent ) +{ + buildable_t buildable = cent->currentState.modelindex; + lerpFrame_t *lf = ¢->lerpFrame; + buildableAnimNumber_t newAnimation = cent->buildableAnim & ~( ANIM_TOGGLEBIT|ANIM_FORCEBIT ); + + // see if the animation sequence is switching + if( newAnimation != lf->animationNumber || !lf->animation ) + { + if( cg_debugRandom.integer ) + CG_Printf( "newAnimation: %d lf->animationNumber: %d lf->animation: %d\n", + newAnimation, lf->animationNumber, lf->animation ); + + CG_SetBuildableLerpFrameAnimation( buildable, lf, newAnimation ); + + if( !cg_buildables[ buildable ].sounds[ newAnimation ].looped && + cg_buildables[ buildable ].sounds[ newAnimation ].enabled ) + { + if( cg_debugRandom.integer ) + CG_Printf( "Sound for animation %d for a %s\n", + newAnimation, BG_Buildable( buildable, NULL )->humanName ); + + trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, + cg_buildables[ buildable ].sounds[ newAnimation ].sound ); + } + } + + if( cg_buildables[ buildable ].sounds[ lf->animationNumber ].looped && + cg_buildables[ buildable ].sounds[ lf->animationNumber ].enabled ) + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cg_buildables[ buildable ].sounds[ lf->animationNumber ].sound ); + + CG_RunLerpFrame( lf, 1.0f ); + + // animation ended + if( lf->frameTime == cg.time ) + { + cent->buildableAnim = cent->currentState.torsoAnim; + cent->buildableIdleAnim = qtrue; + } +} + +/* +=============== +CG_BuildableAnimation +=============== +*/ +static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ + entityState_t *es = ¢->currentState; + + //if no animation is set default to idle anim + if( cent->buildableAnim == BANIM_NONE ) + { + cent->buildableAnim = es->torsoAnim; + cent->buildableIdleAnim = qtrue; + } + + //display the first frame of the construction anim if not yet spawned + if( !( es->eFlags & EF_B_SPAWNED ) ) + { + animation_t *anim = &cg_buildables[ es->modelindex ].animations[ BANIM_CONSTRUCT1 ]; + + //so that when animation starts for real it has sensible numbers + cent->lerpFrame.oldFrameTime = + cent->lerpFrame.frameTime = + cent->lerpFrame.animationTime = + cg.time; + + *old = cent->lerpFrame.oldFrame = anim->firstFrame; + *now = cent->lerpFrame.frame = anim->firstFrame; + *backLerp = cent->lerpFrame.backlerp = 0.0f; + + //ensure that an animation is triggered once the buildable has spawned + cent->oldBuildableAnim = BANIM_NONE; + } + else + { + if( ( cent->oldBuildableAnim ^ es->legsAnim ) & ANIM_TOGGLEBIT ) + { + if( cg_debugAnim.integer ) + CG_Printf( "%d->%d l:%d t:%d %s(%d)\n", + cent->oldBuildableAnim, cent->buildableAnim, + es->legsAnim, es->torsoAnim, + BG_Buildable( es->modelindex, NULL )->humanName, es->number ); + + if( cent->buildableAnim == es->torsoAnim || es->legsAnim & ANIM_FORCEBIT ) + { + cent->buildableAnim = cent->oldBuildableAnim = es->legsAnim; + cent->buildableIdleAnim = qfalse; + } + else + { + cent->buildableAnim = cent->oldBuildableAnim = es->torsoAnim; + cent->buildableIdleAnim = qtrue; + } + } + else if( cent->buildableIdleAnim == qtrue && + cent->buildableAnim != es->torsoAnim ) + { + cent->buildableAnim = es->torsoAnim; + } + + CG_RunBuildableLerpFrame( cent ); + + *old = cent->lerpFrame.oldFrame; + *now = cent->lerpFrame.frame; + *backLerp = cent->lerpFrame.backlerp; + } +} + +#define TRACE_DEPTH 64.0f + +/* +=============== +CG_PositionAndOrientateBuildable +=============== +*/ +static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t inOrigin, + const vec3_t normal, const int skipNumber, + const vec3_t mins, const vec3_t maxs, + vec3_t outAxis[ 3 ], vec3_t outOrigin, + qboolean cuboid ) +{ + vec3_t forward, start, end; + trace_t tr, box_tr; + float mag, fraction; + + AngleVectors( angles, forward, NULL, NULL ); + VectorCopy( normal, outAxis[ 2 ] ); + ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] ); + + if( !VectorNormalize( outAxis[ 0 ] ) ) + { + if(cuboid) + forward[0]=forward[1]=0.0f,forward[2]=1.0f; + else + AngleVectors( angles, NULL, NULL, forward ); + ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] ); + VectorNormalize( outAxis[ 0 ] ); + } + + CrossProduct( outAxis[ 0 ], outAxis[ 2 ], outAxis[ 1 ] ); + outAxis[ 1 ][ 0 ] = -outAxis[ 1 ][ 0 ]; + outAxis[ 1 ][ 1 ] = -outAxis[ 1 ][ 1 ]; + outAxis[ 1 ][ 2 ] = -outAxis[ 1 ][ 2 ]; + + if(cuboid) + { + VectorCopy(inOrigin,outOrigin); + return; + } + VectorMA( inOrigin, -TRACE_DEPTH, normal, end ); + VectorMA( inOrigin, 1.0f, normal, start ); + + // Take both capsule and box traces. If the capsule trace does not differ + // significantly from the box trace use it. This may cause buildables to be + // positioned *inside* the surface on which it is placed. This is intentional + + CG_CapTrace( &tr, start, mins, maxs, end, skipNumber, + MASK_PLAYERSOLID ); + + if(tr.contents&(~MASK_SOLID)) //if we hit a player or another buildable then display buildable exactly at its origin + VectorCopy(inOrigin,outOrigin); + else + { + CG_Trace( &box_tr, start, mins, maxs, end, skipNumber, + MASK_PLAYERSOLID ); + + mag = Distance( tr.endpos, box_tr.endpos ); + + fraction = tr.fraction; + + // this is either too far off of the bbox to be useful for gameplay purposes + // or the model is positioned in thin air anyways. + if( mag > 15.0f || tr.fraction == 1.0f ) + fraction = box_tr.fraction; + + VectorMA( inOrigin, fraction * -TRACE_DEPTH, normal, outOrigin ); + } +} + +/* +================== +CG_GhostBuildable +================== +*/ +void CG_Cuboid_Info(void); +void CG_GhostBuildable( buildable_t buildable, vec3_t dims ) +{ + refEntity_t ent; + playerState_t *ps; + vec3_t angles, entity_origin; + vec3_t mins, maxs; + trace_t tr; + float scale; + vec3_t viewangles; + + ps = &cg.predictedPlayerState; + + memset( &ent, 0, sizeof( ent ) ); + + if(BG_Buildable(buildable,NULL)->cuboid) + { + BG_CuboidBBox(dims,mins,maxs); + CG_Cuboid_Info(); + } + else BG_BuildableBoundingBox( buildable, mins, maxs ); + + if(!BG_PositionBuildableRelativeToPlayer( ps, BG_Buildable(buildable,NULL)->cuboid, mins, maxs, CG_Trace, entity_origin, angles, &tr )) + return; + + if(BG_Buildable(buildable, NULL)->cuboid) + { + qhandle_t shader, ashader; + + CG_Cuboid_Send(); //NOTE: CG_Cuboid_Send has its own timer so we don't spam server with commands every frame + + if(cg.forbidCuboids) + shader=cgs.media.cuboidYellowBuildShader; + else if(ps->stats[STAT_BUILDABLE]&SB_VALID_TOGGLEBIT) + shader=cgs.media.cuboidGreenBuildShader; + else + shader=cgs.media.cuboidRedBuildShader; + CG_DrawCuboid(entity_origin,dims,shader,0); + CG_DrawCuboidAxis(entity_origin,dims,cg_cuboidResizeAxis.integer,cgs.media.cuboidAxis); + return; + } + + VectorCopy(ps->viewangles,viewangles); + + CG_PositionAndOrientateBuildable( viewangles, entity_origin, tr.plane.normal, ps->clientNum, + mins, maxs, ent.axis, ent.origin, qfalse ); + + //offset on the Z axis if required + + VectorMA( ent.origin, BG_BuildableConfig( buildable )->zOffset, tr.plane.normal, ent.origin ); + + VectorCopy( ent.origin, ent.lightingOrigin ); + VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all + + ent.hModel = cg_buildables[ buildable ].models[ 0 ]; + + if( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT ) + ent.customShader = cgs.media.greenBuildShader; + else + ent.customShader = cgs.media.redBuildShader; + + ent.nonNormalizedAxes = qfalse; + + //rescale the model + scale = BG_BuildableConfig( buildable )->modelScale; + + if( scale != 1.0f ) + { + VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] ); + VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] ); + VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] ); + + ent.nonNormalizedAxes = qtrue; + } + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + +/* +================== +CG_BuildableParticleEffects +================== +*/ +static void CG_BuildableParticleEffects( centity_t *cent ) +{ + entityState_t *es = ¢->currentState; + team_t team = BG_Buildable( es->modelindex, NULL )->team; + int health = es->generic1; + float healthFrac = (float)health / BG_Buildable( es->modelindex, es->angles )->health; + + if( !( es->eFlags & EF_B_SPAWNED ) ) + return; + + if( team == TEAM_HUMANS ) + { + if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDamagedPS ); + + if( CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + CG_SetAttachmentCent( ¢->buildablePS->attachment, cent ); + CG_AttachToCent( ¢->buildablePS->attachment ); + } + } + else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) + CG_DestroyParticleSystem( ¢->buildablePS ); + } + else if( team == TEAM_ALIENS ) + { + if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDamagedPS ); + + if( CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + CG_SetAttachmentCent( ¢->buildablePS->attachment, cent ); + CG_SetParticleSystemNormal( cent->buildablePS, es->origin2 ); + CG_AttachToCent( ¢->buildablePS->attachment ); + } + } + else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) + CG_DestroyParticleSystem( ¢->buildablePS ); + } +} + +/* +================== +CG_BuildableStatusParse +================== +*/ +void CG_BuildableStatusParse( const char *filename, buildStat_t *bs ) +{ + pc_token_t token; + int handle; + const char *s; + int i; + float f; + vec4_t c; + + handle = trap_Parse_LoadSource( filename ); + if( !handle ) + return; + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + break; + if( !Q_stricmp( token.string, "frameShader" ) ) + { + if( PC_String_Parse( handle, &s ) ) + bs->frameShader = trap_R_RegisterShader( s ); + continue; + } + else if( !Q_stricmp( token.string, "overlayShader" ) ) + { + if( PC_String_Parse( handle, &s ) ) + bs->overlayShader = trap_R_RegisterShader( s ); + continue; + } + else if( !Q_stricmp( token.string, "noPowerShader" ) ) + { + if( PC_String_Parse( handle, &s ) ) + bs->noPowerShader = trap_R_RegisterShader( s ); + continue; + } + else if( !Q_stricmp( token.string, "markedShader" ) ) + { + if( PC_String_Parse( handle, &s ) ) + bs->markedShader = trap_R_RegisterShader( s ); + continue; + } + else if( !Q_stricmp( token.string, "healthSevereColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthSevereColor ); + continue; + } + else if( !Q_stricmp( token.string, "healthHighColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthHighColor ); + continue; + } + else if( !Q_stricmp( token.string, "healthElevatedColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthElevatedColor ); + continue; + } + else if( !Q_stricmp( token.string, "healthGuardedColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthGuardedColor ); + continue; + } + else if( !Q_stricmp( token.string, "healthLowColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthLowColor ); + continue; + } + else if( !Q_stricmp( token.string, "foreColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->foreColor ); + continue; + } + else if( !Q_stricmp( token.string, "backColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->backColor ); + continue; + } + else if( !Q_stricmp( token.string, "frameHeight" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->frameHeight = i; + continue; + } + else if( !Q_stricmp( token.string, "frameWidth" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->frameWidth = i; + continue; + } + else if( !Q_stricmp( token.string, "healthPadding" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->healthPadding = i; + continue; + } + else if( !Q_stricmp( token.string, "overlayHeight" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->overlayHeight = i; + continue; + } + else if( !Q_stricmp( token.string, "overlayWidth" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->overlayWidth = i; + continue; + } + else if( !Q_stricmp( token.string, "verticalMargin" ) ) + { + if( PC_Float_Parse( handle, &f ) ) + bs->verticalMargin = f; + continue; + } + else if( !Q_stricmp( token.string, "horizontalMargin" ) ) + { + if( PC_Float_Parse( handle, &f ) ) + bs->horizontalMargin = f; + continue; + } + else + { + Com_Printf("CG_BuildableStatusParse: unknown token %s in %s\n", + token.string, filename ); + bs->loaded = qfalse; + trap_Parse_FreeSource( handle ); + return; + } + } + bs->loaded = qtrue; + trap_Parse_FreeSource( handle ); +} + +#define STATUS_FADE_TIME 200 +#define STATUS_MAX_VIEW_DIST 900.0f +#define STATUS_PEEK_DIST 20 +/* +================== +CG_BuildableStatusDisplay +================== +*/ +static void CG_BuildableStatusDisplay( centity_t *cent, qboolean cuboid, vec3_t trac ) +{ + entityState_t *es = ¢->currentState; + vec3_t origin; + float healthScale; + int health; + float x, y; + vec4_t color; + qboolean powered, marked; + trace_t tr; + float d; + buildStat_t *bs; + int i, j; + int entNum; + vec3_t trOrigin; + vec3_t right; + qboolean visible = qfalse; + vec3_t mins, maxs; + entityState_t *hit; + int anim; + char buf[12]; + int bufl; + + if( BG_Buildable( es->modelindex, NULL )->team == TEAM_ALIENS ) + bs = &cgs.alienBuildStat; + else + bs = &cgs.humanBuildStat; + + if( !bs->loaded ) + return; + + d = Distance( cent->lerpOrigin, cg.refdef.vieworg ); + if( d > STATUS_MAX_VIEW_DIST ) + return; + + Vector4Copy( bs->foreColor, color ); + + // trace for center point + if(BG_Buildable(es->modelindex, NULL)->cuboid) + BG_CuboidBBox( es->angles, mins, maxs ); + else + BG_BuildableBoundingBox( es->modelindex, mins, maxs ); + + // hack for shrunken barricades + anim = es->torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); + if( es->modelindex == BA_A_BARRICADE && + ( anim == BANIM_DESTROYED || !( es->eFlags & EF_B_SPAWNED ) ) ) + maxs[ 2 ] = (int)( maxs[ 2 ] * BARRICADE_SHRINKPROP ); + + VectorCopy( cent->lerpOrigin, origin ); + + // center point + origin[ 2 ] += mins[ 2 ]; + origin[ 2 ] += ( abs( mins[ 2 ] ) + abs( maxs[ 2 ] ) ) / 2; + + entNum = cg.predictedPlayerState.clientNum; + + // if first try fails, step left, step right + for( j = 0; j < 3 && !cuboid; j++ ) + { + VectorCopy( cg.refdef.vieworg, trOrigin ); + switch( j ) + { + case 1: + // step right + AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); + VectorMA( trOrigin, STATUS_PEEK_DIST, right, trOrigin ); + break; + case 2: + // step left + AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); + VectorMA( trOrigin, -STATUS_PEEK_DIST, right, trOrigin ); + break; + default: + break; + } + // look through up to 3 players and/or transparent buildables + for( i = 0; i < 3; i++ ) + { + CG_Trace( &tr, trOrigin, NULL, NULL, origin, entNum, MASK_SHOT ); + if( tr.entityNum == cent->currentState.number ) + { + visible = qtrue; + break; + } + + if( tr.entityNum == ENTITYNUM_WORLD ) + break; + + hit = &cg_entities[ tr.entityNum ].currentState; + + if( tr.entityNum < MAX_CLIENTS || ( hit->eType == ET_BUILDABLE && + ( !( es->eFlags & EF_B_SPAWNED ) || + BG_Buildable( hit->modelindex, NULL )->transparentTest ) ) ) + { + entNum = tr.entityNum; + VectorCopy( tr.endpos, trOrigin ); + } + else + break; + } + } + + if(cuboid) + visible=qtrue; + + // hack to make the kit obscure view + if( cg_drawGun.integer && visible && + cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS && + CG_WorldToScreen( origin, &x, &y ) ) + { + if( x > 450 && y > 290 ) + visible = qfalse; + } + + if( !visible && cent->buildableStatus.visible ) + { + cent->buildableStatus.visible = qfalse; + cent->buildableStatus.lastTime = cg.time; + } + else if( visible && !cent->buildableStatus.visible ) + { + cent->buildableStatus.visible = qtrue; + cent->buildableStatus.lastTime = cg.time; + } + + // Fade up + if( cent->buildableStatus.visible ) + { + if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time ) + color[ 3 ] = (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME; + } + + // Fade down + if( !cent->buildableStatus.visible ) + { + if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time ) + color[ 3 ] = 1.0f - (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME; + else + return; + } + + health = es->generic1; + if(cuboid) + health = BG_CuboidUnpackHealth(es); + healthScale = (float)health / BG_Buildable( es->modelindex, es->angles )->health; + + if( health > 0 && healthScale < 0.01f ) + healthScale = 0.01f; + else if( healthScale < 0.0f ) + healthScale = 0.0f; + else if( healthScale > 1.0f ) + healthScale = 1.0f; + + if(cuboid) + { + x=320; + y=240; + d=Distance(cg.refdef.vieworg,trac); + if(d<64.0f) + d=64.0f; + } + else + if( !CG_WorldToScreen( origin, &x, &y ) ) + return; + + { + float picH = bs->frameHeight; + float picW = bs->frameWidth; + float picX = x; + float picY = y; + float scale; + float subH, subY; + float clipX, clipY, clipW, clipH; + vec4_t frameColor; + + // this is fudged to get the width/height in the cfg to be more realistic + scale = ( picH / d ) * 3; + + powered = es->eFlags & EF_B_POWERED; + marked = es->eFlags & EF_B_MARKED; + + picH *= scale; + picW *= scale; + picX -= ( picW * 0.5f ); + picY -= ( picH * 0.5f ); + + // sub-elements such as icons and number + subH = picH - ( picH * bs->verticalMargin ); + subY = picY + ( picH * 0.5f ) - ( subH * 0.5f ); + + clipW = ( 640.0f * cg_viewsize.integer ) / 100.0f; + clipH = ( 480.0f * cg_viewsize.integer ) / 100.0f; + clipX = 320.0f - ( clipW * 0.5f ); + clipY = 240.0f - ( clipH * 0.5f ); + CG_SetClipRegion( clipX, clipY, clipW, clipH ); + + if( bs->frameShader ) + { + Vector4Copy( bs->backColor, frameColor ); + frameColor[ 3 ] = color[ 3 ]; + trap_R_SetColor( frameColor ); + CG_DrawPic( picX, picY, picW, picH, bs->frameShader ); + trap_R_SetColor( NULL ); + } + + if( health > 0 ) + { + float hX, hY, hW, hH; + vec4_t healthColor; + + hX = picX + ( bs->healthPadding * scale ); + hY = picY + ( bs->healthPadding * scale ); + hH = picH - ( bs->healthPadding * 2.0f * scale ); + hW = picW * healthScale - ( bs->healthPadding * 2.0f * scale ); + + if( healthScale == 1.0f ) + Vector4Copy( bs->healthLowColor, healthColor ); + else if( healthScale >= 0.75f ) + Vector4Copy( bs->healthGuardedColor, healthColor ); + else if( healthScale >= 0.50f ) + Vector4Copy( bs->healthElevatedColor, healthColor ); + else if( healthScale >= 0.25f ) + Vector4Copy( bs->healthHighColor, healthColor ); + else + Vector4Copy( bs->healthSevereColor, healthColor ); + + healthColor[ 3 ] = color[ 3 ]; + trap_R_SetColor( healthColor ); + + CG_DrawPic( hX, hY, hW, hH, cgs.media.whiteShader ); + trap_R_SetColor( NULL ); + } + + if( bs->overlayShader ) + { + float oW = bs->overlayWidth; + float oH = bs->overlayHeight; + float oX = x; + float oY = y; + + oH *= scale; + oW *= scale; + oX -= ( oW * 0.5f ); + oY -= ( oH * 0.5f ); + + trap_R_SetColor( frameColor ); + CG_DrawPic( oX, oY, oW, oH, bs->overlayShader ); + trap_R_SetColor( NULL ); + } + + trap_R_SetColor( color ); + if( !powered ) + { + float pX; + + pX = picX + ( subH * bs->horizontalMargin ); + CG_DrawPic( pX, subY, subH, subH, bs->noPowerShader ); + } + + if( marked ) + { + float mX; + + mX = picX + picW - ( subH * bs->horizontalMargin ) - subH; + CG_DrawPic( mX, subY, subH, subH, bs->markedShader ); + } + + //NOTE: dont use CG_DrawField, too few digits + { + float nX,cW,cH; + int healthMax; + int healthPoints; + int frame; + + healthMax = BG_Buildable( es->modelindex, es->angles )->health; + healthPoints = (int)( healthScale * healthMax ); + if( health > 0 && healthPoints < 1 ) + healthPoints = 1; + + Com_sprintf(buf,sizeof(buf),"%i",healthPoints); + bufl=strlen(buf); + cW=subH*cgDC.aspectScale; + cH=subH; + nX=picX+picW*0.5f-cW*bufl*0.5f; + + for(i=0;iverticalMargin-subH*0.5f,cW,cH,cgs.media.numberShaders[frame]); + } + } + + trap_R_SetColor( NULL ); + CG_ClearClipRegion( ); + } +} + +/* +================== +CG_SortDistance +================== +*/ +static int CG_SortDistance( const void *a, const void *b ) +{ + centity_t *aent, *bent; + float adist, bdist; + + aent = &cg_entities[ *(int *)a ]; + bent = &cg_entities[ *(int *)b ]; + adist = Distance( cg.refdef.vieworg, aent->lerpOrigin ); + bdist = Distance( cg.refdef.vieworg, bent->lerpOrigin ); + if( adist > bdist ) + return -1; + else if( adist < bdist ) + return 1; + else + return 0; +} + +/* +================== +CG_PlayerIsBuilder +================== +*/ +static qboolean CG_PlayerIsBuilder( buildable_t buildable ) +{ + switch( cg.predictedPlayerState.weapon ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + return BG_Buildable( buildable, NULL )->team == + BG_Weapon( cg.predictedPlayerState.weapon )->team; + + default: + return qfalse; + } +} + +/* +================== +CG_BuildableRemovalPending +================== +*/ +static qboolean CG_BuildableRemovalPending( int entityNum ) +{ + int i; + playerState_t *ps = &cg.snap->ps; + + if( !( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT ) ) + return qfalse; + + for( i = 0; i < MAX_MISC; i++ ) + { + if( ps->misc[ i ] == entityNum ) + return qtrue; + } + + return qfalse; +} + +/* +================== +CG_DrawBuildableStatus +================== +*/ +void CG_DrawBuildableStatus( void ) +{ + int i; + centity_t *cent; + entityState_t *es; + int buildableList[ MAX_ENTITIES_IN_SNAPSHOT ]; + int buildables = 0; + vec3_t end; + trace_t tr; + qboolean cuboid; + + if((cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT)>BA_NONE) + return; //hide buildstats if we're placing a buildable + + for( i = 0; i < cg.snap->numEntities; i++ ) + { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + es = ¢->currentState; + + if( es->eType == ET_BUILDABLE && CG_PlayerIsBuilder( es->modelindex ) ) + buildableList[ buildables++ ] = cg.snap->entities[ i ].number; + } + + VectorMA(cg.refdef.vieworg,STATUS_MAX_VIEW_DIST,cg.refdef.viewaxis[0],end); + CG_Trace(&tr,cg.refdef.vieworg,NULL,NULL,end,cg.predictedPlayerState.clientNum,MASK_SHOT); + + qsort( buildableList, buildables, sizeof( int ), CG_SortDistance ); + for( i = 0; i < buildables; i++ ) + { + cuboid = BG_Buildable(cg_entities[buildableList[i]].currentState.modelindex,NULL)->cuboid; + if(cuboid && tr.entityNum!=buildableList[i] ) + continue; + CG_BuildableStatusDisplay( &cg_entities[ buildableList[ i ] ], cuboid, tr.endpos ); + } +} + +#define BUILDABLE_SOUND_PERIOD 500 + +/* +================== +CG_Buildable +================== +*/ +void CG_Buildable( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *es = ¢->currentState; + vec3_t angles; + vec3_t surfNormal, xNormal, mins, maxs; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + float rotAngle; + team_t team = BG_Buildable( es->modelindex, NULL )->team; + float scale; + int health; + + //must be before EF_NODRAW check + if( team == TEAM_ALIENS ) + CG_Creep( cent ); + + // if set to invisible, skip + if( es->eFlags & EF_NODRAW ) + { + if( CG_IsParticleSystemValid( ¢->buildablePS ) ) + CG_DestroyParticleSystem( ¢->buildablePS ); + + return; + } + + // cuboids use a bit different rendering code !@#CUBOID + if( BG_IsCuboid( es->modelindex ) ) + { + qhandle_t texture=0,cracks=0; + vec3_t dims; + const cuboidAttributes_t *cuboidAttr; + const cuboidInfo_t *cuboidInfo; + int i, health, sound; + float healthPct; + + cuboidAttr = BG_CuboidAttributes( es->modelindex ); + cuboidInfo = &cg_cuboids[ es->modelindex - CUBOID_FIRST ]; + VectorCopy( es->angles, dims ); + healthPct = (float)( health = BG_CuboidUnpackHealth(es) ) / (float)BG_Buildable(es->modelindex,dims)->health; + + if( healthPct > 1.0f ) + healthPct = 1.0f; + else if ( healthPct < 0.0f ) + healthPct = 0.0f; + + if( cuboidInfo->useCracks ) + { + if( cuboidInfo->textureCount ) + texture = cuboidInfo->textures[ 0 ]; + if( healthPct < 0.75f ) + cracks = cgs.media.cuboidCracks[ (int)( CUBOID_CRACK_TEXTURES - 1 - floor( CUBOID_CRACK_TEXTURES * healthPct ) ) - 1 ]; + } + else + for( i = 0; i < cuboidInfo->textureCount; i++ ) + if( healthPct >= cuboidInfo->textureThresholds[ i ] ) + { + texture = cuboidInfo->textures[ i ]; + break; + } + + if( !( es->eFlags & EF_B_SPAWNED ) ) + { + sfxHandle_t prebuildSound=cgs.media.humanBuildablePrebuild; + if( team == TEAM_HUMANS ) + { + texture = cgs.media.humanSpawningShader; + prebuildSound = cgs.media.humanBuildablePrebuild; + } + else if(team==TEAM_ALIENS) + { + texture = cgs.media.cuboidAlienPrebuild; + prebuildSound = cgs.media.alienBuildablePrebuild; + } + cracks = 0; + trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound ); + } + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + VectorCopy( cent->lerpOrigin, ent.lightingOrigin ); + + //NOTE: don't use CG_PositionAndOrientateBuildable, it screws up everything + ent.axis[0][0]=-dims[0]/2.0f;ent.axis[0][1]=0.0f; ent.axis[0][2]=0.0f; + ent.axis[1][0]=0.0f; ent.axis[1][1]=-dims[1]/2.0f;ent.axis[1][2]=0.0f; + ent.axis[2][0]=0.0f; ent.axis[2][1]=0.0f; ent.axis[2][2]=dims[2]/2.0f; + ent.nonNormalizedAxes = qtrue; + + ent.customShader = texture; + ent.hModel = cgs.media.cuboidModel; + trap_R_AddRefEntityToScene( &ent ); + if( cracks ) + CG_DrawCuboid( ent.origin, dims, cracks, 1 ); + + if( health < cent->lastBuildableHealth && ( es->eFlags & EF_B_SPAWNED ) ) + { + if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time ) + { + sound = (int)( cuboidInfo->painSoundCount * random() ); + trap_S_StartSound( NULL, es->number, CHAN_BODY, cuboidInfo->painSounds[sound] ); + cent->lastBuildableDamageSoundTime = cg.time; + } + cent->lastBuildableHealth = health; + } + } + else + { + + memset ( &ent, 0, sizeof( ent ) ); + + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + VectorCopy( cent->lerpOrigin, ent.lightingOrigin ); + + VectorCopy( es->origin2, surfNormal ); + + VectorCopy( es->angles, angles ); + BG_BuildableBoundingBox( es->modelindex, mins, maxs ); + + if( es->pos.trType == TR_STATIONARY ) + { + // Positioning a buildable involves potentially up to two traces, and + // seeing as buildables rarely move, we cache the results and recalculate + // only if the buildable moves or changes orientation + if( VectorCompare( cent->buildableCache.cachedOrigin, cent->lerpOrigin ) && + VectorCompare( cent->buildableCache.cachedNormal, surfNormal ) ) + { + VectorCopy( cent->buildableCache.axis[ 0 ], ent.axis[ 0 ] ); + VectorCopy( cent->buildableCache.axis[ 1 ], ent.axis[ 1 ] ); + VectorCopy( cent->buildableCache.axis[ 2 ], ent.axis[ 2 ] ); + VectorCopy( cent->buildableCache.origin, ent.origin ); + } + else + { + CG_PositionAndOrientateBuildable( angles, ent.origin, surfNormal, + es->number, mins, maxs, ent.axis, + ent.origin, qfalse ); + VectorCopy( ent.axis[ 0 ], cent->buildableCache.axis[ 0 ] ); + VectorCopy( ent.axis[ 1 ], cent->buildableCache.axis[ 1 ] ); + VectorCopy( ent.axis[ 2 ], cent->buildableCache.axis[ 2 ] ); + VectorCopy( ent.origin, cent->buildableCache.origin ); + VectorCopy( cent->lerpOrigin, cent->buildableCache.cachedOrigin ); + VectorCopy( surfNormal, cent->buildableCache.cachedNormal ); + } + } + + VectorMA( ent.origin, BG_BuildableConfig( es->modelindex )->zOffset, surfNormal, ent.origin ); + + + VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all + VectorCopy( ent.origin, ent.lightingOrigin ); + + ent.hModel = cg_buildables[ es->modelindex ].models[ 0 ]; + + if( !( es->eFlags & EF_B_SPAWNED ) ) + { + sfxHandle_t prebuildSound = cgs.media.humanBuildablePrebuild; + + if( team == TEAM_HUMANS ) + { + ent.customShader = cgs.media.humanSpawningShader; + prebuildSound = cgs.media.humanBuildablePrebuild; + } + else if( team == TEAM_ALIENS ) + prebuildSound = cgs.media.alienBuildablePrebuild; + + trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound ); + } + + CG_BuildableAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); + + //rescale the model + scale = BG_BuildableConfig( es->modelindex )->modelScale; + + if( scale != 1.0f ) + { + VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] ); + VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] ); + VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] ); + + ent.nonNormalizedAxes = qtrue; + } + else + ent.nonNormalizedAxes = qfalse; + + + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + ent.customShader = cgs.media.redBuildShader; + + //add to refresh list + trap_R_AddRefEntityToScene( &ent ); + + CrossProduct( surfNormal, refNormal, xNormal ); + VectorNormalize( xNormal ); + rotAngle = RAD2DEG( acos( DotProduct( surfNormal, refNormal ) ) ); + + //turret barrel bit + if( cg_buildables[ es->modelindex ].models[ 1 ] ) + { + refEntity_t turretBarrel; + vec3_t flatAxis[ 3 ]; + + memset( &turretBarrel, 0, sizeof( turretBarrel ) ); + + turretBarrel.hModel = cg_buildables[ es->modelindex ].models[ 1 ]; + + CG_PositionEntityOnTag( &turretBarrel, &ent, ent.hModel, "tag_turret" ); + VectorCopy( cent->lerpOrigin, turretBarrel.lightingOrigin ); + AnglesToAxis( es->angles2, flatAxis ); + + RotatePointAroundVector( turretBarrel.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle ); + RotatePointAroundVector( turretBarrel.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle ); + RotatePointAroundVector( turretBarrel.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle ); + + turretBarrel.oldframe = ent.oldframe; + turretBarrel.frame = ent.frame; + turretBarrel.backlerp = ent.backlerp; + + turretBarrel.customShader = ent.customShader; + + if( scale != 1.0f ) + { + VectorScale( turretBarrel.axis[ 0 ], scale, turretBarrel.axis[ 0 ] ); + VectorScale( turretBarrel.axis[ 1 ], scale, turretBarrel.axis[ 1 ] ); + VectorScale( turretBarrel.axis[ 2 ], scale, turretBarrel.axis[ 2 ] ); + + turretBarrel.nonNormalizedAxes = qtrue; + } + else + turretBarrel.nonNormalizedAxes = qfalse; + + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + turretBarrel.customShader = cgs.media.redBuildShader; + + trap_R_AddRefEntityToScene( &turretBarrel ); + } + + //turret barrel bit + if( cg_buildables[ es->modelindex ].models[ 2 ] ) + { + refEntity_t turretTop; + vec3_t flatAxis[ 3 ]; + vec3_t swivelAngles; + + memset( &turretTop, 0, sizeof( turretTop ) ); + + VectorCopy( es->angles2, swivelAngles ); + swivelAngles[ PITCH ] = 0.0f; + + turretTop.hModel = cg_buildables[ es->modelindex ].models[ 2 ]; + + CG_PositionRotatedEntityOnTag( &turretTop, &ent, ent.hModel, "tag_turret" ); + VectorCopy( cent->lerpOrigin, turretTop.lightingOrigin ); + AnglesToAxis( swivelAngles, flatAxis ); + + RotatePointAroundVector( turretTop.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle ); + RotatePointAroundVector( turretTop.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle ); + RotatePointAroundVector( turretTop.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle ); + + turretTop.oldframe = ent.oldframe; + turretTop.frame = ent.frame; + turretTop.backlerp = ent.backlerp; + + turretTop.customShader = ent.customShader; + + if( scale != 1.0f ) + { + VectorScale( turretTop.axis[ 0 ], scale, turretTop.axis[ 0 ] ); + VectorScale( turretTop.axis[ 1 ], scale, turretTop.axis[ 1 ] ); + VectorScale( turretTop.axis[ 2 ], scale, turretTop.axis[ 2 ] ); + + turretTop.nonNormalizedAxes = qtrue; + } + else + turretTop.nonNormalizedAxes = qfalse; + + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + turretTop.customShader = cgs.media.redBuildShader; + + trap_R_AddRefEntityToScene( &turretTop ); + } + + //weapon effects for turrets + if( es->eFlags & EF_FIRING ) + { + weaponInfo_t *weapon = &cg_weapons[ es->weapon ]; + + if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME || + BG_Buildable( es->modelindex, NULL )->turretProjType == WP_TESLAGEN ) + { + if( weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ] || + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ] || + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] ) + { + trap_R_AddLightToScene( cent->lerpOrigin, 300 + ( rand( ) & 31 ), + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ], + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ], + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] ); + } + } + + if( weapon->wim[ WPM_PRIMARY ].firingSound ) + { + trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, + weapon->wim[ WPM_PRIMARY ].firingSound ); + } + else if( weapon->readySound ) + trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, weapon->readySound ); + } + + //smoke etc for damaged buildables + CG_BuildableParticleEffects( cent ); + + + + + health = es->generic1; + + if( health < cent->lastBuildableHealth && + ( es->eFlags & EF_B_SPAWNED ) ) + { + if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time ) + { + if( team == TEAM_HUMANS ) + { + int i = rand( ) % 4; + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.humanBuildableDamage[ i ] ); + } + else if( team == TEAM_ALIENS ) + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienBuildableDamage ); + + cent->lastBuildableDamageSoundTime = cg.time; + } + } + + cent->lastBuildableHealth = health; + + } //if (is a cuboid) +} + +char cuboidInfo[128]; + +/* +====================== +CG_Cuboid_DrawInfo + +Draw the cuboid info string generated by CG_Cuboid_Info. +====================== +*/ +void CG_Cuboid_DrawInfo(void) +{ + float x,y,w,h,s=0.5f; + + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + + w=UI_Text_Width(cuboidInfo,s); + h=UI_Text_Height(cuboidInfo,s); + x=320.0f-w/2.0f+cg_cuboidInfoX.value; + y=240.0f-h/2.0f+cg_cuboidInfoY.value; + + UI_Text_Paint(x,y,s,colorWhite,cuboidInfo,0,0,ITEM_TEXTSTYLE_SHADOWEDMORE); +} + +/* +====================== +CG_Cuboid_Info + +Update the cuboid info string with current cuboid's dimensions, +name, health, build time and build points (pretty much everything +one needs to know about his cuboid). +====================== +*/ +void CG_Cuboid_Info(void) +{ + const buildableAttributes_t *attr; + int axis=cg_cuboidResizeAxis.integer; + + attr=BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,cg.cuboidSelection); + Com_sprintf(cuboidInfo,sizeof(cuboidInfo), + "^7[^3%c^7] | ^%c%.1f^7x^%c%.1f^7x^%c%.1f ^7| ^3%i^7HP | ^3%i^7ms | ^3%i^7BP", + (axis==0?'X':(axis==1?'Y':'Z')), + (axis==0?'3':'5'), + cg.cuboidSelection[0], + (axis==1?'3':'5'), + cg.cuboidSelection[1], + (axis==2?'3':'5'), + cg.cuboidSelection[2], + attr->health, + attr->buildTime, + attr->buildPoints); +} + +/* +====================== +CG_Cuboid_Send + +Send the cuboid selection via commands. +====================== +*/ +void CG_Cuboid_Send(void) +{ + static qboolean init=qfalse; + static int lastupdate; + static vec3_t lastcuboid; + + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + + if(!init) + { + lastupdate=cg.time; + VectorCopy(cg.cuboidSelection,lastcuboid); + init=qtrue; + } + + if(lastupdate+100>cg.time) + return; + + if(!VectorCompareEpsilon(lastcuboid,cg.cuboidSelection,1e-3)) + { + cg.latestCBNumber++; + cg.latestCBNumber%=100; + trap_SendClientCommand(va("cb %i %f %f %f\n",cg.latestCBNumber,cg.cuboidSelection[0],cg.cuboidSelection[1],cg.cuboidSelection[2])); + cg.forbidCuboids=qtrue; //wait for response + } + lastupdate=cg.time; + VectorCopy(cg.cuboidSelection,lastcuboid); +} + +/* +====================== +CG_Cuboid_Response + +Server responded to our cb with either cb2 or cb3. +====================== +*/ +void CG_Cuboid_Response(void) +{ + // cb2 : server sets client-side cuboid + // cb3 : server agrees on player's cuboid + // cb3 : server doesnt agree on player's cuboid and corrects it + static qboolean init=qfalse; + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + if(!Q_stricmp(CG_Argv(0),"cb2")&&trap_Argc()==4) + { + cg.cuboidSelection[0]=atof(CG_Argv(1)); + cg.cuboidSelection[1]=atof(CG_Argv(2)); + cg.cuboidSelection[2]=atof(CG_Argv(3)); + return; + } + else if(!Q_stricmp(CG_Argv(0),"cb3")) + { + if(trap_Argc()==2) + { + if(atoi(CG_Argv(1))==cg.latestCBNumber) + cg.forbidCuboids=qfalse; + return; + } + else if(trap_Argc()==5) + { + cg.cuboidSelection[0]=atof(CG_Argv(2)); + cg.cuboidSelection[1]=atof(CG_Argv(3)); + cg.cuboidSelection[2]=atof(CG_Argv(4)); + if(atoi(CG_Argv(1))==cg.latestCBNumber) + { + cg.forbidCuboids=qfalse; + if(cg.lastCuboidError+250cuboid) + return; + VectorCopy(cg.cuboidSelection,dims); + switch(cg_cuboidResizeAxis.integer) + { + case 0: + dims[0]+=rate; + break; + case 1: + dims[1]+=rate; + break; + default: + dims[2]+=rate; + break; + } + if(dims[0]<1||dims[1]<1||dims[2]<1|| + dims[0]*dims[1]*dims[2]cuboid) + return; + axis=cg_cuboidResizeAxis.integer; + switch(axis) + { + case 0: + SWAPFLOATS(cg.cuboidSelection[1],cg.cuboidSelection[2]); + break; + case 1: + SWAPFLOATS(cg.cuboidSelection[2],cg.cuboidSelection[0]); + break; + case 2: + SWAPFLOATS(cg.cuboidSelection[0],cg.cuboidSelection[1]); + break; + } + trap_S_StartLocalSound(cgs.media.cuboidRotateSound,CHAN_LOCAL_SOUND); + CG_Cuboid_Send(); +} + +/* +====================== +CG_CuboidAxis_f + +Control the axis along which cuboids are resized and around which they are rotated. +Syntax: + cuboidAxis next - next axis (order is X->Y->Z) + cuboidAxis 0|1|2 - switch to a specific axis (respectively: X, Y and Z) +====================== +*/ +void CG_CuboidAxis_f(void) +{ + int axis; + + axis=cg_cuboidResizeAxis.integer; + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + if(!CG_Argv(1)) + { + Com_Printf("cuboidAxis next|0|1|2 : set axis on which you want to resize your cuboid selection (0 - X, 1 - Y, 2 - Z)\n"); + return; + } + if(!Q_stricmp(CG_Argv(1),"next")) + axis++; + else + axis=atoi(CG_Argv(1)); + trap_Cvar_Set("cg_cuboidResizeAxis",va("%i",(axis+3)%3)); + trap_S_StartLocalSound(cgs.media.cuboidAxisChangeSound,CHAN_LOCAL_SOUND); +} + +/* +====================== +CG_CuboidAttack_f + +Replaces +attack/-attack. +If building a cuboid and the selection is somehow invalid, play an error sound. +Otherwise send the normal +attack / -attack; +====================== +*/ +void CG_CuboidAttack_f(void) +{ + if(BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid&&cg.forbidCuboids) + { + trap_S_StartLocalSound(cgs.media.cuboidErrorSound,CHAN_LOCAL_SOUND); + return; + } + trap_SendClientCommand(va("%s",CG_Argv(0))); +} + diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c new file mode 100644 index 0000000..0f1a3cf --- /dev/null +++ b/src/cgame/cg_consolecmds.c @@ -0,0 +1,298 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_consolecmds.c -- text commands typed in at the local console, or +// executed by a key binding + + +#include "cg_local.h" + + + +/* +================= +CG_SizeUp_f + +Keybinding command +================= +*/ +static void CG_SizeUp_f( void ) +{ + trap_Cvar_Set( "cg_viewsize", va( "%i", MIN( cg_viewsize.integer + 10, 100 ) ) ); +} + + +/* +================= +CG_SizeDown_f + +Keybinding command +================= +*/ +static void CG_SizeDown_f( void ) +{ + trap_Cvar_Set( "cg_viewsize", va( "%i", MAX( cg_viewsize.integer - 10, 30 ) ) ); +} + + +/* +============= +CG_Viewpos_f + +Debugging command to print the current position +============= +*/ +static void CG_Viewpos_f( void ) +{ + CG_Printf( "(%i %i %i) : %i\n", (int)cg.refdef.vieworg[ 0 ], + (int)cg.refdef.vieworg[ 1 ], (int)cg.refdef.vieworg[ 2 ], + (int)cg.refdefViewAngles[ YAW ] ); +} + +qboolean CG_RequestScores( void ) +{ + if( cg.scoresRequestTime + 2000 < cg.time ) + { + // the scores are more than two seconds out of data, + // so request new ones + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score\n" ); + + return qtrue; + } + else + return qfalse; +} + +extern menuDef_t *menuScoreboard; + +static void CG_scrollScoresDown_f( void ) +{ + if( menuScoreboard && cg.scoreBoardShowing ) + { + Menu_ScrollFeeder( menuScoreboard, FEEDER_ALIENTEAM_LIST, qtrue ); + Menu_ScrollFeeder( menuScoreboard, FEEDER_HUMANTEAM_LIST, qtrue ); + } +} + + +static void CG_scrollScoresUp_f( void ) +{ + if( menuScoreboard && cg.scoreBoardShowing ) + { + Menu_ScrollFeeder( menuScoreboard, FEEDER_ALIENTEAM_LIST, qfalse ); + Menu_ScrollFeeder( menuScoreboard, FEEDER_HUMANTEAM_LIST, qfalse ); + } +} + +static void CG_ScoresDown_f( void ) +{ + if( !cg.showScores ) + { + Menu_SetFeederSelection( menuScoreboard, FEEDER_ALIENTEAM_LIST, 0, NULL ); + Menu_SetFeederSelection( menuScoreboard, FEEDER_HUMANTEAM_LIST, 0, NULL ); + } + + if( CG_RequestScores( ) ) + { + // leave the current scores up if they were already + // displayed, but if this is the first hit, clear them out + if( !cg.showScores ) + { + if( cg_debugRandom.integer ) + CG_Printf( "CG_ScoresDown_f: scores out of date\n" ); + + cg.showScores = qtrue; + cg.numScores = 0; + } + } + else + { + // show the cached contents even if they just pressed if it + // is within two seconds + cg.showScores = qtrue; + } +} + +static void CG_ScoresUp_f( void ) +{ + if( cg.showScores ) + { + cg.showScores = qfalse; + cg.scoreFadeTime = cg.time; + } +} + +void CG_ClientList_f( void ) +{ + clientInfo_t *ci; + int i; + int count = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + ci = &cgs.clientinfo[ i ]; + if( !ci->infoValid ) + continue; + + switch( ci->team ) + { + case TEAM_ALIENS: + Com_Printf( "%2d " S_COLOR_RED "A " S_COLOR_WHITE "%s\n", i, + ci->name ); + break; + + case TEAM_HUMANS: + Com_Printf( "%2d " S_COLOR_CYAN "H " S_COLOR_WHITE "%s\n", i, + ci->name ); + break; + + default: + case TEAM_NONE: + case NUM_TEAMS: + Com_Printf( "%2d S %s\n", i, ci->name ); + break; + } + + count++; + } + + Com_Printf( "Listed %2d clients\n", count ); +} + +static void CG_UIMenu_f( void ) +{ + trap_SendConsoleCommand( va( "menu %s\n", CG_Argv( 1 ) ) ); +} + +static consoleCommand_t commands[ ] = +{ + { "+attack", CG_CuboidAttack_f }, + { "+scores", CG_ScoresDown_f }, + { "-attack", CG_CuboidAttack_f }, + { "-scores", CG_ScoresUp_f }, + { "cgame_memory", BG_MemoryInfo }, + { "clientlist", CG_ClientList_f }, + { "cuboidAxis", CG_CuboidAxis_f }, + { "cuboidRotate", CG_CuboidRotate_f }, + { "destroyTestPS", CG_DestroyTestPS_f }, + { "destroyTestTS", CG_DestroyTestTS_f }, + { "nextframe", CG_TestModelNextFrame_f }, + { "nextskin", CG_TestModelNextSkin_f }, + { "prevframe", CG_TestModelPrevFrame_f }, + { "prevskin", CG_TestModelPrevSkin_f }, + { "scoresDown", CG_scrollScoresDown_f }, + { "scoresUp", CG_scrollScoresUp_f }, + { "sizedown", CG_SizeDown_f }, + { "sizeup", CG_SizeUp_f }, + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, + { "testPS", CG_TestPS_f }, + { "testTS", CG_TestTS_f }, + { "ui_menu", CG_UIMenu_f }, + { "viewpos", CG_Viewpos_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapon", CG_Weapon_f }, + { "weapprev", CG_PrevWeapon_f } +}; + +/* +================= +CG_ConsoleCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_ConsoleCommand( void ) +{ + consoleCommand_t *cmd; + + cmd = bsearch( CG_Argv( 0 ), commands, + sizeof( commands ) / sizeof( commands[ 0 ]), sizeof( commands[ 0 ] ), + cmdcmp ); + + if( !cmd ) + return qfalse; + + cmd->function( ); + return qtrue; +} + + +/* +================= +CG_InitConsoleCommands + +Let the client system know about all of our commands +so it can perform tab completion +================= +*/ +void CG_InitConsoleCommands( void ) +{ + int i; + + for( i = 0 ; i < sizeof( commands ) / sizeof( commands[ 0 ] ) ; i++ ) + trap_AddCommand( commands[ i ].cmd ); + + // + // the game server will interpret these commands, which will be automatically + // forwarded to the server after they are not recognized locally + // + trap_AddCommand( "kill" ); + trap_AddCommand( "ui_messagemode" ); + trap_AddCommand( "ui_messagemode2" ); + trap_AddCommand( "ui_messagemode3" ); + trap_AddCommand( "ui_messagemode4" ); + trap_AddCommand( "say" ); + trap_AddCommand( "say_team" ); + trap_AddCommand( "vsay" ); + trap_AddCommand( "vsay_team" ); + trap_AddCommand( "vsay_local" ); + trap_AddCommand( "m" ); + trap_AddCommand( "mt" ); + trap_AddCommand( "give" ); + trap_AddCommand( "god" ); + trap_AddCommand( "notarget" ); + trap_AddCommand( "noclip" ); + trap_AddCommand( "team" ); + trap_AddCommand( "follow" ); + trap_AddCommand( "levelshot" ); + trap_AddCommand( "setviewpos" ); + trap_AddCommand( "callvote" ); + trap_AddCommand( "vote" ); + trap_AddCommand( "callteamvote" ); + trap_AddCommand( "teamvote" ); + trap_AddCommand( "class" ); + trap_AddCommand( "build" ); + trap_AddCommand( "buy" ); + trap_AddCommand( "sell" ); + trap_AddCommand( "reload" ); + trap_AddCommand( "itemact" ); + trap_AddCommand( "itemdeact" ); + trap_AddCommand( "itemtoggle" ); + trap_AddCommand( "destroy" ); + trap_AddCommand( "deconstruct" ); + trap_AddCommand( "ignore" ); + trap_AddCommand( "unignore" ); +} diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c new file mode 100644 index 0000000..8fde56f --- /dev/null +++ b/src/cgame/cg_draw.c @@ -0,0 +1,3813 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + + +#include "cg_local.h" +#include "../ui/ui_shared.h" + +menuDef_t *menuScoreboard = NULL; + +static void CG_AlignText( rectDef_t *rect, const char *text, float scale, + float w, float h, + int align, int valign, + float *x, float *y ) +{ + float tx, ty; + + if( scale > 0.0f ) + { + w = UI_Text_Width( text, scale ); + h = UI_Text_Height( text, scale ); + } + + switch( align ) + { + default: + case ALIGN_LEFT: + tx = 0.0f; + break; + + case ALIGN_RIGHT: + tx = rect->w - w; + break; + + case ALIGN_CENTER: + tx = ( rect->w - w ) / 2.0f; + break; + + case ALIGN_NONE: + tx = 0; + break; + } + + switch( valign ) + { + default: + case VALIGN_BOTTOM: + ty = rect->h; + break; + + case VALIGN_TOP: + ty = h; + break; + + case VALIGN_CENTER: + ty = h + ( ( rect->h - h ) / 2.0f ); + break; + + case VALIGN_NONE: + ty = 0; + break; + } + + if( x ) + *x = rect->x + tx; + + if( y ) + *y = rect->y + ty; +} + +/* +============== +CG_DrawFieldPadded + +Draws large numbers for status bar +============== +*/ +static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int value ) +{ + char num[ 16 ], *ptr; + int l, orgL; + int frame; + int charWidth, charHeight; + + if( !( charWidth = cw ) ) + charWidth = CHAR_WIDTH; + + if( !( charHeight = ch ) ) + charHeight = CHAR_HEIGHT; + + if( width < 1 ) + return; + + // draw number string + if( width > 4 ) + width = 4; + + switch( width ) + { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf( num, sizeof( num ), "%d", value ); + l = strlen( num ); + + if( l > width ) + l = width; + + orgL = l; + + x += ( 2.0f * cgDC.aspectScale ); + + ptr = num; + while( *ptr && l ) + { + if( width > orgL ) + { + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ 0 ] ); + width--; + x += charWidth; + continue; + } + + if( *ptr == '-' ) + frame = STAT_MINUS; + else + frame = *ptr - '0'; + + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] ); + x += charWidth; + ptr++; + l--; + } +} + +/* +============== +CG_DrawField + +Draws large numbers for status bar +============== +*/ +void CG_DrawField( float x, float y, int width, float cw, float ch, int value ) +{ + char num[ 16 ], *ptr; + int l; + int frame; + float charWidth, charHeight; + + if( !( charWidth = cw ) ) + charWidth = CHAR_WIDTH; + + if( !( charHeight = ch ) ) + charHeight = CHAR_HEIGHT; + + if( width < 1 ) + return; + + // draw number string + if( width > 4 ) + width = 4; + + switch( width ) + { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf( num, sizeof( num ), "%d", value ); + l = strlen( num ); + + if( l > width ) + l = width; + + x += ( 2.0f * cgDC.aspectScale ) + charWidth * ( width - l ); + + ptr = num; + while( *ptr && l ) + { + if( *ptr == '-' ) + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] ); + x += charWidth; + ptr++; + l--; + } +} + +static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale, + int align, int textalign, int textStyle, + float borderSize, float progress ) +{ + float rimWidth; + float doneWidth, leftWidth; + float tx, ty; + char textBuffer[ 8 ]; + + if( borderSize >= 0.0f ) + rimWidth = borderSize; + else + { + rimWidth = rect->h / 20.0f; + if( rimWidth < 0.6f ) + rimWidth = 0.6f; + } + + if( progress < 0.0f ) + progress = 0.0f; + else if( progress > 1.0f ) + progress = 1.0f; + + doneWidth = ( rect->w - 2 * rimWidth ) * progress; + leftWidth = ( rect->w - 2 * rimWidth ) - doneWidth; + + trap_R_SetColor( color ); + + //draw rim and bar + if( align == ALIGN_RIGHT ) + { + CG_DrawPic( rect->x, rect->y, rimWidth, rect->h, cgs.media.whiteShader ); + CG_DrawPic( rect->x + rimWidth, rect->y, + leftWidth, rimWidth, cgs.media.whiteShader ); + CG_DrawPic( rect->x + rimWidth, rect->y + rect->h - rimWidth, + leftWidth, rimWidth, cgs.media.whiteShader ); + CG_DrawPic( rect->x + rimWidth + leftWidth, rect->y, + rimWidth + doneWidth, rect->h, cgs.media.whiteShader ); + } + else + { + CG_DrawPic( rect->x, rect->y, rimWidth + doneWidth, rect->h, cgs.media.whiteShader ); + CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y, + leftWidth, rimWidth, cgs.media.whiteShader ); + CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y + rect->h - rimWidth, + leftWidth, rimWidth, cgs.media.whiteShader ); + CG_DrawPic( rect->x + rect->w - rimWidth, rect->y, rimWidth, rect->h, cgs.media.whiteShader ); + } + + trap_R_SetColor( NULL ); + + //draw text + if( scale > 0.0 ) + { + Com_sprintf( textBuffer, sizeof( textBuffer ), "%d%%", (int)( progress * 100 ) ); + CG_AlignText( rect, textBuffer, scale, 0.0f, 0.0f, textalign, VALIGN_CENTER, &tx, &ty ); + + UI_Text_Paint( tx, ty, scale, color, textBuffer, 0, 0, textStyle ); + } +} + +//=============== TA: was cg_newdraw.c + +#define NO_CREDITS_TIME 2000 + +static void CG_DrawPlayerCreditsValue( rectDef_t *rect, vec4_t color, qboolean padding ) +{ + int value; + playerState_t *ps; + centity_t *cent; + + cent = &cg_entities[ cg.snap->ps.clientNum ]; + ps = &cg.snap->ps; + + //if the build timer pie is showing don't show this + if( ( cent->currentState.weapon == WP_ABUILD || + cent->currentState.weapon == WP_ABUILD2 ) && ps->stats[ STAT_MISC ] ) + return; + + value = ps->persistant[ PERS_CREDIT ]; + if( value > -1 ) + { + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + if( !BG_AlienCanEvolve( cg.predictedPlayerState.stats[ STAT_CLASS ], + value, cgs.alienStage ) && + cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME && + ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) & 1 ) + { + color[ 3 ] = 0.0f; + } + + value /= ALIEN_CREDITS_PER_KILL; + } + + trap_R_SetColor( color ); + + if( padding ) + CG_DrawFieldPadded( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); + else + CG_DrawField( rect->x, rect->y, 1, rect->w, rect->h, value ); + + trap_R_SetColor( NULL ); + } +} + +static void CG_DrawPlayerCreditsFraction( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ + float fraction; + float height; + + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_ALIENS ) + return; + + fraction = ((float)(cg.predictedPlayerState.persistant[ PERS_CREDIT ] % + ALIEN_CREDITS_PER_KILL)) / ALIEN_CREDITS_PER_KILL; + + CG_AdjustFrom640( &rect->x, &rect->y, &rect->w, &rect->h ); + height = rect->h * fraction; + + trap_R_SetColor( color ); + trap_R_DrawStretchPic( rect->x, rect->y - height + rect->h, rect->w, + height, 0.0f, 1.0f - fraction, 1.0f, 1.0f, shader ); + trap_R_SetColor( NULL ); +} + + +/* +============== +CG_DrawPlayerStamina +============== +*/ +static void CG_DrawPlayerStamina( int ownerDraw, rectDef_t *rect, + vec4_t backColor, vec4_t foreColor, + qhandle_t shader ) +{ + playerState_t *ps = &cg.snap->ps; + float stamina = ps->stats[ STAT_STAMINA ]; + float maxStaminaBy3 = (float)STAMINA_MAX / 3.0f; + float progress; + vec4_t color; + + switch( ownerDraw ) + { + case CG_PLAYER_STAMINA_1: + progress = ( stamina - 2 * (int)maxStaminaBy3 ) / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_2: + progress = ( stamina - (int)maxStaminaBy3 ) / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_3: + progress = stamina / maxStaminaBy3; + break; + case CG_PLAYER_STAMINA_4: + progress = ( stamina + STAMINA_MAX ) / STAMINA_MAX; + break; + default: + return; + } + + if( progress > 1.0f ) + progress = 1.0f; + else if( progress < 0.0f ) + progress = 0.0f; + + Vector4Lerp( progress, backColor, foreColor, color ); + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerStaminaBolt +============== +*/ +static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) +{ + float stamina = cg.snap->ps.stats[ STAT_STAMINA ]; + vec4_t color; + + if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_SPEEDBOOST ) + { + if( stamina >= 0 ) + Vector4Lerp( ( sin( cg.time / 150.0f ) + 1 ) / 2, + backColor, foreColor, color ); + else + Vector4Lerp( ( sin( cg.time / 2000.0f ) + 1 ) / 2, + backColor, foreColor, color ); + } + else + { + if( stamina < 0 ) + Vector4Copy( backColor, color ); + else + Vector4Copy( foreColor, color ); + } + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerClipsRing +============== +*/ +static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) +{ + playerState_t *ps = &cg.snap->ps; + centity_t *cent; + float buildTime = ps->stats[ STAT_MISC ]; + float progress; + float maxDelay; + weapon_t weapon; + vec4_t color; + + cent = &cg_entities[ cg.snap->ps.clientNum ]; + weapon = BG_GetPlayerWeapon( ps ); + + switch( weapon ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + if( buildTime > MAXIMUM_BUILD_TIME ) + buildTime = MAXIMUM_BUILD_TIME; + progress = ( MAXIMUM_BUILD_TIME - buildTime ) / MAXIMUM_BUILD_TIME; + + Vector4Lerp( progress, backColor, foreColor, color ); + break; + + default: + if( ps->weaponstate == WEAPON_RELOADING ) + { + maxDelay = (float)BG_Weapon( cent->currentState.weapon )->reloadTime; + progress = ( maxDelay - (float)ps->weaponTime ) / maxDelay; + + Vector4Lerp( progress, backColor, foreColor, color ); + } + else + Com_Memcpy( color, foreColor, sizeof( color ) ); + break; + } + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerBuildTimerRing +============== +*/ +static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) +{ + playerState_t *ps = &cg.snap->ps; + centity_t *cent; + float buildTime = ps->stats[ STAT_MISC ]; + float progress; + vec4_t color; + + cent = &cg_entities[ cg.snap->ps.clientNum ]; + + if( buildTime > MAXIMUM_BUILD_TIME ) + buildTime = MAXIMUM_BUILD_TIME; + + progress = ( MAXIMUM_BUILD_TIME - buildTime ) / MAXIMUM_BUILD_TIME; + + Vector4Lerp( progress, backColor, foreColor, color ); + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerBoosted +============== +*/ +static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) +{ + if( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + trap_R_SetColor( foreColor ); + else + trap_R_SetColor( backColor ); + + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerBoosterBolt +============== +*/ +static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t backColor, + vec4_t foreColor, qhandle_t shader ) +{ + vec4_t color; + + // Flash bolts when the boost is almost out + if( ( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTED ) && + ( cg.snap->ps.stats[ STAT_STATE ] & SS_BOOSTEDWARNING ) ) + Vector4Lerp( ( sin( cg.time / 100.0f ) + 1 ) / 2, + backColor, foreColor, color ); + else + Vector4Copy( foreColor, color ); + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerPoisonBarbs +============== +*/ +static void CG_DrawPlayerPoisonBarbs( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ + qboolean vertical; + float x = rect->x, y = rect->y; + float width = rect->w, height = rect->h; + float diff; + int iconsize, numBarbs, maxBarbs; + + maxBarbs = BG_Weapon( cg.snap->ps.weapon )->maxAmmo; + numBarbs = cg.snap->ps.ammo; + if( maxBarbs <= 0 || numBarbs <= 0 ) + return; + + // adjust these first to ensure the aspect ratio of the barb image is + // preserved + CG_AdjustFrom640( &x, &y, &width, &height ); + + if( height > width ) + { + vertical = qtrue; + iconsize = width; + if( maxBarbs != 1 ) // avoid division by zero + diff = ( height - iconsize ) / (float)( maxBarbs - 1 ); + else + diff = 0; // doesn't matter, won't be used + } + else + { + vertical = qfalse; + iconsize = height; + if( maxBarbs != 1 ) + diff = ( width - iconsize ) / (float)( maxBarbs - 1 ); + else + diff = 0; + } + + trap_R_SetColor( color ); + + for( ; numBarbs > 0; numBarbs-- ) + { + trap_R_DrawStretchPic( x, y, iconsize, iconsize, 0, 0, 1, 1, shader ); + if( vertical ) + y += diff; + else + x += diff; + } + + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerWallclimbing +============== +*/ +static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t backColor, vec4_t foreColor, qhandle_t shader ) +{ + if( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) + trap_R_SetColor( foreColor ); + else + trap_R_SetColor( backColor ); + + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +static void CG_DrawPlayerAmmoValue( rectDef_t *rect, vec4_t color ) +{ + int value; + int valueMarked = -1; + qboolean bp = qfalse; + + switch( BG_PrimaryWeapon( cg.snap->ps.stats ) ) + { + case WP_NONE: + case WP_BLASTER: + return; + + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + value = cg.snap->ps.persistant[ PERS_BP ]; + valueMarked = cg.snap->ps.persistant[ PERS_MARKEDBP ]; + bp = qtrue; + break; + + default: + value = cg.snap->ps.ammo; + break; + } + + if( value > 999 ) + value = 999; + if( valueMarked > 999 ) + valueMarked = 999; + + if( value > -1 ) + { + float tx, ty; + char *text; + float scale; + int len; + + trap_R_SetColor( color ); + if( !bp ) + { + CG_DrawField( rect->x - 5, rect->y, 4, rect->w / 4, rect->h, value ); + trap_R_SetColor( NULL ); + return; + } + + if( valueMarked > 0 ) + text = va( "%d+(%d)", value, valueMarked ); + else + text = va( "%d", value ); + + len = strlen( text ); + + if( len <= 4 ) + scale = 0.50; + else if( len <= 6 ) + scale = 0.43; + else if( len == 7 ) + scale = 0.36; + else if( len == 8 ) + scale = 0.33; + else + scale = 0.31; + + CG_AlignText( rect, text, scale, 0.0f, 0.0f, ALIGN_RIGHT, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint( tx + 1, ty, scale, color, text, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + trap_R_SetColor( NULL ); + } +} + + +/* +============== +CG_DrawAlienSense +============== +*/ +static void CG_DrawAlienSense( rectDef_t *rect ) +{ + if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_CLASS ], SCA_ALIENSENSE ) ) + CG_AlienSense( rect ); +} + + +/* +============== +CG_DrawHumanScanner +============== +*/ +static void CG_DrawHumanScanner( rectDef_t *rect, qhandle_t shader, vec4_t color ) +{ + if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, cg.snap->ps.stats ) ) + CG_Scanner( rect, shader, color ); +} + + +/* +============== +CG_DrawUsableBuildable +============== +*/ +static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t color ) +{ + vec3_t view, point; + trace_t trace; + entityState_t *es; + + AngleVectors( cg.refdefViewAngles, view, NULL, NULL ); + VectorMA( cg.refdef.vieworg, 64, view, point ); + CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL, + point, cg.predictedPlayerState.clientNum, MASK_SHOT ); + + es = &cg_entities[ trace.entityNum ].currentState; + + if( es->eType == ET_BUILDABLE && BG_Buildable( es->modelindex, NULL )->usable && + cg.predictedPlayerState.stats[ STAT_TEAM ] == BG_Buildable( es->modelindex, NULL )->team ) + { + //hack to prevent showing the usable buildable when you aren't carrying an energy weapon + if( ( es->modelindex == BA_H_REACTOR || es->modelindex == BA_H_REPEATER ) && + ( !BG_Weapon( cg.snap->ps.weapon )->usesEnergy || + BG_Weapon( cg.snap->ps.weapon )->infiniteAmmo ) ) + { + cg.nearUsableBuildable = BA_NONE; + return; + } + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + cg.nearUsableBuildable = es->modelindex; + } + else + cg.nearUsableBuildable = BA_NONE; +} + + +#define BUILD_DELAY_TIME 2000 + +static void CG_DrawPlayerBuildTimer( rectDef_t *rect, vec4_t color ) +{ + int index; + playerState_t *ps; + + ps = &cg.snap->ps; + + if( ps->stats[ STAT_MISC ] <= 0 ) + return; + + switch( BG_PrimaryWeapon( ps->stats ) ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + break; + + default: + return; + } + + index = 8 * ( ps->stats[ STAT_MISC ] - 1 ) / MAXIMUM_BUILD_TIME; + if( index > 7 ) + index = 7; + else if( index < 0 ) + index = 0; + + if( cg.time - cg.lastBuildAttempt <= BUILD_DELAY_TIME && + ( ( cg.time - cg.lastBuildAttempt ) / 300 ) % 2 ) + { + color[ 0 ] = 1.0f; + color[ 1 ] = color[ 2 ] = 0.0f; + color[ 3 ] = 1.0f; + } + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, + cgs.media.buildWeaponTimerPie[ index ] ); + trap_R_SetColor( NULL ); +} + +static void CG_DrawPlayerClipsValue( rectDef_t *rect, vec4_t color ) +{ + int value; + playerState_t *ps = &cg.snap->ps; + + switch( BG_PrimaryWeapon( ps->stats ) ) + { + case WP_NONE: + case WP_BLASTER: + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + return; + + default: + value = ps->clips; + + if( value > -1 ) + { + trap_R_SetColor( color ); + CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value ); + trap_R_SetColor( NULL ); + } + break; + } +} + +static void CG_DrawPlayerHealthValue( rectDef_t *rect, vec4_t color ) +{ + trap_R_SetColor( color ); + CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, + cg.snap->ps.stats[ STAT_HEALTH ] ); + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawPlayerHealthCross +============== +*/ +static void CG_DrawPlayerHealthCross( rectDef_t *rect, vec4_t ref_color ) +{ + qhandle_t shader; + vec4_t color; + float ref_alpha; + + // Pick the current icon + shader = cgs.media.healthCross; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_3X ) + shader = cgs.media.healthCross3X; + else if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_2X ) + { + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + shader = cgs.media.healthCross2X; + else + shader = cgs.media.healthCrossMedkit; + } + else if( cg.snap->ps.stats[ STAT_STATE ] & SS_POISONED ) + shader = cgs.media.healthCrossPoisoned; + + // Pick the alpha value + Vector4Copy( ref_color, color ); + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + cg.snap->ps.stats[ STAT_HEALTH ] < 10 ) + { + color[ 0 ] = 1.0f; + color[ 1 ] = color[ 2 ] = 0.0f; + } + ref_alpha = ref_color[ 3 ]; + if( cg.snap->ps.stats[ STAT_STATE ] & SS_HEALING_ACTIVE ) + ref_alpha = 1.0f; + + // Don't fade from nothing + if( !cg.lastHealthCross ) + cg.lastHealthCross = shader; + + // Fade the icon during transition + if( cg.lastHealthCross != shader ) + { + cg.healthCrossFade += cg.frametime / 500.0f; + if( cg.healthCrossFade > 1.0f ) + { + cg.healthCrossFade = 0.0f; + cg.lastHealthCross = shader; + } + else + { + // Fading between two icons + color[ 3 ] = ref_alpha * cg.healthCrossFade; + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + color[ 3 ] = ref_alpha * ( 1.0f - cg.healthCrossFade ); + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg.lastHealthCross ); + trap_R_SetColor( NULL ); + return; + } + } + + // Not fading, draw a single icon + color[ 3 ] = ref_alpha; + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +static float CG_ChargeProgress( void ) +{ + float progress; + int min = 0, max = 0; + + if( cg.snap->ps.weapon == WP_ALEVEL3 ) + { + min = LEVEL3_POUNCE_TIME_MIN; + max = LEVEL3_POUNCE_TIME; + } + else if( cg.snap->ps.weapon == WP_ALEVEL3_UPG ) + { + min = LEVEL3_POUNCE_TIME_MIN; + max = LEVEL3_POUNCE_TIME_UPG; + } + else if( cg.snap->ps.weapon == WP_ALEVEL4 ) + { + if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_CHARGING ) + { + min = 0; + max = LEVEL4_TRAMPLE_DURATION; + } + else + { + min = LEVEL4_TRAMPLE_CHARGE_MIN; + max = LEVEL4_TRAMPLE_CHARGE_MAX; + } + } + else if( cg.snap->ps.weapon == WP_LUCIFER_CANNON ) + { + min = LCANNON_CHARGE_TIME_MIN; + max = LCANNON_CHARGE_TIME_MAX; + } + + if( max - min <= 0.0f ) + return 0.0f; + + progress = ( (float)cg.predictedPlayerState.stats[ STAT_MISC ] - min ) / + ( max - min ); + + if( progress > 1.0f ) + return 1.0f; + + if( progress < 0.0f ) + return 0.0f; + + return progress; +} + +#define CHARGE_BAR_FADE_RATE 0.002f + +static void CG_DrawPlayerChargeBarBG( rectDef_t *rect, vec4_t ref_color, + qhandle_t shader ) +{ + vec4_t color; + + if( !cg_drawChargeBar.integer || cg.chargeMeterAlpha <= 0.0f ) + return; + + color[ 0 ] = ref_color[ 0 ]; + color[ 1 ] = ref_color[ 1 ]; + color[ 2 ] = ref_color[ 2 ]; + color[ 3 ] = ref_color[ 3 ] * cg.chargeMeterAlpha; + + // Draw meter background + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +// FIXME: This should come from the element info +#define CHARGE_BAR_CAP_SIZE 3 + +static void CG_DrawPlayerChargeBar( rectDef_t *rect, vec4_t ref_color, + qhandle_t shader ) +{ + vec4_t color; + float x, y, width, height, cap_size, progress; + + if( !cg_drawChargeBar.integer ) + return; + + // Get progress proportion and pump fade + progress = CG_ChargeProgress(); + if( progress <= 0.0f ) + { + cg.chargeMeterAlpha -= CHARGE_BAR_FADE_RATE * cg.frametime; + if( cg.chargeMeterAlpha <= 0.0f ) + { + cg.chargeMeterAlpha = 0.0f; + return; + } + } + else + { + cg.chargeMeterValue = progress; + cg.chargeMeterAlpha += CHARGE_BAR_FADE_RATE * cg.frametime; + if( cg.chargeMeterAlpha > 1.0f ) + cg.chargeMeterAlpha = 1.0f; + } + + color[ 0 ] = ref_color[ 0 ]; + color[ 1 ] = ref_color[ 1 ]; + color[ 2 ] = ref_color[ 2 ]; + color[ 3 ] = ref_color[ 3 ] * cg.chargeMeterAlpha; + + // Flash red for Lucifer Cannon warning + if( cg.snap->ps.weapon == WP_LUCIFER_CANNON && + cg.snap->ps.stats[ STAT_MISC ] >= LCANNON_CHARGE_TIME_WARN && + ( cg.time & 128 ) ) + { + color[ 0 ] = 1.0f; + color[ 1 ] = 0.0f; + color[ 2 ] = 0.0f; + } + + x = rect->x; + y = rect->y; + + // Horizontal charge bar + if( rect->w >= rect->h ) + { + width = ( rect->w - CHARGE_BAR_CAP_SIZE * 2 ) * cg.chargeMeterValue; + height = rect->h; + CG_AdjustFrom640( &x, &y, &width, &height ); + cap_size = CHARGE_BAR_CAP_SIZE * cgs.screenXScale; + + // Draw the meter + trap_R_SetColor( color ); + trap_R_DrawStretchPic( x, y, cap_size, height, 0, 0, 1, 1, shader ); + trap_R_DrawStretchPic( x + width + cap_size, y, cap_size, height, + 1, 0, 0, 1, shader ); + trap_R_DrawStretchPic( x + cap_size, y, width, height, 1, 0, 1, 1, shader ); + trap_R_SetColor( NULL ); + } + + // Vertical charge bar + else + { + y += rect->h; + width = rect->w; + height = ( rect->h - CHARGE_BAR_CAP_SIZE * 2 ) * cg.chargeMeterValue; + CG_AdjustFrom640( &x, &y, &width, &height ); + cap_size = CHARGE_BAR_CAP_SIZE * cgs.screenYScale; + + // Draw the meter + trap_R_SetColor( color ); + trap_R_DrawStretchPic( x, y - cap_size, width, cap_size, + 0, 1, 1, 0, shader ); + trap_R_DrawStretchPic( x, y - height - cap_size * 2, width, + cap_size, 0, 0, 1, 1, shader ); + trap_R_DrawStretchPic( x, y - height - cap_size, width, height, + 0, 1, 1, 1, shader ); + trap_R_SetColor( NULL ); + } +} + +static void CG_DrawProgressLabel( rectDef_t *rect, float text_x, float text_y, vec4_t color, + float scale, int textalign, int textvalign, + const char *s, float fraction ) +{ + vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; + float tx, ty; + + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); + + if( fraction < 1.0f ) + UI_Text_Paint( text_x + tx, text_y + ty, scale, white, + s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + else + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, + s, 0, 0, ITEM_TEXTSTYLE_NEON ); +} + +static void CG_DrawMediaProgress( rectDef_t *rect, vec4_t color, float scale, + int align, int textalign, int textStyle, + float borderSize ) +{ + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.mediaFraction ); +} + +static void CG_DrawMediaProgressLabel( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign ) +{ + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Map and Textures", cg.mediaFraction ); +} + +static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color, + float scale, int align, int textalign, + int textStyle, float borderSize ) +{ + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.buildablesFraction ); +} + +static void CG_DrawBuildablesProgressLabel( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign ) +{ + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Buildable Models", cg.buildablesFraction ); +} + +static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color, + float scale, int align, int textalign, + int textStyle, float borderSize ) +{ + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, cg.charModelFraction ); +} + +static void CG_DrawCharModelProgressLabel( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign ) +{ + CG_DrawProgressLabel( rect, text_x, text_y, color, scale, textalign, textvalign, + "Character Models", cg.charModelFraction ); +} + +static void CG_DrawOverallProgress( rectDef_t *rect, vec4_t color, float scale, + int align, int textalign, int textStyle, + float borderSize ) +{ + float total; + + total = cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction; + total /= 3.0f; + + CG_DrawProgressBar( rect, color, scale, align, textalign, textStyle, + borderSize, total ); +} + +static void CG_DrawLevelShot( rectDef_t *rect ) +{ + const char *s; + const char *info; + qhandle_t levelshot; + qhandle_t detail; + + info = CG_ConfigString( CS_SERVERINFO ); + s = Info_ValueForKey( info, "mapname" ); + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); + + if( !levelshot ) + levelshot = trap_R_RegisterShaderNoMip( "gfx/2d/load_screen" ); + + trap_R_SetColor( NULL ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, levelshot ); + + // blend a detail texture over it + detail = trap_R_RegisterShader( "gfx/misc/detail" ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, detail ); +} + +static void CG_DrawLevelName( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) +{ + const char *s; + + s = CG_ConfigString( CS_MESSAGE ); + + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, s ); +} + +static void CG_DrawMOTD( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) +{ + const char *s; + char parsed[ MAX_STRING_CHARS ]; + + s = CG_ConfigString( CS_MOTD ); + + Q_ParseNewlines( parsed, s, sizeof( parsed ) ); + + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, parsed ); +} + +static void CG_DrawHostname( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, + int textalign, int textvalign, int textStyle ) +{ + char buffer[ 1024 ]; + const char *info; + + info = CG_ConfigString( CS_SERVERINFO ); + + UI_EscapeEmoticons( buffer, Info_ValueForKey( info, "sv_hostname" ), sizeof( buffer ) ); + Q_CleanStr( buffer ); + + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, buffer ); +} + +/* +============== +CG_DrawDemoPlayback +============== +*/ +static void CG_DrawDemoPlayback( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ + if( !cg_drawDemoState.integer ) + return; + + if( trap_GetDemoState( ) != DS_PLAYBACK ) + return; + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +/* +============== +CG_DrawDemoRecording +============== +*/ +static void CG_DrawDemoRecording( rectDef_t *rect, vec4_t color, qhandle_t shader ) +{ + if( !cg_drawDemoState.integer ) + return; + + if( trap_GetDemoState( ) != DS_RECORDING ) + return; + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); +} + +/* +====================== +CG_UpdateMediaFraction + +====================== +*/ +void CG_UpdateMediaFraction( float newFract ) +{ + cg.mediaFraction = newFract; + + trap_UpdateScreen( ); +} + +/* +==================== +CG_DrawLoadingScreen + +Draw all the status / pacifier stuff during level loading +==================== +*/ +void CG_DrawLoadingScreen( void ) +{ + menuDef_t *menu = Menus_FindByName( "Loading" ); + + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); +} + +float CG_GetValue( int ownerDraw ) +{ + centity_t *cent; + playerState_t *ps; + weapon_t weapon; + + cent = &cg_entities[ cg.snap->ps.clientNum ]; + ps = &cg.snap->ps; + weapon = BG_GetPlayerWeapon( ps ); + + switch( ownerDraw ) + { + case CG_PLAYER_AMMO_VALUE: + if( weapon ) + return ps->ammo; + break; + case CG_PLAYER_CLIPS_VALUE: + if( weapon ) + return ps->clips; + break; + case CG_PLAYER_HEALTH: + return ps->stats[ STAT_HEALTH ]; + break; + default: + break; + } + + return -1; +} + +const char *CG_GetKillerText( ) +{ + const char *s = ""; + if( cg.killerName[ 0 ] ) + s = va( "Fragged by %s", cg.killerName ); + + return s; +} + + +static void CG_DrawKiller( rectDef_t *rect, float scale, vec4_t color, + qhandle_t shader, int textStyle ) +{ + // fragged by ... line + if( cg.killerName[ 0 ] ) + { + int x = rect->x + rect->w / 2; + UI_Text_Paint( x - UI_Text_Width( CG_GetKillerText( ), scale ) / 2, + rect->y + rect->h, scale, color, CG_GetKillerText( ), 0, 0, textStyle ); + } +} + + +#define SPECTATORS_PIXELS_PER_SECOND 30.0f + +/* +================== +CG_DrawTeamSpectators +================== +*/ +static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, int textvalign, vec4_t color, qhandle_t shader ) +{ + float y; + char *text = cg.spectatorList; + float textWidth = UI_Text_Width( text, scale ); + + CG_AlignText( rect, text, scale, 0.0f, 0.0f, ALIGN_LEFT, textvalign, NULL, &y ); + + if( textWidth > rect->w ) + { + // The text is too wide to fit, so scroll it + int now = trap_Milliseconds( ); + int delta = now - cg.spectatorTime; + + CG_SetClipRegion( rect->x, rect->y, rect->w, rect->h ); + + UI_Text_Paint( rect->x - cg.spectatorOffset, y, scale, color, text, 0, 0, 0 ); + UI_Text_Paint( rect->x + textWidth - cg.spectatorOffset, y, scale, color, text, 0, 0, 0 ); + + CG_ClearClipRegion( ); + + cg.spectatorOffset += ( delta / 1000.0f ) * SPECTATORS_PIXELS_PER_SECOND; + + while( cg.spectatorOffset > textWidth ) + cg.spectatorOffset -= textWidth; + + cg.spectatorTime = now; + } + else + { + UI_Text_Paint( rect->x, y, scale, color, text, 0, 0, 0 ); + } +} + +#define FOLLOWING_STRING "following " +#define CHASING_STRING "chasing " + +/* +================== +CG_DrawFollow +================== +*/ +static void CG_DrawFollow( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) +{ + float tx, ty; + + if( cg.snap && cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + char buffer[ MAX_STRING_CHARS ]; + + if( !cg.chaseFollow ) + strcpy( buffer, FOLLOWING_STRING ); + else + strcpy( buffer, CHASING_STRING ); + + strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name ); + + CG_AlignText( rect, buffer, scale, 0, 0, textalign, textvalign, &tx, &ty ); + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, buffer, 0, 0, + textStyle ); + } +} + +/* +================== +CG_DrawTeamLabel +================== +*/ +static void CG_DrawTeamLabel( rectDef_t *rect, team_t team, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) +{ + char *t; + char stage[ MAX_TOKEN_CHARS ]; + char *s; + float tx, ty; + + stage[ 0 ] = '\0'; + + switch( team ) + { + case TEAM_ALIENS: + t = "Aliens"; + if( cg.intermissionStarted ) + Com_sprintf( stage, MAX_TOKEN_CHARS, "(Stage %d)", cgs.alienStage + 1 ); + break; + + case TEAM_HUMANS: + t = "Humans"; + if( cg.intermissionStarted ) + Com_sprintf( stage, MAX_TOKEN_CHARS, "(Stage %d)", cgs.humanStage + 1 ); + break; + + default: + t = ""; + break; + } + + switch( textalign ) + { + default: + case ALIGN_LEFT: + s = va( "%s %s", t, stage ); + break; + + case ALIGN_RIGHT: + s = va( "%s %s", stage, t ); + break; + } + + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); +} + +/* +================== +CG_DrawStageReport +================== +*/ +static const char *CG_SpawnReport( qboolean eggs ) +{ + const char *s; + + s = ( cg.snap->ps.persistant[ PERS_SPAWNS ] + cg.snap->ps.persistant[ PERS_SPAWNS_IMPLANTED ] == 1 ? "" : "s" ); + + if( cg.snap->ps.persistant[ PERS_SPAWNS_IMPLANTED ] ) + return va( "%d(+%d) %s%s left", + cg.snap->ps.persistant[ PERS_SPAWNS ], + cg.snap->ps.persistant[ PERS_SPAWNS_IMPLANTED ], + ( eggs ? "egg" : "telenode" ), + s ); + else + return va( "%d %s%s left", + cg.snap->ps.persistant[ PERS_SPAWNS ], + ( eggs ? "egg" : "telenode" ), + s ); +} + +static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y, + vec4_t color, float scale, int textalign, int textvalign, int textStyle ) +{ + char s[ MAX_TOKEN_CHARS ]; + float tx, ty; + + if( cg.intermissionStarted ) + return; + + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_NONE ) + return; + + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + int kills = ceil( (float)(cgs.alienNextStageThreshold - cgs.alienCredits) / ALIEN_CREDITS_PER_KILL ); + if( kills < 0 ) + kills = 0; + + if( cgs.alienNextStageThreshold < 0 ) + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %s", cgs.alienStage + 1, CG_SpawnReport( qtrue ) ); + else if( kills == 1 ) + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, 1 frag for next stage, %s", + cgs.alienStage + 1, CG_SpawnReport( qtrue ) ); + else + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d frags for next stage, %s", + cgs.alienStage + 1, kills, CG_SpawnReport( qtrue ) ); + } + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + int credits = cgs.humanNextStageThreshold - cgs.humanCredits; + + if( credits < 0 ) + credits = 0; + + if( cgs.humanNextStageThreshold < 0 ) + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %s", cgs.humanStage + 1, CG_SpawnReport( qfalse ) ); + else if( credits == 1 ) + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, 1 credit for next stage, %s", + cgs.humanStage + 1, CG_SpawnReport( qfalse ) ); + else + Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d credits for next stage, %s", + cgs.humanStage + 1, credits, CG_SpawnReport( qfalse ) ); + } + + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); + + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 20 +#define FPS_STRING "fps" +static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t color, + int textalign, int textvalign, int textStyle, + qboolean scalableText ) +{ + char *s; + float tx, ty; + float w, h, totalWidth; + int strLength; + static int previousTimes[ FPS_FRAMES ]; + static int index; + int i, total; + int fps; + static int previous; + int t, frameTime; + + if( !cg_drawFPS.integer ) + return; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = trap_Milliseconds( ); + frameTime = t - previous; + previous = t; + + previousTimes[ index % FPS_FRAMES ] = frameTime; + index++; + + if( index > FPS_FRAMES ) + { + // average multiple frames together to smooth changes out a bit + total = 0; + + for( i = 0 ; i < FPS_FRAMES ; i++ ) + total += previousTimes[ i ]; + + if( !total ) + total = 1; + + fps = 1000 * FPS_FRAMES / total; + + s = va( "%d", fps ); + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); + strLength = CG_DrawStrlen( s ); + totalWidth = UI_Text_Width( FPS_STRING, scale ) + w * strLength; + + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); + + if( scalableText ) + { + for( i = 0; i < strLength; i++ ) + { + char c[ 2 ]; + + c[ 0 ] = s[ i ]; + c[ 1 ] = '\0'; + + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); + } + + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, FPS_STRING, 0, 0, textStyle ); + } + else + { + trap_R_SetColor( color ); + CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, fps ); + trap_R_SetColor( NULL ); + } + } +} + + +/* +================= +CG_DrawTimerMins +================= +*/ +static void CG_DrawTimerMins( rectDef_t *rect, vec4_t color ) +{ + int mins, seconds; + int msec; + + if( !cg_drawTimer.integer ) + return; + + msec = cg.time - cgs.levelStartTime; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + + trap_R_SetColor( color ); + CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, mins ); + trap_R_SetColor( NULL ); +} + + +/* +================= +CG_DrawTimerSecs +================= +*/ +static void CG_DrawTimerSecs( rectDef_t *rect, vec4_t color ) +{ + int mins, seconds; + int msec; + + if( !cg_drawTimer.integer ) + return; + + msec = cg.time - cgs.levelStartTime; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + + trap_R_SetColor( color ); + CG_DrawFieldPadded( rect->x, rect->y, 2, rect->w / 2, rect->h, seconds ); + trap_R_SetColor( NULL ); +} + + +/* +================= +CG_DrawTimer +================= +*/ +static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) +{ + char *s; + float tx, ty; + int i, strLength; + float w, h, totalWidth; + int mins, seconds, tens; + int msec; + + if( !cg_drawTimer.integer ) + return; + + msec = cg.time - cgs.levelStartTime; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + s = va( "%d:%d%d", mins, tens, seconds ); + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); + strLength = CG_DrawStrlen( s ); + totalWidth = w * strLength; + + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); + + for( i = 0; i < strLength; i++ ) + { + char c[ 2 ]; + + c[ 0 ] = s[ i ]; + c[ 1 ] = '\0'; + + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); + } +} + +/* +================= +CG_DrawTeamOverlay +================= +*/ + +typedef enum +{ + TEAMOVERLAY_OFF, + TEAMOVERLAY_ALL, + TEAMOVERLAY_SUPPORT, + TEAMOVERLAY_NEARBY, +} teamOverlayMode_t; + +typedef enum +{ + TEAMOVERLAY_SORT_NONE, + TEAMOVERLAY_SORT_SCORE, + TEAMOVERLAY_SORT_WEAPONCLASS, +} teamOverlaySort_t; + +static int QDECL SortScore( const void *a, const void *b ) +{ + int na = *(int *)a; + int nb = *(int *)b; + + return( cgs.clientinfo[ nb ].score - cgs.clientinfo[ na ].score ); +} + +static int QDECL SortWeaponClass( const void *a, const void *b ) +{ + int out; + clientInfo_t *ca = cgs.clientinfo + *(int *)a; + clientInfo_t *cb = cgs.clientinfo + *(int *)b; + + out = cb->curWeaponClass - ca->curWeaponClass; + + // We want grangers on top. ckits are already on top without the special case. + if( ca->team == TEAM_ALIENS ) + { + if( ca->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + cb->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + ca->curWeaponClass == PCL_ALIEN_BUILDER0 || + cb->curWeaponClass == PCL_ALIEN_BUILDER0 ) + { + out = -out; + } + } + + return( out ); +} + +static void CG_DrawTeamOverlay( rectDef_t *rect, float scale, vec4_t color ) +{ + char *s; + int i; + float x = rect->x; + float y; + clientInfo_t *ci, *pci; + vec4_t tcolor; + float iconSize = rect->h / 8.0f; + float leftMargin = 4.0f; + float iconTopMargin = 2.0f; + float midSep = 2.0f; + float backgroundWidth = rect->w; + float fontScale = 0.30f; + float vPad = 0.0f; + float nameWidth = 0.5f * rect->w; + char name[ MAX_NAME_LENGTH + 2 ]; + int maxDisplayCount = 0; + int displayCount = 0; + float nameMaxX, nameMaxXCp; + float maxX = rect->x + rect->w; + float maxXCp = maxX; + weapon_t curWeapon = WP_NONE; + teamOverlayMode_t mode = cg_drawTeamOverlay.integer; + teamOverlaySort_t sort = cg_teamOverlaySortMode.integer; + int displayClients[ MAX_CLIENTS ]; + + if( cg.predictedPlayerState.pm_type == PM_SPECTATOR ) + return; + + if( mode == TEAMOVERLAY_OFF || !cg_teamOverlayMaxPlayers.integer ) + return; + + if( !cgs.teaminfoReceievedTime ) + return; + + if( cg.showScores || + cg.predictedPlayerState.pm_type == PM_INTERMISSION ) + return; + + pci = cgs.clientinfo + cg.snap->ps.clientNum; + + if( mode == TEAMOVERLAY_ALL || mode == TEAMOVERLAY_SUPPORT ) + { + for( i = 0; i < MAX_CLIENTS; i++ ) + { + ci = cgs.clientinfo + i; + if( ci->infoValid && pci != ci && ci->team == pci->team ) + { + if( mode == TEAMOVERLAY_ALL ) + displayClients[ maxDisplayCount++ ] = i; + else + { + if( ci->curWeaponClass == PCL_ALIEN_BUILDER0 || + ci->curWeaponClass == PCL_ALIEN_BUILDER0_UPG || + ci->curWeaponClass == PCL_ALIEN_LEVEL1 || + ci->curWeaponClass == PCL_ALIEN_LEVEL1_UPG || + ci->curWeaponClass == WP_HBUILD ) + { + displayClients[ maxDisplayCount++ ] = i; + } + } + } + } + } + else // find nearby + { + for( i = 0; i < cg.snap->numEntities; i++ ) + { + centity_t *cent = &cg_entities[ cg.snap->entities[ i ].number ]; + vec3_t relOrigin = { 0.0f, 0.0f, 0.0f }; + int team = cent->currentState.misc & 0x00FF; + + if( cent->currentState.eType != ET_PLAYER || + team != pci->team || + cent->currentState.eFlags & EF_DEAD ) + { + continue; + } + + VectorSubtract( cent->lerpOrigin, cg.predictedPlayerState.origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE ) + displayClients[ maxDisplayCount++ ] = cg.snap->entities[ i ].number; + } + } + + // Sort + if( sort == TEAMOVERLAY_SORT_SCORE ) + { + qsort( displayClients, maxDisplayCount, + sizeof( displayClients[ 0 ] ), SortScore ); + } + else if( sort == TEAMOVERLAY_SORT_WEAPONCLASS ) + { + qsort( displayClients, maxDisplayCount, + sizeof( displayClients[ 0 ] ), SortWeaponClass ); + } + + if( maxDisplayCount > cg_teamOverlayMaxPlayers.integer ) + maxDisplayCount = cg_teamOverlayMaxPlayers.integer; + + iconSize *= scale; + leftMargin *= scale; + iconTopMargin *= scale; + midSep *= scale; + backgroundWidth *= scale; + fontScale *= scale; + nameWidth *= scale; + + vPad = ( rect->h - ( (float) maxDisplayCount * iconSize ) ) / 2.0f; + y = rect->y + vPad; + + tcolor[ 0 ] = 1.0f; + tcolor[ 1 ] = 1.0f; + tcolor[ 2 ] = 1.0f; + tcolor[ 3 ] = color[ 3 ]; + + for( i = 0; i < MAX_CLIENTS && displayCount < maxDisplayCount; i++ ) + { + ci = cgs.clientinfo + displayClients[ i ]; + + if( !ci->infoValid || pci == ci || ci->team != pci->team ) + continue; + + Com_sprintf( name, sizeof( name ), "%s^7", ci->name ); + + trap_R_SetColor( color ); + CG_DrawPic( x, y, backgroundWidth, + iconSize, cgs.media.teamOverlayShader ); + trap_R_SetColor( tcolor ); + if( ci->health <= 0 || !ci->curWeaponClass ) + s = ""; + else + { + if( ci->team == TEAM_HUMANS ) + curWeapon = ci->curWeaponClass; + else if( ci->team == TEAM_ALIENS ) + curWeapon = BG_Class( ci->curWeaponClass )->startWeapon; + + CG_DrawPic( x + leftMargin, y, iconSize, iconSize, + cg_weapons[ curWeapon ].weaponIcon ); + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + if( ci->upgrade != UP_NONE ) + { + CG_DrawPic( x + iconSize + leftMargin, y, iconSize, + iconSize, cg_upgrades[ ci->upgrade ].upgradeIcon ); + } + } + else + { + if( curWeapon == WP_ABUILD2 || curWeapon == WP_ALEVEL1_UPG || + curWeapon == WP_ALEVEL2_UPG || curWeapon == WP_ALEVEL3_UPG ) + { + CG_DrawPic( x + iconSize + leftMargin, y, iconSize, + iconSize, cgs.media.upgradeClassIconShader ); + } + } + + s = va( " [^%c%3d^7] ^7%s", + CG_GetColorCharForHealth( displayClients[ i ] ), + ci->health, + CG_ConfigString( CS_LOCATIONS + ci->location ) ); + } + + trap_R_SetColor( NULL ); + nameMaxX = nameMaxXCp = x + 2.0f * iconSize + + leftMargin + midSep + nameWidth; + UI_Text_Paint_Limit( &nameMaxXCp, x + 2.0f * iconSize + leftMargin + midSep, + y + iconSize - iconTopMargin, fontScale, tcolor, name, + 0, 0 ); + + maxXCp = maxX; + + UI_Text_Paint_Limit( &maxXCp, nameMaxX, y + iconSize - iconTopMargin, + fontScale, tcolor, s, 0, 0 ); + y += iconSize; + displayCount++; + } +} + +/* +================= +CG_DrawClock +================= +*/ +static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) +{ + char *s; + float tx, ty; + int i, strLength; + float w, h, totalWidth; + qtime_t qt; + int t; + + if( !cg_drawClock.integer ) + return; + + t = trap_RealTime( &qt ); + + if( cg_drawClock.integer == 2 ) + { + s = va( "%02d%s%02d", qt.tm_hour, ( qt.tm_sec % 2 ) ? ":" : " ", + qt.tm_min ); + } + else + { + char *pm = "am"; + int h = qt.tm_hour; + + if( h == 0 ) + h = 12; + else if( h == 12 ) + pm = "pm"; + else if( h > 12 ) + { + h -= 12; + pm = "pm"; + } + + s = va( "%d%s%02d%s", h, ( qt.tm_sec % 2 ) ? ":" : " ", qt.tm_min, pm ); + } + w = UI_Text_Width( "0", scale ); + h = UI_Text_Height( "0", scale ); + strLength = CG_DrawStrlen( s ); + totalWidth = w * strLength; + + CG_AlignText( rect, s, 0.0f, totalWidth, h, textalign, textvalign, &tx, &ty ); + + for( i = 0; i < strLength; i++ ) + { + char c[ 2 ]; + + c[ 0 ] = s[ i ]; + c[ 1 ] = '\0'; + + UI_Text_Paint( text_x + tx + i * w, text_y + ty, scale, color, c, 0, 0, textStyle ); + } +} + +/* +================== +CG_DrawSnapshot +================== +*/ +static void CG_DrawSnapshot( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t color, + int textalign, int textvalign, int textStyle ) +{ + char *s; + float tx, ty; + + if( !cg_drawSnapshot.integer ) + return; + + s = va( "time:%d snap:%d cmd:%d", cg.snap->serverTime, + cg.latestSnapshotNum, cgs.serverCommandSequence ); + + CG_AlignText( rect, s, scale, 0.0f, 0.0f, textalign, textvalign, &tx, &ty ); + + UI_Text_Paint( text_x + tx, text_y + ty, scale, color, s, 0, 0, textStyle ); +} + +/* +=============================================================================== + +LAGOMETER + +=============================================================================== +*/ + +#define LAG_SAMPLES 128 + +typedef struct +{ + int frameSamples[ LAG_SAMPLES ]; + int frameCount; + int snapshotFlags[ LAG_SAMPLES ]; + int snapshotSamples[ LAG_SAMPLES ]; + int snapshotCount; +} lagometer_t; + +lagometer_t lagometer; + +/* +============== +CG_AddLagometerFrameInfo + +Adds the current interpolate / extrapolate bar for this frame +============== +*/ +void CG_AddLagometerFrameInfo( void ) +{ + int offset; + + offset = cg.time - cg.latestSnapshotTime; + lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1 ) ] = offset; + lagometer.frameCount++; +} + +/* +============== +CG_AddLagometerSnapshotInfo + +Each time a snapshot is received, log its ping time and +the number of snapshots that were dropped before it. + +Pass NULL for a dropped packet. +============== +*/ +#define PING_FRAMES 40 +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) +{ + static int previousPings[ PING_FRAMES ]; + static int index; + int i; + + // dropped packet + if( !snap ) + { + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = -1; + lagometer.snapshotCount++; + return; + } + + // add this snapshot's info + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->ping; + lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->snapFlags; + lagometer.snapshotCount++; + + cg.ping = 0; + if( cg.snap ) + { + previousPings[ index++ ] = cg.snap->ping; + index = index % PING_FRAMES; + + for( i = 0; i < PING_FRAMES; i++ ) + { + cg.ping += previousPings[ i ]; + } + + cg.ping /= PING_FRAMES; + } +} + +/* +============== +CG_DrawDisconnect + +Should we draw something differnet for long lag vs no packets? +============== +*/ +static void CG_DrawDisconnect( void ) +{ + float x, y; + int cmdNum; + usercmd_t cmd; + const char *s; + int w; + vec4_t color = { 1.0f, 1.0f, 1.0f, 1.0f }; + + // draw the phone jack if we are completely past our buffers + cmdNum = trap_GetCurrentCmdNumber( ) - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &cmd ); + + // special check for map_restart + if( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time ) + return; + + // also add text in center of screen + s = "Connection Interrupted"; + w = UI_Text_Width( s, 0.7f ); + UI_Text_Paint( 320 - w / 2, 100, 0.7f, color, s, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + + // blink the icon + if( ( cg.time >> 9 ) & 1 ) + return; + + x = 640 - 48; + y = 480 - 48; + + CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader( "gfx/2d/net.tga" ) ); +} + +#define MAX_LAGOMETER_PING 900 +#define MAX_LAGOMETER_RANGE 300 + + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t textColor ) +{ + int a, x, y, i; + float v; + float ax, ay, aw, ah, mid, range; + int color; + vec4_t adjustedColor; + float vscale; + char *ping; + + if( cg.snap->ps.pm_type == PM_INTERMISSION ) + return; + + if( !cg_lagometer.integer ) + return; + + if( cg.demoPlayback ) + return; + + Vector4Copy( textColor, adjustedColor ); + adjustedColor[ 3 ] = 0.25f; + + trap_R_SetColor( adjustedColor ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader ); + trap_R_SetColor( NULL ); + + // + // draw the graph + // + ax = x = rect->x; + ay = y = rect->y; + aw = rect->w; + ah = rect->h; + + trap_R_SetColor( NULL ); + + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + color = -1; + range = ah / 3; + mid = ay + range; + + vscale = range / MAX_LAGOMETER_RANGE; + + // draw the frame interpoalte / extrapolate graph + for( a = 0 ; a < aw ; a++ ) + { + i = ( lagometer.frameCount - 1 - a ) & ( LAG_SAMPLES - 1 ); + v = lagometer.frameSamples[ i ]; + v *= vscale; + + if( v > 0 ) + { + if( color != 1 ) + { + color = 1; + trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] ); + } + + if( v > range ) + v = range; + + trap_R_DrawStretchPic( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + else if( v < 0 ) + { + if( color != 2 ) + { + color = 2; + trap_R_SetColor( g_color_table[ ColorIndex( COLOR_BLUE ) ] ); + } + + v = -v; + if( v > range ) + v = range; + + trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + // draw the snapshot latency / drop graph + range = ah / 2; + vscale = range / MAX_LAGOMETER_PING; + + for( a = 0 ; a < aw ; a++ ) + { + i = ( lagometer.snapshotCount - 1 - a ) & ( LAG_SAMPLES - 1 ); + v = lagometer.snapshotSamples[ i ]; + + if( v > 0 ) + { + if( lagometer.snapshotFlags[ i ] & SNAPFLAG_RATE_DELAYED ) + { + if( color != 5 ) + { + color = 5; // YELLOW for rate delay + trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] ); + } + } + else + { + if( color != 3 ) + { + color = 3; + + trap_R_SetColor( g_color_table[ ColorIndex( COLOR_GREEN ) ] ); + } + } + + v = v * vscale; + + if( v > range ) + v = range; + + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + else if( v < 0 ) + { + if( color != 4 ) + { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ ColorIndex( COLOR_RED ) ] ); + } + + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + if( cg_nopredict.integer || cg_synchronousClients.integer ) + ping = "snc"; + else + ping = va( "%d", cg.ping ); + ax = rect->x + ( rect->w / 2.0f ) - + ( UI_Text_Width( ping, scale ) / 2.0f ) + text_x; + ay = rect->y + ( rect->h / 2.0f ) + + ( UI_Text_Height( ping, scale ) / 2.0f ) + text_y; + + Vector4Copy( textColor, adjustedColor ); + adjustedColor[ 3 ] = 0.5f; + UI_Text_Paint( ax, ay, scale, adjustedColor, ping, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); + + CG_DrawDisconnect( ); +} + +#define SPEEDOMETER_NUM_SAMPLES 160 +#define SPEEDOMETER_DRAW_TEXT 0x1 +#define SPEEDOMETER_DRAW_GRAPH 0x2 +#define SPEEDOMETER_IGNORE_Z 0x4 +float speedSamples[ SPEEDOMETER_NUM_SAMPLES ]; +// array indices +int oldestSpeedSample = 0; +int maxSpeedSample = 0; + +/* +=================== +CG_AddSpeed + +append a speed to the sample history +=================== +*/ +void CG_AddSpeed( void ) +{ + float speed; + vec3_t vel; + + VectorCopy( cg.snap->ps.velocity, vel ); + + if( cg_drawSpeed.integer & SPEEDOMETER_IGNORE_Z ) + vel[ 2 ] = 0; + + speed = VectorLength( vel ); + + if( speed > speedSamples[ maxSpeedSample ] ) + { + maxSpeedSample = oldestSpeedSample; + speedSamples[ oldestSpeedSample++ ] = speed; + oldestSpeedSample %= SPEEDOMETER_NUM_SAMPLES; + return; + } + + speedSamples[ oldestSpeedSample ] = speed; + if( maxSpeedSample == oldestSpeedSample++ ) + { + // if old max was overwritten find a new one + int i; + for( maxSpeedSample = 0, i = 1; i < SPEEDOMETER_NUM_SAMPLES; i++ ) + { + if( speedSamples[ i ] > speedSamples[ maxSpeedSample ] ) + maxSpeedSample = i; + } + } + + oldestSpeedSample %= SPEEDOMETER_NUM_SAMPLES; +} + +#define SPEEDOMETER_MIN_RANGE 900 +#define SPEED_MED 1000.f +#define SPEED_FAST 1600.f + +/* +=================== +CG_DrawSpeedGraph +=================== +*/ +static void CG_DrawSpeedGraph( rectDef_t *rect, vec4_t foreColor, + vec4_t backColor ) +{ + int i; + float val, max, top; + // colour of graph is interpolated between these values + const vec3_t slow = { 0.0, 0.0, 1.0 }; + const vec3_t medium = { 0.0, 1.0, 0.0 }; + const vec3_t fast = { 1.0, 0.0, 0.0 }; + vec4_t color; + + max = speedSamples[ maxSpeedSample ]; + if( max < SPEEDOMETER_MIN_RANGE ) + max = SPEEDOMETER_MIN_RANGE; + + trap_R_SetColor( backColor ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader ); + + Vector4Copy( foreColor, color ); + + for( i = 1; i < SPEEDOMETER_NUM_SAMPLES; i++ ) + { + val = speedSamples[ ( oldestSpeedSample + i ) % SPEEDOMETER_NUM_SAMPLES ]; + if( val < SPEED_MED ) + VectorLerp( val / SPEED_MED, slow, medium, color ); + else if( val < SPEED_FAST ) + VectorLerp( ( val - SPEED_MED ) / ( SPEED_FAST - SPEED_MED ), + medium, fast, color ); + else + VectorCopy( fast, color ); + trap_R_SetColor( color ); + top = rect->y + ( 1 - val / max ) * rect->h; + CG_DrawPic( rect->x + ( i / (float)SPEEDOMETER_NUM_SAMPLES ) * rect->w, top, + rect->w / (float)SPEEDOMETER_NUM_SAMPLES, val * rect->h / max, + cgs.media.whiteShader ); + } + trap_R_SetColor( NULL ); +} + +/* +=================== +CG_DrawSpeedText +=================== +*/ +static void CG_DrawSpeedText( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t foreColor ) +{ + char speedstr[ 16 ]; + float val; + vec4_t color; + + VectorCopy( foreColor, color ); + color[ 3 ] = 1; + if( cg.predictedPlayerState.clientNum == cg.clientNum ) + { + vec3_t vel; + VectorCopy( cg.predictedPlayerState.velocity, vel ); + if( cg_drawSpeed.integer & SPEEDOMETER_IGNORE_Z ) + vel[ 2 ] = 0; + val = VectorLength( vel ); + } + else if( oldestSpeedSample == 0 ) + val = speedSamples[ SPEEDOMETER_NUM_SAMPLES - 1 ]; + else + val = speedSamples[ oldestSpeedSample - 1 ]; + + Com_sprintf( speedstr, sizeof( speedstr ), "%d", (int)val ); + + UI_Text_Paint( + rect->x + ( rect->w - UI_Text_Width( speedstr, scale ) ) / 2.0f, + rect->y + ( rect->h + UI_Text_Height( speedstr, scale ) ) / 2.0f, + scale, color, speedstr, 0, 0, ITEM_TEXTSTYLE_NORMAL ); +} + +/* +=================== +CG_DrawSpeed +=================== +*/ +static void CG_DrawSpeed( rectDef_t *rect, float text_x, float text_y, + float scale, vec4_t foreColor, vec4_t backColor ) +{ + if( cg_drawSpeed.integer & SPEEDOMETER_DRAW_GRAPH ) + CG_DrawSpeedGraph( rect, foreColor, backColor ); + if( cg_drawSpeed.integer & SPEEDOMETER_DRAW_TEXT ) + CG_DrawSpeedText( rect, text_x, text_y, scale, foreColor ); +} + +/* +=================== +CG_DrawConsole +=================== +*/ +static void CG_DrawConsole( rectDef_t *rect, float text_x, float text_y, vec4_t color, + float scale, int textalign, int textvalign, int textStyle ) +{ + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, cg.consoleText ); +} + +/* +=================== +CG_DrawTutorial +=================== +*/ +static void CG_DrawTutorial( rectDef_t *rect, float text_x, float text_y, vec4_t color, + float scale, int textalign, int textvalign, int textStyle ) +{ + if( !cg_tutorial.integer && !cg_modTutorial.integer ) + return; + + UI_DrawTextBlock( rect, text_x, text_y, color, scale, textalign, textvalign, textStyle, CG_TutorialText( ) ); +} + +/* +=================== +CG_DrawWeaponIcon +=================== +*/ +void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color ) +{ + int maxAmmo; + centity_t *cent; + playerState_t *ps; + weapon_t weapon; + + cent = &cg_entities[ cg.snap->ps.clientNum ]; + ps = &cg.snap->ps; + weapon = BG_GetPlayerWeapon( ps ); + + maxAmmo = BG_Weapon( weapon )->maxAmmo; + + // don't display if dead + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) + return; + + if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) + { + CG_Error( "CG_DrawWeaponIcon: weapon out of range: %d\n", weapon ); + return; + } + + if( !cg_weapons[ weapon ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawWeaponIcon: weapon %d (%s) " + "is not registered\n", weapon, BG_Weapon( weapon )->name ); + return; + } + + if( ps->clips == 0 && !BG_Weapon( weapon )->infiniteAmmo ) + { + float ammoPercent = (float)ps->ammo / (float)maxAmmo; + + if( ammoPercent < 0.33f ) + { + color[ 0 ] = 1.0f; + color[ 1 ] = color[ 2 ] = 0.0f; + } + } + + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS && + !BG_AlienCanEvolve( cg.predictedPlayerState.stats[ STAT_CLASS ], + ps->persistant[ PERS_CREDIT ], cgs.alienStage ) ) + { + if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME ) + { + if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 ) + color[ 3 ] = 0.0f; + } + } + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, + cg_weapons[ weapon ].weaponIcon ); + trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIR + +================================================================================ +*/ + + + +/* +================= +CG_DrawCrosshair +================= +*/ +static void CG_DrawCrosshair( rectDef_t *rect, vec4_t color ) +{ + float w, h; + qhandle_t hShader; + float x, y; + weaponInfo_t *wi; + weapon_t weapon; + + weapon = BG_GetPlayerWeapon( &cg.snap->ps ); + + if( cg_drawCrosshair.integer == CROSSHAIR_ALWAYSOFF ) + return; + + if( cg_drawCrosshair.integer == CROSSHAIR_RANGEDONLY && + !BG_Weapon( weapon )->longRanged ) + return; + + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + return; + + if( cg.renderingThirdPerson ) + return; + + if( cg.snap->ps.pm_type == PM_INTERMISSION ) + return; + + wi = &cg_weapons[ weapon ]; + + w = h = wi->crossHairSize * cg_crosshairSize.value; + w *= cgDC.aspectScale; + + //FIXME: this still ignores the width/height of the rect, but at least it's + //neater than cg_crosshairX/cg_crosshairY + x = rect->x + ( rect->w / 2 ) - ( w / 2 ); + y = rect->y + ( rect->h / 2 ) - ( h / 2 ); + + hShader = wi->crossHair; + + //aiming at a friendly player/buildable, dim the crosshair + if( cg.time == cg.crosshairClientTime || cg.crosshairBuildable >= 0 ) + { + int i; + for( i = 0; i < 3; i++ ) + color[i] *= .5f; + + } + + if( hShader != 0 ) + { + + trap_R_SetColor( color ); + CG_DrawPic( x, y, w, h, hShader ); + trap_R_SetColor( NULL ); + } +} + + + +/* +================= +CG_ScanForCrosshairEntity +================= +*/ +static void CG_ScanForCrosshairEntity( void ) +{ + trace_t trace; + vec3_t start, end; + int content; + team_t team; + + VectorCopy( cg.refdef.vieworg, start ); + VectorMA( start, 131072, cg.refdef.viewaxis[ 0 ], end ); + + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + + // if the player is in fog, don't show it + content = trap_CM_PointContents( trace.endpos, 0 ); + if( content & CONTENTS_FOG ) + return; + + if( trace.entityNum >= MAX_CLIENTS ) + { + entityState_t *s = &cg_entities[ trace.entityNum ].currentState; + if( s->eType == ET_BUILDABLE && BG_Buildable( s->modelindex, NULL )->team == + cg.snap->ps.stats[ STAT_TEAM ] ) + cg.crosshairBuildable = trace.entityNum; + else + cg.crosshairBuildable = -1; + + return; + } + + team = cgs.clientinfo[ trace.entityNum ].team; + + if( cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE ) + { + //only display team names of those on the same team as this player + if( team != cg.snap->ps.stats[ STAT_TEAM ] ) + return; + } + + // update the fade timer + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; +} + + +/* +===================== +CG_DrawLocation +===================== +*/ +static void CG_DrawLocation( rectDef_t *rect, float scale, int textalign, vec4_t color ) +{ + const char *location; + centity_t *locent; + float maxX; + float tx = rect->x, ty = rect->y; + + if( cg.intermissionStarted ) + return; + + maxX = rect->x + rect->w; + + locent = CG_GetPlayerLocation( ); + if( locent ) + location = CG_ConfigString( CS_LOCATIONS + locent->currentState.generic1 ); + else + location = CG_ConfigString( CS_LOCATIONS ); + + // need to skip horiz. align if it's too long, but valign must be run either way + if( UI_Text_Width( location, scale ) < rect->w ) + { + CG_AlignText( rect, location, scale, 0.0f, 0.0f, textalign, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint( tx, ty, scale, color, location, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + } + else + { + CG_AlignText( rect, location, scale, 0.0f, 0.0f, ALIGN_NONE, VALIGN_CENTER, &tx, &ty ); + UI_Text_Paint_Limit( &maxX, tx, ty, scale, color, location, 0, 0 ); + } + + trap_R_SetColor( NULL ); +} + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle ) +{ + float *color; + char *name; + float w, x; + + if( !cg_drawCrosshairNames.integer ) + return; + + if( cg.renderingThirdPerson ) + return; + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity( ); + + // draw the name of the player being looked at + color = CG_FadeColor( cg.crosshairClientTime, CROSSHAIR_CLIENT_TIMEOUT ); + if( !color ) + { + trap_R_SetColor( NULL ); + return; + } + + // add health from overlay info to the crosshair client name + name = cgs.clientinfo[ cg.crosshairClientNum ].name; + if( cg_teamOverlayUserinfo.integer && + cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE && + cgs.teaminfoReceievedTime && + cgs.clientinfo[ cg.crosshairClientNum ].health > 0 ) + { + name = va( "%s ^7[^%c%d^7]", name, + CG_GetColorCharForHealth( cg.crosshairClientNum ), + cgs.clientinfo[ cg.crosshairClientNum ].health ); + } + + w = UI_Text_Width( name, scale ); + x = rect->x + rect->w / 2.0f; + UI_Text_Paint( x - w / 2.0f, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + trap_R_SetColor( NULL ); +} + +/* +=============== +CG_OwnerDraw + +Draw an owner drawn item +=============== +*/ +void CG_OwnerDraw( float x, float y, float w, float h, float text_x, + float text_y, int ownerDraw, int ownerDrawFlags, + int align, int textalign, int textvalign, float borderSize, + float scale, vec4_t foreColor, vec4_t backColor, + qhandle_t shader, int textStyle ) +{ + rectDef_t rect; + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + switch( ownerDraw ) + { + case CG_PLAYER_CREDITS_VALUE: + CG_DrawPlayerCreditsValue( &rect, foreColor, qtrue ); + break; + case CG_PLAYER_CREDITS_FRACTION: + CG_DrawPlayerCreditsFraction( &rect, foreColor, shader ); + break; + case CG_PLAYER_CREDITS_VALUE_NOPAD: + CG_DrawPlayerCreditsValue( &rect, foreColor, qfalse ); + break; + case CG_PLAYER_STAMINA_1: + case CG_PLAYER_STAMINA_2: + case CG_PLAYER_STAMINA_3: + case CG_PLAYER_STAMINA_4: + CG_DrawPlayerStamina( ownerDraw, &rect, backColor, foreColor, shader ); + break; + case CG_PLAYER_STAMINA_BOLT: + CG_DrawPlayerStaminaBolt( &rect, backColor, foreColor, shader ); + break; + case CG_PLAYER_AMMO_VALUE: + CG_DrawPlayerAmmoValue( &rect, foreColor ); + break; + case CG_PLAYER_CLIPS_VALUE: + CG_DrawPlayerClipsValue( &rect, foreColor ); + break; + case CG_PLAYER_BUILD_TIMER: + CG_DrawPlayerBuildTimer( &rect, foreColor ); + break; + case CG_PLAYER_HEALTH: + CG_DrawPlayerHealthValue( &rect, foreColor ); + break; + case CG_PLAYER_HEALTH_CROSS: + CG_DrawPlayerHealthCross( &rect, foreColor ); + break; + case CG_PLAYER_CHARGE_BAR_BG: + CG_DrawPlayerChargeBarBG( &rect, foreColor, shader ); + break; + case CG_PLAYER_CHARGE_BAR: + CG_DrawPlayerChargeBar( &rect, foreColor, shader ); + break; + case CG_PLAYER_CLIPS_RING: + CG_DrawPlayerClipsRing( &rect, backColor, foreColor, shader ); + break; + case CG_PLAYER_BUILD_TIMER_RING: + CG_DrawPlayerBuildTimerRing( &rect, backColor, foreColor, shader ); + break; + case CG_PLAYER_WALLCLIMBING: + CG_DrawPlayerWallclimbing( &rect, backColor, foreColor, shader ); + break; + case CG_PLAYER_BOOSTED: + CG_DrawPlayerBoosted( &rect, backColor, foreColor, shader ); + break; + case CG_PLAYER_BOOST_BOLT: + CG_DrawPlayerBoosterBolt( &rect, backColor, foreColor, shader ); + break; + case CG_PLAYER_POISON_BARBS: + CG_DrawPlayerPoisonBarbs( &rect, foreColor, shader ); + break; + case CG_PLAYER_ALIEN_SENSE: + CG_DrawAlienSense( &rect ); + break; + case CG_PLAYER_HUMAN_SCANNER: + CG_DrawHumanScanner( &rect, shader, foreColor ); + break; + case CG_PLAYER_USABLE_BUILDABLE: + CG_DrawUsableBuildable( &rect, shader, foreColor ); + break; + case CG_KILLER: + CG_DrawKiller( &rect, scale, foreColor, shader, textStyle ); + break; + case CG_PLAYER_SELECT: + CG_DrawItemSelect( &rect, foreColor ); + break; + case CG_PLAYER_WEAPONICON: + CG_DrawWeaponIcon( &rect, foreColor ); + break; + case CG_PLAYER_SELECTTEXT: + CG_DrawItemSelectText( &rect, scale, textStyle ); + break; + case CG_SPECTATORS: + CG_DrawTeamSpectators( &rect, scale, textvalign, foreColor, shader ); + break; + case CG_PLAYER_LOCATION: + CG_DrawLocation( &rect, scale, textalign, foreColor ); + break; + case CG_FOLLOW: + CG_DrawFollow( &rect, text_x, text_y, foreColor, scale, + textalign, textvalign, textStyle ); + break; + case CG_PLAYER_CROSSHAIRNAMES: + CG_DrawCrosshairNames( &rect, scale, textStyle ); + break; + case CG_PLAYER_CROSSHAIR: + CG_DrawCrosshair( &rect, foreColor ); + break; + case CG_STAGE_REPORT_TEXT: + CG_DrawStageReport( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + case CG_ALIENS_SCORE_LABEL: + CG_DrawTeamLabel( &rect, TEAM_ALIENS, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + case CG_HUMANS_SCORE_LABEL: + CG_DrawTeamLabel( &rect, TEAM_HUMANS, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + + //loading screen + case CG_LOAD_LEVELSHOT: + CG_DrawLevelShot( &rect ); + break; + case CG_LOAD_MEDIA: + CG_DrawMediaProgress( &rect, foreColor, scale, align, textalign, textStyle, + borderSize ); + break; + case CG_LOAD_MEDIA_LABEL: + CG_DrawMediaProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); + break; + case CG_LOAD_BUILDABLES: + CG_DrawBuildablesProgress( &rect, foreColor, scale, align, textalign, + textStyle, borderSize ); + break; + case CG_LOAD_BUILDABLES_LABEL: + CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); + break; + case CG_LOAD_CHARMODEL: + CG_DrawCharModelProgress( &rect, foreColor, scale, align, textalign, + textStyle, borderSize ); + break; + case CG_LOAD_CHARMODEL_LABEL: + CG_DrawCharModelProgressLabel( &rect, text_x, text_y, foreColor, scale, textalign, textvalign ); + break; + case CG_LOAD_OVERALL: + CG_DrawOverallProgress( &rect, foreColor, scale, align, textalign, textStyle, + borderSize ); + break; + case CG_LOAD_LEVELNAME: + CG_DrawLevelName( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + case CG_LOAD_MOTD: + CG_DrawMOTD( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + case CG_LOAD_HOSTNAME: + CG_DrawHostname( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + + case CG_FPS: + CG_DrawFPS( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle, qtrue ); + break; + case CG_FPS_FIXED: + CG_DrawFPS( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle, qfalse ); + break; + case CG_TIMER: + CG_DrawTimer( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); + break; + case CG_CLOCK: + CG_DrawClock( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); + break; + case CG_TIMER_MINS: + CG_DrawTimerMins( &rect, foreColor ); + break; + case CG_TIMER_SECS: + CG_DrawTimerSecs( &rect, foreColor ); + break; + case CG_SNAPSHOT: + CG_DrawSnapshot( &rect, text_x, text_y, scale, foreColor, textalign, textvalign, textStyle ); + break; + case CG_LAGOMETER: + CG_DrawLagometer( &rect, text_x, text_y, scale, foreColor ); + break; + case CG_TEAMOVERLAY: + CG_DrawTeamOverlay( &rect, scale, foreColor ); + break; + case CG_SPEEDOMETER: + CG_DrawSpeed( &rect, text_x, text_y, scale, foreColor, backColor ); + break; + + case CG_DEMO_PLAYBACK: + CG_DrawDemoPlayback( &rect, foreColor, shader ); + break; + case CG_DEMO_RECORDING: + CG_DrawDemoRecording( &rect, foreColor, shader ); + break; + + case CG_CONSOLE: + CG_DrawConsole( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + + case CG_TUTORIAL: + CG_DrawTutorial( &rect, text_x, text_y, foreColor, scale, textalign, textvalign, textStyle ); + break; + + default: + break; + } +} + +void CG_MouseEvent( int x, int y ) +{ + int n; + + if( ( cg.predictedPlayerState.pm_type == PM_NORMAL || + cg.predictedPlayerState.pm_type == PM_SPECTATOR ) && + cg.showScores == qfalse ) + { + trap_Key_SetCatcher( 0 ); + return; + } + + cgs.cursorX += x; + if( cgs.cursorX < 0 ) + cgs.cursorX = 0; + else if( cgs.cursorX > 640 ) + cgs.cursorX = 640; + + cgs.cursorY += y; + if( cgs.cursorY < 0 ) + cgs.cursorY = 0; + else if( cgs.cursorY > 480 ) + cgs.cursorY = 480; + + n = Display_CursorType( cgs.cursorX, cgs.cursorY ); + cgs.activeCursor = 0; + if( n == CURSOR_ARROW ) + cgs.activeCursor = cgs.media.selectCursor; + else if( n == CURSOR_SIZER ) + cgs.activeCursor = cgs.media.sizeCursor; + + if( cgs.capturedItem ) + Display_MouseMove( cgs.capturedItem, x, y ); + else + Display_MouseMove( NULL, cgs.cursorX, cgs.cursorY ); +} + +/* +================== +CG_HideTeamMenus +================== + +*/ +void CG_HideTeamMenu( void ) +{ + Menus_CloseByName( "teamMenu" ); + Menus_CloseByName( "getMenu" ); +} + +/* +================== +CG_ShowTeamMenus +================== + +*/ +void CG_ShowTeamMenu( void ) +{ + Menus_ActivateByName( "teamMenu" ); +} + +/* +================== +CG_EventHandling + +type 0 - no event handling + 1 - team menu + 2 - hud editor +================== +*/ +void CG_EventHandling( int type ) +{ + cgs.eventHandling = type; + + if( type == CGAME_EVENT_NONE ) + CG_HideTeamMenu( ); +} + + + +void CG_KeyEvent( int key, qboolean down ) +{ + if( !down ) + return; + + if( cg.predictedPlayerState.pm_type == PM_NORMAL || + ( cg.predictedPlayerState.pm_type == PM_SPECTATOR && + cg.showScores == qfalse ) ) + { + CG_EventHandling( CGAME_EVENT_NONE ); + trap_Key_SetCatcher( 0 ); + return; + } + + Display_HandleKey( key, down, cgs.cursorX, cgs.cursorY ); + + if( cgs.capturedItem ) + cgs.capturedItem = NULL; + else + { + if( key == K_MOUSE2 && down ) + cgs.capturedItem = Display_CaptureItem( cgs.cursorX, cgs.cursorY ); + } +} + +int CG_ClientNumFromName( const char *p ) +{ + int i; + + for( i = 0; i < cgs.maxclients; i++ ) + { + if( cgs.clientinfo[ i ].infoValid && + Q_stricmp( cgs.clientinfo[ i ].name, p ) == 0 ) + return i; + } + + return -1; +} + +void CG_RunMenuScript( char **args ) +{ +} +//END TA UI + + +/* +================ +CG_DrawLighting + +================ +*/ +static void CG_DrawLighting( void ) +{ + centity_t *cent; + + cent = &cg_entities[ cg.snap->ps.clientNum ]; + + //fade to black if stamina is low + if( ( cg.snap->ps.stats[ STAT_STAMINA ] < STAMINA_BLACKOUT_LEVEL ) && + ( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) ) + { + vec4_t black = { 0, 0, 0, 0 }; + black[ 3 ] = 1.0 - ( (float)( cg.snap->ps.stats[ STAT_STAMINA ] + 1000 ) / 200.0f ); + trap_R_SetColor( black ); + CG_DrawPic( 0, 0, 640, 480, cgs.media.whiteShader ); + trap_R_SetColor( NULL ); + } +} + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + + +/* +============== +CG_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_CenterPrint( const char *str, int y, int charWidth ) +{ + char *s; + char newlineParsed[ MAX_STRING_CHARS ]; + const char *wrapped; + static int maxWidth = (int)( ( 2.0f / 3.0f ) * (float)SCREEN_WIDTH ); + + Q_ParseNewlines( newlineParsed, str, sizeof( newlineParsed ) ); + + wrapped = Item_Text_Wrap( newlineParsed, 0.5f, maxWidth ); + + Q_strncpyz( cg.centerPrint, wrapped, sizeof( cg.centerPrint ) ); + + cg.centerPrintTime = cg.time; + cg.centerPrintY = y; + cg.centerPrintCharWidth = charWidth; + + // count the number of lines for centering + cg.centerPrintLines = 1; + s = cg.centerPrint; + while( *s ) + { + if( *s == '\n' ) + cg.centerPrintLines++; + + s++; + } +} + + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) +{ + char *start; + int l; + int x, y, w; + int h; + float *color; + + if( !cg.centerPrintTime ) + return; + + color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); + if( !color ) + return; + + trap_R_SetColor( color ); + + start = cg.centerPrint; + + y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; + + while( 1 ) + { + char linebuffer[ MAX_STRING_CHARS ]; + + for( l = 0; l < sizeof(linebuffer) - 1; l++ ) + { + if( !start[ l ] || start[ l ] == '\n' ) + break; + + linebuffer[ l ] = start[ l ]; + } + + linebuffer[ l ] = 0; + + w = UI_Text_Width( linebuffer, 0.5 ); + h = UI_Text_Height( linebuffer, 0.5 ); + x = ( SCREEN_WIDTH - w ) / 2; + UI_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); + y += h + 6; + + while( *start && ( *start != '\n' ) ) + start++; + + if( !*start ) + break; + + start++; + } + + trap_R_SetColor( NULL ); +} + + + + + +//============================================================================== + +//FIXME: both vote notes are hardcoded, change to ownerdrawn? + +/* +================= +CG_DrawVote +================= +*/ +static void CG_DrawVote( team_t team ) +{ + char *s; + int sec; + int offset = 0; + vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; + char yeskey[ 32 ] = "", nokey[ 32 ] = ""; + + if( !cgs.voteTime[ team ] ) + return; + + // play a talk beep whenever it is modified + if( cgs.voteModified[ team ] ) + { + cgs.voteModified[ team ] = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg.time - cgs.voteTime[ team ] ) ) / 1000; + + if( sec < 0 ) + sec = 0; + + if( cg_tutorial.integer ) + { + Com_sprintf( yeskey, sizeof( yeskey ), "[%s]", + CG_KeyBinding( va( "%svote yes", team == TEAM_NONE ? "" : "team" ) ) ); + Com_sprintf( nokey, sizeof( nokey ), "[%s]", + CG_KeyBinding( va( "%svote no", team == TEAM_NONE ? "" : "team" ) ) ); + } + + if( team != TEAM_NONE ) + offset = 80; + + s = va( "%sVOTE(%i): %s", + team == TEAM_NONE ? "" : "TEAM", sec, cgs.voteString[ team ] ); + + UI_Text_Paint( 8, 300 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); + + s = va( " Called by: \"%s\"", cgs.voteCaller[ team ] ); + + UI_Text_Paint( 8, 320 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); + + s = va( " %sYes:%i %sNo:%i", + yeskey, cgs.voteYes[ team ], nokey, cgs.voteNo[ team ] ); + + UI_Text_Paint( 8, 340 + offset, 0.3f, white, s, 0, 0, + ITEM_TEXTSTYLE_NORMAL ); +} + + +static qboolean CG_DrawScoreboard( void ) +{ + static qboolean firstTime = qtrue; + float fade, *fadeColor; + + if( menuScoreboard ) + menuScoreboard->window.flags &= ~WINDOW_FORCED; + + if( cg_paused.integer ) + { + cg.deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + if( cg.showScores || + cg.predictedPlayerState.pm_type == PM_INTERMISSION ) + { + fade = 1.0; + fadeColor = colorWhite; + } + else + { + cg.deferredPlayerLoading = 0; + cg.killerName[ 0 ] = 0; + firstTime = qtrue; + return qfalse; + } + + + if( menuScoreboard == NULL ) + menuScoreboard = Menus_FindByName( "teamscore_menu" ); + + if( menuScoreboard ) + { + if( firstTime ) + { + cg.spectatorTime = trap_Milliseconds(); + CG_SetScoreSelection( menuScoreboard ); + firstTime = qfalse; + } + + Menu_Update( menuScoreboard ); + Menu_Paint( menuScoreboard, qtrue ); + } + + return qtrue; +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) +{ + menuDef_t *menu = Menus_FindByName( "default_hud" ); + + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); + + cg.scoreFadeTime = cg.time; + cg.scoreBoardShowing = CG_DrawScoreboard( ); +} + +/* +================= +CG_DrawQueue +================= +*/ +static qboolean CG_DrawQueue( void ) +{ + float w; + vec4_t color; + int position; + char *ordinal, buffer[ MAX_STRING_CHARS ]; + + if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) ) + return qfalse; + + color[ 0 ] = 1; + color[ 1 ] = 1; + color[ 2 ] = 1; + color[ 3 ] = 1; + + position = cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1; + if( position < 1 ) + return qfalse; + + switch( position % 100 ) + { + case 11: + case 12: + case 13: + ordinal = "th"; + break; + default: + switch( position % 10 ) + { + case 1: ordinal = "st"; break; + case 2: ordinal = "nd"; break; + case 3: ordinal = "rd"; break; + default: ordinal = "th"; break; + } + break; + } + + Com_sprintf( buffer, MAX_STRING_CHARS, "You are %d%s in the spawn queue", + position, ordinal ); + + w = UI_Text_Width( buffer, 0.7f ); + UI_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + + if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 0 ) + Com_sprintf( buffer, MAX_STRING_CHARS, "There are no spawns remaining" ); + else if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 1 ) + Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining" ); + else + Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining", + cg.snap->ps.persistant[ PERS_SPAWNS ] ); + + w = UI_Text_Width( buffer, 0.7f ); + UI_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + + return qtrue; +} + + +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) +{ + int sec = 0; + int w; + int h; + float size = 0.5f; + char text[ MAX_STRING_CHARS ] = "Warmup Time:"; + + if( !cg.warmupTime ) + return; + + sec = ( cg.warmupTime - cg.time ) / 1000; + + if( sec < 0 ) + return; + + w = UI_Text_Width( text, size ); + h = UI_Text_Height( text, size ); + UI_Text_Paint( 320 - w / 2, 200, size, colorWhite, text, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); + + Com_sprintf( text, sizeof( text ), "%s", sec ? va( "%d", sec ) : "FIGHT!" ); + + w = UI_Text_Width( text, size ); + UI_Text_Paint( 320 - w / 2, 200 + 1.5f * h, size, colorWhite, text, 0, 0, ITEM_TEXTSTYLE_SHADOWED ); +} + +/* +====================== +CG_DrawFuel +====================== +*/ +void CG_DrawFuel( void ) +{ + int i; + float x, y, w, h, scale, fuel; + vec4_t color; + char text[20]; + qboolean active; + + + if( !BG_InventoryContainsUpgrade( UP_JETPACK, cg.predictedPlayerState.stats ) ) + return; + + active = BG_UpgradeIsActive( UP_JETPACK, cg.predictedPlayerState.stats ) || + cg.predictedPlayerEntity.jetPackJumpTime + 100 > cg.time; + + fuel = (float)cg.predictedPlayerState.stats[ STAT_FUEL ] / JETPACK_FUEL_FULL; + fuel = ceil( fuel * 100 ); + + Com_sprintf( text, sizeof(text), "%.0f%%", fuel ); + + scale = cg_fuelInfoScale.value; + + w = UI_Text_Width( text, scale ); + h = UI_Text_Height( text, scale ); + x = 320.0f - w / 2.0f + cg_fuelInfoX.value; + y = 240.0f - h / 2.0f + cg_fuelInfoY.value; + + for( i = 0; i < 3; i++ ) + color[ i ] = ( active ? 1 : 0.5 ); + color[ 3 ] = 1.0f; + + UI_Text_Paint( x, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); +} + +/* +====================== +CG_DrawSplash +====================== +*/ +void CG_DrawSplash( void ) +{ + float t, x, y, w, h, ar; + vec4_t color; + int i; + static qboolean first = qtrue; + + if( first ) + { + cg.splashTime = cg.time; + first = qfalse; + } + + if( cg.splashTime + 4000 < cg.time ) + return; + + t = (float)( cg.time - cg.splashTime ) / 1000; + + ar = ( 4.0f / 3.0f ) / ( (float)cgs.glconfig.vidWidth / cgs.glconfig.vidHeight ); + h = 40; + w = h * ar; + x = 320 - w/2; + y = 240 - h/2; + + for( i = 0; i < 3; i++ ) + color[ i ] = 1.0f; + + if( t > 1.0f ) + { + float tw, th, ts; + + if( t < 2.0f ) + color[ 3 ] = t - 1.0f; + else if ( t > 3.5f ) + color[ 3 ] = ( 3.5f - t ) * 2; + else + color[ 3 ] = 1; + + trap_R_SetColor( color ); + + ts = 0.33f; + tw = UI_Text_Width( MODVER_TITLE, ts ); + th = UI_Text_Height( MODVER_TITLE, ts ); + UI_Text_Paint( x + w/2 - tw/2, y + h + th, ts, color, MODVER_TITLE, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); + } + + color[ 3 ] = ( t > 3.0f ) ? 4.0f - t : 1.0f; + + trap_R_SetColor( color ); + + CG_DrawPic( x, y, w, h, cgs.media.splashLogo[ cg.time / 25 % 15 ] ); + + for( i = 0; i < 3; i++ ) + color[ i ] = 0.7f; + + trap_R_SetColor( color ); + + x += w; + if( t < 1.0f ) + x += ( 1.0f - t ) * 400.0f * ar; + w *= 251.0f / 181.0f; + CG_DrawPic( x, y, w, h, cgs.media.splashRight ); + + + w = h * ar; + x = 320 - w/2; + if( t < 1.0f ) + x -= ( 1.0f - t ) * 400.0f * ar; + w *= 512.0f / 181.0f; + x -= w; + + CG_DrawPic( x, y, w, h, cgs.media.splashLeft ); + + trap_R_SetColor( NULL ); +} + +//================================================================================== + + +/* +================= +CG_Draw2D +================= +*/ +static void CG_Draw2D( void ) +{ + menuDef_t *menu = NULL; + + // if we are taking a levelshot for the menu, don't draw anything + if( cg.levelShot ) + return; + + // fading to black if stamina runs out + // (only 2D that can't be disabled) + CG_DrawLighting( ); + + if( cg_draw2D.integer == 0 ) + return; + + if( cg.snap->ps.pm_type == PM_INTERMISSION ) + { + CG_DrawIntermission( ); + return; + } + + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] == SPECTATOR_NOT && + cg.snap->ps.stats[ STAT_HEALTH ] > 0 ) + { + menu = Menus_FindByName( BG_ClassConfig( + cg.predictedPlayerState.stats[ STAT_CLASS ] )->hudName ); + + CG_DrawBuildableStatus( ); + CG_Cuboid_DrawInfo( ); + CG_DrawFuel( ); + } + + if( !menu ) + { + menu = Menus_FindByName( "default_hud" ); + + if( !menu ) // still couldn't find it + CG_Error( "Default HUD could not be found" ); + } + + Menu_Update( menu ); + Menu_Paint( menu, qtrue ); + + CG_DrawVote( TEAM_NONE ); + CG_DrawVote( cg.predictedPlayerState.stats[ STAT_TEAM ] ); + CG_DrawWarmup( ); + CG_DrawQueue( ); + + // don't draw center string if scoreboard is up + cg.scoreBoardShowing = CG_DrawScoreboard( ); + + if( !cg.scoreBoardShowing ) + CG_DrawCenterString( ); + + CG_DrawSplash( ); +} + +/* +=============== +CG_ScalePainBlendTCs +=============== +*/ +static void CG_ScalePainBlendTCs( float* s1, float *t1, float *s2, float *t2 ) +{ + *s1 -= 0.5f; + *t1 -= 0.5f; + *s2 -= 0.5f; + *t2 -= 0.5f; + + *s1 *= cg_painBlendZoom.value; + *t1 *= cg_painBlendZoom.value; + *s2 *= cg_painBlendZoom.value; + *t2 *= cg_painBlendZoom.value; + + *s1 += 0.5f; + *t1 += 0.5f; + *s2 += 0.5f; + *t2 += 0.5f; +} + +#define PAINBLEND_BORDER 0.15f + +/* +=============== +CG_PainBlend +=============== +*/ +static void CG_PainBlend( void ) +{ + vec4_t color; + int damage; + float damageAsFracOfMax; + qhandle_t shader = cgs.media.viewBloodShader; + float x, y, w, h; + float s1, t1, s2, t2; + + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT || cg.intermissionStarted ) + return; + + damage = cg.lastHealth - cg.snap->ps.stats[ STAT_HEALTH ]; + + if( damage < 0 ) + damage = 0; + + damageAsFracOfMax = (float)damage / cg.snap->ps.stats[ STAT_MAX_HEALTH ]; + cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ]; + + cg.painBlendValue += damageAsFracOfMax * cg_painBlendScale.value; + + if( cg.painBlendValue > 0.0f ) + { + cg.painBlendValue -= ( cg.frametime / 1000.0f ) * + cg_painBlendDownRate.value; + } + + if( cg.painBlendValue > 1.0f ) + cg.painBlendValue = 1.0f; + else if( cg.painBlendValue <= 0.0f ) + { + cg.painBlendValue = 0.0f; + return; + } + + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + VectorSet( color, 0.43f, 0.8f, 0.37f ); + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + VectorSet( color, 0.8f, 0.0f, 0.0f ); + + if( cg.painBlendValue > cg.painBlendTarget ) + { + cg.painBlendTarget += ( cg.frametime / 1000.0f ) * + cg_painBlendUpRate.value; + } + else if( cg.painBlendValue < cg.painBlendTarget ) + cg.painBlendTarget = cg.painBlendValue; + + if( cg.painBlendTarget > cg_painBlendMax.value ) + cg.painBlendTarget = cg_painBlendMax.value; + + color[ 3 ] = cg.painBlendTarget; + + trap_R_SetColor( color ); + + //left + x = 0.0f; y = 0.0f; + w = PAINBLEND_BORDER * 640.0f; h = 480.0f; + CG_AdjustFrom640( &x, &y, &w, &h ); + s1 = 0.0f; t1 = 0.0f; + s2 = PAINBLEND_BORDER; t2 = 1.0f; + CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 ); + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader ); + + //right + x = 640.0f - ( PAINBLEND_BORDER * 640.0f ); y = 0.0f; + w = PAINBLEND_BORDER * 640.0f; h = 480.0f; + CG_AdjustFrom640( &x, &y, &w, &h ); + s1 = 1.0f - PAINBLEND_BORDER; t1 = 0.0f; + s2 = 1.0f; t2 =1.0f; + CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 ); + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader ); + + //top + x = PAINBLEND_BORDER * 640.0f; y = 0.0f; + w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f; + CG_AdjustFrom640( &x, &y, &w, &h ); + s1 = PAINBLEND_BORDER; t1 = 0.0f; + s2 = 1.0f - PAINBLEND_BORDER; t2 = PAINBLEND_BORDER; + CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 ); + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader ); + + //bottom + x = PAINBLEND_BORDER * 640.0f; y = 480.0f - ( PAINBLEND_BORDER * 480.0f ); + w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f; + CG_AdjustFrom640( &x, &y, &w, &h ); + s1 = PAINBLEND_BORDER; t1 = 1.0f - PAINBLEND_BORDER; + s2 = 1.0f - PAINBLEND_BORDER; t2 = 1.0f; + CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 ); + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader ); + + trap_R_SetColor( NULL ); +} + +/* +===================== +CG_ResetPainBlend +===================== +*/ +void CG_ResetPainBlend( void ) +{ + cg.painBlendValue = 0.0f; + cg.painBlendTarget = 0.0f; + cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ]; +} + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) +{ + float separation; + vec3_t baseOrg; + + // optionally draw the info screen instead + if( !cg.snap ) + return; + + switch ( stereoView ) + { + case STEREO_CENTER: + separation = 0; + break; + case STEREO_LEFT: + separation = -cg_stereoSeparation.value / 2; + break; + case STEREO_RIGHT: + separation = cg_stereoSeparation.value / 2; + break; + default: + separation = 0; + CG_Error( "CG_DrawActive: Undefined stereoView" ); + } + + // clear around the rendered view if sized down + CG_TileClear( ); + + // offset vieworg appropriately if we're doing stereo separation + VectorCopy( cg.refdef.vieworg, baseOrg ); + + if( separation != 0 ) + VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[ 1 ], + cg.refdef.vieworg ); + + // draw 3D view + trap_R_RenderScene( &cg.refdef ); + + // restore original viewpoint if running stereo + if( separation != 0 ) + VectorCopy( baseOrg, cg.refdef.vieworg ); + + // first person blend blobs, done after AnglesToAxis + if( !cg.renderingThirdPerson ) + CG_PainBlend( ); + + // draw status bar and other floating elements + CG_Draw2D( ); +} + diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c new file mode 100644 index 0000000..f35ed23 --- /dev/null +++ b/src/cgame/cg_drawtools.c @@ -0,0 +1,439 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc + + +#include "cg_local.h" + +/* +=============== +CG_DrawPlane + +Draw a quad in 3 space - basically CG_DrawPic in 3 space +=============== +*/ +void CG_DrawPlane( vec3_t origin, vec3_t down, vec3_t right, qhandle_t shader ) +{ + polyVert_t verts[ 4 ]; + vec3_t temp; + + VectorCopy( origin, verts[ 0 ].xyz ); + verts[ 0 ].st[ 0 ] = 0; + verts[ 0 ].st[ 1 ] = 0; + verts[ 0 ].modulate[ 0 ] = 255; + verts[ 0 ].modulate[ 1 ] = 255; + verts[ 0 ].modulate[ 2 ] = 255; + verts[ 0 ].modulate[ 3 ] = 255; + + VectorAdd( origin, right, temp ); + VectorCopy( temp, verts[ 1 ].xyz ); + verts[ 1 ].st[ 0 ] = 1; + verts[ 1 ].st[ 1 ] = 0; + verts[ 1 ].modulate[ 0 ] = 255; + verts[ 1 ].modulate[ 1 ] = 255; + verts[ 1 ].modulate[ 2 ] = 255; + verts[ 1 ].modulate[ 3 ] = 255; + + VectorAdd( origin, right, temp ); + VectorAdd( temp, down, temp ); + VectorCopy( temp, verts[ 2 ].xyz ); + verts[ 2 ].st[ 0 ] = 1; + verts[ 2 ].st[ 1 ] = 1; + verts[ 2 ].modulate[ 0 ] = 255; + verts[ 2 ].modulate[ 1 ] = 255; + verts[ 2 ].modulate[ 2 ] = 255; + verts[ 2 ].modulate[ 3 ] = 255; + + VectorAdd( origin, down, temp ); + VectorCopy( temp, verts[ 3 ].xyz ); + verts[ 3 ].st[ 0 ] = 0; + verts[ 3 ].st[ 1 ] = 1; + verts[ 3 ].modulate[ 0 ] = 255; + verts[ 3 ].modulate[ 1 ] = 255; + verts[ 3 ].modulate[ 2 ] = 255; + verts[ 3 ].modulate[ 3 ] = 255; + + trap_R_AddPolyToScene( shader, 4, verts ); +} + +/* +================ +CG_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) +{ +#if 0 + // adjust for wide screens + if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + // scale for screen sizes + *x *= cgs.screenXScale; + *y *= cgs.screenYScale; + *w *= cgs.screenXScale; + *h *= cgs.screenYScale; +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) +{ + trap_R_SetColor( color ); + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); + + trap_R_SetColor( NULL ); +} + + +/* +================ +CG_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides( float x, float y, float w, float h, float size ) +{ + float sizeY; + + CG_AdjustFrom640( &x, &y, &w, &h ); + sizeY = size * cgs.screenYScale; + size *= cgs.screenXScale; + + trap_R_DrawStretchPic( x, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, cgs.media.whiteShader ); +} + +void CG_DrawTopBottom( float x, float y, float w, float h, float size ) +{ + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenYScale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); +} + + +/* +================ +CG_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) +{ + trap_R_SetColor( color ); + + CG_DrawTopBottom( x, y, width, height, size ); + CG_DrawSides( x, y, width, height, size ); + + trap_R_SetColor( NULL ); +} + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) +{ + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +/* +================ +CG_SetClipRegion +================= +*/ +void CG_SetClipRegion( float x, float y, float w, float h ) +{ + vec4_t clip; + + CG_AdjustFrom640( &x, &y, &w, &h ); + + clip[ 0 ] = x; + clip[ 1 ] = y; + clip[ 2 ] = x + w; + clip[ 3 ] = y + h; + + trap_R_SetClipRegion( clip ); +} + +/* +================ +CG_ClearClipRegion +================= +*/ +void CG_ClearClipRegion( void ) +{ + trap_R_SetClipRegion( NULL ); +} + +/* +================ +CG_DrawFadePic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor, + vec4_t tcolor, float amount, qhandle_t hShader ) +{ + vec4_t finalcolor; + float inverse; + + inverse = 100 - amount; + + CG_AdjustFrom640( &x, &y, &width, &height ); + + finalcolor[ 0 ] = ( ( inverse * fcolor[ 0 ] ) + ( amount * tcolor[ 0 ] ) ) / 100; + finalcolor[ 1 ] = ( ( inverse * fcolor[ 1 ] ) + ( amount * tcolor[ 1 ] ) ) / 100; + finalcolor[ 2 ] = ( ( inverse * fcolor[ 2 ] ) + ( amount * tcolor[ 2 ] ) ) / 100; + finalcolor[ 3 ] = ( ( inverse * fcolor[ 3 ] ) + ( amount * tcolor[ 3 ] ) ) / 100; + + trap_R_SetColor( finalcolor ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); + trap_R_SetColor( NULL ); +} + +/* +================= +CG_DrawStrlen + +Returns character count, skiping color escape codes +================= +*/ +int CG_DrawStrlen( const char *str ) +{ + const char *s = str; + int count = 0; + + while( *s ) + { + if( Q_IsColorString( s ) ) + s += 2; + else + { + count++; + s++; + } + } + + return count; +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) +{ + float s1, t1, s2, t2; + + s1 = x / 64.0; + t1 = y / 64.0; + s2 = ( x + w ) / 64.0; + t2 = ( y + h ) / 64.0; + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) +{ + int top, bottom, left, right; + int w, h; + + w = cgs.glconfig.vidWidth; + h = cgs.glconfig.vidHeight; + + if( cg.refdef.x == 0 && cg.refdef.y == 0 && + cg.refdef.width == w && cg.refdef.height == h ) + return; // full screen rendering + + top = cg.refdef.y; + bottom = top + cg.refdef.height - 1; + left = cg.refdef.x; + right = left + cg.refdef.width - 1; + + // clear above view screen + CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + + // clear below view screen + CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + + // clear left of view screen + CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + + // clear right of view screen + CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +} + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) +{ + static vec4_t color; + int t; + + if( startMsec == 0 ) + return NULL; + + t = cg.time - startMsec; + + if( t >= totalMsec ) + return NULL; + + // fade out + if( totalMsec - t < FADE_TIME ) + color[ 3 ] = ( totalMsec - t ) * 1.0 / FADE_TIME; + else + color[ 3 ] = 1.0; + + color[ 0 ] = color[ 1 ] = color[ 2 ] = 1; + + return color; +} + +/* +================ +CG_WorldToScreen +================ +*/ +qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ) +{ + vec3_t trans; + float xc, yc; + float px, py; + float z; + + px = tan( cg.refdef.fov_x * M_PI / 360.0f ); + py = tan( cg.refdef.fov_y * M_PI / 360.0f ); + + VectorSubtract( point, cg.refdef.vieworg, trans ); + + xc = ( 640.0f * cg_viewsize.integer ) / 200.0f; + yc = ( 480.0f * cg_viewsize.integer ) / 200.0f; + + z = DotProduct( trans, cg.refdef.viewaxis[ 0 ] ); + if( z <= 0.001f ) + return qfalse; + + if( x ) + *x = 320.0f - DotProduct( trans, cg.refdef.viewaxis[ 1 ] ) * xc / ( z * px ); + + if( y ) + *y = 240.0f - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py ); + + return qtrue; +} + +/* +================ +CG_KeyBinding +================ +*/ +char *CG_KeyBinding( const char *bind ) +{ + static char key[ 32 ]; + char bindbuff[ MAX_CVAR_VALUE_STRING ]; + int i; + + key[ 0 ] = '\0'; + + // NOTE: change K_LAST_KEY to MAX_KEYS for full key support (eventually) + for( i = 0; i < K_LAST_KEY; i++ ) + { + trap_Key_GetBindingBuf( i, bindbuff, sizeof( bindbuff ) ); + if( !Q_stricmp( bindbuff, bind ) ) + { + trap_Key_KeynumToStringBuf( i, key, sizeof( key ) ); + break; + } + } + + if( !key[ 0 ] ) + { + Q_strncpyz( key, "\\", sizeof( key ) ); + Q_strcat( key, sizeof( key ), bind ); + } + + return key; +} + +/* +================= +CG_GetColorCharForHealth +================= +*/ +char CG_GetColorCharForHealth( int clientnum ) +{ + char health_char = '2'; + int healthPercent; + int maxHealth; + int curWeaponClass = cgs.clientinfo[ clientnum ].curWeaponClass; + + if( cgs.clientinfo[ clientnum ].team == TEAM_ALIENS ) + maxHealth = BG_Class( curWeaponClass )->health; + else + maxHealth = BG_Class( PCL_HUMAN )->health; + + healthPercent = (int) ( 100.0f * (float) cgs.clientinfo[ clientnum ].health / + (float) maxHealth ); + + if( healthPercent < 33 ) + health_char = '1'; + else if( healthPercent < 67 ) + health_char = '3'; + return health_char; +} diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c new file mode 100644 index 0000000..a68c5eb --- /dev/null +++ b/src/cgame/cg_ents.c @@ -0,0 +1,1252 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_ents.c -- present snapshot entities, happens every single frame + + +#include "cg_local.h" + +/* +====================== +CG_DrawBoxFace + +Draws a bounding box face +====================== +*/ +static void CG_DrawBoxFace( vec3_t a, vec3_t b, vec3_t c, vec3_t d ) +{ + polyVert_t verts[ 4 ]; + vec4_t color = { 255.0f, 0.0f, 0.0f, 128.0f }; + + VectorCopy( d, verts[ 0 ].xyz ); + verts[ 0 ].st[ 0 ] = 1; + verts[ 0 ].st[ 1 ] = 1; + Vector4Copy( color, verts[ 0 ].modulate ); + + VectorCopy( c, verts[ 1 ].xyz ); + verts[ 1 ].st[ 0 ] = 1; + verts[ 1 ].st[ 1 ] = 0; + Vector4Copy( color, verts[ 1 ].modulate ); + + VectorCopy( b, verts[ 2 ].xyz ); + verts[ 2 ].st[ 0 ] = 0; + verts[ 2 ].st[ 1 ] = 0; + Vector4Copy( color, verts[ 2 ].modulate ); + + VectorCopy( a, verts[ 3 ].xyz ); + verts[ 3 ].st[ 0 ] = 0; + verts[ 3 ].st[ 1 ] = 1; + Vector4Copy( color, verts[ 3 ].modulate ); + + trap_R_AddPolyToScene( cgs.media.outlineShader, 4, verts ); +} + +/* +====================== +CG_DrawBoundingBox + +Draws a bounding box +====================== +*/ +void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs ) +{ + vec3_t ppp, mpp, mmp, pmp; + vec3_t mmm, pmm, ppm, mpm; + + ppp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + ppp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + ppp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + + mpp[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mpp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + mpp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + + mmp[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; + mmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + + pmp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + pmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; + pmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + + ppm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + ppm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + ppm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + + mpm[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mpm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + mpm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + + mmm[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; + mmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + + pmm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + pmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; + pmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + + //phew! + + CG_DrawBoxFace( ppp, mpp, mmp, pmp ); + CG_DrawBoxFace( ppp, pmp, pmm, ppm ); + CG_DrawBoxFace( mpp, ppp, ppm, mpm ); + CG_DrawBoxFace( mmp, mpp, mpm, mmm ); + CG_DrawBoxFace( pmp, mmp, mmm, pmm ); + CG_DrawBoxFace( mmm, mpm, ppm, pmm ); +} + + +/* +====================== +CG_PositionEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) +{ + int i; + orientation_t lerped; + + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for( i = 0; i < 3; i++ ) + VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin ); + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( lerped.axis, ( (refEntity_t *)parent )->axis, entity->axis ); + entity->backlerp = parent->backlerp; +} + + +/* +====================== +CG_PositionRotatedEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) +{ + int i; + orientation_t lerped; + vec3_t tempAxis[ 3 ]; + +//AxisClear( entity->axis ); + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for( i = 0; i < 3; i++ ) + VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin ); + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( entity->axis, lerped.axis, tempAxis ); + MatrixMultiply( tempAxis, ( (refEntity_t *)parent )->axis, entity->axis ); +} + + + +/* +========================================================================== + +FUNCTIONS CALLED EACH FRAME + +========================================================================== +*/ + +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +void CG_SetEntitySoundPosition( centity_t *cent ) +{ + if( cent->currentState.solid == SOLID_BMODEL ) + { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_UpdateEntityPosition( cent->currentState.number, origin ); + } + else + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); +} + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) +{ + // update sound origins + CG_SetEntitySoundPosition( cent ); + + // add loop sound + if( cent->currentState.loopSound ) + { + if( cent->currentState.eType != ET_SPEAKER ) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } + else + { + trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } + } + + + // constant light glow + if ( cent->currentState.constantLight ) + { + int cl; + int i, r, g, b; + + cl = cent->currentState.constantLight; + r = cl & 255; + g = ( cl >> 8 ) & 255; + b = ( cl >> 16 ) & 255; + i = ( ( cl >> 24 ) & 255 ) * 4; + trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); + } + + if( CG_IsTrailSystemValid( ¢->muzzleTS ) ) + { + //FIXME hack to prevent tesla trails reaching too far + if( cent->currentState.eType == ET_BUILDABLE ) + { + vec3_t front, back; + + CG_AttachmentPoint( ¢->muzzleTS->frontAttachment, front ); + CG_AttachmentPoint( ¢->muzzleTS->backAttachment, back ); + + if( Distance( front, back ) > ( TESLAGEN_RANGE * M_ROOT3 ) ) + CG_DestroyTrailSystem( ¢->muzzleTS ); + } + + if( cg.time > cent->muzzleTSDeathTime && CG_IsTrailSystemValid( ¢->muzzleTS ) ) + CG_DestroyTrailSystem( ¢->muzzleTS ); + } +} + + +/* +================== +CG_General +================== +*/ +static void CG_General( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // if set to invisible, skip + if( !s1->modelindex ) + return; + + memset( &ent, 0, sizeof( ent ) ); + + // set frame + + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.hModel = cgs.gameModels[ s1->modelindex ]; + + // player model + if( s1->number == cg.snap->ps.clientNum ) + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + + // convert angles to axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) +{ + if( ! cent->currentState.clientNum ) + { // FIXME: use something other than clientNum... + return; // not auto triggering + } + + if( cg.time < cent->miscTime ) + return; + + trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[ cent->currentState.eventParm ] ); + + // ent->s.frame = ent->wait * 10; + // ent->s.clientNum = ent->random * 10; + cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom( ); +} + + +//============================================================================ + +/* +=============== +CG_LaunchMissile +=============== +*/ +static void CG_LaunchMissile( centity_t *cent ) +{ + entityState_t *es; + const weaponInfo_t *wi; + particleSystem_t *ps; + trailSystem_t *ts; + weapon_t weapon; + weaponMode_t weaponMode; + + es = ¢->currentState; + + weapon = es->weapon; + if( weapon > WP_NUM_WEAPONS ) + weapon = WP_NONE; + + wi = &cg_weapons[ weapon ]; + weaponMode = es->generic1; + + if( wi->wim[ weaponMode ].missileParticleSystem ) + { + ps = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].missileParticleSystem ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentCent( &ps->attachment, cent ); + CG_AttachToCent( &ps->attachment ); + ps->charge = es->torsoAnim; + } + } + + if( wi->wim[ weaponMode ].missileTrailSystem ) + { + ts = CG_SpawnNewTrailSystem( wi->wim[ weaponMode ].missileTrailSystem ); + + if( CG_IsTrailSystemValid( &ts ) ) + { + CG_SetAttachmentCent( &ts->frontAttachment, cent ); + CG_AttachToCent( &ts->frontAttachment ); + } + } +} + +/* +=============== +CG_Missile +=============== +*/ +static void CG_Missile( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *es; + const weaponInfo_t *wi; + weapon_t weapon; + weaponMode_t weaponMode; + const weaponInfoMode_t *wim; + + es = ¢->currentState; + + weapon = es->weapon; + if( weapon > WP_NUM_WEAPONS ) + weapon = WP_NONE; + + wi = &cg_weapons[ weapon ]; + weaponMode = es->generic1; + + wim = &wi->wim[ weaponMode ]; + + // calculate the axis + VectorCopy( es->angles, cent->lerpAngles ); + + // add dynamic light + if( wim->missileDlight ) + { + trap_R_AddLightToScene( cent->lerpOrigin, wim->missileDlight, + wim->missileDlightColor[ 0 ], + wim->missileDlightColor[ 1 ], + wim->missileDlightColor[ 2 ] ); + } + + // add missile sound + if( wim->missileSound ) + { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, wim->missileSound ); + } + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + + if( wim->usesSpriteMissle ) + { + ent.reType = RT_SPRITE; + ent.radius = wim->missileSpriteSize + + wim->missileSpriteCharge * es->torsoAnim; + ent.rotation = 0; + ent.customShader = wim->missileSprite; + ent.shaderRGBA[ 0 ] = 0xFF; + ent.shaderRGBA[ 1 ] = 0xFF; + ent.shaderRGBA[ 2 ] = 0xFF; + ent.shaderRGBA[ 3 ] = 0xFF; + } + else + { + ent.hModel = wim->missileModel; + ent.renderfx = wim->missileRenderfx | RF_NOSHADOW; + + // convert direction of travel into axis + if( VectorNormalize2( es->pos.trDelta, ent.axis[ 0 ] ) == 0 ) + ent.axis[ 0 ][ 2 ] = 1; + + // spin as it moves + if( es->pos.trType != TR_STATIONARY && wim->missileRotates ) + RotateAroundDirection( ent.axis, cg.time / 4 ); + else + RotateAroundDirection( ent.axis, es->time ); + + if( wim->missileAnimates ) + { + int timeSinceStart = cg.time - es->time; + + if( wim->missileAnimLooping ) + { + ent.frame = wim->missileAnimStartFrame + + (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ) % + wim->missileAnimNumFrames; + } + else + { + ent.frame = wim->missileAnimStartFrame + + (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ); + + if( ent.frame > ( wim->missileAnimStartFrame + wim->missileAnimNumFrames ) ) + ent.frame = wim->missileAnimStartFrame + wim->missileAnimNumFrames; + } + } + } + + //only refresh if there is something to display + if( wim->missileSprite || wim->missileModel ) + trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + // flicker between two skins (FIXME?) + ent.skinNum = ( cg.time >> 6 ) & 1; + + // get the model, either as a bmodel or a modelindex + if( s1->solid == SOLID_BMODEL ) + ent.hModel = cgs.inlineDrawModel[ s1->modelindex ]; + else + ent.hModel = cgs.gameModels[ s1->modelindex ]; + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); + + // add the secondary model + if( s1->modelindex2 ) + { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[ s1->modelindex2 ]; + trap_R_AddRefEntityToScene( &ent ); + } + +} + +/* +=============== +CG_Beam + +Also called as an event +=============== +*/ +void CG_Beam( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( s1->pos.trBase, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + AxisClear( ent.axis ); + ent.reType = RT_BEAM; + + ent.renderfx = RF_NOSHADOW; + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + ByteToDir( s1->eventParm, ent.axis[ 0 ] ); + PerpendicularVector( ent.axis[ 1 ], ent.axis[ 0 ] ); + + // negating this tends to get the directions like they want + // we really should have a camera roll value + VectorSubtract( vec3_origin, ent.axis[ 1 ], ent.axis[ 1 ] ); + + CrossProduct( ent.axis[ 0 ], ent.axis[ 1 ], ent.axis[ 2 ] ); + ent.reType = RT_PORTALSURFACE; + ent.oldframe = s1->misc; + ent.frame = s1->frame; // rotation speed + ent.skinNum = s1->clientNum / 256.0 * 360; // roll offset + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + +//============================================================================ + +#define SETBOUNDS(v1,v2,r) ((v1)[0]=(-r/2),(v1)[1]=(-r/2),(v1)[2]=(-r/2),\ + (v2)[0]=(r/2),(v2)[1]=(r/2),(v2)[2]=(r/2)) +#define RADIUSSTEP 0.5f + +#define FLARE_OFF 0 +#define FLARE_NOFADE 1 +#define FLARE_TIMEFADE 2 +#define FLARE_REALFADE 3 + +/* +========================= +CG_LightFlare +========================= +*/ +static void CG_LightFlare( centity_t *cent ) +{ + refEntity_t flare; + entityState_t *es; + vec3_t forward, delta; + float len; + trace_t tr; + float maxAngle; + vec3_t mins, maxs, start, end; + float srcRadius, srLocal, ratio = 1.0f; + int entityNum; + + es = ¢->currentState; + + if( cg.renderingThirdPerson ) + entityNum = MAGIC_TRACE_HACK; + else + entityNum = cg.predictedPlayerState.clientNum; + + //don't draw light flares + if( cg_lightFlare.integer == FLARE_OFF ) + return; + + //flare is "off" + if( es->eFlags & EF_NODRAW ) + return; + + CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, es->angles2, + entityNum, MASK_SHOT ); + + //if there is no los between the view and the flare source + //it definately cannot be seen + if( tr.fraction < 1.0f || tr.allsolid ) + return; + + memset( &flare, 0, sizeof( flare ) ); + + flare.reType = RT_SPRITE; + flare.customShader = cgs.gameShaders[ es->modelindex ]; + flare.shaderRGBA[ 0 ] = 0xFF; + flare.shaderRGBA[ 1 ] = 0xFF; + flare.shaderRGBA[ 2 ] = 0xFF; + flare.shaderRGBA[ 3 ] = 0xFF; + + //flares always drawn before the rest of the scene + flare.renderfx |= RF_DEPTHHACK; + + //bunch of geometry + AngleVectors( es->angles, forward, NULL, NULL ); + VectorCopy( cent->lerpOrigin, flare.origin ); + VectorSubtract( flare.origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + VectorNormalize( delta ); + + //flare is too close to camera to be drawn + if( len < es->generic1 ) + return; + + //don't bother for flares behind the view plane + if( DotProduct( delta, cg.refdef.viewaxis[ 0 ] ) < 0.0 ) + return; + + //only recalculate radius and ratio every three frames + if( !( cg.clientFrame % 2 ) ) + { + //can only see the flare when in front of it + flare.radius = len / es->origin2[ 0 ]; + + if( es->origin2[ 2 ] == 0 ) + srcRadius = srLocal = flare.radius / 2.0f; + else + srcRadius = srLocal = len / es->origin2[ 2 ]; + + maxAngle = es->origin2[ 1 ]; + + if( maxAngle > 0.0f ) + { + float radiusMod = 1.0f - ( 180.0f - RAD2DEG( + acos( DotProduct( delta, forward ) ) ) ) / maxAngle; + + if( radiusMod < 0.0f ) + radiusMod = 0.0f; + + flare.radius *= radiusMod; + } + + if( flare.radius < 0.0f ) + flare.radius = 0.0f; + + VectorMA( flare.origin, -flare.radius, delta, end ); + VectorMA( cg.refdef.vieworg, flare.radius, delta, start ); + + if( cg_lightFlare.integer == FLARE_REALFADE ) + { + //"correct" flares + CG_BiSphereTrace( &tr, cg.refdef.vieworg, end, + 1.0f, srcRadius, entityNum, MASK_SHOT ); + + if( tr.fraction < 1.0f ) + ratio = tr.lateralFraction; + else + ratio = 1.0f; + } + else if( cg_lightFlare.integer == FLARE_TIMEFADE ) + { + //draw timed flares + SETBOUNDS( mins, maxs, srcRadius ); + CG_Trace( &tr, start, mins, maxs, end, + entityNum, MASK_SHOT ); + + if( ( tr.fraction < 1.0f || tr.startsolid ) && cent->lfs.status ) + { + cent->lfs.status = qfalse; + cent->lfs.lastTime = cg.time; + } + else if( ( tr.fraction == 1.0f && !tr.startsolid ) && !cent->lfs.status ) + { + cent->lfs.status = qtrue; + cent->lfs.lastTime = cg.time; + } + + //fade flare up + if( cent->lfs.status ) + { + if( cent->lfs.lastTime + es->time > cg.time ) + ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time; + } + + //fade flare down + if( !cent->lfs.status ) + { + if( cent->lfs.lastTime + es->time > cg.time ) + { + ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time; + ratio = 1.0f - ratio; + } + else + ratio = 0.0f; + } + } + else if( cg_lightFlare.integer == FLARE_NOFADE ) + { + //draw nofade flares + SETBOUNDS( mins, maxs, srcRadius ); + CG_Trace( &tr, start, mins, maxs, end, + entityNum, MASK_SHOT ); + + //flare source occluded + if( ( tr.fraction < 1.0f || tr.startsolid ) ) + ratio = 0.0f; + } + } + else + { + ratio = cent->lfs.lastRatio; + flare.radius = cent->lfs.lastRadius; + } + + cent->lfs.lastRatio = ratio; + cent->lfs.lastRadius = flare.radius; + + if( ratio < 1.0f ) + { + flare.radius *= ratio; + flare.shaderRGBA[ 3 ] = (byte)( (float)flare.shaderRGBA[ 3 ] * ratio ); + } + + if( flare.radius <= 0.0f ) + return; + + trap_R_AddRefEntityToScene( &flare ); +} + +/* +========================= +CG_Lev2ZapChain +========================= +*/ +static void CG_Lev2ZapChain( centity_t *cent ) +{ + int i; + entityState_t *es; + centity_t *source = NULL, *target = NULL; + int entityNums[ LEVEL2_AREAZAP_MAX_TARGETS + 1 ]; + int count; + + es = ¢->currentState; + + count = BG_UnpackEntityNumbers( es, entityNums, LEVEL2_AREAZAP_MAX_TARGETS + 1 ); + + for( i = 1; i < count; i++ ) + { + if( i == 1 ) + { + // First entity is the attacker + source = &cg_entities[ entityNums[ 0 ] ]; + } + else + { + // Subsequent zaps come from the first target + source = &cg_entities[ entityNums[ 1 ] ]; + } + + target = &cg_entities[ entityNums[ i ] ]; + + if( !CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) + cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); + + if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) + { + CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->frontAttachment, source ); + CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->backAttachment, target ); + CG_AttachToCent( ¢->level2ZapTS[ i ]->frontAttachment ); + CG_AttachToCent( ¢->level2ZapTS[ i ]->backAttachment ); + } + } +} + +/* +========================= +CG_AdjustPositionForMover + +Also called by client movement prediction code +========================= +*/ +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) +{ + centity_t *cent; + vec3_t oldOrigin, origin, deltaOrigin; + vec3_t oldAngles, angles, deltaAngles; + + if( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) + { + VectorCopy( in, out ); + return; + } + + cent = &cg_entities[ moverNum ]; + + if( cent->currentState.eType != ET_MOVER ) + { + VectorCopy( in, out ); + return; + } + + BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); + + BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); + BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); + + VectorSubtract( origin, oldOrigin, deltaOrigin ); + VectorSubtract( angles, oldAngles, deltaAngles ); + + VectorAdd( in, deltaOrigin, out ); + + // FIXME: origin change when on a rotating object +} + + +/* +============================= +CG_InterpolateEntityPosition +============================= +*/ +static void CG_InterpolateEntityPosition( centity_t *cent ) +{ + vec3_t current, next; + float f; + + // it would be an internal error to find an entity that interpolates without + // a snapshot ahead of the current one + if( cg.nextSnap == NULL ) + CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); + + f = cg.frameInterpolation; + + // this will linearize a sine or parabolic curve, but it is important + // to not extrapolate player positions if more recent data is available + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + + cent->lerpOrigin[ 0 ] = current[ 0 ] + f * ( next[ 0 ] - current[ 0 ] ); + cent->lerpOrigin[ 1 ] = current[ 1 ] + f * ( next[ 1 ] - current[ 1 ] ); + cent->lerpOrigin[ 2 ] = current[ 2 ] + f * ( next[ 2 ] - current[ 2 ] ); + + BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); + + cent->lerpAngles[ 0 ] = LerpAngle( current[ 0 ], next[ 0 ], f ); + cent->lerpAngles[ 1 ] = LerpAngle( current[ 1 ], next[ 1 ], f ); + cent->lerpAngles[ 2 ] = LerpAngle( current[ 2 ], next[ 2 ], f ); + +} + +/* +=============== +CG_CalcEntityLerpPositions + +=============== +*/ +static void CG_CalcEntityLerpPositions( centity_t *cent ) +{ + // this will be set to how far forward projectiles will be extrapolated + int timeshift = 0; + + // if this player does not want to see extrapolated players + if( !cg_smoothClients.integer ) + { + // make sure the clients use TR_INTERPOLATE + if( cent->currentState.number < MAX_CLIENTS ) + { + cent->currentState.pos.trType = TR_INTERPOLATE; + cent->nextState.pos.trType = TR_INTERPOLATE; + } + } + + if( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) + { + CG_InterpolateEntityPosition( cent ); + return; + } + + // first see if we can interpolate between two snaps for + // linear extrapolated clients + if( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && + cent->currentState.number < MAX_CLIENTS ) + { + CG_InterpolateEntityPosition( cent ); + return; + } + + if( cg_projectileNudge.integer && + !cg.demoPlayback && + cent->currentState.eType == ET_MISSILE && + !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + { + timeshift = cg.ping; + } + + // just use the current frame and evaluate as best we can + BG_EvaluateTrajectory( ¢->currentState.pos, + ( cg.time + timeshift ), cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, + ( cg.time + timeshift ), cent->lerpAngles ); + + if( timeshift ) + { + trace_t tr; + vec3_t lastOrigin; + + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, lastOrigin ); + + CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin, + cent->currentState.number, MASK_SHOT ); + + // don't let the projectile go through the floor + if( tr.fraction < 1.0f ) + VectorLerp( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin ); + } + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if( cent != &cg.predictedPlayerEntity ) + { + CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, + cg.snap->serverTime, cg.time, cent->lerpOrigin ); + } +} + + +/* +=============== +CG_CEntityPVSEnter + +=============== +*/ +static void CG_CEntityPVSEnter( centity_t *cent ) +{ + entityState_t *es = ¢->currentState; + + if( cg_debugPVS.integer ) + CG_Printf( "Entity %d entered PVS\n", cent->currentState.number ); + + switch( es->eType ) + { + case ET_MISSILE: + CG_LaunchMissile( cent ); + break; + + case ET_BUILDABLE: + cent->lastBuildableHealth = es->generic1; + break; + } + + //clear any particle systems from previous uses of this centity_t + cent->muzzlePS = NULL; + cent->muzzlePsTrigger = qfalse; + cent->jetPackPS = NULL; + cent->jetPackState = JPS_OFF; + cent->buildablePS = NULL; + cent->entityPS = NULL; + cent->entityPSMissing = qfalse; + + //make sure that the buildable animations are in a consistent state + //when a buildable enters the PVS + cent->buildableAnim = cent->lerpFrame.animationNumber = BANIM_NONE; + cent->oldBuildableAnim = es->legsAnim; +} + + +/* +=============== +CG_CEntityPVSLeave + +=============== +*/ +static void CG_CEntityPVSLeave( centity_t *cent ) +{ + int i; + entityState_t *es = ¢->currentState; + + if( cg_debugPVS.integer ) + CG_Printf( "Entity %d left PVS\n", cent->currentState.number ); + switch( es->eType ) + { + case ET_LEV2_ZAP_CHAIN: + for( i = 0; i <= LEVEL2_AREAZAP_MAX_TARGETS; i++ ) + { + if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) + CG_DestroyTrailSystem( ¢->level2ZapTS[ i ] ); + } + break; + } +} + +/* +=============== +CG_AddCEntity + +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) +{ + // event-only entities will have been dealt with already + if( cent->currentState.eType >= ET_EVENTS ) + return; + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + // add automatic effects + CG_EntityEffects( cent ); + + switch( cent->currentState.eType ) + { + default: + CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + break; + + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + case ET_TELEPORT_TRIGGER: + case ET_LOCATION: + break; + + case ET_GENERAL: + CG_General( cent ); + break; + + case ET_CORPSE: + CG_Corpse( cent ); + break; + + case ET_PLAYER: + CG_Player( cent ); + break; + + case ET_BUILDABLE: + CG_Buildable( cent ); + break; + + case ET_MISSILE: + CG_Missile( cent ); + break; + + case ET_MOVER: + CG_Mover( cent ); + break; + + case ET_BEAM: + CG_Beam( cent ); + break; + + case ET_PORTAL: + CG_Portal( cent ); + break; + + case ET_SPEAKER: + CG_Speaker( cent ); + break; + + case ET_PARTICLE_SYSTEM: + CG_ParticleSystemEntity( cent ); + break; + + case ET_ANIMMAPOBJ: + CG_AnimMapObj( cent ); + break; + + case ET_MODELDOOR: + CG_ModelDoor( cent ); + break; + + case ET_LIGHTFLARE: + CG_LightFlare( cent ); + break; + + case ET_LEV2_ZAP_CHAIN: + CG_Lev2ZapChain( cent ); + break; + } +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( void ) +{ + int num; + centity_t *cent; + playerState_t *ps; + + // set cg.frameInterpolation + if( cg.nextSnap ) + { + int delta; + + delta = ( cg.nextSnap->serverTime - cg.snap->serverTime ); + + if( delta == 0 ) + cg.frameInterpolation = 0; + else + cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; + } + else + { + cg.frameInterpolation = 0; // actually, it should never be used, because + // no entities should be marked as interpolating + } + + // the auto-rotating items will all have the same axis + cg.autoAngles[ 0 ] = 0; + cg.autoAngles[ 1 ] = ( cg.time & 2047 ) * 360 / 2048.0; + cg.autoAngles[ 2 ] = 0; + + cg.autoAnglesFast[ 0 ] = 0; + cg.autoAnglesFast[ 1 ] = ( cg.time & 1023 ) * 360 / 1024.0f; + cg.autoAnglesFast[ 2 ] = 0; + + AnglesToAxis( cg.autoAngles, cg.autoAxis ); + AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + + // generate and add the entity from the playerstate + ps = &cg.predictedPlayerState; + BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); + cg.predictedPlayerEntity.valid = qtrue; + CG_AddCEntity( &cg.predictedPlayerEntity ); + + // lerp the non-predicted value for lightning gun origins + CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + + // scanner + CG_UpdateEntityPositions( ); + + for( num = 0; num < MAX_GENTITIES; num++ ) + cg_entities[ num ].valid = qfalse; + + // add each entity sent over by the server + for( num = 0; num < cg.snap->numEntities; num++ ) + { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + cent->valid = qtrue; + } + + for( num = 0; num < MAX_GENTITIES; num++ ) + { + cent = &cg_entities[ num ]; + + if( cent->valid && !cent->oldValid ) + CG_CEntityPVSEnter( cent ); + else if( !cent->valid && cent->oldValid ) + CG_CEntityPVSLeave( cent ); + + cent->oldValid = cent->valid; + } + + // add each entity sent over by the server + for( num = 0; num < cg.snap->numEntities; num++ ) + { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + CG_AddCEntity( cent ); + } + + //make an attempt at drawing bounding boxes of selected entity types + if( cg_drawBBOX.integer ) + { + for( num = 0; num < cg.snap->numEntities; num++ ) + { + float x, zd, zu; + vec3_t mins, maxs; + entityState_t *es; + + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + es = ¢->currentState; + + switch( es->eType ) + { + case ET_BUILDABLE: + case ET_MISSILE: + case ET_CORPSE: + x = ( es->solid & 255 ); + zd = ( ( es->solid >> 8 ) & 255 ); + zu = ( ( es->solid >> 16 ) & 255 ) - 32; + + mins[ 0 ] = mins[ 1 ] = -x; + maxs[ 0 ] = maxs[ 1 ] = x; + mins[ 2 ] = -zd; + maxs[ 2 ] = zu; + + CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs ); + break; + + default: + break; + } + } + } +} + diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c new file mode 100644 index 0000000..ec7b9a6 --- /dev/null +++ b/src/cgame/cg_event.c @@ -0,0 +1,1116 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_event.c -- handle entity events at snapshot or playerstate transitions + + +#include "cg_local.h" + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) +{ + int mod; + int target, attacker; + char *message; + char *message2; + const char *targetInfo; + const char *attackerInfo; + char targetName[ MAX_NAME_LENGTH ]; + char attackerName[ MAX_NAME_LENGTH ]; + char className[ 64 ]; + gender_t gender; + clientInfo_t *ci; + qboolean teamKill = qfalse; + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm; + + if( target < 0 || target >= MAX_CLIENTS ) + CG_Error( "CG_Obituary: target out of range" ); + + ci = &cgs.clientinfo[ target ]; + gender = ci->gender; + + if( attacker < 0 || attacker >= MAX_CLIENTS ) + { + attacker = ENTITYNUM_WORLD; + attackerInfo = NULL; + } + else + { + attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); + if( ci && cgs.clientinfo[ attacker ].team == ci->team ) + teamKill = qtrue; + } + + targetInfo = CG_ConfigString( CS_PLAYERS + target ); + + if( !targetInfo ) + return; + + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName )); + + message2 = ""; + + // check for single client messages + + switch( mod ) + { + case MOD_FALLING: + message = "fell fowl to gravity"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "forgot to pack a snorkel"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "did a back flip into the lava"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + case MOD_HSPAWN: + message = "should have run further"; + break; + case MOD_ASPAWN: + message = "shouldn't have trod in the acid"; + break; + case MOD_MGTURRET: + message = "was gunned down by a turret"; + break; + case MOD_TESLAGEN: + message = "was zapped by a tesla generator"; + break; + case MOD_ATUBE: + message = "was melted by an acid tube"; + break; + case MOD_OVERMIND: + message = "got too close to the overmind"; + break; + case MOD_REACTOR: + message = "got too close to the reactor"; + break; + case MOD_SLOWBLOB: + message = "should have visited a medical station"; + break; + case MOD_SWARM: + message = "was hunted down by the swarm"; + break; + default: + message = NULL; + break; + } + + if( !message && attacker == target ) + { + switch( mod ) + { + case MOD_FLAMER_SPLASH: + if( gender == GENDER_FEMALE ) + message = "toasted herself"; + else if( gender == GENDER_NEUTER ) + message = "toasted itself"; + else + message = "toasted himself"; + break; + + case MOD_LCANNON_SPLASH: + if( gender == GENDER_FEMALE ) + message = "irradiated herself"; + else if( gender == GENDER_NEUTER ) + message = "irradiated itself"; + else + message = "irradiated himself"; + break; + + case MOD_GRENADE: + if( gender == GENDER_FEMALE ) + message = "blew herself up"; + else if( gender == GENDER_NEUTER ) + message = "blew itself up"; + else + message = "blew himself up"; + break; + + case MOD_LEVEL3_BOUNCEBALL: + if( gender == GENDER_FEMALE ) + message = "sniped herself"; + else if( gender == GENDER_NEUTER ) + message = "sniped itself"; + else + message = "sniped himself"; + break; + + case MOD_PRIFLE: + if( gender == GENDER_FEMALE ) + message = "pulse rifled herself"; + else if( gender == GENDER_NEUTER ) + message = "pulse rifled itself"; + else + message = "pulse rifled himself"; + break; + + default: + if( gender == GENDER_FEMALE ) + message = "killed herself"; + else if( gender == GENDER_NEUTER ) + message = "killed itself"; + else + message = "killed himself"; + break; + } + } + + if( message ) + { + CG_Printf( "%s" S_COLOR_WHITE " %s\n", targetName, message ); + return; + } + + // check for double client messages + if( !attackerInfo ) + { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } + else + { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName )); + // check for kill messages about the current clientNum + if( target == cg.snap->ps.clientNum ) + Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); + } + + if( attacker != ENTITYNUM_WORLD ) + { + switch( mod ) + { + case MOD_PAINSAW: + message = "was sawn by"; + break; + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was chaingunned by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_PRIFLE: + message = "was pulse rifled by"; + break; + case MOD_MDRIVER: + message = "was mass driven by"; + break; + case MOD_LASGUN: + message = "was lasgunned by"; + break; + case MOD_FLAMER: + message = "was grilled by"; + message2 = "'s flamer"; + break; + case MOD_FLAMER_SPLASH: + message = "was toasted by"; + message2 = "'s flamer"; + break; + case MOD_LCANNON: + message = "felt the full force of"; + message2 = "'s lucifer cannon"; + break; + case MOD_LCANNON_SPLASH: + message = "was caught in the fallout of"; + message2 = "'s lucifer cannon"; + break; + case MOD_GRENADE: + message = "couldn't escape"; + message2 = "'s grenade"; + break; + + case MOD_ABUILDER_CLAW: + message = "should leave"; + message2 = "'s buildings alone"; + break; + case MOD_LEVEL0_BITE: + message = "was bitten by"; + break; + case MOD_LEVEL1_CLAW: + message = "was swiped by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL1 )->humanName ); + message2 = className; + break; + case MOD_LEVEL2_CLAW: + message = "was clawed by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL2 )->humanName ); + message2 = className; + break; + case MOD_LEVEL2_ZAP: + message = "was zapped by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL2 )->humanName ); + message2 = className; + break; + case MOD_LEVEL3_CLAW: + message = "was chomped by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); + message2 = className; + break; + case MOD_LEVEL3_POUNCE: + message = "was pounced upon by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); + message2 = className; + break; + case MOD_LEVEL3_BOUNCEBALL: + message = "was sniped by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); + message2 = className; + break; + case MOD_LEVEL4_CLAW: + message = "was mauled by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL4 )->humanName ); + message2 = className; + break; + case MOD_LEVEL4_TRAMPLE: + message = "should have gotten out of the way of"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL4 )->humanName ); + message2 = className; + break; + case MOD_LEVEL4_CRUSH: + message = "was crushed under"; + message2 = "'s weight"; + break; + + case MOD_POISON: + message = "should have used a medkit against"; + message2 = "'s poison"; + break; + case MOD_LEVEL1_PCLOUD: + message = "was gassed by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL1 )->humanName ); + message2 = className; + break; + + case MOD_ALIEN_HATCH: + message = "gave birth to"; + break; + + case MOD_ALIEN_HATCH_FAILED: + message = "tried to jump out of"; + message2 = "'s chest"; + break; + + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + default: + message = "was killed by"; + break; + } + + if( message ) + { + CG_Printf( "%s" S_COLOR_WHITE " %s %s%s" S_COLOR_WHITE "%s\n", + targetName, message, + ( teamKill ) ? S_COLOR_RED "TEAMMATE " S_COLOR_WHITE : "", + attackerName, message2 ); + if( teamKill && attacker == cg.clientNum ) + { + CG_CenterPrint( va ( "You killed " S_COLOR_RED "TEAMMATE " + S_COLOR_WHITE "%s", targetName ), + SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } + return; + } + } + + // we don't know what it was + CG_Printf( "%s" S_COLOR_WHITE " died\n", targetName ); +} + + + +//========================================================================== + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) +{ + char *snd; + + // don't do more than two pain sounds a second + if( cg.time - cent->pe.painTime < 500 ) + return; + + if( health < 25 ) + snd = "*pain25_1.wav"; + else if( health < 50 ) + snd = "*pain50_1.wav"; + else if( health < 75 ) + snd = "*pain75_1.wav"; + else + snd = "*pain100_1.wav"; + + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, snd ) ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg.time; + cent->pe.painDirection ^= 1; +} + +/* +========================= +CG_Level2Zap +========================= +*/ +static void CG_Level2Zap( entityState_t *es ) +{ + int i; + centity_t *source = NULL, *target = NULL; + + if( es->misc < 0 || es->misc >= MAX_CLIENTS ) + return; + + source = &cg_entities[ es->misc ]; + for( i = 0; i <= 2; i++ ) + { + switch( i ) + { + case 0: + if( es->time <= 0 ) + continue; + + target = &cg_entities[ es->time ]; + break; + + case 1: + if( es->time2 <= 0 ) + continue; + + target = &cg_entities[ es->time2 ]; + break; + + case 2: + if( es->constantLight <= 0 ) + continue; + + target = &cg_entities[ es->constantLight ]; + break; + } + + if( !CG_IsTrailSystemValid( &source->level2ZapTS[ i ] ) ) + source->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); + + if( CG_IsTrailSystemValid( &source->level2ZapTS[ i ] ) ) + { + CG_SetAttachmentCent( &source->level2ZapTS[ i ]->frontAttachment, source ); + CG_SetAttachmentCent( &source->level2ZapTS[ i ]->backAttachment, target ); + CG_AttachToCent( &source->level2ZapTS[ i ]->frontAttachment ); + CG_AttachToCent( &source->level2ZapTS[ i ]->backAttachment ); + } + } + source->level2ZapTime = cg.time; +} + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +void CG_EntityEvent( centity_t *cent, vec3_t position ) +{ + entityState_t *es; + int event; + vec3_t dir; + const char *s; + int clientNum; + clientInfo_t *ci; + int steptime; + vec3_t cuboid; + buildable_t cuboidType; + + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + steptime = 200; + else + steptime = BG_Class( cg.snap->ps.stats[ STAT_CLASS ] )->steptime; + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + + if( cg_debugEvents.integer ) + CG_Printf( "ent:%3i event:%3i %s\n", es->number, event, + BG_EventName( event ) ); + + if( !event ) + return; + + clientNum = es->clientNum; + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) + clientNum = 0; + + ci = &cgs.clientinfo[ clientNum ]; + + switch( event ) + { + // + // movement generated events + // + case EV_FOOTSTEP: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + if( ci->footsteps == FOOTSTEP_CUSTOM ) + trap_S_StartSound( NULL, es->number, CHAN_BODY, + ci->customFootsteps[ rand( ) & 3 ] ); + else + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ ci->footsteps ][ rand( ) & 3 ] ); + } + break; + + case EV_FOOTSTEP_METAL: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + if( ci->footsteps == FOOTSTEP_CUSTOM ) + trap_S_StartSound( NULL, es->number, CHAN_BODY, + ci->customMetalFootsteps[ rand( ) & 3 ] ); + else + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_METAL ][ rand( ) & 3 ] ); + } + break; + + case EV_FOOTSTEP_SQUELCH: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_FLESH ][ rand( ) & 3 ] ); + } + break; + + case EV_FOOTSPLASH: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); + } + break; + + case EV_FOOTWADE: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); + } + break; + + case EV_SWIM: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); + } + break; + + + case EV_FALL_SHORT: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + + if( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + + case EV_FALL_MEDIUM: + // use normal pain sound + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); + + if( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + + case EV_FALL_FAR: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + + if( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_FALLING: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*falling1.wav" ) ); + break; + + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + case EV_STEPDN_4: + case EV_STEPDN_8: + case EV_STEPDN_12: + case EV_STEPDN_16: // smooth out step down transitions + { + float oldStep; + int delta; + int step; + + if( clientNum != cg.predictedPlayerState.clientNum ) + break; + + // if we are interpolating, we don't need to smooth steps + if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || + cg_nopredict.integer || cg_synchronousClients.integer ) + break; + + // check for stepping up before a previous step is completed + delta = cg.time - cg.stepTime; + + if( delta < steptime ) + oldStep = cg.stepChange * ( steptime - delta ) / steptime; + else + oldStep = 0; + + // add this amount + if( event >= EV_STEPDN_4 ) + { + step = 4 * ( event - EV_STEPDN_4 + 1 ); + cg.stepChange = oldStep - step; + } + else + { + step = 4 * ( event - EV_STEP_4 + 1 ); + cg.stepChange = oldStep + step; + } + + if( cg.stepChange > MAX_STEP_CHANGE ) + cg.stepChange = MAX_STEP_CHANGE; + else if( cg.stepChange < -MAX_STEP_CHANGE ) + cg.stepChange = -MAX_STEP_CHANGE; + + cg.stepTime = cg.time; + break; + } + + case EV_JUMP: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + + if( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) + { + vec3_t surfNormal, refNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t rotAxis; + + if( clientNum != cg.predictedPlayerState.clientNum ) + break; + + //set surfNormal + VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal ); + + //if we are moving from one surface to another smooth the transition + if( !VectorCompare( surfNormal, cg.lastNormal ) && surfNormal[ 2 ] != 1.0f ) + { + CrossProduct( refNormal, surfNormal, rotAxis ); + VectorNormalize( rotAxis ); + + //add the op + CG_addSmoothOp( rotAxis, 15.0f, 1.0f ); + } + + //copy the current normal to the lastNormal + VectorCopy( surfNormal, cg.lastNormal ); + } + + break; + + case EV_JETJUMP: + cent->jetPackJumpTime = cg.time; //for visual effects + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackJumpSound ); + break; + + + case EV_JETPACK_DEACTIVATE: + switch( cent->jetPackState ) + { + case JPS_DESCENDING: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackDescendDeactivateSound ); + break; + case JPS_HOVERING: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackIdleDeactivateSound ); + break; + case JPS_ASCENDING: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackAscendDeactivateSound ); + break; + } + break; + + case EV_JETPACK_REFUEL: + if( cent->jetPackRefuelTime + 1000 < cg.time ) + { + cent->jetPackRefuelTime = cg.time; + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackRefuelSound); + } + break; + + case EV_LEV1_GRAB: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL1Grab ); + break; + + case EV_LEV4_TRAMPLE_PREPARE: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargePrepare ); + break; + + case EV_LEV4_TRAMPLE_START: + //FIXME: stop cgs.media.alienL4ChargePrepare playing here + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargeStart ); + break; + + case EV_TAUNT: + if( !cg_noTaunt.integer ) + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); + break; + + case EV_WATER_TOUCH: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); + break; + + case EV_WATER_LEAVE: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); + break; + + case EV_WATER_UNDER: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); + break; + + case EV_WATER_CLEAR: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + break; + + // + // weapon events + // + case EV_NOAMMO: + trap_S_StartSound( NULL, es->number, CHAN_WEAPON, + cgs.media.weaponEmptyClick ); + break; + + case EV_CHANGE_WEAPON: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + break; + + case EV_FIRE_WEAPON: + CG_FireWeapon( cent, WPM_PRIMARY ); + break; + + case EV_FIRE_WEAPON2: + CG_FireWeapon( cent, WPM_SECONDARY ); + break; + + case EV_FIRE_WEAPON3: + CG_FireWeapon( cent, WPM_TERTIARY ); + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + //deprecated + break; + + case EV_PLAYER_TELEPORT_OUT: + CG_PlayerDisconnect( position ); + break; + + case EV_BUILD_CONSTRUCT: + //do something useful here + break; + + case EV_BUILD_DESTROY: + //do something useful here + break; + + case EV_RPTUSE_SOUND: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.repeaterUseSound ); + break; + + case EV_GRENADE_BOUNCE: + if( rand( ) & 1 ) + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound1 ); + else + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound2 ); + break; + + // + // missile impacts + // + case EV_MISSILE_HIT: + ByteToDir( es->eventParm, dir ); + CG_MissileHitEntity( es->weapon, es->generic1, position, dir, es->otherEntityNum, es->torsoAnim ); + break; + + case EV_MISSILE_MISS: + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT, es->torsoAnim ); + break; + + case EV_MISSILE_MISS_METAL: + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_METAL, es->torsoAnim ); + break; + + case EV_HUMAN_BUILDABLE_EXPLOSION: + ByteToDir( es->eventParm, dir ); + CG_HumanBuildableExplosion( position, dir ); + break; + + case EV_ALIEN_BUILDABLE_EXPLOSION: + ByteToDir( es->eventParm, dir ); + CG_AlienBuildableExplosion( position, dir ); + break; + + case EV_CUBOID_EXPLOSION: + cuboidType=es->modelindex; + CG_CuboidExplosion(cuboidType,position,es->angles); + break; + + case EV_TESLATRAIL: + cent->currentState.weapon = WP_TESLAGEN; + { + centity_t *source = &cg_entities[ es->generic1 ]; + centity_t *target = &cg_entities[ es->clientNum ]; + vec3_t sourceOffset = { 0.0f, 0.0f, 28.0f }; + + if( !CG_IsTrailSystemValid( &source->muzzleTS ) ) + { + source->muzzleTS = CG_SpawnNewTrailSystem( cgs.media.teslaZapTS ); + + if( CG_IsTrailSystemValid( &source->muzzleTS ) ) + { + CG_SetAttachmentCent( &source->muzzleTS->frontAttachment, source ); + CG_SetAttachmentCent( &source->muzzleTS->backAttachment, target ); + CG_AttachToCent( &source->muzzleTS->frontAttachment ); + CG_AttachToCent( &source->muzzleTS->backAttachment ); + CG_SetAttachmentOffset( &source->muzzleTS->frontAttachment, sourceOffset ); + + source->muzzleTSDeathTime = cg.time + cg_teslaTrailTime.integer; + } + } + } + break; + + case EV_BULLET_HIT_WALL: + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); + break; + + case EV_BULLET_HIT_FLESH: + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); + break; + + case EV_SHOTGUN: + CG_ShotgunFire( es ); + break; + + case EV_GENERAL_SOUND: + if( cgs.gameSounds[ es->eventParm ] ) + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); + else + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes + if( cgs.gameSounds[ es->eventParm ] ) + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); + else + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + if( cent->currentState.number != cg.snap->ps.clientNum ) + CG_PainEvent( cent, es->eventParm ); + break; + + case EV_DEATH1: + case EV_DEATH2: + case EV_DEATH3: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, + CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) ); + break; + + case EV_OBITUARY: + CG_Obituary( es ); + break; + + case EV_GIB_PLAYER: + // no gibbing + break; + + case EV_STOPLOOPINGSOUND: + trap_S_StopLoopingSound( es->number ); + es->loopSound = 0; + break; + + case EV_DEBUG_LINE: + CG_Beam( cent ); + break; + + case EV_BUILD_DELAY: + if( clientNum == cg.predictedPlayerState.clientNum ) + { + trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); + cg.lastBuildAttempt = cg.time; + } + break; + + case EV_BUILD_REPAIR: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairSound ); + break; + + case EV_BUILD_REPAIRED: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairedSound ); + break; + + case EV_OVERMIND_ATTACK: + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + trap_S_StartLocalSound( cgs.media.alienOvermindAttack, CHAN_ANNOUNCER ); + CG_CenterPrint( "The Overmind is under attack!", 200, GIANTCHAR_WIDTH * 4 ); + } + break; + + case EV_OVERMIND_DYING: + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + trap_S_StartLocalSound( cgs.media.alienOvermindDying, CHAN_ANNOUNCER ); + CG_CenterPrint( "The Overmind is dying!", 200, GIANTCHAR_WIDTH * 4 ); + } + break; + + case EV_DCC_ATTACK: + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + //trap_S_StartLocalSound( cgs.media.humanDCCAttack, CHAN_ANNOUNCER ); + CG_CenterPrint( "Our base is under attack!", 200, GIANTCHAR_WIDTH * 4 ); + } + break; + + case EV_MGTURRET_SPINUP: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.turretSpinupSound ); + break; + + case EV_OVERMIND_SPAWNS: + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + trap_S_StartLocalSound( cgs.media.alienOvermindSpawns, CHAN_ANNOUNCER ); + CG_CenterPrint( "The Overmind needs spawns!", 200, GIANTCHAR_WIDTH * 4 ); + } + break; + + case EV_ALIEN_EVOLVE: + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienEvolveSound ); + { + particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienEvolvePS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentCent( &ps->attachment, cent ); + CG_AttachToCent( &ps->attachment ); + } + } + + if( es->number == cg.clientNum ) + { + CG_ResetPainBlend( ); + cg.spawnTime = cg.time; + } + break; + + case EV_ALIEN_EVOLVE_FAILED: + if( clientNum == cg.predictedPlayerState.clientNum ) + { + //FIXME: change to "negative" sound + trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); + cg.lastEvolveAttempt = cg.time; + } + break; + + case EV_ALIEN_ACIDTUBE: + { + particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentCent( &ps->attachment, cent ); + ByteToDir( es->eventParm, dir ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToCent( &ps->attachment ); + } + } + break; + + case EV_MEDKIT_USED: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.medkitUseSound ); + break; + + case EV_PLAYER_RESPAWN: + if( es->number == cg.clientNum ) + cg.spawnTime = cg.time; + break; + + case EV_LEV2_ZAP: + CG_Level2Zap( es ); + + case EV_ALIEN_HATCH: + { + particleSystem_t *ps; + + VectorCopy( es->angles, dir ); + ps = CG_SpawnNewParticleSystem( cgs.media.alienHatchPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, position ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToPoint( &ps->attachment ); + } + + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienHatchSound ); + } + break; + + case EV_ALIEN_HATCH_FAILURE: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienFailedHatchSound ); + break; + + default: + CG_Error( "Unknown event: %i", event ); + break; + } +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) +{ + entity_event_t event; + entity_event_t oldEvent = EV_NONE; + + // check for event-only entities + if( cent->currentState.eType > ET_EVENTS ) + { + event = cent->currentState.eType - ET_EVENTS; + + if( cent->previousEvent ) + return; // already fired + + cent->previousEvent = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + + // Move the pointer to the entity that the + // event was originally attached to + if( cent->currentState.eFlags & EF_PLAYER_EVENT ) + { + cent = &cg_entities[ cent->currentState.otherEntityNum ]; + oldEvent = cent->currentState.event; + cent->currentState.event = event; + } + } + else + { + // check for events riding with another entity + if( cent->currentState.event == cent->previousEvent ) + return; + + cent->previousEvent = cent->currentState.event; + if( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) + return; + } + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); + CG_SetEntitySoundPosition( cent ); + + CG_EntityEvent( cent, cent->lerpOrigin ); + + // If this was a reattached spilled event, restore the original event + if( oldEvent != EV_NONE ) + cent->currentState.event = oldEvent; +} + diff --git a/src/cgame/cg_event.c.orig b/src/cgame/cg_event.c.orig new file mode 100644 index 0000000..e394023 --- /dev/null +++ b/src/cgame/cg_event.c.orig @@ -0,0 +1,1086 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_event.c -- handle entity events at snapshot or playerstate transitions + + +#include "cg_local.h" + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) +{ + int mod; + int target, attacker; + char *message; + char *message2; + const char *targetInfo; + const char *attackerInfo; + char targetName[ MAX_NAME_LENGTH ]; + char attackerName[ MAX_NAME_LENGTH ]; + char className[ 64 ]; + gender_t gender; + clientInfo_t *ci; + qboolean teamKill = qfalse; + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm; + + if( target < 0 || target >= MAX_CLIENTS ) + CG_Error( "CG_Obituary: target out of range" ); + + ci = &cgs.clientinfo[ target ]; + gender = ci->gender; + + if( attacker < 0 || attacker >= MAX_CLIENTS ) + { + attacker = ENTITYNUM_WORLD; + attackerInfo = NULL; + } + else + { + attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); + if( ci && cgs.clientinfo[ attacker ].team == ci->team ) + teamKill = qtrue; + } + + targetInfo = CG_ConfigString( CS_PLAYERS + target ); + + if( !targetInfo ) + return; + + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName )); + + message2 = ""; + + // check for single client messages + + switch( mod ) + { + case MOD_FALLING: + message = "fell fowl to gravity"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "forgot to pack a snorkel"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "did a back flip into the lava"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + case MOD_HSPAWN: + message = "should have run further"; + break; + case MOD_ASPAWN: + message = "shouldn't have trod in the acid"; + break; + case MOD_MGTURRET: + message = "was gunned down by a turret"; + break; + case MOD_TESLAGEN: + message = "was zapped by a tesla generator"; + break; + case MOD_ATUBE: + message = "was melted by an acid tube"; + break; + case MOD_OVERMIND: + message = "got too close to the overmind"; + break; + case MOD_REACTOR: + message = "got too close to the reactor"; + break; + case MOD_SLOWBLOB: + message = "should have visited a medical station"; + break; + case MOD_SWARM: + message = "was hunted down by the swarm"; + break; + default: + message = NULL; + break; + } + + if( !message && attacker == target ) + { + switch( mod ) + { + case MOD_FLAMER_SPLASH: + if( gender == GENDER_FEMALE ) + message = "toasted herself"; + else if( gender == GENDER_NEUTER ) + message = "toasted itself"; + else + message = "toasted himself"; + break; + + case MOD_LCANNON_SPLASH: + if( gender == GENDER_FEMALE ) + message = "irradiated herself"; + else if( gender == GENDER_NEUTER ) + message = "irradiated itself"; + else + message = "irradiated himself"; + break; + + case MOD_GRENADE: + if( gender == GENDER_FEMALE ) + message = "blew herself up"; + else if( gender == GENDER_NEUTER ) + message = "blew itself up"; + else + message = "blew himself up"; + break; + + case MOD_LEVEL3_BOUNCEBALL: + if( gender == GENDER_FEMALE ) + message = "sniped herself"; + else if( gender == GENDER_NEUTER ) + message = "sniped itself"; + else + message = "sniped himself"; + break; + + case MOD_PRIFLE: + if( gender == GENDER_FEMALE ) + message = "pulse rifled herself"; + else if( gender == GENDER_NEUTER ) + message = "pulse rifled itself"; + else + message = "pulse rifled himself"; + break; + + default: + if( gender == GENDER_FEMALE ) + message = "killed herself"; + else if( gender == GENDER_NEUTER ) + message = "killed itself"; + else + message = "killed himself"; + break; + } + } + + if( message ) + { + CG_Printf( "%s" S_COLOR_WHITE " %s\n", targetName, message ); + return; + } + + // check for double client messages + if( !attackerInfo ) + { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } + else + { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName )); + // check for kill messages about the current clientNum + if( target == cg.snap->ps.clientNum ) + Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); + } + + if( attacker != ENTITYNUM_WORLD ) + { + switch( mod ) + { + case MOD_PAINSAW: + message = "was sawn by"; + break; + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was chaingunned by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_PRIFLE: + message = "was pulse rifled by"; + break; + case MOD_MDRIVER: + message = "was mass driven by"; + break; + case MOD_LASGUN: + message = "was lasgunned by"; + break; + case MOD_FLAMER: + message = "was grilled by"; + message2 = "'s flamer"; + break; + case MOD_FLAMER_SPLASH: + message = "was toasted by"; + message2 = "'s flamer"; + break; + case MOD_LCANNON: + message = "felt the full force of"; + message2 = "'s lucifer cannon"; + break; + case MOD_LCANNON_SPLASH: + message = "was caught in the fallout of"; + message2 = "'s lucifer cannon"; + break; + case MOD_GRENADE: + message = "couldn't escape"; + message2 = "'s grenade"; + break; + + case MOD_ABUILDER_CLAW: + message = "should leave"; + message2 = "'s buildings alone"; + break; + case MOD_LEVEL0_BITE: + message = "was bitten by"; + break; + case MOD_LEVEL1_CLAW: + message = "was swiped by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL1 )->humanName ); + message2 = className; + break; + case MOD_LEVEL2_CLAW: + message = "was clawed by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL2 )->humanName ); + message2 = className; + break; + case MOD_LEVEL2_ZAP: + message = "was zapped by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL2 )->humanName ); + message2 = className; + break; + case MOD_LEVEL3_CLAW: + message = "was chomped by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); + message2 = className; + break; + case MOD_LEVEL3_POUNCE: + message = "was pounced upon by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); + message2 = className; + break; + case MOD_LEVEL3_BOUNCEBALL: + message = "was sniped by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL3 )->humanName ); + message2 = className; + break; + case MOD_LEVEL4_CLAW: + message = "was mauled by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL4 )->humanName ); + message2 = className; + break; + case MOD_LEVEL4_TRAMPLE: + message = "should have gotten out of the way of"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL4 )->humanName ); + message2 = className; + break; + case MOD_LEVEL4_CRUSH: + message = "was crushed under"; + message2 = "'s weight"; + break; + + case MOD_POISON: + message = "should have used a medkit against"; + message2 = "'s poison"; + break; + case MOD_LEVEL1_PCLOUD: + message = "was gassed by"; + Com_sprintf( className, 64, "'s %s", + BG_ClassConfig( PCL_ALIEN_LEVEL1 )->humanName ); + message2 = className; + break; + + + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + default: + message = "was killed by"; + break; + } + + if( message ) + { + CG_Printf( "%s" S_COLOR_WHITE " %s %s%s" S_COLOR_WHITE "%s\n", + targetName, message, + ( teamKill ) ? S_COLOR_RED "TEAMMATE " S_COLOR_WHITE : "", + attackerName, message2 ); + if( teamKill && attacker == cg.clientNum ) + { + CG_CenterPrint( va ( "You killed " S_COLOR_RED "TEAMMATE " + S_COLOR_WHITE "%s", targetName ), + SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } + return; + } + } + + // we don't know what it was + CG_Printf( "%s" S_COLOR_WHITE " died\n", targetName ); +} + + + +//========================================================================== + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) +{ + char *snd; + + // don't do more than two pain sounds a second + if( cg.time - cent->pe.painTime < 500 ) + return; + + if( health < 25 ) + snd = "*pain25_1.wav"; + else if( health < 50 ) + snd = "*pain50_1.wav"; + else if( health < 75 ) + snd = "*pain75_1.wav"; + else + snd = "*pain100_1.wav"; + + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, snd ) ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg.time; + cent->pe.painDirection ^= 1; +} + +/* +========================= +CG_Level2Zap +========================= +*/ +static void CG_Level2Zap( entityState_t *es ) +{ + int i; + centity_t *source = NULL, *target = NULL; + + if( es->misc < 0 || es->misc >= MAX_CLIENTS ) + return; + + source = &cg_entities[ es->misc ]; + for( i = 0; i <= 2; i++ ) + { + switch( i ) + { + case 0: + if( es->time <= 0 ) + continue; + + target = &cg_entities[ es->time ]; + break; + + case 1: + if( es->time2 <= 0 ) + continue; + + target = &cg_entities[ es->time2 ]; + break; + + case 2: + if( es->constantLight <= 0 ) + continue; + + target = &cg_entities[ es->constantLight ]; + break; + } + + if( !CG_IsTrailSystemValid( &source->level2ZapTS[ i ] ) ) + source->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); + + if( CG_IsTrailSystemValid( &source->level2ZapTS[ i ] ) ) + { + CG_SetAttachmentCent( &source->level2ZapTS[ i ]->frontAttachment, source ); + CG_SetAttachmentCent( &source->level2ZapTS[ i ]->backAttachment, target ); + CG_AttachToCent( &source->level2ZapTS[ i ]->frontAttachment ); + CG_AttachToCent( &source->level2ZapTS[ i ]->backAttachment ); + } + } + source->level2ZapTime = cg.time; +} + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +void CG_EntityEvent( centity_t *cent, vec3_t position ) +{ + entityState_t *es; + int event; + vec3_t dir; + const char *s; + int clientNum; + clientInfo_t *ci; + int steptime; + vec3_t cuboid; + buildable_t cuboidType; + + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + steptime = 200; + else + steptime = BG_Class( cg.snap->ps.stats[ STAT_CLASS ] )->steptime; + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + + if( cg_debugEvents.integer ) + CG_Printf( "ent:%3i event:%3i %s\n", es->number, event, + BG_EventName( event ) ); + + if( !event ) + return; + + clientNum = es->clientNum; + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) + clientNum = 0; + + ci = &cgs.clientinfo[ clientNum ]; + + switch( event ) + { + // + // movement generated events + // + case EV_FOOTSTEP: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + if( ci->footsteps == FOOTSTEP_CUSTOM ) + trap_S_StartSound( NULL, es->number, CHAN_BODY, + ci->customFootsteps[ rand( ) & 3 ] ); + else + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ ci->footsteps ][ rand( ) & 3 ] ); + } + break; + + case EV_FOOTSTEP_METAL: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + if( ci->footsteps == FOOTSTEP_CUSTOM ) + trap_S_StartSound( NULL, es->number, CHAN_BODY, + ci->customMetalFootsteps[ rand( ) & 3 ] ); + else + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_METAL ][ rand( ) & 3 ] ); + } + break; + + case EV_FOOTSTEP_SQUELCH: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_FLESH ][ rand( ) & 3 ] ); + } + break; + + case EV_FOOTSPLASH: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); + } + break; + + case EV_FOOTWADE: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); + } + break; + + case EV_SWIM: + if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) + { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] ); + } + break; + + + case EV_FALL_SHORT: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + + if( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + + case EV_FALL_MEDIUM: + // use normal pain sound + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); + + if( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + + case EV_FALL_FAR: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + + if( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_FALLING: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*falling1.wav" ) ); + break; + + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + case EV_STEPDN_4: + case EV_STEPDN_8: + case EV_STEPDN_12: + case EV_STEPDN_16: // smooth out step down transitions + { + float oldStep; + int delta; + int step; + + if( clientNum != cg.predictedPlayerState.clientNum ) + break; + + // if we are interpolating, we don't need to smooth steps + if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || + cg_nopredict.integer || cg_synchronousClients.integer ) + break; + + // check for stepping up before a previous step is completed + delta = cg.time - cg.stepTime; + + if( delta < steptime ) + oldStep = cg.stepChange * ( steptime - delta ) / steptime; + else + oldStep = 0; + + // add this amount + if( event >= EV_STEPDN_4 ) + { + step = 4 * ( event - EV_STEPDN_4 + 1 ); + cg.stepChange = oldStep - step; + } + else + { + step = 4 * ( event - EV_STEP_4 + 1 ); + cg.stepChange = oldStep + step; + } + + if( cg.stepChange > MAX_STEP_CHANGE ) + cg.stepChange = MAX_STEP_CHANGE; + else if( cg.stepChange < -MAX_STEP_CHANGE ) + cg.stepChange = -MAX_STEP_CHANGE; + + cg.stepTime = cg.time; + break; + } + + case EV_JUMP: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + + if( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) + { + vec3_t surfNormal, refNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t rotAxis; + + if( clientNum != cg.predictedPlayerState.clientNum ) + break; + + //set surfNormal + VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal ); + + //if we are moving from one surface to another smooth the transition + if( !VectorCompare( surfNormal, cg.lastNormal ) && surfNormal[ 2 ] != 1.0f ) + { + CrossProduct( refNormal, surfNormal, rotAxis ); + VectorNormalize( rotAxis ); + + //add the op + CG_addSmoothOp( rotAxis, 15.0f, 1.0f ); + } + + //copy the current normal to the lastNormal + VectorCopy( surfNormal, cg.lastNormal ); + } + + break; + + case EV_JETJUMP: + cent->jetPackJumpTime = cg.time; //for visual effects + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackJumpSound ); + break; + + + case EV_JETPACK_DEACTIVATE: + switch( cent->jetPackState ) + { + case JPS_DESCENDING: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackDescendDeactivateSound ); + break; + case JPS_HOVERING: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackIdleDeactivateSound ); + break; + case JPS_ASCENDING: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackAscendDeactivateSound ); + break; + } + break; + + case EV_JETPACK_REFUEL: + if( cent->jetPackRefuelTime + 1000 < cg.time ) + { + cent->jetPackRefuelTime = cg.time; + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.jetpackRefuelSound); + } + break; + + case EV_LEV1_GRAB: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL1Grab ); + break; + + case EV_LEV4_TRAMPLE_PREPARE: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargePrepare ); + break; + + case EV_LEV4_TRAMPLE_START: + //FIXME: stop cgs.media.alienL4ChargePrepare playing here + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargeStart ); + break; + + case EV_TAUNT: + if( !cg_noTaunt.integer ) + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); + break; + + case EV_WATER_TOUCH: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); + break; + + case EV_WATER_LEAVE: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); + break; + + case EV_WATER_UNDER: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); + break; + + case EV_WATER_CLEAR: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + break; + + // + // weapon events + // + case EV_NOAMMO: + trap_S_StartSound( NULL, es->number, CHAN_WEAPON, + cgs.media.weaponEmptyClick ); + break; + + case EV_CHANGE_WEAPON: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + break; + + case EV_FIRE_WEAPON: + CG_FireWeapon( cent, WPM_PRIMARY ); + break; + + case EV_FIRE_WEAPON2: + CG_FireWeapon( cent, WPM_SECONDARY ); + break; + + case EV_FIRE_WEAPON3: + CG_FireWeapon( cent, WPM_TERTIARY ); + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + //deprecated + break; + + case EV_PLAYER_TELEPORT_OUT: + CG_PlayerDisconnect( position ); + break; + + case EV_BUILD_CONSTRUCT: + //do something useful here + break; + + case EV_BUILD_DESTROY: + //do something useful here + break; + + case EV_RPTUSE_SOUND: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.repeaterUseSound ); + break; + + case EV_GRENADE_BOUNCE: + if( rand( ) & 1 ) + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound1 ); + else + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound2 ); + break; + + // + // missile impacts + // + case EV_MISSILE_HIT: + ByteToDir( es->eventParm, dir ); + CG_MissileHitEntity( es->weapon, es->generic1, position, dir, es->otherEntityNum, es->torsoAnim ); + break; + + case EV_MISSILE_MISS: + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT, es->torsoAnim ); + break; + + case EV_MISSILE_MISS_METAL: + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_METAL, es->torsoAnim ); + break; + + case EV_HUMAN_BUILDABLE_EXPLOSION: + ByteToDir( es->eventParm, dir ); + CG_HumanBuildableExplosion( position, dir ); + break; + + case EV_ALIEN_BUILDABLE_EXPLOSION: + ByteToDir( es->eventParm, dir ); + CG_AlienBuildableExplosion( position, dir ); + break; + + case EV_CUBOID_EXPLOSION: + cuboidType=es->modelindex; + CG_CuboidExplosion(cuboidType,position,es->angles); + break; + + case EV_TESLATRAIL: + cent->currentState.weapon = WP_TESLAGEN; + { + centity_t *source = &cg_entities[ es->generic1 ]; + centity_t *target = &cg_entities[ es->clientNum ]; + vec3_t sourceOffset = { 0.0f, 0.0f, 28.0f }; + + if( !CG_IsTrailSystemValid( &source->muzzleTS ) ) + { + source->muzzleTS = CG_SpawnNewTrailSystem( cgs.media.teslaZapTS ); + + if( CG_IsTrailSystemValid( &source->muzzleTS ) ) + { + CG_SetAttachmentCent( &source->muzzleTS->frontAttachment, source ); + CG_SetAttachmentCent( &source->muzzleTS->backAttachment, target ); + CG_AttachToCent( &source->muzzleTS->frontAttachment ); + CG_AttachToCent( &source->muzzleTS->backAttachment ); + CG_SetAttachmentOffset( &source->muzzleTS->frontAttachment, sourceOffset ); + + source->muzzleTSDeathTime = cg.time + cg_teslaTrailTime.integer; + } + } + } + break; + + case EV_BULLET_HIT_WALL: + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); + break; + + case EV_BULLET_HIT_FLESH: + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); + break; + + case EV_SHOTGUN: + CG_ShotgunFire( es ); + break; + + case EV_GENERAL_SOUND: + if( cgs.gameSounds[ es->eventParm ] ) + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); + else + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes + if( cgs.gameSounds[ es->eventParm ] ) + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); + else + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + if( cent->currentState.number != cg.snap->ps.clientNum ) + CG_PainEvent( cent, es->eventParm ); + break; + + case EV_DEATH1: + case EV_DEATH2: + case EV_DEATH3: + trap_S_StartSound( NULL, es->number, CHAN_VOICE, + CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) ); + break; + + case EV_OBITUARY: + CG_Obituary( es ); + break; + + case EV_GIB_PLAYER: + // no gibbing + break; + + case EV_STOPLOOPINGSOUND: + trap_S_StopLoopingSound( es->number ); + es->loopSound = 0; + break; + + case EV_DEBUG_LINE: + CG_Beam( cent ); + break; + + case EV_BUILD_DELAY: + if( clientNum == cg.predictedPlayerState.clientNum ) + { + trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); + cg.lastBuildAttempt = cg.time; + } + break; + + case EV_BUILD_REPAIR: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairSound ); + break; + + case EV_BUILD_REPAIRED: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairedSound ); + break; + + case EV_OVERMIND_ATTACK: + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + trap_S_StartLocalSound( cgs.media.alienOvermindAttack, CHAN_ANNOUNCER ); + CG_CenterPrint( "The Overmind is under attack!", 200, GIANTCHAR_WIDTH * 4 ); + } + break; + + case EV_OVERMIND_DYING: + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + trap_S_StartLocalSound( cgs.media.alienOvermindDying, CHAN_ANNOUNCER ); + CG_CenterPrint( "The Overmind is dying!", 200, GIANTCHAR_WIDTH * 4 ); + } + break; + + case EV_DCC_ATTACK: + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + //trap_S_StartLocalSound( cgs.media.humanDCCAttack, CHAN_ANNOUNCER ); + CG_CenterPrint( "Our base is under attack!", 200, GIANTCHAR_WIDTH * 4 ); + } + break; + + case EV_MGTURRET_SPINUP: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.turretSpinupSound ); + break; + + case EV_OVERMIND_SPAWNS: + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + trap_S_StartLocalSound( cgs.media.alienOvermindSpawns, CHAN_ANNOUNCER ); + CG_CenterPrint( "The Overmind needs spawns!", 200, GIANTCHAR_WIDTH * 4 ); + } + break; + + case EV_ALIEN_EVOLVE: + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienEvolveSound ); + { + particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienEvolvePS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentCent( &ps->attachment, cent ); + CG_AttachToCent( &ps->attachment ); + } + } + + if( es->number == cg.clientNum ) + { + CG_ResetPainBlend( ); + cg.spawnTime = cg.time; + } + break; + + case EV_ALIEN_EVOLVE_FAILED: + if( clientNum == cg.predictedPlayerState.clientNum ) + { + //FIXME: change to "negative" sound + trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); + cg.lastEvolveAttempt = cg.time; + } + break; + + case EV_ALIEN_ACIDTUBE: + { + particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentCent( &ps->attachment, cent ); + ByteToDir( es->eventParm, dir ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToCent( &ps->attachment ); + } + } + break; + + case EV_MEDKIT_USED: + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.medkitUseSound ); + break; + + case EV_PLAYER_RESPAWN: + if( es->number == cg.clientNum ) + cg.spawnTime = cg.time; + break; + + case EV_LEV2_ZAP: + CG_Level2Zap( es ); + + default: + CG_Error( "Unknown event: %i", event ); + break; + } +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) +{ + entity_event_t event; + entity_event_t oldEvent = EV_NONE; + + // check for event-only entities + if( cent->currentState.eType > ET_EVENTS ) + { + event = cent->currentState.eType - ET_EVENTS; + + if( cent->previousEvent ) + return; // already fired + + cent->previousEvent = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + + // Move the pointer to the entity that the + // event was originally attached to + if( cent->currentState.eFlags & EF_PLAYER_EVENT ) + { + cent = &cg_entities[ cent->currentState.otherEntityNum ]; + oldEvent = cent->currentState.event; + cent->currentState.event = event; + } + } + else + { + // check for events riding with another entity + if( cent->currentState.event == cent->previousEvent ) + return; + + cent->previousEvent = cent->currentState.event; + if( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) + return; + } + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); + CG_SetEntitySoundPosition( cent ); + + CG_EntityEvent( cent, cent->lerpOrigin ); + + // If this was a reattached spilled event, restore the original event + if( oldEvent != EV_NONE ) + cent->currentState.event = oldEvent; +} + diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h new file mode 100644 index 0000000..0cc0977 --- /dev/null +++ b/src/cgame/cg_local.h @@ -0,0 +1,2177 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#include "../qcommon/q_shared.h" +#include "../renderer/tr_types.h" +#include "../game/bg_public.h" +#include "cg_public.h" +#include "../ui/ui_shared.h" + +// The entire cgame module is unloaded and reloaded on each level change, +// so there is NO persistant data between levels on the client side. +// If you absolutely need something stored, it can either be kept +// by the server in the server stored userinfos, or stashed in a cvar. + +#define CG_FONT_THRESHOLD 0.1 + +#define POWERUP_BLINKS 5 + +#define POWERUP_BLINK_TIME 1000 +#define FADE_TIME 200 +#define PULSE_TIME 200 +#define DAMAGE_DEFLECT_TIME 100 +#define DAMAGE_RETURN_TIME 400 +#define DAMAGE_TIME 500 +#define LAND_DEFLECT_TIME 150 +#define LAND_RETURN_TIME 300 +#define DUCK_TIME 100 +#define PAIN_TWITCH_TIME 200 +#define WEAPON_SELECT_TIME 1400 +#define ITEM_SCALEUP_TIME 1000 +#define ZOOM_TIME 150 +#define ITEM_BLOB_TIME 200 +#define MUZZLE_FLASH_TIME 20 +#define SINK_TIME 1000 // time for fragments to sink into ground before going away +#define ATTACKER_HEAD_TIME 10000 +#define REWARD_TIME 3000 + +#define PULSE_SCALE 1.5 // amount to scale up the icons when activating + +#define MAX_STEP_CHANGE 32 + +#define MAX_VERTS_ON_POLY 10 +#define MAX_MARK_POLYS 256 + +#define STAT_MINUS 10 // num frame for '-' stats digit + +#define ICON_SIZE 48 +#define CHAR_WIDTH 32 +#define CHAR_HEIGHT 48 +#define TEXT_ICON_SPACE 4 + +// very large characters +#define GIANT_WIDTH 32 +#define GIANT_HEIGHT 48 + +#define NUM_CROSSHAIRS 10 + +#define TEAM_OVERLAY_MAXNAME_WIDTH 12 +#define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 + +#define CUBOID_CRACK_TEXTURES 4 + +typedef enum +{ + FOOTSTEP_NORMAL, + FOOTSTEP_FLESH, + FOOTSTEP_METAL, + FOOTSTEP_SPLASH, + FOOTSTEP_CUSTOM, + FOOTSTEP_NONE, + + FOOTSTEP_TOTAL +} footstep_t; + +typedef enum +{ + IMPACTSOUND_DEFAULT, + IMPACTSOUND_METAL, + IMPACTSOUND_FLESH +} impactSound_t; + +typedef enum +{ + JPS_OFF, + JPS_DESCENDING, + JPS_HOVERING, + JPS_ASCENDING +} jetPackState_t; + +//====================================================================== + +// when changing animation, set animationTime to frameTime + lerping time +// The current lerp will finish out, then it will lerp to the new animation +typedef struct +{ + int oldFrame; + int oldFrameTime; // time when ->oldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + int animationNumber; // may include ANIM_TOGGLEBIT + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact +} lerpFrame_t; + +//====================================================================== + +//attachment system +typedef enum +{ + AT_STATIC, + AT_TAG, + AT_CENT, + AT_PARTICLE +} attachmentType_t; + +//forward declaration for particle_t +struct particle_s; + +typedef struct attachment_s +{ + attachmentType_t type; + qboolean attached; + + qboolean staticValid; + qboolean tagValid; + qboolean centValid; + qboolean particleValid; + + qboolean hasOffset; + vec3_t offset; + + vec3_t lastValidAttachmentPoint; + + //AT_STATIC + vec3_t origin; + + //AT_TAG + refEntity_t re; //FIXME: should be pointers? + refEntity_t parent; // + qhandle_t model; + char tagName[ MAX_STRING_CHARS ]; + + //AT_CENT + int centNum; + + //AT_PARTICLE + struct particle_s *particle; +} attachment_t; + +//====================================================================== + +//particle system stuff +#define MAX_PARTICLE_FILES 128 + +#define MAX_PS_SHADER_FRAMES 32 +#define MAX_PS_MODELS 8 +#define MAX_EJECTORS_PER_SYSTEM 4 +#define MAX_PARTICLES_PER_EJECTOR 4 + +#define MAX_BASEPARTICLE_SYSTEMS 192 +#define MAX_BASEPARTICLE_EJECTORS MAX_BASEPARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM +#define MAX_BASEPARTICLES MAX_BASEPARTICLE_EJECTORS*MAX_PARTICLES_PER_EJECTOR + +#define MAX_PARTICLE_SYSTEMS 48 +#define MAX_PARTICLE_EJECTORS MAX_PARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM +#define MAX_PARTICLES MAX_PARTICLE_EJECTORS*5 + +#define PARTICLES_INFINITE -1 +#define PARTICLES_SAME_AS_INITIAL -2 + +//COMPILE TIME STRUCTURES +typedef enum +{ + PMT_STATIC, + PMT_STATIC_TRANSFORM, + PMT_TAG, + PMT_CENT_ANGLES, + PMT_NORMAL +} pMoveType_t; + +typedef enum +{ + PMD_LINEAR, + PMD_POINT +} pDirType_t; + +typedef struct pMoveValues_u +{ + pDirType_t dirType; + + //PMD_LINEAR + vec3_t dir; + float dirRandAngle; + + //PMD_POINT + vec3_t point; + float pointRandAngle; + + float mag; + float magRandFrac; + + float parentVelFrac; + float parentVelFracRandFrac; +} pMoveValues_t; + +typedef struct pLerpValues_s +{ + int delay; + float delayRandFrac; + + float initial; + float initialRandFrac; + + float final; + float finalRandFrac; + + float randFrac; +} pLerpValues_t; + +//particle template +typedef struct baseParticle_s +{ + vec3_t displacement; + vec3_t randDisplacement; + float normalDisplacement; + + pMoveType_t velMoveType; + pMoveValues_t velMoveValues; + + pMoveType_t accMoveType; + pMoveValues_t accMoveValues; + + int lifeTime; + float lifeTimeRandFrac; + + float bounceFrac; + float bounceFracRandFrac; + qboolean bounceCull; + + char bounceMarkName[ MAX_QPATH ]; + qhandle_t bounceMark; + float bounceMarkRadius; + float bounceMarkRadiusRandFrac; + float bounceMarkCount; + float bounceMarkCountRandFrac; + + char bounceSoundName[ MAX_QPATH ]; + qhandle_t bounceSound; + float bounceSoundCount; + float bounceSoundCountRandFrac; + + pLerpValues_t radius; + int physicsRadius; + pLerpValues_t alpha; + pLerpValues_t rotation; + + qboolean dynamicLight; + pLerpValues_t dLightRadius; + byte dLightColor[ 3 ]; + + int colorDelay; + float colorDelayRandFrac; + byte initialColor[ 3 ]; + byte finalColor[ 3 ]; + + char childSystemName[ MAX_QPATH ]; + qhandle_t childSystemHandle; + + char onDeathSystemName[ MAX_QPATH ]; + qhandle_t onDeathSystemHandle; + + char childTrailSystemName[ MAX_QPATH ]; + qhandle_t childTrailSystemHandle; + + //particle invariant stuff + char shaderNames[ MAX_PS_SHADER_FRAMES ][ MAX_QPATH ]; + qhandle_t shaders[ MAX_PS_SHADER_FRAMES ]; + int numFrames; + float framerate; + + char modelNames[ MAX_PS_MODELS ][ MAX_QPATH ]; + qhandle_t models[ MAX_PS_MODELS ]; + int numModels; + animation_t modelAnimation; + + qboolean overdrawProtection; + qboolean realLight; + qboolean cullOnStartSolid; + + float scaleWithCharge; +} baseParticle_t; + + +//ejector template +typedef struct baseParticleEjector_s +{ + baseParticle_t *particles[ MAX_PARTICLES_PER_EJECTOR ]; + int numParticles; + + pLerpValues_t eject; //zero period indicates creation of all particles at once + + int totalParticles; //can be infinite + float totalParticlesRandFrac; +} baseParticleEjector_t; + + +//particle system template +typedef struct baseParticleSystem_s +{ + char name[ MAX_QPATH ]; + baseParticleEjector_t *ejectors[ MAX_EJECTORS_PER_SYSTEM ]; + int numEjectors; + + qboolean thirdPersonOnly; + qboolean registered; //whether or not the assets for this particle have been loaded +} baseParticleSystem_t; + + +//RUN TIME STRUCTURES +typedef struct particleSystem_s +{ + baseParticleSystem_t *class; + + attachment_t attachment; + + qboolean valid; + qboolean lazyRemove; //mark this system for later removal + + //for PMT_NORMAL + qboolean normalValid; + vec3_t normal; + + int charge; +} particleSystem_t; + + +typedef struct particleEjector_s +{ + baseParticleEjector_t *class; + particleSystem_t *parent; + + pLerpValues_t ejectPeriod; + + int count; + int totalParticles; + + int nextEjectionTime; + + qboolean valid; +} particleEjector_t; + + +//used for actual particle evaluation +typedef struct particle_s +{ + baseParticle_t *class; + particleEjector_t *parent; + + int birthTime; + int lifeTime; + + float bounceMarkRadius; + int bounceMarkCount; + int bounceSoundCount; + qboolean atRest; + + vec3_t origin; + vec3_t velocity; + + pMoveType_t accMoveType; + pMoveValues_t accMoveValues; + + int lastEvalTime; + + int nextChildTime; + + pLerpValues_t radius; + pLerpValues_t alpha; + pLerpValues_t rotation; + + pLerpValues_t dLightRadius; + + int colorDelay; + + qhandle_t model; + lerpFrame_t lf; + vec3_t lastAxis[ 3 ]; + + qboolean valid; + int frameWhenInvalidated; + + int sortKey; +} particle_t; + +//====================================================================== + +//trail system stuff +#define MAX_TRAIL_FILES 128 + +#define MAX_BEAMS_PER_SYSTEM 4 + +#define MAX_BASETRAIL_SYSTEMS 64 +#define MAX_BASETRAIL_BEAMS MAX_BASETRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM + +#define MAX_TRAIL_SYSTEMS 32 +#define MAX_TRAIL_BEAMS MAX_TRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM +#define MAX_TRAIL_BEAM_NODES 128 + +#define MAX_TRAIL_BEAM_JITTERS 4 + +typedef enum +{ + TBTT_STRETCH, + TBTT_REPEAT +} trailBeamTextureType_t; + +typedef struct baseTrailJitter_s +{ + float magnitude; + int period; +} baseTrailJitter_t; + +//beam template +typedef struct baseTrailBeam_s +{ + int numSegments; + float frontWidth; + float backWidth; + float frontAlpha; + float backAlpha; + byte frontColor[ 3 ]; + byte backColor[ 3 ]; + + // the time it takes for a segment to vanish (single attached only) + int segmentTime; + + // the time it takes for a beam to fade out (double attached only) + int fadeOutTime; + + char shaderName[ MAX_QPATH ]; + qhandle_t shader; + + trailBeamTextureType_t textureType; + + //TBTT_STRETCH + float frontTextureCoord; + float backTextureCoord; + + //TBTT_REPEAT + float repeatLength; + qboolean clampToBack; + + qboolean realLight; + + int numJitters; + baseTrailJitter_t jitters[ MAX_TRAIL_BEAM_JITTERS ]; + qboolean jitterAttachments; +} baseTrailBeam_t; + + +//trail system template +typedef struct baseTrailSystem_s +{ + char name[ MAX_QPATH ]; + baseTrailBeam_t *beams[ MAX_BEAMS_PER_SYSTEM ]; + int numBeams; + + int lifeTime; + qboolean thirdPersonOnly; + qboolean registered; //whether or not the assets for this trail have been loaded +} baseTrailSystem_t; + +typedef struct trailSystem_s +{ + baseTrailSystem_t *class; + + attachment_t frontAttachment; + attachment_t backAttachment; + + int birthTime; + int destroyTime; + qboolean valid; +} trailSystem_t; + +typedef struct trailBeamNode_s +{ + vec3_t refPosition; + vec3_t position; + + int timeLeft; + + float textureCoord; + float halfWidth; + byte alpha; + byte color[ 3 ]; + + vec2_t jitters[ MAX_TRAIL_BEAM_JITTERS ]; + + struct trailBeamNode_s *prev; + struct trailBeamNode_s *next; + + qboolean used; +} trailBeamNode_t; + +typedef struct trailBeam_s +{ + baseTrailBeam_t *class; + trailSystem_t *parent; + + trailBeamNode_t nodePool[ MAX_TRAIL_BEAM_NODES ]; + trailBeamNode_t *nodes; + + int lastEvalTime; + + qboolean valid; + + int nextJitterTimes[ MAX_TRAIL_BEAM_JITTERS ]; +} trailBeam_t; + +//====================================================================== + +// player entities need to track more information +// than any other type of entity. + +// note that not every player entity is a client entity, +// because corpses after respawn are outside the normal +// client numbering range + +// smoothing of view and model for WW transitions +#define MAXSMOOTHS 32 + +typedef struct +{ + float time; + float timeMod; + + vec3_t rotAxis; + float rotAngle; +} smooth_t; + + +typedef struct +{ + lerpFrame_t legs, torso, nonseg, weapon; + int painTime; + int painDirection; // flip from 0 to 1 + + // machinegun spinning + float barrelAngle; + int barrelTime; + qboolean barrelSpinning; + + vec3_t lastNormal; + vec3_t lastAxis[ 3 ]; + smooth_t sList[ MAXSMOOTHS ]; +} playerEntity_t; + +typedef struct lightFlareStatus_s +{ + float lastRadius; //caching of likely flare radius + float lastRatio; //caching of likely flare ratio + int lastTime; //last time flare was visible/occluded + qboolean status; //flare is visble? +} lightFlareStatus_t; + +typedef struct buildableStatus_s +{ + int lastTime; // Last time status was visible + qboolean visible; // Status is visble? +} buildableStatus_t; + +typedef struct buildableCache_s +{ + vec3_t cachedOrigin; // If either the cached entity origin or the + vec3_t cachedNormal; // cached surfNormal change the cache is invalid + vec3_t axis[ 3 ]; + vec3_t origin; +} buildableCache_t; + +//================================================= + +// centity_t have a direct corespondence with gentity_t in the game, but +// only the entityState_t is directly communicated to the cgame +typedef struct centity_s +{ + entityState_t currentState; // from cg.frame + entityState_t nextState; // from cg.nextFrame, if available + qboolean interpolate; // true if next is valid to interpolate to + qboolean currentValid; // true if cg.frame holds this entity + + int muzzleFlashTime; // move to playerEntity? + int muzzleFlashTime2; // move to playerEntity? + int muzzleFlashTime3; // move to playerEntity? + int previousEvent; + int teleportFlag; + + int trailTime; // so missile trails can handle dropped initial packets + int dustTrailTime; + int miscTime; + int snapShotTime; // last time this entity was found in a snapshot + + playerEntity_t pe; + + int errorTime; // decay the error from this time + vec3_t errorOrigin; + vec3_t errorAngles; + + qboolean extrapolated; // false if origin / angles is an interpolation + vec3_t rawOrigin; + vec3_t rawAngles; + + vec3_t beamEnd; + + // exact interpolated position of entity on this frame + vec3_t lerpOrigin; + vec3_t lerpAngles; + + lerpFrame_t lerpFrame; + + buildableAnimNumber_t buildableAnim; //persistant anim number + buildableAnimNumber_t oldBuildableAnim; //to detect when new anims are set + qboolean buildableIdleAnim; //to check if new idle anim + particleSystem_t *buildablePS; + buildableStatus_t buildableStatus; + buildableCache_t buildableCache; // so we don't recalculate things + float lastBuildableHealth; + int lastBuildableDamageSoundTime; + + lightFlareStatus_t lfs; + + qboolean doorState; + + qboolean animInit; + qboolean animPlaying; + qboolean animLastState; + + particleSystem_t *muzzlePS; + qboolean muzzlePsTrigger; + + particleSystem_t *jetPackPS; + jetPackState_t jetPackState; + int jetPackJumpTime; + int jetPackRefuelTime; //to avoid spamming sounds + + particleSystem_t *poisonCloudedPS; + + particleSystem_t *entityPS; + qboolean entityPSMissing; + + trailSystem_t *level2ZapTS[ LEVEL2_AREAZAP_MAX_TARGETS ]; + int level2ZapTime; + + trailSystem_t *muzzleTS; //used for the tesla and reactor + int muzzleTSDeathTime; + + qboolean valid; + qboolean oldValid; + struct centity_s *nextLocation; +} centity_t; + + +//====================================================================== + +typedef struct markPoly_s +{ + struct markPoly_s *prevMark, *nextMark; + int time; + qhandle_t markShader; + qboolean alphaFade; // fade alpha instead of rgb + float color[ 4 ]; + poly_t poly; + polyVert_t verts[ MAX_VERTS_ON_POLY ]; +} markPoly_t; + +//====================================================================== + + +typedef struct +{ + int client; + int score; + int ping; + int time; + int team; + weapon_t weapon; + upgrade_t upgrade; +} score_t; + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a client's configstring changes, +// usually as a result of a userinfo (name, model, etc) change +#define MAX_CUSTOM_SOUNDS 32 +typedef struct +{ + qboolean infoValid; + + char name[ MAX_QPATH ]; + team_t team; + + int score; // updated by score servercmds + int location; // location index for team mode + int health; // you only get this info about your teammates + int upgrade; + int curWeaponClass; // sends current weapon for H, current class for A + + // when clientinfo is changed, the loading of models/skins/sounds + // can be deferred until you are dead, to prevent hitches in + // gameplay + char modelName[ MAX_QPATH ]; + char skinName[ MAX_QPATH ]; + + qboolean newAnims; // true if using the new mission pack animations + qboolean fixedlegs; // true if legs yaw is always the same as torso yaw + qboolean fixedtorso; // true if torso never changes yaw + qboolean nonsegmented; // true if model is Q2 style nonsegmented + + vec3_t headOffset; // move head in icon views + footstep_t footsteps; + gender_t gender; // from model + + qhandle_t legsModel; + qhandle_t legsSkin; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + + qhandle_t headModel; + qhandle_t headSkin; + + qhandle_t nonSegModel; //non-segmented model system + qhandle_t nonSegSkin; //non-segmented model system + + qhandle_t modelIcon; + + animation_t animations[ MAX_PLAYER_TOTALANIMATIONS ]; + + sfxHandle_t sounds[ MAX_CUSTOM_SOUNDS ]; + + sfxHandle_t customFootsteps[ 4 ]; + sfxHandle_t customMetalFootsteps[ 4 ]; + + char voice[ MAX_VOICE_NAME_LEN ]; + int voiceTime; +} clientInfo_t; + + +typedef struct weaponInfoMode_s +{ + float flashDlight; + vec3_t flashDlightColor; + sfxHandle_t flashSound[ 4 ]; // fast firing weapons randomly choose + qboolean continuousFlash; + + qhandle_t missileModel; + sfxHandle_t missileSound; + float missileDlight; + vec3_t missileDlightColor; + int missileRenderfx; + qboolean usesSpriteMissle; + qhandle_t missileSprite; + int missileSpriteSize; + float missileSpriteCharge; + qhandle_t missileParticleSystem; + qhandle_t missileTrailSystem; + qboolean missileRotates; + qboolean missileAnimates; + int missileAnimStartFrame; + int missileAnimNumFrames; + int missileAnimFrameRate; + int missileAnimLooping; + + sfxHandle_t firingSound; + + qhandle_t muzzleParticleSystem; + + qboolean alwaysImpact; + qhandle_t impactParticleSystem; + qhandle_t impactMark; + qhandle_t impactMarkSize; + sfxHandle_t impactSound[ 4 ]; //random impact sound + sfxHandle_t impactFleshSound[ 4 ]; //random impact sound +} weaponInfoMode_t; + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s +{ + qboolean registered; + char *humanName; + + qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + qhandle_t weaponModel; + qhandle_t barrelModel; + qhandle_t flashModel; + + qhandle_t weaponModel3rdPerson; + qhandle_t barrelModel3rdPerson; + qhandle_t flashModel3rdPerson; + + animation_t animations[ MAX_WEAPON_ANIMATIONS ]; + qboolean noDrift; + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + qhandle_t weaponIcon; + qhandle_t ammoIcon; + + qhandle_t crossHair; + int crossHairSize; + + sfxHandle_t readySound; + + qboolean disableIn3rdPerson; + + weaponInfoMode_t wim[ WPM_NUM_WEAPONMODES ]; +} weaponInfo_t; + +typedef struct upgradeInfo_s +{ + qboolean registered; + char *humanName; + + qhandle_t upgradeIcon; +} upgradeInfo_t; + +typedef struct +{ + qboolean looped; + qboolean enabled; + + sfxHandle_t sound; +} sound_t; + +typedef struct +{ + qhandle_t models[ MAX_BUILDABLE_MODELS ]; + animation_t animations[ MAX_BUILDABLE_ANIMATIONS ]; + + //same number of sounds as animations + sound_t sounds[ MAX_BUILDABLE_ANIMATIONS ]; +} buildableInfo_t; + +#define MAX_CUBOID_TEXTURES 8 +#define MAX_CUBOID_FRAGMENTS 4 +#define MAX_CUBOID_SOUNDS 8 + +typedef struct +{ + int textureCount; + qhandle_t textures[ MAX_CUBOID_TEXTURES ]; + float textureThresholds[ MAX_CUBOID_TEXTURES ]; + qboolean useCracks; + + int fragmentCount; + qhandle_t fragments[ MAX_CUBOID_FRAGMENTS ]; + + int painSoundCount; + sfxHandle_t painSounds[ MAX_CUBOID_SOUNDS ]; + + int destroySoundCount; + sfxHandle_t destroySounds[ MAX_CUBOID_SOUNDS ]; + float destroySoundThresholds[ MAX_CUBOID_SOUNDS ]; + + sfxHandle_t idleSound; +} cuboidInfo_t; + +#define MAX_REWARDSTACK 10 +#define MAX_SOUNDBUFFER 20 + +//====================================================================== + +typedef struct +{ + vec3_t alienBuildablePos[ MAX_GENTITIES ]; + int alienBuildableTimes[ MAX_GENTITIES ]; + int numAlienBuildables; + + vec3_t humanBuildablePos[ MAX_GENTITIES ]; + int numHumanBuildables; + + vec3_t alienClientPos[ MAX_CLIENTS ]; + int numAlienClients; + + vec3_t humanClientPos[ MAX_CLIENTS ]; + int numHumanClients; + + int lastUpdateTime; + vec3_t origin; + vec3_t vangles; +} entityPos_t; + +typedef struct +{ + int time; + int length; +} consoleLine_t; + +#define MAX_CONSOLE_TEXT 8192 +#define MAX_CONSOLE_LINES 32 + +// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action +// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after + +#define MAX_PREDICTED_EVENTS 16 + +#define NUM_SAVED_STATES ( CMD_BACKUP + 2 ) + +// After this many msec the crosshair name fades out completely +#define CROSSHAIR_CLIENT_TIMEOUT 1000 + +typedef struct +{ + int clientFrame; // incremented each frame + + int clientNum; + + qboolean demoPlayback; + qboolean levelShot; // taking a level menu screenshot + int deferredPlayerLoading; + qboolean loading; // don't defer players at initial startup + qboolean intermissionStarted; // don't play voice rewards, because game will end shortly + + // there are only one or two snapshot_t that are relevent at a time + int latestSnapshotNum; // the number of snapshots the client system has received + int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet + + snapshot_t *snap; // cg.snap->serverTime <= cg.time + snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL + snapshot_t activeSnapshots[ 2 ]; + + float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / + // (cg.nextFrame->serverTime - cg.frame->serverTime) + + qboolean thisFrameTeleport; + qboolean nextFrameTeleport; + + int frametime; // cg.time - cg.oldTime + + int time; // this is the time value that the client + // is rendering at. + int oldTime; // time at last frame, used for missile trails and prediction checking + + int physicsTime; // either cg.snap->time or cg.nextSnap->time + + int splashTime; // cg.time when loading ended + + int timelimitWarnings; // 5 min, 1 min, overtime + int fraglimitWarnings; + + qboolean mapRestart; // set on a map restart to set back the weapon + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + pmoveExt_t pmext; + centity_t predictedPlayerEntity; + qboolean validPPS; // clear until the first call to CG_PredictPlayerState + int predictedErrorTime; + vec3_t predictedError; + + int eventSequence; + int predictableEvents[MAX_PREDICTED_EVENTS]; + + float stepChange; // for stair up smoothing + int stepTime; + + float duckChange; // for duck viewheight smoothing + int duckTime; + + float landChange; // for landing hard + int landTime; + + // input state sent to server + int weaponSelect; + + // auto rotating items + vec3_t autoAngles; + vec3_t autoAxis[ 3 ]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[ 3 ]; + + // view rendering + refdef_t refdef; + vec3_t refdefViewAngles; // will be converted to refdef.viewaxis + + // zoom key + qboolean zoomed; + int zoomTime; + float zoomSensitivity; + + // information screen text during loading + char infoScreenText[ MAX_STRING_CHARS ]; + + // scoreboard + int scoresRequestTime; + int numScores; + int selectedScore; + int teamScores[ 2 ]; + score_t scores[MAX_CLIENTS]; + qboolean showScores; + qboolean scoreBoardShowing; + int scoreFadeTime; + char killerName[ MAX_NAME_LENGTH ]; + char spectatorList[ MAX_STRING_CHARS ]; // list of names + int spectatorTime; // next time to offset + float spectatorOffset; // current offset from start + + // centerprinting + int centerPrintTime; + int centerPrintCharWidth; + int centerPrintY; + char centerPrint[ MAX_STRING_CHARS ]; + int centerPrintLines; + + // low ammo warning state + int lowAmmoWarning; // 1 = low, 2 = empty + + // kill timers for carnage reward + int lastKillTime; + + // crosshair client ID + int crosshairBuildable; + int crosshairClientNum; + int crosshairClientTime; + + // powerup active flashing + int powerupActive; + int powerupTime; + + // attacking player + int attackerTime; + + // reward medals + int rewardStack; + int rewardTime; + int rewardCount[ MAX_REWARDSTACK ]; + qhandle_t rewardShader[ MAX_REWARDSTACK ]; + qhandle_t rewardSound[ MAX_REWARDSTACK ]; + + // sound buffer mainly for announcer sounds + int soundBufferIn; + int soundBufferOut; + int soundTime; + qhandle_t soundBuffer[ MAX_SOUNDBUFFER ]; + + // for voice chat buffer + int voiceChatTime; + int voiceChatBufferIn; + int voiceChatBufferOut; + + // warmup countdown + int warmupTime; + + //========================== + + int itemPickup; + int itemPickupTime; + int itemPickupBlendTime; // the pulse around the crosshair is timed seperately + + int weaponSelectTime; + int weaponAnimation; + int weaponAnimationTime; + + // blend blobs + float damageTime; + float damageX, damageY, damageValue; + + // status bar head + float headYaw; + float headEndPitch; + float headEndYaw; + int headEndTime; + float headStartPitch; + float headStartYaw; + int headStartTime; + + // view movement + float v_dmg_time; + float v_dmg_pitch; + float v_dmg_roll; + + qboolean chaseFollow; + + // temp working variables for player view + float bobfracsin; + int bobcycle; + float xyspeed; + int nextOrbitTime; + + // development tool + refEntity_t testModelEntity; + refEntity_t testModelBarrelEntity; + char testModelName[MAX_QPATH]; + char testModelBarrelName[MAX_QPATH]; + qboolean testGun; + + int spawnTime; // fovwarp + int weapon1Time; // time when BUTTON_ATTACK went t->f f->t + int weapon2Time; // time when BUTTON_ATTACK2 went t->f f->t + int weapon3Time; // time when BUTTON_USE_HOLDABLE went t->f f->t + qboolean weapon1Firing; + qboolean weapon2Firing; + qboolean weapon3Firing; + + int poisonedTime; + + vec3_t lastNormal; // view smoothage + vec3_t lastVangles; // view smoothage + smooth_t sList[ MAXSMOOTHS ]; // WW smoothing + + int forwardMoveTime; // for struggling + int rightMoveTime; + int upMoveTime; + + float charModelFraction; // loading percentages + float mediaFraction; + float buildablesFraction; + + int lastBuildAttempt; + int lastEvolveAttempt; + + char consoleText[ MAX_CONSOLE_TEXT ]; + consoleLine_t consoleLines[ MAX_CONSOLE_LINES ]; + int numConsoleLines; + + particleSystem_t *poisonCloudPS; + particleSystem_t *poisonCloudedPS; + + float painBlendValue; + float painBlendTarget; + float healBlendValue; + int lastHealth; + qboolean wasDeadLastFrame; + + int lastPredictedCommand; + int lastServerTime; + playerState_t savedPmoveStates[ NUM_SAVED_STATES ]; + int stateHead, stateTail; + int ping; + + float chargeMeterAlpha; + float chargeMeterValue; + qhandle_t lastHealthCross; + float healthCrossFade; + int nearUsableBuildable; + + int nextWeaponClickTime; + + vec3_t cuboidSelection; + qboolean forbidCuboids; //if true then dont let player build a cuboid + int latestCBNumber; //wait for this number from server before building a cuboid + int lastCuboidError; //last time error sound was played +} cg_t; + + +// all of the model, shader, and sound references that are +// loaded at gamestate time are stored in cgMedia_t +// Other media that can be tied to clients, weapons, or items are +// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t +typedef struct +{ + qhandle_t charsetShader; + qhandle_t whiteShader; + qhandle_t outlineShader; + + qhandle_t level2ZapTS; + + qhandle_t balloonShader; + qhandle_t connectionShader; + + qhandle_t viewBloodShader; + qhandle_t tracerShader; + qhandle_t crosshairShader[ WP_NUM_WEAPONS ]; + qhandle_t backTileShader; + + qhandle_t creepShader; + + qhandle_t scannerShader; + qhandle_t scannerBlipShader; + qhandle_t scannerLineShader; + + qhandle_t teamOverlayShader; + + qhandle_t numberShaders[ 11 ]; + + qhandle_t shadowMarkShader; + qhandle_t wakeMarkShader; + + // buildable shaders + qhandle_t greenBuildShader; + qhandle_t redBuildShader; + qhandle_t humanSpawningShader; + + // disconnect + qhandle_t disconnectPS; + qhandle_t disconnectSound; + + // sounds + sfxHandle_t tracerSound; + sfxHandle_t weaponEmptyClick; + sfxHandle_t selectSound; + sfxHandle_t footsteps[ FOOTSTEP_TOTAL ][ 4 ]; + sfxHandle_t talkSound; + sfxHandle_t alienTalkSound; + sfxHandle_t humanTalkSound; + sfxHandle_t landSound; + sfxHandle_t fallSound; + sfxHandle_t turretSpinupSound; + + sfxHandle_t hardBounceSound1; + sfxHandle_t hardBounceSound2; + + sfxHandle_t voteNow; + sfxHandle_t votePassed; + sfxHandle_t voteFailed; + + sfxHandle_t watrInSound; + sfxHandle_t watrOutSound; + sfxHandle_t watrUnSound; + + sfxHandle_t jetpackDescendSound; + sfxHandle_t jetpackIdleSound; + sfxHandle_t jetpackAscendSound; + sfxHandle_t jetpackDescendDeactivateSound; + sfxHandle_t jetpackIdleDeactivateSound; + sfxHandle_t jetpackAscendDeactivateSound; + sfxHandle_t jetpackJumpSound; + sfxHandle_t jetpackLowFuelSound; + sfxHandle_t jetpackNoJumpFuelSound; + sfxHandle_t jetpackRefuelSound; + + qhandle_t jetPackDescendPS; + qhandle_t jetPackHoverPS; + qhandle_t jetPackAscendPS; + + sfxHandle_t medkitUseSound; + + sfxHandle_t alienStageTransition; + sfxHandle_t humanStageTransition; + + sfxHandle_t alienOvermindAttack; + sfxHandle_t alienOvermindDying; + sfxHandle_t alienOvermindSpawns; + + sfxHandle_t alienBuildableExplosion; + sfxHandle_t alienBuildableDamage; + sfxHandle_t alienBuildablePrebuild; + sfxHandle_t humanBuildableExplosion; + sfxHandle_t humanBuildablePrebuild; + sfxHandle_t humanBuildableDamage[ 4 ]; + + sfxHandle_t alienL1Grab; + sfxHandle_t alienL4ChargePrepare; + sfxHandle_t alienL4ChargeStart; + + qhandle_t cursor; + qhandle_t selectCursor; + qhandle_t sizeCursor; + + //light armour + qhandle_t larmourHeadSkin; + qhandle_t larmourMk2HeadSkin; + qhandle_t larmourLegsSkin; + qhandle_t larmourTorsoSkin; + + qhandle_t jetpackModel; + qhandle_t jetpackFlashModel; + qhandle_t battpackModel; + + sfxHandle_t repeaterUseSound; + + sfxHandle_t buildableRepairSound; + sfxHandle_t buildableRepairedSound; + + qhandle_t poisonCloudPS; + qhandle_t poisonCloudedPS; + qhandle_t alienEvolvePS; + qhandle_t alienAcidTubePS; + + sfxHandle_t alienEvolveSound; + + qhandle_t humanBuildableDamagedPS; + qhandle_t humanBuildableDestroyedPS; + qhandle_t alienBuildableDamagedPS; + qhandle_t alienBuildableDestroyedPS; + + qhandle_t alienBleedPS; + qhandle_t humanBleedPS; + qhandle_t alienBuildableBleedPS; + qhandle_t humanBuildableBleedPS; + + + qhandle_t teslaZapTS; + + sfxHandle_t lCannonWarningSound; + sfxHandle_t lCannonWarningSound2; + + qhandle_t buildWeaponTimerPie[ 8 ]; + qhandle_t upgradeClassIconShader; + qhandle_t healthCross; + qhandle_t healthCross2X; + qhandle_t healthCross3X; + qhandle_t healthCrossMedkit; + qhandle_t healthCrossPoisoned; + + qhandle_t cuboidCracks[CUBOID_CRACK_TEXTURES-1]; + qhandle_t cuboidModel; + qhandle_t cuboidRedBuildShader; + qhandle_t cuboidYellowBuildShader; + qhandle_t cuboidGreenBuildShader; + qhandle_t cuboidAxis; + qhandle_t cuboidAlienPrebuild; + + sfxHandle_t cuboidErrorSound; + sfxHandle_t cuboidResizeSoundA; + sfxHandle_t cuboidResizeSoundB; + sfxHandle_t cuboidRotateSound; + sfxHandle_t cuboidAxisChangeSound; + + qhandle_t splashLogo[15]; + qhandle_t splashLeft; + qhandle_t splashRight; + + qhandle_t alienHatchPS; + sfxHandle_t alienHatchSound; + sfxHandle_t alienFailedHatchSound; +} cgMedia_t; + +typedef struct +{ + qhandle_t frameShader; + qhandle_t overlayShader; + qhandle_t noPowerShader; + qhandle_t markedShader; + vec4_t healthSevereColor; + vec4_t healthHighColor; + vec4_t healthElevatedColor; + vec4_t healthGuardedColor; + vec4_t healthLowColor; + int frameHeight; + int frameWidth; + int healthPadding; + int overlayHeight; + int overlayWidth; + float verticalMargin; + float horizontalMargin; + vec4_t foreColor; + vec4_t backColor; + qboolean loaded; +} buildStat_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate. It will NOT +// be cleared when a tournement restart is done, allowing +// all clients to begin playing instantly +typedef struct +{ + gameState_t gameState; // gamestate from server + glconfig_t glconfig; // rendering configuration + float screenXScale; // derived from glconfig + float screenYScale; + float screenXBias; + + int serverCommandSequence; // reliable command stream counter + int processedSnapshotNum; // the number of snapshots cgame has requested + + qboolean localServer; // detected on startup by checking sv_running + + // parsed from serverinfo + int timelimit; + int maxclients; + char mapname[ MAX_QPATH ]; + qboolean markDeconstruct; // Whether or not buildables are marked + + int voteTime[ NUM_TEAMS ]; + int voteYes[ NUM_TEAMS ]; + int voteNo[ NUM_TEAMS ]; + char voteCaller[ NUM_TEAMS ][ MAX_NAME_LENGTH ]; + qboolean voteModified[ NUM_TEAMS ];// beep whenever changed + char voteString[ NUM_TEAMS ][ MAX_STRING_TOKENS ]; + + int levelStartTime; + + int scores1, scores2; // from configstrings + + qboolean newHud; + + int alienStage; + int humanStage; + int alienCredits; + int humanCredits; + int alienNextStageThreshold; + int humanNextStageThreshold; + + // + // locally derived information from gamestate + // + qhandle_t gameModels[ MAX_MODELS ]; + qhandle_t gameShaders[ MAX_GAME_SHADERS ]; + qhandle_t gameParticleSystems[ MAX_GAME_PARTICLE_SYSTEMS ]; + sfxHandle_t gameSounds[ MAX_SOUNDS ]; + + int numInlineModels; + qhandle_t inlineDrawModel[ MAX_MODELS ]; + vec3_t inlineModelMidpoints[ MAX_MODELS ]; + + clientInfo_t clientinfo[ MAX_CLIENTS ]; + + int teaminfoReceievedTime; + + // corpse info + clientInfo_t corpseinfo[ MAX_CLIENTS ]; + + int cursorX; + int cursorY; + qboolean eventHandling; + qboolean mouseCaptured; + qboolean sizingHud; + void *capturedItem; + qhandle_t activeCursor; + + buildStat_t alienBuildStat; + buildStat_t humanBuildStat; + + // media + cgMedia_t media; + + voice_t *voices; + clientList_t ignoreList; +} cgs_t; + +typedef struct +{ + char *cmd; + void ( *function )( void ); +} consoleCommand_t; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t cg; +extern centity_t cg_entities[ MAX_GENTITIES ]; +extern displayContextDef_t cgDC; + +extern weaponInfo_t cg_weapons[ 32 ]; +extern upgradeInfo_t cg_upgrades[ 32 ]; + +extern buildableInfo_t cg_buildables[ CUBOID_FIRST ]; +extern cuboidInfo_t cg_cuboids[ CUBOID_TYPES ]; + +extern markPoly_t cg_markPolys[ MAX_MARK_POLYS ]; + +extern vmCvar_t cg_teslaTrailTime; +extern vmCvar_t cg_centertime; +extern vmCvar_t cg_runpitch; +extern vmCvar_t cg_runroll; +extern vmCvar_t cg_swingSpeed; +extern vmCvar_t cg_shadows; +extern vmCvar_t cg_drawTimer; +extern vmCvar_t cg_drawClock; +extern vmCvar_t cg_drawFPS; +extern vmCvar_t cg_drawDemoState; +extern vmCvar_t cg_drawSnapshot; +extern vmCvar_t cg_drawChargeBar; +extern vmCvar_t cg_drawCrosshair; +extern vmCvar_t cg_drawCrosshairNames; +extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlaySortMode; +extern vmCvar_t cg_teamOverlayMaxPlayers; +extern vmCvar_t cg_teamOverlayUserinfo; +extern vmCvar_t cg_draw2D; +extern vmCvar_t cg_animSpeed; +extern vmCvar_t cg_debugAnim; +extern vmCvar_t cg_debugPosition; +extern vmCvar_t cg_debugEvents; +extern vmCvar_t cg_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_debugMove; +extern vmCvar_t cg_noPlayerAnims; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_addMarks; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_drawGun; +extern vmCvar_t cg_gun_frame; +extern vmCvar_t cg_gun_x; +extern vmCvar_t cg_gun_y; +extern vmCvar_t cg_gun_z; +extern vmCvar_t cg_tracerChance; +extern vmCvar_t cg_tracerWidth; +extern vmCvar_t cg_tracerLength; +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPersonShoulderViewMode; +extern vmCvar_t cg_staticDeathCam; +extern vmCvar_t cg_thirdPersonPitchFollow; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_stereoSeparation; +extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_drawSpeed; +extern vmCvar_t cg_synchronousClients; +extern vmCvar_t cg_stats; +extern vmCvar_t cg_paused; +extern vmCvar_t cg_blood; +extern vmCvar_t cg_teamOverlayUserinfo; +extern vmCvar_t cg_teamChatsOnly; +extern vmCvar_t cg_noVoiceChats; +extern vmCvar_t cg_noVoiceText; +extern vmCvar_t cg_hudFiles; +extern vmCvar_t cg_smoothClients; +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; +extern vmCvar_t cg_cameraMode; +extern vmCvar_t cg_timescaleFadeEnd; +extern vmCvar_t cg_timescaleFadeSpeed; +extern vmCvar_t cg_timescale; +extern vmCvar_t cg_noTaunt; +extern vmCvar_t cg_drawSurfNormal; +extern vmCvar_t cg_drawBBOX; +extern vmCvar_t cg_wwSmoothTime; +extern vmCvar_t cg_disableBlueprintErrors; +extern vmCvar_t cg_depthSortParticles; +extern vmCvar_t cg_bounceParticles; +extern vmCvar_t cg_consoleLatency; +extern vmCvar_t cg_lightFlare; +extern vmCvar_t cg_debugParticles; +extern vmCvar_t cg_debugTrails; +extern vmCvar_t cg_debugPVS; +extern vmCvar_t cg_disableWarningDialogs; +extern vmCvar_t cg_disableUpgradeDialogs; +extern vmCvar_t cg_disableBuildDialogs; +extern vmCvar_t cg_disableCommandDialogs; +extern vmCvar_t cg_disableScannerPlane; +extern vmCvar_t cg_tutorial; + +extern vmCvar_t cg_modTutorial; +extern vmCvar_t cg_modTutorialReference; +extern vmCvar_t cg_lastModVersion; + +extern vmCvar_t cg_painBlendUpRate; +extern vmCvar_t cg_painBlendDownRate; +extern vmCvar_t cg_painBlendMax; +extern vmCvar_t cg_painBlendScale; +extern vmCvar_t cg_painBlendZoom; + +extern vmCvar_t cg_stickySpec; +extern vmCvar_t cg_sprintToggle; +extern vmCvar_t cg_unlagged; + +extern vmCvar_t cg_debugVoices; + +extern vmCvar_t ui_currentClass; +extern vmCvar_t ui_carriage; +extern vmCvar_t ui_stages; +extern vmCvar_t ui_dialog; +extern vmCvar_t ui_voteActive; +extern vmCvar_t ui_alienTeamVoteActive; +extern vmCvar_t ui_humanTeamVoteActive; + +extern vmCvar_t cg_debugRandom; + +extern vmCvar_t cg_optimizePrediction; +extern vmCvar_t cg_projectileNudge; + +extern vmCvar_t cg_voice; + +extern vmCvar_t cg_emoticons; + +extern vmCvar_t cg_chatTeamPrefix; + +extern vmCvar_t cg_cuboidResizeAxis; +extern vmCvar_t cg_cuboidPSQuality; +extern vmCvar_t cg_cuboidInfoX; +extern vmCvar_t cg_cuboidInfoY; + +extern vmCvar_t cg_fuelInfoX; +extern vmCvar_t cg_fuelInfoY; +extern vmCvar_t cg_fuelInfoScale; + +// +// cg_main.c +// +const char *CG_ConfigString( int index ); +const char *CG_Argv( int arg ); + +void QDECL CG_Printf( const char *msg, ... ); +void QDECL CG_Error( const char *msg, ... ); + +void CG_StartMusic( void ); +int CG_PlayerCount( void ); + +void CG_UpdateCvars( void ); + +int CG_CrosshairPlayer( void ); +int CG_LastAttacker( void ); +void CG_LoadMenus( const char *menuFile ); +void CG_KeyEvent( int key, qboolean down ); +void CG_MouseEvent( int x, int y ); +void CG_EventHandling( int type ); +void CG_SetScoreSelection( void *menu ); +qboolean CG_ClientIsReady( int clientNum ); +void CG_BuildSpectatorString( void ); + +qboolean CG_FileExists( char *filename ); +void CG_RemoveNotifyLine( void ); +void CG_AddNotifyText( void ); + + +// +// cg_view.c +// +void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ); +void CG_TestModel_f( void ); +void CG_TestGun_f( void ); +void CG_TestModelNextFrame_f( void ); +void CG_TestModelPrevFrame_f( void ); +void CG_TestModelNextSkin_f( void ); +void CG_TestModelPrevSkin_f( void ); +void CG_AddBufferedSound( sfxHandle_t sfx ); +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); +void CG_OffsetFirstPersonView( void ); +void CG_OffsetThirdPersonView( void ); +void CG_OffsetShoulderView( void ); + + +// +// cg_drawtools.c +// +void CG_DrawPlane( vec3_t origin, vec3_t down, vec3_t right, qhandle_t shader ); +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); +void CG_FillRect( float x, float y, float width, float height, const float *color ); +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void CG_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor, + vec4_t tcolor, float amount, qhandle_t hShader ); +void CG_SetClipRegion( float x, float y, float w, float h ); +void CG_ClearClipRegion( void ); + +int CG_DrawStrlen( const char *str ); + +float *CG_FadeColor( int startMsec, int totalMsec ); +void CG_TileClear( void ); +void CG_ColorForHealth( vec4_t hcolor ); +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); +void CG_DrawSides(float x, float y, float w, float h, float size); +void CG_DrawTopBottom(float x, float y, float w, float h, float size); +qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ); +char *CG_KeyBinding( const char *bind ); +char CG_GetColorCharForHealth( int clientnum ); + +// +// cg_draw.c +// + +void CG_AddLagometerFrameInfo( void ); +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_AddSpeed( void ); +void CG_CenterPrint( const char *str, int y, int charWidth ); +void CG_DrawActive( stereoFrame_t stereoView ); +void CG_OwnerDraw( float x, float y, float w, float h, float text_x, + float text_y, int ownerDraw, int ownerDrawFlags, + int align, int textalign, int textvalign, + float borderSize, float scale, vec4_t foreColor, + vec4_t backColor, qhandle_t shader, int textStyle ); +float CG_GetValue(int ownerDraw); +void CG_RunMenuScript(char **args); +void CG_SetPrintString( int type, const char *p ); +void CG_GetTeamColor( vec4_t *color ); +const char *CG_GetKillerText( void ); +void CG_Text_PaintChar( float x, float y, float width, float height, float scale, + float s, float t, float s2, float t2, qhandle_t hShader ); +void CG_DrawLoadingScreen( void ); +void CG_UpdateMediaFraction( float newFract ); +void CG_ResetPainBlend( void ); +void CG_DrawField( float x, float y, int width, float cw, float ch, int value ); + +// +// cg_players.c +// +void CG_Player( centity_t *cent ); +void CG_Corpse( centity_t *cent ); +void CG_ResetPlayerEntity( centity_t *cent ); +void CG_NewClientInfo( int clientNum ); +void CG_PrecacheClientInfo( class_t class, char *model, char *skin ); +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); +void CG_PlayerDisconnect( vec3_t org ); +void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ); +centity_t *CG_GetPlayerLocation( void ); + +// +// cg_buildable.c +// +void CG_GhostBuildable( buildable_t buildable, vec3_t cuboidSize ); +void CG_Buildable( centity_t *cent ); +void CG_BuildableStatusParse( const char *filename, buildStat_t *bs ); +void CG_DrawBuildableStatus( void ); +void CG_InitBuildables( void ); +void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ); +void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir ); +void CG_CuboidAxis_f(void); +void CG_CuboidRotate_f(void); +void CG_Cuboid_Send(void); +void CG_Cuboid_Response(void); +void CG_CuboidResize( qboolean enlarge ); +void CG_CuboidExplosion(buildable_t buildable, vec3_t origin, vec3_t cuboid); +void CG_DrawCuboidParticles(void); +void CG_CuboidAttack_f(void); + +// +// cg_animation.c +// +void CG_RunLerpFrame( lerpFrame_t *lf, float scale ); + +// +// cg_animmapobj.c +// +void CG_AnimMapObj( centity_t *cent ); +void CG_ModelDoor( centity_t *cent ); + +// +// cg_predict.c +// + +#define MAGIC_TRACE_HACK -2 + +void CG_BuildSolidList( void ); +int CG_PointContents( const vec3_t point, int passEntityNum ); +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, + const vec3_t end, int skipNumber, int mask ); +void CG_CapTrace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, + const vec3_t end, int skipNumber, int mask ); +void CG_BiSphereTrace( trace_t *result, const vec3_t start, const vec3_t end, + const float startRadius, const float endRadius, int skipNumber, int mask ); +void CG_PredictPlayerState( void ); + + +// +// cg_events.c +// +void CG_CheckEvents( centity_t *cent ); +void CG_EntityEvent( centity_t *cent, vec3_t position ); +void CG_PainEvent( centity_t *cent, int health ); + + +// +// cg_ents.c +// +void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs ); +void CG_SetEntitySoundPosition( centity_t *cent ); +void CG_AddPacketEntities( void ); +void CG_Beam( centity_t *cent ); +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); + +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); + + +// +// cg_weapons.c +// +void CG_NextWeapon_f( void ); +void CG_PrevWeapon_f( void ); +void CG_Weapon_f( void ); + +void CG_InitUpgrades( void ); +void CG_RegisterUpgrade( int upgradeNum ); +void CG_InitWeapons( void ); +void CG_RegisterWeapon( int weaponNum ); + +void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode ); +void CG_MissileHitWall( weapon_t weapon, weaponMode_t weaponMode, int clientNum, + vec3_t origin, vec3_t dir, impactSound_t soundType, int charge ); +void CG_MissileHitEntity( weapon_t weaponNum, weaponMode_t weaponMode, + vec3_t origin, vec3_t dir, int entityNum, int charge ); +void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); +void CG_ShotgunFire( entityState_t *es ); + +void CG_AddViewWeapon (playerState_t *ps); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ); +void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ); +void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ); + + +// +// cg_scanner.c +// +void CG_UpdateEntityPositions( void ); +void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color ); +void CG_AlienSense( rectDef_t *rect ); + +// +// cg_marks.c +// +void CG_InitMarkPolys( void ); +void CG_AddMarks( void ); +void CG_ImpactMark( qhandle_t markShader, + const vec3_t origin, const vec3_t dir, + float orientation, + float r, float g, float b, float a, + qboolean alphaFade, + float radius, qboolean temporary ); + +// +// cg_snapshot.c +// +void CG_ProcessSnapshots( void ); + +// +// cg_consolecmds.c +// +qboolean CG_ConsoleCommand( void ); +void CG_InitConsoleCommands( void ); +qboolean CG_RequestScores( void ); +// +// cg_servercmds.c +// +void CG_ExecuteNewServerCommands( int latestSequence ); +void CG_ParseServerinfo( void ); +void CG_SetConfigValues( void ); +void CG_ShaderStateChanged(void); + +// +// cg_playerstate.c +// +void CG_Respawn( void ); +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); +void CG_CheckChangedPredictableEvents( playerState_t *ps ); + +// +// cg_attachment.c +// +qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v ); +qboolean CG_AttachmentDir( attachment_t *a, vec3_t v ); +qboolean CG_AttachmentAxis( attachment_t *a, vec3_t axis[ 3 ] ); +qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v ); +int CG_AttachmentCentNum( attachment_t *a ); + +qboolean CG_Attached( attachment_t *a ); + +void CG_AttachToPoint( attachment_t *a ); +void CG_AttachToCent( attachment_t *a ); +void CG_AttachToTag( attachment_t *a ); +void CG_AttachToParticle( attachment_t *a ); +void CG_SetAttachmentPoint( attachment_t *a, vec3_t v ); +void CG_SetAttachmentCent( attachment_t *a, centity_t *cent ); +void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent, + qhandle_t model, char *tagName ); +void CG_SetAttachmentParticle( attachment_t *a, particle_t *p ); + +void CG_SetAttachmentOffset( attachment_t *a, vec3_t v ); + +// +// cg_particles.c +// +void CG_LoadParticleSystems( void ); +qhandle_t CG_RegisterParticleSystem( char *name ); + +particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle ); +void CG_DestroyParticleSystem( particleSystem_t **ps ); + +qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps ); +qboolean CG_IsParticleSystemValid( particleSystem_t **ps ); + +void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ); + +void CG_AddParticles( void ); + +void CG_ParticleSystemEntity( centity_t *cent ); + +void CG_TestPS_f( void ); +void CG_DestroyTestPS_f( void ); + +// +// cg_trails.c +// +void CG_LoadTrailSystems( void ); +qhandle_t CG_RegisterTrailSystem( char *name ); + +trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle ); +void CG_DestroyTrailSystem( trailSystem_t **ts ); + +qboolean CG_IsTrailSystemValid( trailSystem_t **ts ); + +void CG_AddTrails( void ); + +void CG_TestTS_f( void ); +void CG_DestroyTestTS_f( void ); + +// +// cg_ptr.c +// +int CG_ReadPTRCode( void ); +void CG_WritePTRCode( int code ); + +// +// cg_tutorial.c +// +const char *CG_TutorialText( void ); + +// +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + + +// print message on the local console +void trap_Print( const char *fmt ); + +// abort the game +void trap_Error( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related. Get time from the CG_DrawActiveFrame parameter +int trap_Milliseconds( void ); + +// console variable interaction +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +// ServerCommand and ConsoleCommand parameter access +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); +void trap_LiteralArgs( char *buffer, int bufferLength ); + +// filesystem access +// returns length of file +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t +int trap_FS_GetFileList( const char *path, const char *extension, + char *listbuf, int bufsize ); + +// add commands to the local console as if they were typed in +// for map changing, etc. The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +void trap_SendConsoleCommand( const char *text ); + +// register a command name so the console can perform command completion. +// FIXME: replace this with a normal console command "defineCommand"? +void trap_AddCommand( const char *cmdName ); + +// send a string to the server over the network +void trap_SendClientCommand( const char *s ); + +// force a screen update, only used during gamestate load +void trap_UpdateScreen( void ); + +// model collision +void trap_CM_LoadMap( const char *mapname ); +int trap_CM_NumInlineModels( void ); +clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ); +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ); +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ); +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ); +void trap_CM_BiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask ); +void trap_CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask, + const vec3_t origin ); + +// Returns the projection of a polygon onto the solid brushes in the world +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ); + +// normal sounds will have their volume dynamically changed as their entity +// moves and the listener moves +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +void trap_S_StopLoopingSound( int entnum ); + +// a local sound is always played full volume +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void trap_S_ClearLoopingSounds( qboolean killall ); +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +// respatialize recalculates the volumes of sound as they should be heard by the +// given entityNum and position +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music +void trap_S_StopBackgroundTrack( void ); + + +void trap_R_LoadWorldMap( const char *mapname ); + +// all media should be registered during level startup to prevent +// hitches during gameplay +qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); + +// polys are intended for simple wall marks, not really for doing +// significant construction +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_SetClipRegion( const float *region ); +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ); +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in. The time is returned seperately so that +// snapshot latency can be calculated. +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int trap_GetCurrentCmdNumber( void ); + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon select and zoom +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +int trap_MemoryRemaining( void ); +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); +qboolean trap_Key_IsDown( int keynum ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +int trap_Key_GetKey( const char *binding ); +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ); +void trap_Key_SetBinding( int keynum, const char *binding ); +void trap_Key_SetOverstrikeMode( qboolean state ); +qboolean trap_Key_GetOverstrikeMode( void ); + +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ); +e_status trap_CIN_StopCinematic( int handle ); +e_status trap_CIN_RunCinematic( int handle ); +void trap_CIN_DrawCinematic( int handle ); +void trap_CIN_SetExtents( int handle, int x, int y, int w, int h ); + +void trap_SnapVector( float *v ); +int trap_RealTime( qtime_t *tm ); + +qboolean trap_loadCamera( const char *name ); +void trap_startCamera( int time ); +qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles ); + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); + +int trap_GetDemoState( void ); +int trap_GetDemoPos( void ); +void trap_GetDemoName( char *buffer, int size ); + +void CG_Cuboid_DrawInfo(void); + +// cg_drawCrosshair settings +#define CROSSHAIR_ALWAYSOFF 0 +#define CROSSHAIR_RANGEDONLY 1 +#define CROSSHAIR_ALWAYSON 2 + +// menu types for cg_disable*Dialogs +typedef enum +{ + DT_INTERACTIVE, // team, class, armoury + DT_ARMOURYEVOLVE, // Insufficient funds et al + DT_BUILD, // build errors + DT_COMMAND, // You must be living/human/spec etc. + +} dialogType_t; + + +// mod version data +#define MODVER_CURRENT 1 +#define MODVER_C2_0_1_0 1 +#define MODVER_TITLE "0.1.0 (Sep 06)" + + + + diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c new file mode 100644 index 0000000..48df981 --- /dev/null +++ b/src/cgame/cg_main.c @@ -0,0 +1,1985 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_main.c -- initialization and primary entry point for cgame + + +#include "cg_local.h" + +#include "../ui/ui_shared.h" +// display context for new ui stuff +displayContextDef_t cgDC; + +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_Shutdown( void ); +static char *CG_VoIPString( void ); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, + int arg4, int arg5, int arg6, int arg7, + int arg8, int arg9, int arg10, int arg11 ) +{ + switch( command ) + { + case CG_INIT: + CG_Init( arg0, arg1, arg2 ); + return 0; + + case CG_SHUTDOWN: + CG_Shutdown( ); + return 0; + + case CG_CONSOLE_COMMAND: + return CG_ConsoleCommand( ); + + case CG_CONSOLE_TEXT: + CG_AddNotifyText( ); + return 0; + + case CG_DRAW_ACTIVE_FRAME: + CG_DrawActiveFrame( arg0, arg1, arg2 ); + return 0; + + case CG_CROSSHAIR_PLAYER: + return CG_CrosshairPlayer( ); + + case CG_LAST_ATTACKER: + return CG_LastAttacker( ); + + case CG_KEY_EVENT: + CG_KeyEvent( arg0, arg1 ); + return 0; + + case CG_MOUSE_EVENT: + // cgame doesn't care where the cursor is + return 0; + + case CG_EVENT_HANDLING: + CG_EventHandling( arg0 ); + return 0; + + case CG_VOIP_STRING: + return (intptr_t)CG_VoIPString( ); + + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + + return -1; +} + + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[ MAX_GENTITIES ]; + +weaponInfo_t cg_weapons[ 32 ]; +upgradeInfo_t cg_upgrades[ 32 ]; + +buildableInfo_t cg_buildables[ CUBOID_FIRST ]; +cuboidInfo_t cg_cuboids[ CUBOID_TYPES ]; + +vmCvar_t cg_teslaTrailTime; +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawClock; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawDemoState; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_drawChargeBar; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_draw2D; +vmCvar_t cg_animSpeed; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_debugMove; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +vmCvar_t cg_viewsize; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_tracerChance; +vmCvar_t cg_tracerWidth; +vmCvar_t cg_tracerLength; +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_thirdPersonShoulderViewMode; +vmCvar_t cg_staticDeathCam; +vmCvar_t cg_thirdPersonPitchFollow; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawSpeed; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_stats; +vmCvar_t cg_paused; +vmCvar_t cg_blood; +vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlaySortMode; +vmCvar_t cg_teamOverlayMaxPlayers; +vmCvar_t cg_teamOverlayUserinfo; +vmCvar_t cg_noPrintDuplicate; +vmCvar_t cg_noVoiceChats; +vmCvar_t cg_noVoiceText; +vmCvar_t cg_hudFiles; +vmCvar_t cg_hudFilesEnable; +vmCvar_t cg_smoothClients; +vmCvar_t pmove_fixed; +vmCvar_t pmove_msec; +vmCvar_t cg_cameraMode; +vmCvar_t cg_timescaleFadeEnd; +vmCvar_t cg_timescaleFadeSpeed; +vmCvar_t cg_timescale; +vmCvar_t cg_noTaunt; +vmCvar_t cg_drawSurfNormal; +vmCvar_t cg_drawBBOX; +vmCvar_t cg_wwSmoothTime; +vmCvar_t cg_disableBlueprintErrors; +vmCvar_t cg_depthSortParticles; +vmCvar_t cg_bounceParticles; +vmCvar_t cg_consoleLatency; +vmCvar_t cg_lightFlare; +vmCvar_t cg_debugParticles; +vmCvar_t cg_debugTrails; +vmCvar_t cg_debugPVS; +vmCvar_t cg_disableWarningDialogs; +vmCvar_t cg_disableUpgradeDialogs; +vmCvar_t cg_disableBuildDialogs; +vmCvar_t cg_disableCommandDialogs; +vmCvar_t cg_disableScannerPlane; +vmCvar_t cg_tutorial; + +vmCvar_t cg_modTutorial; +vmCvar_t cg_modTutorialReference; +vmCvar_t cg_lastModVersion; + +vmCvar_t cg_painBlendUpRate; +vmCvar_t cg_painBlendDownRate; +vmCvar_t cg_painBlendMax; +vmCvar_t cg_painBlendScale; +vmCvar_t cg_painBlendZoom; + +vmCvar_t cg_stickySpec; +vmCvar_t cg_sprintToggle; +vmCvar_t cg_unlagged; + +vmCvar_t cg_debugVoices; + +vmCvar_t ui_currentClass; +vmCvar_t ui_carriage; +vmCvar_t ui_stages; +vmCvar_t ui_dialog; +vmCvar_t ui_voteActive; +vmCvar_t ui_alienTeamVoteActive; +vmCvar_t ui_humanTeamVoteActive; + +vmCvar_t cg_debugRandom; + +vmCvar_t cg_optimizePrediction; +vmCvar_t cg_projectileNudge; + +vmCvar_t cg_voice; + +vmCvar_t cg_emoticons; + +vmCvar_t cg_chatTeamPrefix; + +vmCvar_t cg_cuboidResizeAxis; +vmCvar_t cg_cuboidPSQuality; +vmCvar_t cg_cuboidInfoX; +vmCvar_t cg_cuboidInfoY; + +vmCvar_t cg_fuelInfoX; +vmCvar_t cg_fuelInfoY; +vmCvar_t cg_fuelInfoScale; + +typedef struct +{ + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +static cvarTable_t cvarTable[ ] = +{ + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "1", CVAR_ARCHIVE }, + { &cg_drawClock, "cg_drawClock", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "1", CVAR_ARCHIVE }, + { &cg_drawDemoState, "cg_drawDemoState", "1", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_drawChargeBar, "cg_drawChargeBar", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "2", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "1", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, + { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE }, + { &cg_drawSpeed, "cg_drawSpeed", "0", CVAR_ARCHIVE }, + { &cg_teslaTrailTime, "cg_teslaTrailTime", "250", CVAR_ARCHIVE }, + { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, + { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, + { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, + { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_debugMove, "cg_debugMove", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, + { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, + { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "75", CVAR_ARCHIVE }, + { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_CHEAT }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPersonPitchFollow, "cg_thirdPersonPitchFollow", "0", 0 }, + { &cg_thirdPersonShoulderViewMode, "cg_thirdPersonShoulderViewMode", "1", CVAR_ARCHIVE }, + { &cg_staticDeathCam, "cg_staticDeathCam", "0", CVAR_ARCHIVE }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "1", CVAR_ARCHIVE }, + { &cg_teamOverlaySortMode, "cg_teamOverlaySortMode", "1", CVAR_ARCHIVE }, + { &cg_teamOverlayMaxPlayers, "cg_teamOverlayMaxPlayers", "8", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noPrintDuplicate, "cg_noPrintDuplicate", "0", CVAR_ARCHIVE }, + { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, + { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, + { &cg_drawSurfNormal, "cg_drawSurfNormal", "0", CVAR_CHEAT }, + { &cg_drawBBOX, "cg_drawBBOX", "0", CVAR_CHEAT }, + { &cg_wwSmoothTime, "cg_wwSmoothTime", "300", CVAR_ARCHIVE }, + { NULL, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_disableBlueprintErrors", "0", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_stickySpec, "cg_stickySpec", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_sprintToggle, "cg_sprintToggle", "0", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_unlagged, "cg_unlagged", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_flySpeed", "600", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_depthSortParticles, "cg_depthSortParticles", "1", CVAR_ARCHIVE }, + { &cg_bounceParticles, "cg_bounceParticles", "0", CVAR_ARCHIVE }, + { &cg_consoleLatency, "cg_consoleLatency", "3000", CVAR_ARCHIVE }, + { &cg_lightFlare, "cg_lightFlare", "3", CVAR_ARCHIVE }, + { &cg_debugParticles, "cg_debugParticles", "0", CVAR_CHEAT }, + { &cg_debugTrails, "cg_debugTrails", "0", CVAR_CHEAT }, + { &cg_debugPVS, "cg_debugPVS", "0", CVAR_CHEAT }, + { &cg_disableWarningDialogs, "cg_disableWarningDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableUpgradeDialogs, "cg_disableUpgradeDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableBuildDialogs, "cg_disableBuildDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableCommandDialogs, "cg_disableCommandDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableScannerPlane, "cg_disableScannerPlane", "0", CVAR_ARCHIVE }, + { &cg_tutorial, "cg_tutorial", "1", CVAR_ARCHIVE }, + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, + { &cg_hudFilesEnable, "cg_hudFilesEnable", "0", CVAR_ARCHIVE}, + { NULL, "cg_alienConfig", "", CVAR_ARCHIVE }, + { NULL, "cg_humanConfig", "", CVAR_ARCHIVE }, + { NULL, "cg_spectatorConfig", "", CVAR_ARCHIVE }, + + { &cg_painBlendUpRate, "cg_painBlendUpRate", "10.0", 0 }, + { &cg_painBlendDownRate, "cg_painBlendDownRate", "0.5", 0 }, + { &cg_painBlendMax, "cg_painBlendMax", "0.7", 0 }, + { &cg_painBlendScale, "cg_painBlendScale", "7.0", 0 }, + { &cg_painBlendZoom, "cg_painBlendZoom", "0.65", 0 }, + + { &cg_debugVoices, "cg_debugVoices", "0", 0 }, + + // communication cvars set by the cgame to be read by ui + { &ui_currentClass, "ui_currentClass", "0", CVAR_ROM }, + { &ui_carriage, "ui_carriage", "", CVAR_ROM }, + { &ui_stages, "ui_stages", "0 0", CVAR_ROM }, + { &ui_dialog, "ui_dialog", "Text not set", CVAR_ROM }, + { &ui_voteActive, "ui_voteActive", "0", CVAR_ROM }, + { &ui_humanTeamVoteActive, "ui_humanTeamVoteActive", "0", CVAR_ROM }, + { &ui_alienTeamVoteActive, "ui_alienTeamVoteActive", "0", CVAR_ROM }, + + { &cg_debugRandom, "cg_debugRandom", "0", 0 }, + + { &cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE }, + { &cg_projectileNudge, "cg_projectileNudge", "1", CVAR_ARCHIVE }, + + // the following variables are created in other parts of the system, + // but we also reference them here + + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", CVAR_CHEAT }, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", CVAR_CHEAT }, + { &cg_timescale, "timescale", "1", 0}, + { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, + + { &pmove_fixed, "pmove_fixed", "0", 0}, + { &pmove_msec, "pmove_msec", "8", 0}, + { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, + + { &cg_voice, "voice", "default", CVAR_USERINFO|CVAR_ARCHIVE}, + + { &cg_emoticons, "cg_emoticons", "1", CVAR_LATCH|CVAR_ARCHIVE}, + + { &cg_chatTeamPrefix, "cg_chatTeamPrefix", "1", CVAR_ARCHIVE}, + + { &cg_cuboidResizeAxis, "cg_cuboidResizeAxis", "2", 0}, + { &cg_cuboidPSQuality, "cg_cuboidPSQuality", "3", CVAR_ARCHIVE}, + + { &cg_cuboidInfoX, "cg_cuboidInfoX" ,"0", CVAR_ARCHIVE }, + { &cg_cuboidInfoY, "cg_cuboidInfoY" ,"150", CVAR_ARCHIVE }, + + { &cg_modTutorial, "cg_modTutorial", "1", CVAR_ARCHIVE }, + { &cg_modTutorialReference, "cg_modTutorialReference", "0", CVAR_ARCHIVE }, + { &cg_lastModVersion, "cg_lastModVersion", "0", CVAR_ARCHIVE }, + + { &cg_fuelInfoX, "cg_fuelInfoX" ,"0", CVAR_ARCHIVE }, + { &cg_fuelInfoY, "cg_fuelInfoY" ,"150", CVAR_ARCHIVE }, + { &cg_fuelInfoScale, "cg_fuelInfoScale" ,"0.5", CVAR_ARCHIVE } +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) +{ + int i; + cvarTable_t *cv; + char var[ MAX_TOKEN_CHARS ]; + + for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ ) + { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); +} + + +/* +=============== +CG_SetUIVars + +Set some cvars used by the UI +=============== +*/ +static void CG_SetUIVars( void ) +{ + int i; + char carriageCvar[ MAX_TOKEN_CHARS ]; + + if( !cg.snap ) + return; + + *carriageCvar = 0; + + //determine what the player is carrying + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) && + BG_Weapon( i )->purchasable ) + strcat( carriageCvar, va( "W%d ", i ) ); + } + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) && + BG_Upgrade( i )->purchasable ) + strcat( carriageCvar, va( "U%d ", i ) ); + } + strcat( carriageCvar, "$" ); + + trap_Cvar_Set( "ui_carriage", carriageCvar ); + + trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) ); +} + +/* +================= +CG_UpdateCvars +================= +*/ +void CG_UpdateCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ ) + if( cv->vmCvar ) + trap_Cvar_Update( cv->vmCvar ); + + // check for modications here + + CG_SetUIVars( ); + +} + + +int CG_CrosshairPlayer( void ) +{ + if( cg.time > ( cg.crosshairClientTime + 1000 ) ) + return -1; + + return cg.crosshairClientNum; +} + + +int CG_LastAttacker( void ) +{ + if( !cg.attackerTime ) + return -1; + + return cg.snap->ps.persistant[ PERS_ATTACKER ]; +} + + +/* +================= +CG_RemoveNotifyLine +================= +*/ +void CG_RemoveNotifyLine( void ) +{ + int i, offset, totalLength; + + if( cg.numConsoleLines == 0 ) + return; + + offset = cg.consoleLines[ 0 ].length; + totalLength = strlen( cg.consoleText ) - offset; + + //slide up consoleText + for( i = 0; i <= totalLength; i++ ) + cg.consoleText[ i ] = cg.consoleText[ i + offset ]; + + //pop up the first consoleLine + for( i = 0; i < cg.numConsoleLines; i++ ) + cg.consoleLines[ i ] = cg.consoleLines[ i + 1 ]; + + cg.numConsoleLines--; +} + +/* +================= +CG_AddNotifyText +================= +*/ +void CG_AddNotifyText( void ) +{ + char buffer[ BIG_INFO_STRING ]; + int bufferLen, textLen; + + trap_LiteralArgs( buffer, BIG_INFO_STRING ); + + if( !buffer[ 0 ] ) + { + cg.consoleText[ 0 ] = '\0'; + cg.numConsoleLines = 0; + return; + } + + bufferLen = strlen( buffer ); + textLen = strlen( cg.consoleText ); + + // Ignore console messages that were just printed + if( cg_noPrintDuplicate.integer && textLen >= bufferLen && + !strcmp( cg.consoleText + textLen - bufferLen, buffer ) ) + return; + + if( cg.numConsoleLines == MAX_CONSOLE_LINES ) + CG_RemoveNotifyLine( ); + + Q_strcat( cg.consoleText, MAX_CONSOLE_TEXT, buffer ); + cg.consoleLines[ cg.numConsoleLines ].time = cg.time; + cg.consoleLines[ cg.numConsoleLines ].length = bufferLen; + cg.numConsoleLines++; +} + +void QDECL CG_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + trap_Error( text ); +} + +void QDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[1024]; + + va_start( argptr, error ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); + va_end( argptr ); + + CG_Error( "%s", text ); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + CG_Printf ("%s", text); +} + + + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) +{ + static char buffer[ MAX_STRING_CHARS ]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +//======================================================================== + +/* +================= +CG_FileExists + +Test if a specific file exists or not +================= +*/ +qboolean CG_FileExists( char *filename ) +{ + return trap_FS_FOpenFile( filename, NULL, FS_READ ); +} + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) +{ + int i; + char name[ MAX_QPATH ]; + const char *soundName; + const cuboidAttributes_t *cuboid; + + cgs.media.alienStageTransition = trap_S_RegisterSound( "sound/announcements/overmindevolved.wav", qtrue ); + cgs.media.humanStageTransition = trap_S_RegisterSound( "sound/announcements/reinforcement.wav", qtrue ); + + cgs.media.alienOvermindAttack = trap_S_RegisterSound( "sound/announcements/overmindattack.wav", qtrue ); + cgs.media.alienOvermindDying = trap_S_RegisterSound( "sound/announcements/overminddying.wav", qtrue ); + cgs.media.alienOvermindSpawns = trap_S_RegisterSound( "sound/announcements/overmindspawns.wav", qtrue ); + + cgs.media.alienL1Grab = trap_S_RegisterSound( "sound/player/level1/grab.wav", qtrue ); + cgs.media.alienL4ChargePrepare = trap_S_RegisterSound( "sound/player/level4/charge_prepare.wav", qtrue ); + cgs.media.alienL4ChargeStart = trap_S_RegisterSound( "sound/player/level4/charge_start.wav", qtrue ); + + cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/tracer.wav", qfalse ); + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); + cgs.media.turretSpinupSound = trap_S_RegisterSound( "sound/buildables/mgturret/spinup.wav", qfalse ); + cgs.media.weaponEmptyClick = trap_S_RegisterSound( "sound/weapons/click.wav", qfalse ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/misc/talk.wav", qfalse ); + cgs.media.alienTalkSound = trap_S_RegisterSound( "sound/misc/alien_talk.wav", qfalse ); + cgs.media.humanTalkSound = trap_S_RegisterSound( "sound/misc/human_talk.wav", qfalse ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse ); + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse ); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse ); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse ); + + cgs.media.disconnectSound = trap_S_RegisterSound( "sound/misc/disconnect.wav", qfalse ); + + for( i = 0; i < 4; i++ ) + { + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/step%i.wav", i + 1 ); + cgs.media.footsteps[ FOOTSTEP_NORMAL ][ i ] = trap_S_RegisterSound( name, qfalse ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/flesh%i.wav", i + 1 ); + cgs.media.footsteps[ FOOTSTEP_FLESH ][ i ] = trap_S_RegisterSound( name, qfalse ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/splash%i.wav", i + 1 ); + cgs.media.footsteps[ FOOTSTEP_SPLASH ][ i ] = trap_S_RegisterSound( name, qfalse ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/clank%i.wav", i + 1 ); + cgs.media.footsteps[ FOOTSTEP_METAL ][ i ] = trap_S_RegisterSound( name, qfalse ); + } + + for( i = 1 ; i < MAX_SOUNDS ; i++ ) + { + soundName = CG_ConfigString( CS_SOUNDS + i ); + + if( !soundName[ 0 ] ) + break; + + if( soundName[ 0 ] == '*' ) + continue; // custom sound + + cgs.gameSounds[ i ] = trap_S_RegisterSound( soundName, qfalse ); + } + + cgs.media.jetpackDescendSound = trap_S_RegisterSound( "sound/upgrades/jetpack/low.wav", qfalse ); + cgs.media.jetpackIdleSound = trap_S_RegisterSound( "sound/upgrades/jetpack/idle.wav", qfalse ); + cgs.media.jetpackAscendSound = trap_S_RegisterSound( "sound/upgrades/jetpack/hi.wav", qfalse ); + cgs.media.jetpackDescendDeactivateSound = trap_S_RegisterSound( "sound/upgrades/jetpack/low_off.wav", qfalse ); + cgs.media.jetpackIdleDeactivateSound = trap_S_RegisterSound( "sound/upgrades/jetpack/idle_off.wav", qfalse ); + cgs.media.jetpackAscendDeactivateSound = trap_S_RegisterSound( "sound/upgrades/jetpack/hi_off.wav", qfalse ); + cgs.media.jetpackJumpSound = trap_S_RegisterSound( "sound/upgrades/jetpack/jump.wav", qfalse ); + cgs.media.jetpackLowFuelSound = trap_S_RegisterSound( "sound/upgrades/jetpack/lowfuel.wav", qfalse ); + cgs.media.jetpackNoJumpFuelSound = trap_S_RegisterSound( "sound/upgrades/jetpack/nojumpfuel.wav", qfalse ); + cgs.media.jetpackRefuelSound = trap_S_RegisterSound( "sound/upgrades/jetpack/refuel.wav", qfalse ); + + cgs.media.medkitUseSound = trap_S_RegisterSound( "sound/upgrades/medkit/medkit.wav", qfalse ); + + cgs.media.alienEvolveSound = trap_S_RegisterSound( "sound/player/alienevolve.wav", qfalse ); + cgs.media.alienHatchSound = trap_S_RegisterSound( "sound/player/alienhatch.wav", qfalse ); + cgs.media.alienFailedHatchSound = trap_S_RegisterSound( "sound/player/alienfailedhatch.wav", qfalse ); + + cgs.media.alienBuildableExplosion = trap_S_RegisterSound( "sound/buildables/alien/explosion.wav", qfalse ); + cgs.media.alienBuildableDamage = trap_S_RegisterSound( "sound/buildables/alien/damage.wav", qfalse ); + cgs.media.alienBuildablePrebuild = trap_S_RegisterSound( "sound/buildables/alien/prebuild.wav", qfalse ); + + cgs.media.humanBuildableExplosion = trap_S_RegisterSound( "sound/buildables/human/explosion.wav", qfalse ); + cgs.media.humanBuildablePrebuild = trap_S_RegisterSound( "sound/buildables/human/prebuild.wav", qfalse ); + + for( i = 0; i < 4; i++ ) + cgs.media.humanBuildableDamage[ i ] = trap_S_RegisterSound( + va( "sound/buildables/human/damage%d.wav", i ), qfalse ); + + cgs.media.hardBounceSound1 = trap_S_RegisterSound( "sound/misc/hard_bounce1.wav", qfalse ); + cgs.media.hardBounceSound2 = trap_S_RegisterSound( "sound/misc/hard_bounce2.wav", qfalse ); + + cgs.media.repeaterUseSound = trap_S_RegisterSound( "sound/buildables/repeater/use.wav", qfalse ); + + cgs.media.buildableRepairSound = trap_S_RegisterSound( "sound/buildables/human/repair.wav", qfalse ); + cgs.media.buildableRepairedSound = trap_S_RegisterSound( "sound/buildables/human/repaired.wav", qfalse ); + + cgs.media.lCannonWarningSound = trap_S_RegisterSound( "models/weapons/lcannon/warning.wav", qfalse ); + cgs.media.lCannonWarningSound2 = trap_S_RegisterSound( "models/weapons/lcannon/warning2.wav", qfalse ); + + cgs.media.cuboidErrorSound = trap_S_RegisterSound( "sound/cuboid/error.wav", qfalse ); + cg.lastCuboidError = cg.time; + cgs.media.cuboidResizeSoundA = trap_S_RegisterSound( "sound/cuboid/resizea.wav", qfalse ); + cgs.media.cuboidResizeSoundB = trap_S_RegisterSound( "sound/cuboid/resizeb.wav", qfalse ); + cgs.media.cuboidRotateSound = trap_S_RegisterSound( "sound/cuboid/rotate.wav", qfalse ); + cgs.media.cuboidAxisChangeSound = trap_S_RegisterSound( "sound/cuboid/axischange.wav", qfalse ); +} + + +//=================================================================================== + +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) +{ + int i,j; + const cuboidAttributes_t *cuboid; + + static char *sb_nums[ 11 ] = + { + "gfx/2d/numbers/zero_32b", + "gfx/2d/numbers/one_32b", + "gfx/2d/numbers/two_32b", + "gfx/2d/numbers/three_32b", + "gfx/2d/numbers/four_32b", + "gfx/2d/numbers/five_32b", + "gfx/2d/numbers/six_32b", + "gfx/2d/numbers/seven_32b", + "gfx/2d/numbers/eight_32b", + "gfx/2d/numbers/nine_32b", + "gfx/2d/numbers/minus_32b", + }; + static char *buildWeaponTimerPieShaders[ 8 ] = + { + "ui/assets/neutral/1_5pie", + "ui/assets/neutral/3_0pie", + "ui/assets/neutral/4_5pie", + "ui/assets/neutral/6_0pie", + "ui/assets/neutral/7_5pie", + "ui/assets/neutral/9_0pie", + "ui/assets/neutral/10_5pie", + "ui/assets/neutral/12_0pie", + }; + + // clear any references to old media + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + trap_R_ClearScene( ); + + trap_R_LoadWorldMap( cgs.mapname ); + CG_UpdateMediaFraction( 0.66f ); + + for( i = 0; i < 11; i++ ) + cgs.media.numberShaders[ i ] = trap_R_RegisterShader( sb_nums[ i ] ); + + cgs.media.viewBloodShader = trap_R_RegisterShader( "gfx/damage/fullscreen_painblend" ); + + cgs.media.connectionShader = trap_R_RegisterShader( "gfx/2d/net" ); + + cgs.media.creepShader = trap_R_RegisterShader( "creep" ); + + cgs.media.scannerBlipShader = trap_R_RegisterShader( "gfx/2d/blip" ); + cgs.media.scannerLineShader = trap_R_RegisterShader( "gfx/2d/stalk" ); + + cgs.media.teamOverlayShader = trap_R_RegisterShader( "gfx/2d/teamoverlay" ); + + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); + + cgs.media.backTileShader = trap_R_RegisterShader( "console" ); + + + // building shaders + cgs.media.greenBuildShader = trap_R_RegisterShader("gfx/misc/greenbuild" ); + cgs.media.redBuildShader = trap_R_RegisterShader("gfx/misc/redbuild" ); + cgs.media.humanSpawningShader = trap_R_RegisterShader("models/buildables/telenode/rep_cyl" ); + + for( i = 0; i < CUBOID_CRACK_TEXTURES - 1; i++ ) + cgs.media.cuboidCracks[ i ] = trap_R_RegisterShader( va( "models/cuboid/cracks_%i", i ) ); + + cgs.media.cuboidModel = trap_R_RegisterModel( "models/cuboid/cuboid.md3" ); + cgs.media.cuboidRedBuildShader = trap_R_RegisterShader( "gfx/cuboid/build_red" ); + cgs.media.cuboidYellowBuildShader = trap_R_RegisterShader( "gfx/cuboid/build_yellow" ); + cgs.media.cuboidGreenBuildShader = trap_R_RegisterShader( "gfx/cuboid/build_green" ); + cgs.media.cuboidAxis = trap_R_RegisterShader( "gfx/cuboid/build_axis" ); + cgs.media.cuboidAlienPrebuild = trap_R_RegisterShader( "gfx/cuboid/prebuild_alien" ); + + cg.forbidCuboids=qfalse; + cg.latestCBNumber=0; + + for( i = 0; i < 15; i++ ) + cgs.media.splashLogo[ i ] = trap_R_RegisterShader( va( "cuboid/logo_%i.tga", i ) ); + cgs.media.splashLeft = trap_R_RegisterShader( "cuboid/logo_left.tga" ); + cgs.media.splashRight = trap_R_RegisterShader( "cuboid/logo_right.tga" ); + + + for( i = 0; i < 8; i++ ) + cgs.media.buildWeaponTimerPie[ i ] = trap_R_RegisterShader( buildWeaponTimerPieShaders[ i ] ); + + // player health cross shaders + cgs.media.healthCross = trap_R_RegisterShader( "ui/assets/neutral/cross.tga" ); + cgs.media.healthCross2X = trap_R_RegisterShader( "ui/assets/neutral/cross2.tga" ); + cgs.media.healthCross3X = trap_R_RegisterShader( "ui/assets/neutral/cross3.tga" ); + cgs.media.healthCrossMedkit = trap_R_RegisterShader( "ui/assets/neutral/cross_medkit.tga" ); + cgs.media.healthCrossPoisoned = trap_R_RegisterShader( "ui/assets/neutral/cross_poison.tga" ); + + cgs.media.upgradeClassIconShader = trap_R_RegisterShader( "icons/icona_upgrade.tga" ); + + cgs.media.balloonShader = trap_R_RegisterShader( "gfx/sprites/chatballoon" ); + + cgs.media.disconnectPS = CG_RegisterParticleSystem( "disconnectPS" ); + + CG_UpdateMediaFraction( 0.7f ); + + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); + + cgs.media.shadowMarkShader = trap_R_RegisterShader( "gfx/marks/shadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "gfx/marks/wake" ); + + cgs.media.poisonCloudPS = CG_RegisterParticleSystem( "firstPersonPoisonCloudPS" ); + cgs.media.poisonCloudedPS = CG_RegisterParticleSystem( "poisonCloudedPS" ); + cgs.media.alienEvolvePS = CG_RegisterParticleSystem( "alienEvolvePS" ); + cgs.media.alienAcidTubePS = CG_RegisterParticleSystem( "alienAcidTubePS" ); + cgs.media.alienHatchPS = CG_RegisterParticleSystem( "alienHatchPS" ); + + cgs.media.jetPackDescendPS = CG_RegisterParticleSystem( "jetPackDescendPS" ); + cgs.media.jetPackHoverPS = CG_RegisterParticleSystem( "jetPackHoverPS" ); + cgs.media.jetPackAscendPS = CG_RegisterParticleSystem( "jetPackAscendPS" ); + + cgs.media.humanBuildableDamagedPS = CG_RegisterParticleSystem( "humanBuildableDamagedPS" ); + cgs.media.alienBuildableDamagedPS = CG_RegisterParticleSystem( "alienBuildableDamagedPS" ); + cgs.media.humanBuildableDestroyedPS = CG_RegisterParticleSystem( "humanBuildableDestroyedPS" ); + cgs.media.alienBuildableDestroyedPS = CG_RegisterParticleSystem( "alienBuildableDestroyedPS" ); + + cgs.media.humanBuildableBleedPS = CG_RegisterParticleSystem( "humanBuildableBleedPS"); + cgs.media.alienBuildableBleedPS = CG_RegisterParticleSystem( "alienBuildableBleedPS" ); + + cgs.media.alienBleedPS = CG_RegisterParticleSystem( "alienBleedPS" ); + cgs.media.humanBleedPS = CG_RegisterParticleSystem( "humanBleedPS" ); + + CG_BuildableStatusParse( "ui/assets/human/buildstat.cfg", &cgs.humanBuildStat ); + CG_BuildableStatusParse( "ui/assets/alien/buildstat.cfg", &cgs.alienBuildStat ); + + // register the inline models + cgs.numInlineModels = trap_CM_NumInlineModels( ); + + for( i = 1; i < cgs.numInlineModels; i++ ) + { + char name[ 10 ]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof( name ), "*%i", i ); + + cgs.inlineDrawModel[ i ] = trap_R_RegisterModel( name ); + trap_R_ModelBounds( cgs.inlineDrawModel[ i ], mins, maxs ); + + for( j = 0 ; j < 3 ; j++ ) + cgs.inlineModelMidpoints[ i ][ j ] = mins[ j ] + 0.5 * ( maxs[ j ] - mins[ j ] ); + } + + // register all the server specified models + for( i = 1; i < MAX_MODELS; i++ ) + { + const char *modelName; + + modelName = CG_ConfigString( CS_MODELS + i ); + + if( !modelName[ 0 ] ) + break; + + cgs.gameModels[ i ] = trap_R_RegisterModel( modelName ); + } + + CG_UpdateMediaFraction( 0.8f ); + + // register all the server specified shaders + for( i = 1; i < MAX_GAME_SHADERS; i++ ) + { + const char *shaderName; + + shaderName = CG_ConfigString( CS_SHADERS + i ); + + if( !shaderName[ 0 ] ) + break; + + cgs.gameShaders[ i ] = trap_R_RegisterShader( shaderName ); + } + + CG_UpdateMediaFraction( 0.9f ); + + // register all the server specified particle systems + for( i = 1; i < MAX_GAME_PARTICLE_SYSTEMS; i++ ) + { + const char *psName; + + psName = CG_ConfigString( CS_PARTICLE_SYSTEMS + i ); + + if( !psName[ 0 ] ) + break; + + cgs.gameParticleSystems[ i ] = CG_RegisterParticleSystem( (char *)psName ); + } +} + + +/* +======================= +CG_BuildSpectatorString + +======================= +*/ +void CG_BuildSpectatorString( void ) +{ + int i; + + cg.spectatorList[ 0 ] = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + if( cgs.clientinfo[ i ].infoValid && cgs.clientinfo[ i ].team == TEAM_NONE ) + { + Q_strcat( cg.spectatorList, sizeof( cg.spectatorList ), + va( S_COLOR_WHITE "%s ", cgs.clientinfo[ i ].name ) ); + } + } +} + + + +/* +=================== +CG_RegisterClients + +=================== +*/ +static void CG_RegisterClients( void ) +{ + int i; + + cg.charModelFraction = 0.0f; + + //precache all the models/sounds/etc + for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) + { + CG_PrecacheClientInfo( i, BG_ClassConfig( i )->modelName, + BG_ClassConfig( i )->skinName ); + + cg.charModelFraction = (float)i / (float)PCL_NUM_CLASSES; + trap_UpdateScreen( ); + } + + cgs.media.larmourHeadSkin = trap_R_RegisterSkin( "models/players/human_base/head_light.skin" ); + cgs.media.larmourMk2HeadSkin = trap_R_RegisterSkin( "models/players/human_base/head_light_mk2.skin" ); + cgs.media.larmourLegsSkin = trap_R_RegisterSkin( "models/players/human_base/lower_light.skin" ); + cgs.media.larmourTorsoSkin = trap_R_RegisterSkin( "models/players/human_base/upper_light.skin" ); + + cgs.media.jetpackModel = trap_R_RegisterModel( "models/players/human_base/jetpack.md3" ); + cgs.media.jetpackFlashModel = trap_R_RegisterModel( "models/players/human_base/jetpack_flash.md3" ); + cgs.media.battpackModel = trap_R_RegisterModel( "models/players/human_base/battpack.md3" ); + + cg.charModelFraction = 1.0f; + trap_UpdateScreen( ); + + //load all the clientinfos of clients already connected to the server + for( i = 0; i < MAX_CLIENTS; i++ ) + { + const char *clientInfo; + + clientInfo = CG_ConfigString( CS_PLAYERS + i ); + if( !clientInfo[ 0 ] ) + continue; + + CG_NewClientInfo( i ); + } + + CG_BuildSpectatorString( ); +} + +//=========================================================================== + +/* +================= +CG_ConfigString +================= +*/ +const char *CG_ConfigString( int index ) +{ + if( index < 0 || index >= MAX_CONFIGSTRINGS ) + CG_Error( "CG_ConfigString: bad index: %i", index ); + + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) +{ + char *s; + char parm1[ MAX_QPATH ], parm2[ MAX_QPATH ]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2 ); +} + +/* +====================== +CG_PlayerCount +====================== +*/ +int CG_PlayerCount( void ) +{ + int i, count = 0; + + CG_RequestScores( ); + + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == TEAM_ALIENS || + cg.scores[ i ].team == TEAM_HUMANS ) + count++; + } + + return count; +} + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +char *CG_GetMenuBuffer( const char *filename ) +{ + int len; + fileHandle_t f; + static char buf[ MAX_MENUFILE ]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + + if( !f ) + { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return NULL; + } + + if( len >= MAX_MENUFILE ) + { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", + filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + return buf; +} + +qboolean CG_Asset_Parse( int handle ) +{ + pc_token_t token; + const char *tempStr; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( Q_stricmp( token.string, "{" ) != 0 ) + return qfalse; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( Q_stricmp( token.string, "}" ) == 0 ) + return qtrue; + + // font + if( Q_stricmp( token.string, "font" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.textFont ); + continue; + } + + // smallFont + if( Q_stricmp( token.string, "smallFont" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.smallFont ); + continue; + } + + // font + if( Q_stricmp( token.string, "bigfont" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.bigFont ); + continue; + } + + // gradientbar + if( Q_stricmp( token.string, "gradientbar" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr ); + continue; + } + + // enterMenuSound + if( Q_stricmp( token.string, "menuEnterSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // exitMenuSound + if( Q_stricmp( token.string, "menuExitSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // itemFocusSound + if( Q_stricmp( token.string, "itemFocusSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // menuBuzzSound + if( Q_stricmp( token.string, "menuBuzzSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if( Q_stricmp( token.string, "cursor" ) == 0 ) + { + if( !PC_String_Parse( handle, &cgDC.Assets.cursorStr ) ) + return qfalse; + + cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr ); + continue; + } + + if( Q_stricmp( token.string, "fadeClamp" ) == 0 ) + { + if( !PC_Float_Parse( handle, &cgDC.Assets.fadeClamp ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "fadeCycle" ) == 0 ) + { + if( !PC_Int_Parse( handle, &cgDC.Assets.fadeCycle ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "fadeAmount" ) == 0 ) + { + if( !PC_Float_Parse( handle, &cgDC.Assets.fadeAmount ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowX" ) == 0 ) + { + if( !PC_Float_Parse( handle, &cgDC.Assets.shadowX ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowY" ) == 0 ) + { + if( !PC_Float_Parse( handle, &cgDC.Assets.shadowY ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowColor" ) == 0 ) + { + if( !PC_Color_Parse( handle, &cgDC.Assets.shadowColor ) ) + return qfalse; + + cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[ 3 ]; + continue; + } + } + + return qfalse; +} + +void CG_ParseMenu( const char *menuFile ) +{ + pc_token_t token; + int handle; + + handle = trap_Parse_LoadSource( menuFile ); + + if( !handle ) + handle = trap_Parse_LoadSource( "ui/testhud.menu" ); + + if( !handle ) + return; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + break; + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if( token.string[ 0 ] == '}' ) + break; + + if( Q_stricmp( token.string, "assetGlobalDef" ) == 0 ) + { + if( CG_Asset_Parse( handle ) ) + continue; + else + break; + } + + + if( Q_stricmp( token.string, "menudef" ) == 0 ) + { + // start a new menu + Menu_New( handle ); + } + } + + trap_Parse_FreeSource( handle ); +} + +qboolean CG_Load_Menu( char **p ) +{ + char *token; + + token = COM_ParseExt( p, qtrue ); + + if( token[ 0 ] != '{' ) + return qfalse; + + while( 1 ) + { + token = COM_ParseExt( p, qtrue ); + + if( Q_stricmp( token, "}" ) == 0 ) + return qtrue; + + if( !token || token[ 0 ] == 0 ) + return qfalse; + + CG_ParseMenu( token ); + } + return qfalse; +} + + + +void CG_LoadMenus( const char *menuFile ) +{ + char *token; + char *p; + int len, start; + fileHandle_t f; + static char buf[ MAX_MENUDEFFILE ]; + + start = trap_Milliseconds( ); + + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + + if( !f ) + { + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); + + if( !f ) + trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n" ) ); + } + + if( len >= MAX_MENUDEFFILE ) + { + trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", + menuFile, len, MAX_MENUDEFFILE ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[ len ] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress( buf ); + + Menu_Reset( ); + + p = buf; + + while( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + + if( !token || token[ 0 ] == 0 || token[ 0 ] == '}' ) + break; + + if( Q_stricmp( token, "}" ) == 0 ) + break; + + if( Q_stricmp( token, "loadmenu" ) == 0 ) + { + if( CG_Load_Menu( &p ) ) + continue; + else + break; + } + } + + Com_Printf( "UI menu load time = %d milli seconds\n", trap_Milliseconds( ) - start ); +} + + + +static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int key ) +{ + return qfalse; +} + + +static int CG_FeederCount( int feederID ) +{ + int i, count = 0; + + if( feederID == FEEDER_ALIENTEAM_LIST ) + { + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == TEAM_ALIENS ) + count++; + } + } + else if( feederID == FEEDER_HUMANTEAM_LIST ) + { + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == TEAM_HUMANS ) + count++; + } + } + + return count; +} + + +void CG_SetScoreSelection( void *p ) +{ + menuDef_t *menu = (menuDef_t*)p; + playerState_t *ps = &cg.snap->ps; + int i, alien, human; + int feeder; + + alien = human = 0; + + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == TEAM_ALIENS ) + alien++; + else if( cg.scores[ i ].team == TEAM_HUMANS ) + human++; + + if( ps->clientNum == cg.scores[ i ].client ) + cg.selectedScore = i; + } + + if( menu == NULL ) + // just interested in setting the selected score + return; + + feeder = FEEDER_ALIENTEAM_LIST; + i = alien; + + if( cg.scores[ cg.selectedScore ].team == TEAM_HUMANS ) + { + feeder = FEEDER_HUMANTEAM_LIST; + i = human; + } + + Menu_SetFeederSelection(menu, feeder, i, NULL); +} + +// FIXME: might need to cache this info +static clientInfo_t * CG_InfoFromScoreIndex( int index, int team, int *scoreIndex ) +{ + int i, count; + count = 0; + + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == team ) + { + if( count == index ) + { + *scoreIndex = i; + return &cgs.clientinfo[ cg.scores[ i ].client ]; + } + count++; + } + } + + *scoreIndex = index; + return &cgs.clientinfo[ cg.scores[ index ].client ]; +} + +qboolean CG_ClientIsReady( int clientNum ) +{ + clientList_t ready; + + Com_ClientListParse( &ready, CG_ConfigString( CS_CLIENTS_READY ) ); + + return Com_ClientListContains( &ready, clientNum ); +} + +static const char *CG_FeederItemText( int feederID, int index, int column, qhandle_t *handle ) +{ + int scoreIndex = 0; + clientInfo_t *info = NULL; + int team = -1; + score_t *sp = NULL; + qboolean showIcons = qfalse; + + *handle = -1; + + if( feederID == FEEDER_ALIENTEAM_LIST ) + team = TEAM_ALIENS; + else if( feederID == FEEDER_HUMANTEAM_LIST ) + team = TEAM_HUMANS; + + info = CG_InfoFromScoreIndex( index, team, &scoreIndex ); + sp = &cg.scores[ scoreIndex ]; + + if( cg.intermissionStarted && CG_ClientIsReady( sp->client ) ) + showIcons = qfalse; + else if( cg.snap->ps.pm_type == PM_SPECTATOR || + cg.snap->ps.pm_type == PM_NOCLIP || + cg.snap->ps.pm_flags & PMF_FOLLOW || + team == cg.snap->ps.stats[ STAT_TEAM ] || + cg.intermissionStarted ) + { + showIcons = qtrue; + } + + if( info && info->infoValid ) + { + switch( column ) + { + case 0: + if( showIcons ) + { + if( sp->weapon != WP_NONE ) + *handle = cg_weapons[ sp->weapon ].weaponIcon; + } + break; + + case 1: + if( showIcons ) + { + if( sp->team == TEAM_HUMANS && sp->upgrade != UP_NONE ) + *handle = cg_upgrades[ sp->upgrade ].upgradeIcon; + else if( sp->team == TEAM_ALIENS ) + { + switch( sp->weapon ) + { + case WP_ABUILD2: + case WP_ALEVEL1_UPG: + case WP_ALEVEL2_UPG: + case WP_ALEVEL3_UPG: + *handle = cgs.media.upgradeClassIconShader; + break; + + default: + break; + } + } + } + break; + + case 2: + if( cg.intermissionStarted && CG_ClientIsReady( sp->client ) ) + return "Ready"; + break; + + case 3: + return va( S_COLOR_WHITE "%s", info->name ); + break; + + case 4: + return va( "%d", sp->score ); + break; + + case 5: + return va( "%4d", sp->time ); + break; + + case 6: + if( sp->ping == -1 ) + return ""; + + return va( "%4d", sp->ping ); + break; + } + } + + return ""; +} + +static qhandle_t CG_FeederItemImage( int feederID, int index ) +{ + return 0; +} + +static void CG_FeederSelection( int feederID, int index ) +{ + int i, count; + int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? TEAM_ALIENS : TEAM_HUMANS; + count = 0; + + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == team ) + { + if( index == count ) + cg.selectedScore = i; + + count++; + } + } +} + +static float CG_Cvar_Get( const char *cvar ) +{ + char buff[ 128 ]; + + memset( buff, 0, sizeof( buff ) ); + trap_Cvar_VariableStringBuffer( cvar, buff, sizeof( buff ) ); + return atof( buff ); +} + +void CG_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text, + int cursorPos, char cursor, int limit, int style ) +{ + UI_Text_Paint( x, y, scale, color, text, 0, limit, style ); +} + +static int CG_OwnerDrawWidth( int ownerDraw, float scale ) +{ + switch( ownerDraw ) + { + case CG_KILLER: + return UI_Text_Width( CG_GetKillerText( ), scale ); + break; + } + + return 0; +} + +static int CG_PlayCinematic( const char *name, float x, float y, float w, float h ) +{ + return trap_CIN_PlayCinematic( name, x, y, w, h, CIN_loop ); +} + +static void CG_StopCinematic( int handle ) +{ + trap_CIN_StopCinematic( handle ); +} + +static void CG_DrawCinematic( int handle, float x, float y, float w, float h ) +{ + trap_CIN_SetExtents( handle, x, y, w, h ); + trap_CIN_DrawCinematic( handle ); +} + +static void CG_RunCinematicFrame( int handle ) +{ + trap_CIN_RunCinematic( handle ); +} + +// hack to prevent warning +static qboolean CG_OwnerDrawVisible( int parameter ) +{ + return qfalse; +} + +/* +================= +CG_LoadHudMenu +================= +*/ +void CG_LoadHudMenu( void ) +{ + char buff[ 1024 ]; + const char *hudSet; + + cgDC.aspectScale = ( ( 640.0f * cgs.glconfig.vidHeight ) / + ( 480.0f * cgs.glconfig.vidWidth ) ); + cgDC.xscale = cgs.glconfig.vidWidth / 640.0f; + cgDC.yscale = cgs.glconfig.vidHeight / 480.0f; + + cgDC.smallFontScale = CG_Cvar_Get( "ui_smallFont" ); + cgDC.bigFontScale = CG_Cvar_Get( "ui_bigFont" ); + + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + cgDC.setColor = &trap_R_SetColor; + cgDC.drawHandlePic = &CG_DrawPic; + cgDC.drawStretchPic = &trap_R_DrawStretchPic; + cgDC.registerModel = &trap_R_RegisterModel; + cgDC.modelBounds = &trap_R_ModelBounds; + cgDC.fillRect = &CG_FillRect; + cgDC.drawRect = &CG_DrawRect; + cgDC.drawSides = &CG_DrawSides; + cgDC.drawTopBottom = &CG_DrawTopBottom; + cgDC.clearScene = &trap_R_ClearScene; + cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + cgDC.renderScene = &trap_R_RenderScene; + cgDC.registerFont = &trap_R_RegisterFont; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.runScript = &CG_RunMenuScript; + cgDC.setCVar = trap_Cvar_Set; + cgDC.getCVarString = trap_Cvar_VariableStringBuffer; + cgDC.getCVarValue = CG_Cvar_Get; + cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.startLocalSound = &trap_S_StartLocalSound; + cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; + cgDC.feederCount = &CG_FeederCount; + cgDC.feederItemImage = &CG_FeederItemImage; + cgDC.feederItemText = &CG_FeederItemText; + cgDC.feederSelection = &CG_FeederSelection; + //cgDC.setBinding = &trap_Key_SetBinding; + //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; + //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + //cgDC.executeText = &trap_Cmd_ExecuteText; + cgDC.Error = &Com_Error; + cgDC.Print = &Com_Printf; + cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.ownerDrawText = &CG_OwnerDrawText; + //cgDC.Pause = &CG_Pause; + cgDC.registerSound = &trap_S_RegisterSound; + cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + cgDC.playCinematic = &CG_PlayCinematic; + cgDC.stopCinematic = &CG_StopCinematic; + cgDC.drawCinematic = &CG_DrawCinematic; + cgDC.runCinematicFrame = &CG_RunCinematicFrame; + + Init_Display( &cgDC ); + + Menu_Reset( ); + + trap_Cvar_VariableStringBuffer( "cg_hudFiles", buff, sizeof( buff ) ); + hudSet = buff; + + if( !cg_hudFilesEnable.integer || hudSet[ 0 ] == '\0' ) + hudSet = "ui/hud.txt"; + + CG_LoadMenus( hudSet ); +} + +void CG_AssetCache( void ) +{ + int i; + + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); + + if( cg_emoticons.integer ) + { + cgDC.Assets.emoticonCount = BG_LoadEmoticons( cgDC.Assets.emoticons, + MAX_EMOTICONS ); + } + else + cgDC.Assets.emoticonCount = 0; + + for( i = 0; i < cgDC.Assets.emoticonCount; i++ ) + { + cgDC.Assets.emoticons[ i ].shader = trap_R_RegisterShaderNoMip( + va( "emoticons/%s_%dx1.tga", cgDC.Assets.emoticons[ i ].name, + cgDC.Assets.emoticons[ i ].width ) ); + } +} + +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) +{ + const char *s; + + // clear everything + memset( &cgs, 0, sizeof( cgs ) ); + memset( &cg, 0, sizeof( cg ) ); + memset( &cg.pmext, 0, sizeof( cg.pmext ) ); + memset( cg_entities, 0, sizeof( cg_entities ) ); + + cg.clientNum = clientNum; + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0f; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0f; + + // load a few needed things before we do any screen updates + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); + cgs.media.outlineShader = trap_R_RegisterShader( "outline" ); + + // load overrides + BG_InitClassConfigs( ); + BG_InitBuildableConfigs( ); + BG_InitAllowedGameElements( ); + + // Dynamic memory + BG_InitMemory( ); + + CG_RegisterCvars( ); + + CG_InitConsoleCommands( ); + + String_Init( ); + + CG_AssetCache( ); + CG_LoadHudMenu( ); + + cg.weaponSelect = WP_NONE; + + // old servers + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + // copy vote display strings so they don't show up blank if we see + // the same one directly after connecting + Q_strncpyz( cgs.voteString[ TEAM_NONE ], + CG_ConfigString( CS_VOTE_STRING + TEAM_NONE ), + sizeof( cgs.voteString ) ); + Q_strncpyz( cgs.voteString[ TEAM_ALIENS ], + CG_ConfigString( CS_VOTE_STRING + TEAM_ALIENS ), + sizeof( cgs.voteString[ TEAM_ALIENS ] ) ); + Q_strncpyz( cgs.voteString[ TEAM_HUMANS ], + CG_ConfigString( CS_VOTE_STRING + TEAM_ALIENS ), + sizeof( cgs.voteString[ TEAM_HUMANS ] ) ); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + + if( strcmp( s, GAME_VERSION ) ) + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo( ); + + // load the new map + trap_CM_LoadMap( cgs.mapname ); + + cg.loading = qtrue; // force players to load instead of defer + + CG_LoadTrailSystems( ); + CG_UpdateMediaFraction( 0.05f ); + + CG_LoadParticleSystems( ); + CG_UpdateMediaFraction( 0.05f ); + + CG_RegisterSounds( ); + CG_UpdateMediaFraction( 0.60f ); + + CG_RegisterGraphics( ); + CG_UpdateMediaFraction( 0.90f ); + + CG_InitWeapons( ); + CG_UpdateMediaFraction( 0.95f ); + + CG_InitUpgrades( ); + CG_UpdateMediaFraction( 1.0f ); + + CG_InitBuildables( ); + + cgs.voices = BG_VoiceInit( ); + BG_PrintVoices( cgs.voices, cg_debugVoices.integer ); + + CG_RegisterClients( ); // if low on memory, some clients will be deferred + + cg.loading = qfalse; // future players will be deferred + + CG_InitMarkPolys( ); + + // remove the last loading update + cg.infoScreenText[ 0 ] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues( ); + + CG_StartMusic( ); + + CG_ShaderStateChanged( ); + + trap_S_ClearLoopingSounds( qtrue ); +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) +{ + // some mods may need to do cleanup work here, + // like closing files or archiving session data +} + +/* +================ +CG_VoIPString +================ +*/ +static char *CG_VoIPString( void ) +{ + // a generous overestimate of the space needed for 0,1,2...61,62,63 + static char voipString[ MAX_CLIENTS * 4 ]; + char voipSendTarget[ MAX_CVAR_VALUE_STRING ]; + + trap_Cvar_VariableStringBuffer( "cl_voipSendTarget", voipSendTarget, + sizeof( voipSendTarget ) ); + + if( Q_stricmp( voipSendTarget, "team" ) == 0 ) + { + int i, slen, nlen; + for( slen = i = 0; i < cgs.maxclients; i++ ) + { + if( !cgs.clientinfo[ i ].infoValid || i == cg.clientNum ) + continue; + if( cgs.clientinfo[ i ].team != cgs.clientinfo[ cg.clientNum ].team ) + continue; + + nlen = Q_snprintf( &voipString[ slen ], sizeof( voipString ) - slen, + "%s%d", ( slen > 0 ) ? "," : "", i ); + if( slen + nlen + 1 >= sizeof( voipString ) ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: voipString overflowed\n" ); + break; + } + + slen += nlen; + } + + // Notice that if the snprintf was truncated, slen was not updated + // so this will remove any trailing commas or partially-completed numbers + voipString[ slen ] = '\0'; + } + else if( Q_stricmp( voipSendTarget, "crosshair" ) == 0 ) + Com_sprintf( voipString, sizeof( voipString ), "%d", + CG_CrosshairPlayer( ) ); + else if( Q_stricmp( voipSendTarget, "attacker" ) == 0 ) + Com_sprintf( voipString, sizeof( voipString ), "%d", + CG_LastAttacker( ) ); + else + return NULL; + + return voipString; +} + diff --git a/src/cgame/cg_main.c.orig b/src/cgame/cg_main.c.orig new file mode 100644 index 0000000..caa80f4 --- /dev/null +++ b/src/cgame/cg_main.c.orig @@ -0,0 +1,1982 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_main.c -- initialization and primary entry point for cgame + + +#include "cg_local.h" + +#include "../ui/ui_shared.h" +// display context for new ui stuff +displayContextDef_t cgDC; + +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_Shutdown( void ); +static char *CG_VoIPString( void ); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, + int arg4, int arg5, int arg6, int arg7, + int arg8, int arg9, int arg10, int arg11 ) +{ + switch( command ) + { + case CG_INIT: + CG_Init( arg0, arg1, arg2 ); + return 0; + + case CG_SHUTDOWN: + CG_Shutdown( ); + return 0; + + case CG_CONSOLE_COMMAND: + return CG_ConsoleCommand( ); + + case CG_CONSOLE_TEXT: + CG_AddNotifyText( ); + return 0; + + case CG_DRAW_ACTIVE_FRAME: + CG_DrawActiveFrame( arg0, arg1, arg2 ); + return 0; + + case CG_CROSSHAIR_PLAYER: + return CG_CrosshairPlayer( ); + + case CG_LAST_ATTACKER: + return CG_LastAttacker( ); + + case CG_KEY_EVENT: + CG_KeyEvent( arg0, arg1 ); + return 0; + + case CG_MOUSE_EVENT: + // cgame doesn't care where the cursor is + return 0; + + case CG_EVENT_HANDLING: + CG_EventHandling( arg0 ); + return 0; + + case CG_VOIP_STRING: + return (intptr_t)CG_VoIPString( ); + + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + + return -1; +} + + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[ MAX_GENTITIES ]; + +weaponInfo_t cg_weapons[ 32 ]; +upgradeInfo_t cg_upgrades[ 32 ]; + +buildableInfo_t cg_buildables[ CUBOID_FIRST ]; +cuboidInfo_t cg_cuboids[ CUBOID_TYPES ]; + +vmCvar_t cg_teslaTrailTime; +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawClock; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawDemoState; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_drawChargeBar; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_draw2D; +vmCvar_t cg_animSpeed; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_debugMove; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +vmCvar_t cg_viewsize; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_tracerChance; +vmCvar_t cg_tracerWidth; +vmCvar_t cg_tracerLength; +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_thirdPersonShoulderViewMode; +vmCvar_t cg_staticDeathCam; +vmCvar_t cg_thirdPersonPitchFollow; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawSpeed; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_stats; +vmCvar_t cg_paused; +vmCvar_t cg_blood; +vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlaySortMode; +vmCvar_t cg_teamOverlayMaxPlayers; +vmCvar_t cg_teamOverlayUserinfo; +vmCvar_t cg_noPrintDuplicate; +vmCvar_t cg_noVoiceChats; +vmCvar_t cg_noVoiceText; +vmCvar_t cg_hudFiles; +vmCvar_t cg_hudFilesEnable; +vmCvar_t cg_smoothClients; +vmCvar_t pmove_fixed; +vmCvar_t pmove_msec; +vmCvar_t cg_cameraMode; +vmCvar_t cg_timescaleFadeEnd; +vmCvar_t cg_timescaleFadeSpeed; +vmCvar_t cg_timescale; +vmCvar_t cg_noTaunt; +vmCvar_t cg_drawSurfNormal; +vmCvar_t cg_drawBBOX; +vmCvar_t cg_wwSmoothTime; +vmCvar_t cg_disableBlueprintErrors; +vmCvar_t cg_depthSortParticles; +vmCvar_t cg_bounceParticles; +vmCvar_t cg_consoleLatency; +vmCvar_t cg_lightFlare; +vmCvar_t cg_debugParticles; +vmCvar_t cg_debugTrails; +vmCvar_t cg_debugPVS; +vmCvar_t cg_disableWarningDialogs; +vmCvar_t cg_disableUpgradeDialogs; +vmCvar_t cg_disableBuildDialogs; +vmCvar_t cg_disableCommandDialogs; +vmCvar_t cg_disableScannerPlane; +vmCvar_t cg_tutorial; + +vmCvar_t cg_modTutorial; +vmCvar_t cg_modTutorialReference; +vmCvar_t cg_lastModVersion; + +vmCvar_t cg_painBlendUpRate; +vmCvar_t cg_painBlendDownRate; +vmCvar_t cg_painBlendMax; +vmCvar_t cg_painBlendScale; +vmCvar_t cg_painBlendZoom; + +vmCvar_t cg_stickySpec; +vmCvar_t cg_sprintToggle; +vmCvar_t cg_unlagged; + +vmCvar_t cg_debugVoices; + +vmCvar_t ui_currentClass; +vmCvar_t ui_carriage; +vmCvar_t ui_stages; +vmCvar_t ui_dialog; +vmCvar_t ui_voteActive; +vmCvar_t ui_alienTeamVoteActive; +vmCvar_t ui_humanTeamVoteActive; + +vmCvar_t cg_debugRandom; + +vmCvar_t cg_optimizePrediction; +vmCvar_t cg_projectileNudge; + +vmCvar_t cg_voice; + +vmCvar_t cg_emoticons; + +vmCvar_t cg_chatTeamPrefix; + +vmCvar_t cg_cuboidResizeAxis; +vmCvar_t cg_cuboidPSQuality; +vmCvar_t cg_cuboidInfoX; +vmCvar_t cg_cuboidInfoY; + +vmCvar_t cg_fuelInfoX; +vmCvar_t cg_fuelInfoY; +vmCvar_t cg_fuelInfoScale; + +typedef struct +{ + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +static cvarTable_t cvarTable[ ] = +{ + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "1", CVAR_ARCHIVE }, + { &cg_drawClock, "cg_drawClock", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "1", CVAR_ARCHIVE }, + { &cg_drawDemoState, "cg_drawDemoState", "1", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_drawChargeBar, "cg_drawChargeBar", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "2", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "1", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, + { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE }, + { &cg_drawSpeed, "cg_drawSpeed", "0", CVAR_ARCHIVE }, + { &cg_teslaTrailTime, "cg_teslaTrailTime", "250", CVAR_ARCHIVE }, + { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, + { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, + { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, + { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_debugMove, "cg_debugMove", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, + { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, + { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "75", CVAR_ARCHIVE }, + { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_CHEAT }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPersonPitchFollow, "cg_thirdPersonPitchFollow", "0", 0 }, + { &cg_thirdPersonShoulderViewMode, "cg_thirdPersonShoulderViewMode", "1", CVAR_ARCHIVE }, + { &cg_staticDeathCam, "cg_staticDeathCam", "0", CVAR_ARCHIVE }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "1", CVAR_ARCHIVE }, + { &cg_teamOverlaySortMode, "cg_teamOverlaySortMode", "1", CVAR_ARCHIVE }, + { &cg_teamOverlayMaxPlayers, "cg_teamOverlayMaxPlayers", "8", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noPrintDuplicate, "cg_noPrintDuplicate", "0", CVAR_ARCHIVE }, + { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, + { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, + { &cg_drawSurfNormal, "cg_drawSurfNormal", "0", CVAR_CHEAT }, + { &cg_drawBBOX, "cg_drawBBOX", "0", CVAR_CHEAT }, + { &cg_wwSmoothTime, "cg_wwSmoothTime", "300", CVAR_ARCHIVE }, + { NULL, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_disableBlueprintErrors", "0", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_stickySpec, "cg_stickySpec", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_sprintToggle, "cg_sprintToggle", "0", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_unlagged, "cg_unlagged", "1", CVAR_ARCHIVE|CVAR_USERINFO }, + { NULL, "cg_flySpeed", "600", CVAR_ARCHIVE|CVAR_USERINFO }, + { &cg_depthSortParticles, "cg_depthSortParticles", "1", CVAR_ARCHIVE }, + { &cg_bounceParticles, "cg_bounceParticles", "0", CVAR_ARCHIVE }, + { &cg_consoleLatency, "cg_consoleLatency", "3000", CVAR_ARCHIVE }, + { &cg_lightFlare, "cg_lightFlare", "3", CVAR_ARCHIVE }, + { &cg_debugParticles, "cg_debugParticles", "0", CVAR_CHEAT }, + { &cg_debugTrails, "cg_debugTrails", "0", CVAR_CHEAT }, + { &cg_debugPVS, "cg_debugPVS", "0", CVAR_CHEAT }, + { &cg_disableWarningDialogs, "cg_disableWarningDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableUpgradeDialogs, "cg_disableUpgradeDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableBuildDialogs, "cg_disableBuildDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableCommandDialogs, "cg_disableCommandDialogs", "0", CVAR_ARCHIVE }, + { &cg_disableScannerPlane, "cg_disableScannerPlane", "0", CVAR_ARCHIVE }, + { &cg_tutorial, "cg_tutorial", "1", CVAR_ARCHIVE }, + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, + { &cg_hudFilesEnable, "cg_hudFilesEnable", "0", CVAR_ARCHIVE}, + { NULL, "cg_alienConfig", "", CVAR_ARCHIVE }, + { NULL, "cg_humanConfig", "", CVAR_ARCHIVE }, + { NULL, "cg_spectatorConfig", "", CVAR_ARCHIVE }, + + { &cg_painBlendUpRate, "cg_painBlendUpRate", "10.0", 0 }, + { &cg_painBlendDownRate, "cg_painBlendDownRate", "0.5", 0 }, + { &cg_painBlendMax, "cg_painBlendMax", "0.7", 0 }, + { &cg_painBlendScale, "cg_painBlendScale", "7.0", 0 }, + { &cg_painBlendZoom, "cg_painBlendZoom", "0.65", 0 }, + + { &cg_debugVoices, "cg_debugVoices", "0", 0 }, + + // communication cvars set by the cgame to be read by ui + { &ui_currentClass, "ui_currentClass", "0", CVAR_ROM }, + { &ui_carriage, "ui_carriage", "", CVAR_ROM }, + { &ui_stages, "ui_stages", "0 0", CVAR_ROM }, + { &ui_dialog, "ui_dialog", "Text not set", CVAR_ROM }, + { &ui_voteActive, "ui_voteActive", "0", CVAR_ROM }, + { &ui_humanTeamVoteActive, "ui_humanTeamVoteActive", "0", CVAR_ROM }, + { &ui_alienTeamVoteActive, "ui_alienTeamVoteActive", "0", CVAR_ROM }, + + { &cg_debugRandom, "cg_debugRandom", "0", 0 }, + + { &cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE }, + { &cg_projectileNudge, "cg_projectileNudge", "1", CVAR_ARCHIVE }, + + // the following variables are created in other parts of the system, + // but we also reference them here + + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", CVAR_CHEAT }, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", CVAR_CHEAT }, + { &cg_timescale, "timescale", "1", 0}, + { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, + + { &pmove_fixed, "pmove_fixed", "0", 0}, + { &pmove_msec, "pmove_msec", "8", 0}, + { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, + + { &cg_voice, "voice", "default", CVAR_USERINFO|CVAR_ARCHIVE}, + + { &cg_emoticons, "cg_emoticons", "1", CVAR_LATCH|CVAR_ARCHIVE}, + + { &cg_chatTeamPrefix, "cg_chatTeamPrefix", "1", CVAR_ARCHIVE}, + + { &cg_cuboidResizeAxis, "cg_cuboidResizeAxis", "2", 0}, + { &cg_cuboidPSQuality, "cg_cuboidPSQuality", "3", CVAR_ARCHIVE}, + + { &cg_cuboidInfoX, "cg_cuboidInfoX" ,"0", CVAR_ARCHIVE }, + { &cg_cuboidInfoY, "cg_cuboidInfoY" ,"150", CVAR_ARCHIVE }, + + { &cg_modTutorial, "cg_modTutorial", "1", CVAR_ARCHIVE }, + { &cg_modTutorialReference, "cg_modTutorialReference", "0", CVAR_ARCHIVE }, + { &cg_lastModVersion, "cg_lastModVersion", "0", CVAR_ARCHIVE }, + + { &cg_fuelInfoX, "cg_fuelInfoX" ,"0", CVAR_ARCHIVE }, + { &cg_fuelInfoY, "cg_fuelInfoY" ,"150", CVAR_ARCHIVE }, + { &cg_fuelInfoScale, "cg_fuelInfoScale" ,"0.5", CVAR_ARCHIVE } +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) +{ + int i; + cvarTable_t *cv; + char var[ MAX_TOKEN_CHARS ]; + + for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ ) + { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); +} + + +/* +=============== +CG_SetUIVars + +Set some cvars used by the UI +=============== +*/ +static void CG_SetUIVars( void ) +{ + int i; + char carriageCvar[ MAX_TOKEN_CHARS ]; + + if( !cg.snap ) + return; + + *carriageCvar = 0; + + //determine what the player is carrying + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) && + BG_Weapon( i )->purchasable ) + strcat( carriageCvar, va( "W%d ", i ) ); + } + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) && + BG_Upgrade( i )->purchasable ) + strcat( carriageCvar, va( "U%d ", i ) ); + } + strcat( carriageCvar, "$" ); + + trap_Cvar_Set( "ui_carriage", carriageCvar ); + + trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) ); +} + +/* +================= +CG_UpdateCvars +================= +*/ +void CG_UpdateCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ ) + if( cv->vmCvar ) + trap_Cvar_Update( cv->vmCvar ); + + // check for modications here + + CG_SetUIVars( ); + +} + + +int CG_CrosshairPlayer( void ) +{ + if( cg.time > ( cg.crosshairClientTime + 1000 ) ) + return -1; + + return cg.crosshairClientNum; +} + + +int CG_LastAttacker( void ) +{ + if( !cg.attackerTime ) + return -1; + + return cg.snap->ps.persistant[ PERS_ATTACKER ]; +} + + +/* +================= +CG_RemoveNotifyLine +================= +*/ +void CG_RemoveNotifyLine( void ) +{ + int i, offset, totalLength; + + if( cg.numConsoleLines == 0 ) + return; + + offset = cg.consoleLines[ 0 ].length; + totalLength = strlen( cg.consoleText ) - offset; + + //slide up consoleText + for( i = 0; i <= totalLength; i++ ) + cg.consoleText[ i ] = cg.consoleText[ i + offset ]; + + //pop up the first consoleLine + for( i = 0; i < cg.numConsoleLines; i++ ) + cg.consoleLines[ i ] = cg.consoleLines[ i + 1 ]; + + cg.numConsoleLines--; +} + +/* +================= +CG_AddNotifyText +================= +*/ +void CG_AddNotifyText( void ) +{ + char buffer[ BIG_INFO_STRING ]; + int bufferLen, textLen; + + trap_LiteralArgs( buffer, BIG_INFO_STRING ); + + if( !buffer[ 0 ] ) + { + cg.consoleText[ 0 ] = '\0'; + cg.numConsoleLines = 0; + return; + } + + bufferLen = strlen( buffer ); + textLen = strlen( cg.consoleText ); + + // Ignore console messages that were just printed + if( cg_noPrintDuplicate.integer && textLen >= bufferLen && + !strcmp( cg.consoleText + textLen - bufferLen, buffer ) ) + return; + + if( cg.numConsoleLines == MAX_CONSOLE_LINES ) + CG_RemoveNotifyLine( ); + + Q_strcat( cg.consoleText, MAX_CONSOLE_TEXT, buffer ); + cg.consoleLines[ cg.numConsoleLines ].time = cg.time; + cg.consoleLines[ cg.numConsoleLines ].length = bufferLen; + cg.numConsoleLines++; +} + +void QDECL CG_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + trap_Error( text ); +} + +void QDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[1024]; + + va_start( argptr, error ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); + va_end( argptr ); + + CG_Error( "%s", text ); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + CG_Printf ("%s", text); +} + + + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) +{ + static char buffer[ MAX_STRING_CHARS ]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +//======================================================================== + +/* +================= +CG_FileExists + +Test if a specific file exists or not +================= +*/ +qboolean CG_FileExists( char *filename ) +{ + return trap_FS_FOpenFile( filename, NULL, FS_READ ); +} + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) +{ + int i; + char name[ MAX_QPATH ]; + const char *soundName; + const cuboidAttributes_t *cuboid; + + cgs.media.alienStageTransition = trap_S_RegisterSound( "sound/announcements/overmindevolved.wav", qtrue ); + cgs.media.humanStageTransition = trap_S_RegisterSound( "sound/announcements/reinforcement.wav", qtrue ); + + cgs.media.alienOvermindAttack = trap_S_RegisterSound( "sound/announcements/overmindattack.wav", qtrue ); + cgs.media.alienOvermindDying = trap_S_RegisterSound( "sound/announcements/overminddying.wav", qtrue ); + cgs.media.alienOvermindSpawns = trap_S_RegisterSound( "sound/announcements/overmindspawns.wav", qtrue ); + + cgs.media.alienL1Grab = trap_S_RegisterSound( "sound/player/level1/grab.wav", qtrue ); + cgs.media.alienL4ChargePrepare = trap_S_RegisterSound( "sound/player/level4/charge_prepare.wav", qtrue ); + cgs.media.alienL4ChargeStart = trap_S_RegisterSound( "sound/player/level4/charge_start.wav", qtrue ); + + cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/tracer.wav", qfalse ); + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); + cgs.media.turretSpinupSound = trap_S_RegisterSound( "sound/buildables/mgturret/spinup.wav", qfalse ); + cgs.media.weaponEmptyClick = trap_S_RegisterSound( "sound/weapons/click.wav", qfalse ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/misc/talk.wav", qfalse ); + cgs.media.alienTalkSound = trap_S_RegisterSound( "sound/misc/alien_talk.wav", qfalse ); + cgs.media.humanTalkSound = trap_S_RegisterSound( "sound/misc/human_talk.wav", qfalse ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse ); + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse ); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse ); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse ); + + cgs.media.disconnectSound = trap_S_RegisterSound( "sound/misc/disconnect.wav", qfalse ); + + for( i = 0; i < 4; i++ ) + { + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/step%i.wav", i + 1 ); + cgs.media.footsteps[ FOOTSTEP_NORMAL ][ i ] = trap_S_RegisterSound( name, qfalse ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/flesh%i.wav", i + 1 ); + cgs.media.footsteps[ FOOTSTEP_FLESH ][ i ] = trap_S_RegisterSound( name, qfalse ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/splash%i.wav", i + 1 ); + cgs.media.footsteps[ FOOTSTEP_SPLASH ][ i ] = trap_S_RegisterSound( name, qfalse ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/clank%i.wav", i + 1 ); + cgs.media.footsteps[ FOOTSTEP_METAL ][ i ] = trap_S_RegisterSound( name, qfalse ); + } + + for( i = 1 ; i < MAX_SOUNDS ; i++ ) + { + soundName = CG_ConfigString( CS_SOUNDS + i ); + + if( !soundName[ 0 ] ) + break; + + if( soundName[ 0 ] == '*' ) + continue; // custom sound + + cgs.gameSounds[ i ] = trap_S_RegisterSound( soundName, qfalse ); + } + + cgs.media.jetpackDescendSound = trap_S_RegisterSound( "sound/upgrades/jetpack/low.wav", qfalse ); + cgs.media.jetpackIdleSound = trap_S_RegisterSound( "sound/upgrades/jetpack/idle.wav", qfalse ); + cgs.media.jetpackAscendSound = trap_S_RegisterSound( "sound/upgrades/jetpack/hi.wav", qfalse ); + cgs.media.jetpackDescendDeactivateSound = trap_S_RegisterSound( "sound/upgrades/jetpack/low_off.wav", qfalse ); + cgs.media.jetpackIdleDeactivateSound = trap_S_RegisterSound( "sound/upgrades/jetpack/idle_off.wav", qfalse ); + cgs.media.jetpackAscendDeactivateSound = trap_S_RegisterSound( "sound/upgrades/jetpack/hi_off.wav", qfalse ); + cgs.media.jetpackJumpSound = trap_S_RegisterSound( "sound/upgrades/jetpack/jump.wav", qfalse ); + cgs.media.jetpackLowFuelSound = trap_S_RegisterSound( "sound/upgrades/jetpack/lowfuel.wav", qfalse ); + cgs.media.jetpackNoJumpFuelSound = trap_S_RegisterSound( "sound/upgrades/jetpack/nojumpfuel.wav", qfalse ); + cgs.media.jetpackRefuelSound = trap_S_RegisterSound( "sound/upgrades/jetpack/refuel.wav", qfalse ); + + cgs.media.medkitUseSound = trap_S_RegisterSound( "sound/upgrades/medkit/medkit.wav", qfalse ); + + cgs.media.alienEvolveSound = trap_S_RegisterSound( "sound/player/alienevolve.wav", qfalse ); + + cgs.media.alienBuildableExplosion = trap_S_RegisterSound( "sound/buildables/alien/explosion.wav", qfalse ); + cgs.media.alienBuildableDamage = trap_S_RegisterSound( "sound/buildables/alien/damage.wav", qfalse ); + cgs.media.alienBuildablePrebuild = trap_S_RegisterSound( "sound/buildables/alien/prebuild.wav", qfalse ); + + cgs.media.humanBuildableExplosion = trap_S_RegisterSound( "sound/buildables/human/explosion.wav", qfalse ); + cgs.media.humanBuildablePrebuild = trap_S_RegisterSound( "sound/buildables/human/prebuild.wav", qfalse ); + + for( i = 0; i < 4; i++ ) + cgs.media.humanBuildableDamage[ i ] = trap_S_RegisterSound( + va( "sound/buildables/human/damage%d.wav", i ), qfalse ); + + cgs.media.hardBounceSound1 = trap_S_RegisterSound( "sound/misc/hard_bounce1.wav", qfalse ); + cgs.media.hardBounceSound2 = trap_S_RegisterSound( "sound/misc/hard_bounce2.wav", qfalse ); + + cgs.media.repeaterUseSound = trap_S_RegisterSound( "sound/buildables/repeater/use.wav", qfalse ); + + cgs.media.buildableRepairSound = trap_S_RegisterSound( "sound/buildables/human/repair.wav", qfalse ); + cgs.media.buildableRepairedSound = trap_S_RegisterSound( "sound/buildables/human/repaired.wav", qfalse ); + + cgs.media.lCannonWarningSound = trap_S_RegisterSound( "models/weapons/lcannon/warning.wav", qfalse ); + cgs.media.lCannonWarningSound2 = trap_S_RegisterSound( "models/weapons/lcannon/warning2.wav", qfalse ); + + cgs.media.cuboidErrorSound = trap_S_RegisterSound( "sound/cuboid/error.wav", qfalse ); + cg.lastCuboidError = cg.time; + cgs.media.cuboidResizeSoundA = trap_S_RegisterSound( "sound/cuboid/resizea.wav", qfalse ); + cgs.media.cuboidResizeSoundB = trap_S_RegisterSound( "sound/cuboid/resizeb.wav", qfalse ); + cgs.media.cuboidRotateSound = trap_S_RegisterSound( "sound/cuboid/rotate.wav", qfalse ); + cgs.media.cuboidAxisChangeSound = trap_S_RegisterSound( "sound/cuboid/axischange.wav", qfalse ); +} + + +//=================================================================================== + +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) +{ + int i,j; + const cuboidAttributes_t *cuboid; + + static char *sb_nums[ 11 ] = + { + "gfx/2d/numbers/zero_32b", + "gfx/2d/numbers/one_32b", + "gfx/2d/numbers/two_32b", + "gfx/2d/numbers/three_32b", + "gfx/2d/numbers/four_32b", + "gfx/2d/numbers/five_32b", + "gfx/2d/numbers/six_32b", + "gfx/2d/numbers/seven_32b", + "gfx/2d/numbers/eight_32b", + "gfx/2d/numbers/nine_32b", + "gfx/2d/numbers/minus_32b", + }; + static char *buildWeaponTimerPieShaders[ 8 ] = + { + "ui/assets/neutral/1_5pie", + "ui/assets/neutral/3_0pie", + "ui/assets/neutral/4_5pie", + "ui/assets/neutral/6_0pie", + "ui/assets/neutral/7_5pie", + "ui/assets/neutral/9_0pie", + "ui/assets/neutral/10_5pie", + "ui/assets/neutral/12_0pie", + }; + + // clear any references to old media + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + trap_R_ClearScene( ); + + trap_R_LoadWorldMap( cgs.mapname ); + CG_UpdateMediaFraction( 0.66f ); + + for( i = 0; i < 11; i++ ) + cgs.media.numberShaders[ i ] = trap_R_RegisterShader( sb_nums[ i ] ); + + cgs.media.viewBloodShader = trap_R_RegisterShader( "gfx/damage/fullscreen_painblend" ); + + cgs.media.connectionShader = trap_R_RegisterShader( "gfx/2d/net" ); + + cgs.media.creepShader = trap_R_RegisterShader( "creep" ); + + cgs.media.scannerBlipShader = trap_R_RegisterShader( "gfx/2d/blip" ); + cgs.media.scannerLineShader = trap_R_RegisterShader( "gfx/2d/stalk" ); + + cgs.media.teamOverlayShader = trap_R_RegisterShader( "gfx/2d/teamoverlay" ); + + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); + + cgs.media.backTileShader = trap_R_RegisterShader( "console" ); + + + // building shaders + cgs.media.greenBuildShader = trap_R_RegisterShader("gfx/misc/greenbuild" ); + cgs.media.redBuildShader = trap_R_RegisterShader("gfx/misc/redbuild" ); + cgs.media.humanSpawningShader = trap_R_RegisterShader("models/buildables/telenode/rep_cyl" ); + + for( i = 0; i < CUBOID_CRACK_TEXTURES - 1; i++ ) + cgs.media.cuboidCracks[ i ] = trap_R_RegisterShader( va( "models/cuboid/cracks_%i", i ) ); + + cgs.media.cuboidModel = trap_R_RegisterModel( "models/cuboid/cuboid.md3" ); + cgs.media.cuboidRedBuildShader = trap_R_RegisterShader( "gfx/cuboid/build_red" ); + cgs.media.cuboidYellowBuildShader = trap_R_RegisterShader( "gfx/cuboid/build_yellow" ); + cgs.media.cuboidGreenBuildShader = trap_R_RegisterShader( "gfx/cuboid/build_green" ); + cgs.media.cuboidAxis = trap_R_RegisterShader( "gfx/cuboid/build_axis" ); + cgs.media.cuboidAlienPrebuild = trap_R_RegisterShader( "gfx/cuboid/prebuild_alien" ); + + cg.forbidCuboids=qfalse; + cg.latestCBNumber=0; + + for( i = 0; i < 15; i++ ) + cgs.media.splashLogo[ i ] = trap_R_RegisterShader( va( "cuboid/logo_%i.tga", i ) ); + cgs.media.splashLeft = trap_R_RegisterShader( "cuboid/logo_left.tga" ); + cgs.media.splashRight = trap_R_RegisterShader( "cuboid/logo_right.tga" ); + + + for( i = 0; i < 8; i++ ) + cgs.media.buildWeaponTimerPie[ i ] = trap_R_RegisterShader( buildWeaponTimerPieShaders[ i ] ); + + // player health cross shaders + cgs.media.healthCross = trap_R_RegisterShader( "ui/assets/neutral/cross.tga" ); + cgs.media.healthCross2X = trap_R_RegisterShader( "ui/assets/neutral/cross2.tga" ); + cgs.media.healthCross3X = trap_R_RegisterShader( "ui/assets/neutral/cross3.tga" ); + cgs.media.healthCrossMedkit = trap_R_RegisterShader( "ui/assets/neutral/cross_medkit.tga" ); + cgs.media.healthCrossPoisoned = trap_R_RegisterShader( "ui/assets/neutral/cross_poison.tga" ); + + cgs.media.upgradeClassIconShader = trap_R_RegisterShader( "icons/icona_upgrade.tga" ); + + cgs.media.balloonShader = trap_R_RegisterShader( "gfx/sprites/chatballoon" ); + + cgs.media.disconnectPS = CG_RegisterParticleSystem( "disconnectPS" ); + + CG_UpdateMediaFraction( 0.7f ); + + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); + + cgs.media.shadowMarkShader = trap_R_RegisterShader( "gfx/marks/shadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "gfx/marks/wake" ); + + cgs.media.poisonCloudPS = CG_RegisterParticleSystem( "firstPersonPoisonCloudPS" ); + cgs.media.poisonCloudedPS = CG_RegisterParticleSystem( "poisonCloudedPS" ); + cgs.media.alienEvolvePS = CG_RegisterParticleSystem( "alienEvolvePS" ); + cgs.media.alienAcidTubePS = CG_RegisterParticleSystem( "alienAcidTubePS" ); + + cgs.media.jetPackDescendPS = CG_RegisterParticleSystem( "jetPackDescendPS" ); + cgs.media.jetPackHoverPS = CG_RegisterParticleSystem( "jetPackHoverPS" ); + cgs.media.jetPackAscendPS = CG_RegisterParticleSystem( "jetPackAscendPS" ); + + cgs.media.humanBuildableDamagedPS = CG_RegisterParticleSystem( "humanBuildableDamagedPS" ); + cgs.media.alienBuildableDamagedPS = CG_RegisterParticleSystem( "alienBuildableDamagedPS" ); + cgs.media.humanBuildableDestroyedPS = CG_RegisterParticleSystem( "humanBuildableDestroyedPS" ); + cgs.media.alienBuildableDestroyedPS = CG_RegisterParticleSystem( "alienBuildableDestroyedPS" ); + + cgs.media.humanBuildableBleedPS = CG_RegisterParticleSystem( "humanBuildableBleedPS"); + cgs.media.alienBuildableBleedPS = CG_RegisterParticleSystem( "alienBuildableBleedPS" ); + + cgs.media.alienBleedPS = CG_RegisterParticleSystem( "alienBleedPS" ); + cgs.media.humanBleedPS = CG_RegisterParticleSystem( "humanBleedPS" ); + + CG_BuildableStatusParse( "ui/assets/human/buildstat.cfg", &cgs.humanBuildStat ); + CG_BuildableStatusParse( "ui/assets/alien/buildstat.cfg", &cgs.alienBuildStat ); + + // register the inline models + cgs.numInlineModels = trap_CM_NumInlineModels( ); + + for( i = 1; i < cgs.numInlineModels; i++ ) + { + char name[ 10 ]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof( name ), "*%i", i ); + + cgs.inlineDrawModel[ i ] = trap_R_RegisterModel( name ); + trap_R_ModelBounds( cgs.inlineDrawModel[ i ], mins, maxs ); + + for( j = 0 ; j < 3 ; j++ ) + cgs.inlineModelMidpoints[ i ][ j ] = mins[ j ] + 0.5 * ( maxs[ j ] - mins[ j ] ); + } + + // register all the server specified models + for( i = 1; i < MAX_MODELS; i++ ) + { + const char *modelName; + + modelName = CG_ConfigString( CS_MODELS + i ); + + if( !modelName[ 0 ] ) + break; + + cgs.gameModels[ i ] = trap_R_RegisterModel( modelName ); + } + + CG_UpdateMediaFraction( 0.8f ); + + // register all the server specified shaders + for( i = 1; i < MAX_GAME_SHADERS; i++ ) + { + const char *shaderName; + + shaderName = CG_ConfigString( CS_SHADERS + i ); + + if( !shaderName[ 0 ] ) + break; + + cgs.gameShaders[ i ] = trap_R_RegisterShader( shaderName ); + } + + CG_UpdateMediaFraction( 0.9f ); + + // register all the server specified particle systems + for( i = 1; i < MAX_GAME_PARTICLE_SYSTEMS; i++ ) + { + const char *psName; + + psName = CG_ConfigString( CS_PARTICLE_SYSTEMS + i ); + + if( !psName[ 0 ] ) + break; + + cgs.gameParticleSystems[ i ] = CG_RegisterParticleSystem( (char *)psName ); + } +} + + +/* +======================= +CG_BuildSpectatorString + +======================= +*/ +void CG_BuildSpectatorString( void ) +{ + int i; + + cg.spectatorList[ 0 ] = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + if( cgs.clientinfo[ i ].infoValid && cgs.clientinfo[ i ].team == TEAM_NONE ) + { + Q_strcat( cg.spectatorList, sizeof( cg.spectatorList ), + va( S_COLOR_WHITE "%s ", cgs.clientinfo[ i ].name ) ); + } + } +} + + + +/* +=================== +CG_RegisterClients + +=================== +*/ +static void CG_RegisterClients( void ) +{ + int i; + + cg.charModelFraction = 0.0f; + + //precache all the models/sounds/etc + for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) + { + CG_PrecacheClientInfo( i, BG_ClassConfig( i )->modelName, + BG_ClassConfig( i )->skinName ); + + cg.charModelFraction = (float)i / (float)PCL_NUM_CLASSES; + trap_UpdateScreen( ); + } + + cgs.media.larmourHeadSkin = trap_R_RegisterSkin( "models/players/human_base/head_light.skin" ); + cgs.media.larmourMk2HeadSkin = trap_R_RegisterSkin( "models/players/human_base/head_light_mk2.skin" ); + cgs.media.larmourLegsSkin = trap_R_RegisterSkin( "models/players/human_base/lower_light.skin" ); + cgs.media.larmourTorsoSkin = trap_R_RegisterSkin( "models/players/human_base/upper_light.skin" ); + + cgs.media.jetpackModel = trap_R_RegisterModel( "models/players/human_base/jetpack.md3" ); + cgs.media.jetpackFlashModel = trap_R_RegisterModel( "models/players/human_base/jetpack_flash.md3" ); + cgs.media.battpackModel = trap_R_RegisterModel( "models/players/human_base/battpack.md3" ); + + cg.charModelFraction = 1.0f; + trap_UpdateScreen( ); + + //load all the clientinfos of clients already connected to the server + for( i = 0; i < MAX_CLIENTS; i++ ) + { + const char *clientInfo; + + clientInfo = CG_ConfigString( CS_PLAYERS + i ); + if( !clientInfo[ 0 ] ) + continue; + + CG_NewClientInfo( i ); + } + + CG_BuildSpectatorString( ); +} + +//=========================================================================== + +/* +================= +CG_ConfigString +================= +*/ +const char *CG_ConfigString( int index ) +{ + if( index < 0 || index >= MAX_CONFIGSTRINGS ) + CG_Error( "CG_ConfigString: bad index: %i", index ); + + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) +{ + char *s; + char parm1[ MAX_QPATH ], parm2[ MAX_QPATH ]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2 ); +} + +/* +====================== +CG_PlayerCount +====================== +*/ +int CG_PlayerCount( void ) +{ + int i, count = 0; + + CG_RequestScores( ); + + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == TEAM_ALIENS || + cg.scores[ i ].team == TEAM_HUMANS ) + count++; + } + + return count; +} + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +char *CG_GetMenuBuffer( const char *filename ) +{ + int len; + fileHandle_t f; + static char buf[ MAX_MENUFILE ]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + + if( !f ) + { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return NULL; + } + + if( len >= MAX_MENUFILE ) + { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", + filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + return buf; +} + +qboolean CG_Asset_Parse( int handle ) +{ + pc_token_t token; + const char *tempStr; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( Q_stricmp( token.string, "{" ) != 0 ) + return qfalse; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( Q_stricmp( token.string, "}" ) == 0 ) + return qtrue; + + // font + if( Q_stricmp( token.string, "font" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.textFont ); + continue; + } + + // smallFont + if( Q_stricmp( token.string, "smallFont" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.smallFont ); + continue; + } + + // font + if( Q_stricmp( token.string, "bigfont" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.bigFont ); + continue; + } + + // gradientbar + if( Q_stricmp( token.string, "gradientbar" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr ); + continue; + } + + // enterMenuSound + if( Q_stricmp( token.string, "menuEnterSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // exitMenuSound + if( Q_stricmp( token.string, "menuExitSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // itemFocusSound + if( Q_stricmp( token.string, "itemFocusSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // menuBuzzSound + if( Q_stricmp( token.string, "menuBuzzSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if( Q_stricmp( token.string, "cursor" ) == 0 ) + { + if( !PC_String_Parse( handle, &cgDC.Assets.cursorStr ) ) + return qfalse; + + cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr ); + continue; + } + + if( Q_stricmp( token.string, "fadeClamp" ) == 0 ) + { + if( !PC_Float_Parse( handle, &cgDC.Assets.fadeClamp ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "fadeCycle" ) == 0 ) + { + if( !PC_Int_Parse( handle, &cgDC.Assets.fadeCycle ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "fadeAmount" ) == 0 ) + { + if( !PC_Float_Parse( handle, &cgDC.Assets.fadeAmount ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowX" ) == 0 ) + { + if( !PC_Float_Parse( handle, &cgDC.Assets.shadowX ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowY" ) == 0 ) + { + if( !PC_Float_Parse( handle, &cgDC.Assets.shadowY ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowColor" ) == 0 ) + { + if( !PC_Color_Parse( handle, &cgDC.Assets.shadowColor ) ) + return qfalse; + + cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[ 3 ]; + continue; + } + } + + return qfalse; +} + +void CG_ParseMenu( const char *menuFile ) +{ + pc_token_t token; + int handle; + + handle = trap_Parse_LoadSource( menuFile ); + + if( !handle ) + handle = trap_Parse_LoadSource( "ui/testhud.menu" ); + + if( !handle ) + return; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + break; + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if( token.string[ 0 ] == '}' ) + break; + + if( Q_stricmp( token.string, "assetGlobalDef" ) == 0 ) + { + if( CG_Asset_Parse( handle ) ) + continue; + else + break; + } + + + if( Q_stricmp( token.string, "menudef" ) == 0 ) + { + // start a new menu + Menu_New( handle ); + } + } + + trap_Parse_FreeSource( handle ); +} + +qboolean CG_Load_Menu( char **p ) +{ + char *token; + + token = COM_ParseExt( p, qtrue ); + + if( token[ 0 ] != '{' ) + return qfalse; + + while( 1 ) + { + token = COM_ParseExt( p, qtrue ); + + if( Q_stricmp( token, "}" ) == 0 ) + return qtrue; + + if( !token || token[ 0 ] == 0 ) + return qfalse; + + CG_ParseMenu( token ); + } + return qfalse; +} + + + +void CG_LoadMenus( const char *menuFile ) +{ + char *token; + char *p; + int len, start; + fileHandle_t f; + static char buf[ MAX_MENUDEFFILE ]; + + start = trap_Milliseconds( ); + + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + + if( !f ) + { + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); + + if( !f ) + trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n" ) ); + } + + if( len >= MAX_MENUDEFFILE ) + { + trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", + menuFile, len, MAX_MENUDEFFILE ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[ len ] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress( buf ); + + Menu_Reset( ); + + p = buf; + + while( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + + if( !token || token[ 0 ] == 0 || token[ 0 ] == '}' ) + break; + + if( Q_stricmp( token, "}" ) == 0 ) + break; + + if( Q_stricmp( token, "loadmenu" ) == 0 ) + { + if( CG_Load_Menu( &p ) ) + continue; + else + break; + } + } + + Com_Printf( "UI menu load time = %d milli seconds\n", trap_Milliseconds( ) - start ); +} + + + +static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int key ) +{ + return qfalse; +} + + +static int CG_FeederCount( int feederID ) +{ + int i, count = 0; + + if( feederID == FEEDER_ALIENTEAM_LIST ) + { + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == TEAM_ALIENS ) + count++; + } + } + else if( feederID == FEEDER_HUMANTEAM_LIST ) + { + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == TEAM_HUMANS ) + count++; + } + } + + return count; +} + + +void CG_SetScoreSelection( void *p ) +{ + menuDef_t *menu = (menuDef_t*)p; + playerState_t *ps = &cg.snap->ps; + int i, alien, human; + int feeder; + + alien = human = 0; + + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == TEAM_ALIENS ) + alien++; + else if( cg.scores[ i ].team == TEAM_HUMANS ) + human++; + + if( ps->clientNum == cg.scores[ i ].client ) + cg.selectedScore = i; + } + + if( menu == NULL ) + // just interested in setting the selected score + return; + + feeder = FEEDER_ALIENTEAM_LIST; + i = alien; + + if( cg.scores[ cg.selectedScore ].team == TEAM_HUMANS ) + { + feeder = FEEDER_HUMANTEAM_LIST; + i = human; + } + + Menu_SetFeederSelection(menu, feeder, i, NULL); +} + +// FIXME: might need to cache this info +static clientInfo_t * CG_InfoFromScoreIndex( int index, int team, int *scoreIndex ) +{ + int i, count; + count = 0; + + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == team ) + { + if( count == index ) + { + *scoreIndex = i; + return &cgs.clientinfo[ cg.scores[ i ].client ]; + } + count++; + } + } + + *scoreIndex = index; + return &cgs.clientinfo[ cg.scores[ index ].client ]; +} + +qboolean CG_ClientIsReady( int clientNum ) +{ + clientList_t ready; + + Com_ClientListParse( &ready, CG_ConfigString( CS_CLIENTS_READY ) ); + + return Com_ClientListContains( &ready, clientNum ); +} + +static const char *CG_FeederItemText( int feederID, int index, int column, qhandle_t *handle ) +{ + int scoreIndex = 0; + clientInfo_t *info = NULL; + int team = -1; + score_t *sp = NULL; + qboolean showIcons = qfalse; + + *handle = -1; + + if( feederID == FEEDER_ALIENTEAM_LIST ) + team = TEAM_ALIENS; + else if( feederID == FEEDER_HUMANTEAM_LIST ) + team = TEAM_HUMANS; + + info = CG_InfoFromScoreIndex( index, team, &scoreIndex ); + sp = &cg.scores[ scoreIndex ]; + + if( cg.intermissionStarted && CG_ClientIsReady( sp->client ) ) + showIcons = qfalse; + else if( cg.snap->ps.pm_type == PM_SPECTATOR || + cg.snap->ps.pm_type == PM_NOCLIP || + cg.snap->ps.pm_flags & PMF_FOLLOW || + team == cg.snap->ps.stats[ STAT_TEAM ] || + cg.intermissionStarted ) + { + showIcons = qtrue; + } + + if( info && info->infoValid ) + { + switch( column ) + { + case 0: + if( showIcons ) + { + if( sp->weapon != WP_NONE ) + *handle = cg_weapons[ sp->weapon ].weaponIcon; + } + break; + + case 1: + if( showIcons ) + { + if( sp->team == TEAM_HUMANS && sp->upgrade != UP_NONE ) + *handle = cg_upgrades[ sp->upgrade ].upgradeIcon; + else if( sp->team == TEAM_ALIENS ) + { + switch( sp->weapon ) + { + case WP_ABUILD2: + case WP_ALEVEL1_UPG: + case WP_ALEVEL2_UPG: + case WP_ALEVEL3_UPG: + *handle = cgs.media.upgradeClassIconShader; + break; + + default: + break; + } + } + } + break; + + case 2: + if( cg.intermissionStarted && CG_ClientIsReady( sp->client ) ) + return "Ready"; + break; + + case 3: + return va( S_COLOR_WHITE "%s", info->name ); + break; + + case 4: + return va( "%d", sp->score ); + break; + + case 5: + return va( "%4d", sp->time ); + break; + + case 6: + if( sp->ping == -1 ) + return ""; + + return va( "%4d", sp->ping ); + break; + } + } + + return ""; +} + +static qhandle_t CG_FeederItemImage( int feederID, int index ) +{ + return 0; +} + +static void CG_FeederSelection( int feederID, int index ) +{ + int i, count; + int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? TEAM_ALIENS : TEAM_HUMANS; + count = 0; + + for( i = 0; i < cg.numScores; i++ ) + { + if( cg.scores[ i ].team == team ) + { + if( index == count ) + cg.selectedScore = i; + + count++; + } + } +} + +static float CG_Cvar_Get( const char *cvar ) +{ + char buff[ 128 ]; + + memset( buff, 0, sizeof( buff ) ); + trap_Cvar_VariableStringBuffer( cvar, buff, sizeof( buff ) ); + return atof( buff ); +} + +void CG_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text, + int cursorPos, char cursor, int limit, int style ) +{ + UI_Text_Paint( x, y, scale, color, text, 0, limit, style ); +} + +static int CG_OwnerDrawWidth( int ownerDraw, float scale ) +{ + switch( ownerDraw ) + { + case CG_KILLER: + return UI_Text_Width( CG_GetKillerText( ), scale ); + break; + } + + return 0; +} + +static int CG_PlayCinematic( const char *name, float x, float y, float w, float h ) +{ + return trap_CIN_PlayCinematic( name, x, y, w, h, CIN_loop ); +} + +static void CG_StopCinematic( int handle ) +{ + trap_CIN_StopCinematic( handle ); +} + +static void CG_DrawCinematic( int handle, float x, float y, float w, float h ) +{ + trap_CIN_SetExtents( handle, x, y, w, h ); + trap_CIN_DrawCinematic( handle ); +} + +static void CG_RunCinematicFrame( int handle ) +{ + trap_CIN_RunCinematic( handle ); +} + +// hack to prevent warning +static qboolean CG_OwnerDrawVisible( int parameter ) +{ + return qfalse; +} + +/* +================= +CG_LoadHudMenu +================= +*/ +void CG_LoadHudMenu( void ) +{ + char buff[ 1024 ]; + const char *hudSet; + + cgDC.aspectScale = ( ( 640.0f * cgs.glconfig.vidHeight ) / + ( 480.0f * cgs.glconfig.vidWidth ) ); + cgDC.xscale = cgs.glconfig.vidWidth / 640.0f; + cgDC.yscale = cgs.glconfig.vidHeight / 480.0f; + + cgDC.smallFontScale = CG_Cvar_Get( "ui_smallFont" ); + cgDC.bigFontScale = CG_Cvar_Get( "ui_bigFont" ); + + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + cgDC.setColor = &trap_R_SetColor; + cgDC.drawHandlePic = &CG_DrawPic; + cgDC.drawStretchPic = &trap_R_DrawStretchPic; + cgDC.registerModel = &trap_R_RegisterModel; + cgDC.modelBounds = &trap_R_ModelBounds; + cgDC.fillRect = &CG_FillRect; + cgDC.drawRect = &CG_DrawRect; + cgDC.drawSides = &CG_DrawSides; + cgDC.drawTopBottom = &CG_DrawTopBottom; + cgDC.clearScene = &trap_R_ClearScene; + cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + cgDC.renderScene = &trap_R_RenderScene; + cgDC.registerFont = &trap_R_RegisterFont; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.runScript = &CG_RunMenuScript; + cgDC.setCVar = trap_Cvar_Set; + cgDC.getCVarString = trap_Cvar_VariableStringBuffer; + cgDC.getCVarValue = CG_Cvar_Get; + cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.startLocalSound = &trap_S_StartLocalSound; + cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; + cgDC.feederCount = &CG_FeederCount; + cgDC.feederItemImage = &CG_FeederItemImage; + cgDC.feederItemText = &CG_FeederItemText; + cgDC.feederSelection = &CG_FeederSelection; + //cgDC.setBinding = &trap_Key_SetBinding; + //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; + //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + //cgDC.executeText = &trap_Cmd_ExecuteText; + cgDC.Error = &Com_Error; + cgDC.Print = &Com_Printf; + cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.ownerDrawText = &CG_OwnerDrawText; + //cgDC.Pause = &CG_Pause; + cgDC.registerSound = &trap_S_RegisterSound; + cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + cgDC.playCinematic = &CG_PlayCinematic; + cgDC.stopCinematic = &CG_StopCinematic; + cgDC.drawCinematic = &CG_DrawCinematic; + cgDC.runCinematicFrame = &CG_RunCinematicFrame; + + Init_Display( &cgDC ); + + Menu_Reset( ); + + trap_Cvar_VariableStringBuffer( "cg_hudFiles", buff, sizeof( buff ) ); + hudSet = buff; + + if( !cg_hudFilesEnable.integer || hudSet[ 0 ] == '\0' ) + hudSet = "ui/hud.txt"; + + CG_LoadMenus( hudSet ); +} + +void CG_AssetCache( void ) +{ + int i; + + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); + + if( cg_emoticons.integer ) + { + cgDC.Assets.emoticonCount = BG_LoadEmoticons( cgDC.Assets.emoticons, + MAX_EMOTICONS ); + } + else + cgDC.Assets.emoticonCount = 0; + + for( i = 0; i < cgDC.Assets.emoticonCount; i++ ) + { + cgDC.Assets.emoticons[ i ].shader = trap_R_RegisterShaderNoMip( + va( "emoticons/%s_%dx1.tga", cgDC.Assets.emoticons[ i ].name, + cgDC.Assets.emoticons[ i ].width ) ); + } +} + +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) +{ + const char *s; + + // clear everything + memset( &cgs, 0, sizeof( cgs ) ); + memset( &cg, 0, sizeof( cg ) ); + memset( &cg.pmext, 0, sizeof( cg.pmext ) ); + memset( cg_entities, 0, sizeof( cg_entities ) ); + + cg.clientNum = clientNum; + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0f; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0f; + + // load a few needed things before we do any screen updates + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); + cgs.media.outlineShader = trap_R_RegisterShader( "outline" ); + + // load overrides + BG_InitClassConfigs( ); + BG_InitBuildableConfigs( ); + BG_InitAllowedGameElements( ); + + // Dynamic memory + BG_InitMemory( ); + + CG_RegisterCvars( ); + + CG_InitConsoleCommands( ); + + String_Init( ); + + CG_AssetCache( ); + CG_LoadHudMenu( ); + + cg.weaponSelect = WP_NONE; + + // old servers + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + // copy vote display strings so they don't show up blank if we see + // the same one directly after connecting + Q_strncpyz( cgs.voteString[ TEAM_NONE ], + CG_ConfigString( CS_VOTE_STRING + TEAM_NONE ), + sizeof( cgs.voteString ) ); + Q_strncpyz( cgs.voteString[ TEAM_ALIENS ], + CG_ConfigString( CS_VOTE_STRING + TEAM_ALIENS ), + sizeof( cgs.voteString[ TEAM_ALIENS ] ) ); + Q_strncpyz( cgs.voteString[ TEAM_HUMANS ], + CG_ConfigString( CS_VOTE_STRING + TEAM_ALIENS ), + sizeof( cgs.voteString[ TEAM_HUMANS ] ) ); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + + if( strcmp( s, GAME_VERSION ) ) + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo( ); + + // load the new map + trap_CM_LoadMap( cgs.mapname ); + + cg.loading = qtrue; // force players to load instead of defer + + CG_LoadTrailSystems( ); + CG_UpdateMediaFraction( 0.05f ); + + CG_LoadParticleSystems( ); + CG_UpdateMediaFraction( 0.05f ); + + CG_RegisterSounds( ); + CG_UpdateMediaFraction( 0.60f ); + + CG_RegisterGraphics( ); + CG_UpdateMediaFraction( 0.90f ); + + CG_InitWeapons( ); + CG_UpdateMediaFraction( 0.95f ); + + CG_InitUpgrades( ); + CG_UpdateMediaFraction( 1.0f ); + + CG_InitBuildables( ); + + cgs.voices = BG_VoiceInit( ); + BG_PrintVoices( cgs.voices, cg_debugVoices.integer ); + + CG_RegisterClients( ); // if low on memory, some clients will be deferred + + cg.loading = qfalse; // future players will be deferred + + CG_InitMarkPolys( ); + + // remove the last loading update + cg.infoScreenText[ 0 ] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues( ); + + CG_StartMusic( ); + + CG_ShaderStateChanged( ); + + trap_S_ClearLoopingSounds( qtrue ); +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) +{ + // some mods may need to do cleanup work here, + // like closing files or archiving session data +} + +/* +================ +CG_VoIPString +================ +*/ +static char *CG_VoIPString( void ) +{ + // a generous overestimate of the space needed for 0,1,2...61,62,63 + static char voipString[ MAX_CLIENTS * 4 ]; + char voipSendTarget[ MAX_CVAR_VALUE_STRING ]; + + trap_Cvar_VariableStringBuffer( "cl_voipSendTarget", voipSendTarget, + sizeof( voipSendTarget ) ); + + if( Q_stricmp( voipSendTarget, "team" ) == 0 ) + { + int i, slen, nlen; + for( slen = i = 0; i < cgs.maxclients; i++ ) + { + if( !cgs.clientinfo[ i ].infoValid || i == cg.clientNum ) + continue; + if( cgs.clientinfo[ i ].team != cgs.clientinfo[ cg.clientNum ].team ) + continue; + + nlen = Q_snprintf( &voipString[ slen ], sizeof( voipString ) - slen, + "%s%d", ( slen > 0 ) ? "," : "", i ); + if( slen + nlen + 1 >= sizeof( voipString ) ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: voipString overflowed\n" ); + break; + } + + slen += nlen; + } + + // Notice that if the snprintf was truncated, slen was not updated + // so this will remove any trailing commas or partially-completed numbers + voipString[ slen ] = '\0'; + } + else if( Q_stricmp( voipSendTarget, "crosshair" ) == 0 ) + Com_sprintf( voipString, sizeof( voipString ), "%d", + CG_CrosshairPlayer( ) ); + else if( Q_stricmp( voipSendTarget, "attacker" ) == 0 ) + Com_sprintf( voipString, sizeof( voipString ), "%d", + CG_LastAttacker( ) ); + else + return NULL; + + return voipString; +} + diff --git a/src/cgame/cg_marks.c b/src/cgame/cg_marks.c new file mode 100644 index 0000000..6e1891f --- /dev/null +++ b/src/cgame/cg_marks.c @@ -0,0 +1,289 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_marks.c -- wall marks + + +#include "cg_local.h" + +/* +=================================================================== + +MARK POLYS + +=================================================================== +*/ + + +markPoly_t cg_activeMarkPolys; // double linked list +markPoly_t *cg_freeMarkPolys; // single linked list +markPoly_t cg_markPolys[ MAX_MARK_POLYS ]; +static int markTotal; + +/* +=================== +CG_InitMarkPolys + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitMarkPolys( void ) +{ + int i; + + memset( cg_markPolys, 0, sizeof( cg_markPolys ) ); + + cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; + cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; + cg_freeMarkPolys = cg_markPolys; + + for( i = 0; i < MAX_MARK_POLYS - 1; i++ ) + cg_markPolys[ i ].nextMark = &cg_markPolys[ i + 1 ]; +} + + +/* +================== +CG_FreeMarkPoly +================== +*/ +void CG_FreeMarkPoly( markPoly_t *le ) +{ + if( !le->prevMark ) + CG_Error( "CG_FreeLocalEntity: not active" ); + + // remove from the doubly linked active list + le->prevMark->nextMark = le->nextMark; + le->nextMark->prevMark = le->prevMark; + + // the free list is only singly linked + le->nextMark = cg_freeMarkPolys; + cg_freeMarkPolys = le; +} + +/* +=================== +CG_AllocMark + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +markPoly_t *CG_AllocMark( void ) +{ + markPoly_t *le; + int time; + + if( !cg_freeMarkPolys ) + { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + time = cg_activeMarkPolys.prevMark->time; + + while( cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time ) + CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); + } + + le = cg_freeMarkPolys; + cg_freeMarkPolys = cg_freeMarkPolys->nextMark; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->nextMark = cg_activeMarkPolys.nextMark; + le->prevMark = &cg_activeMarkPolys; + cg_activeMarkPolys.nextMark->prevMark = le; + cg_activeMarkPolys.nextMark = le; + return le; +} + + + +/* +================= +CG_ImpactMark + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +#define MAX_MARK_FRAGMENTS 128 +#define MAX_MARK_POINTS 384 + +void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, + float orientation, float red, float green, float blue, float alpha, + qboolean alphaFade, float radius, qboolean temporary ) +{ + vec3_t axis[ 3 ]; + float texCoordScale; + vec3_t originalPoints[ 4 ]; + byte colors[ 4 ]; + int i, j; + int numFragments; + markFragment_t markFragments[ MAX_MARK_FRAGMENTS ], *mf; + vec3_t markPoints[ MAX_MARK_POINTS ]; + vec3_t projection; + + if( !cg_addMarks.integer ) + return; + + if( radius <= 0 ) + CG_Error( "CG_ImpactMark called with <= 0 radius" ); + + //if ( markTotal >= MAX_MARK_POLYS ) { + // return; + //} + + // create the texture axis + VectorNormalize2( dir, axis[ 0 ] ); + PerpendicularVector( axis[ 1 ], axis[ 0 ] ); + RotatePointAroundVector( axis[ 2 ], axis[ 0 ], axis[ 1 ], orientation ); + CrossProduct( axis[ 0 ], axis[ 2 ], axis[ 1 ] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for( i = 0; i < 3; i++ ) + { + originalPoints[ 0 ][ i ] = origin[ i ] - radius * axis[ 1 ][ i ] - radius * axis[ 2 ][ i ]; + originalPoints[ 1 ][ i ] = origin[ i ] + radius * axis[ 1 ][ i ] - radius * axis[ 2 ][ i ]; + originalPoints[ 2 ][ i ] = origin[ i ] + radius * axis[ 1 ][ i ] + radius * axis[ 2 ][ i ]; + originalPoints[ 3 ][ i ] = origin[ i ] - radius * axis[ 1 ][ i ] + radius * axis[ 2 ][ i ]; + } + + // get the fragments + VectorScale( dir, -20, projection ); + numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, + projection, MAX_MARK_POINTS, markPoints[ 0 ], + MAX_MARK_FRAGMENTS, markFragments ); + + colors[ 0 ] = red * 255; + colors[ 1 ] = green * 255; + colors[ 2 ] = blue * 255; + colors[ 3 ] = alpha * 255; + + for( i = 0, mf = markFragments; i < numFragments; i++, mf++ ) + { + polyVert_t *v; + polyVert_t verts[ MAX_VERTS_ON_POLY ]; + markPoly_t *mark; + + // we have an upper limit on the complexity of polygons + // that we store persistantly + if( mf->numPoints > MAX_VERTS_ON_POLY ) + mf->numPoints = MAX_VERTS_ON_POLY; + + for( j = 0, v = verts; j < mf->numPoints; j++, v++ ) + { + vec3_t delta; + + VectorCopy( markPoints[ mf->firstPoint + j ], v->xyz ); + + VectorSubtract( v->xyz, origin, delta ); + v->st[ 0 ] = 0.5 + DotProduct( delta, axis[ 1 ] ) * texCoordScale; + v->st[ 1 ] = 0.5 + DotProduct( delta, axis[ 2 ] ) * texCoordScale; + *(int *)v->modulate = *(int *)colors; + } + + // if it is a temporary (shadow) mark, add it immediately and forget about it + if( temporary ) + { + trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); + continue; + } + + // otherwise save it persistantly + mark = CG_AllocMark( ); + mark->time = cg.time; + mark->alphaFade = alphaFade; + mark->markShader = markShader; + mark->poly.numVerts = mf->numPoints; + mark->color[ 0 ] = red; + mark->color[ 1 ] = green; + mark->color[ 2 ] = blue; + mark->color[ 3 ] = alpha; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[ 0 ] ) ); + markTotal++; + } +} + + +/* +=============== +CG_AddMarks +=============== +*/ +#define MARK_TOTAL_TIME 10000 +#define MARK_FADE_TIME 1000 + +void CG_AddMarks( void ) +{ + int j; + markPoly_t *mp, *next; + int t; + int fade; + + if( !cg_addMarks.integer ) + return; + + mp = cg_activeMarkPolys.nextMark; + for ( ; mp != &cg_activeMarkPolys; mp = next ) + { + // grab next now, so if the local entity is freed we + // still have it + next = mp->nextMark; + + // see if it is time to completely remove it + if( cg.time > mp->time + MARK_TOTAL_TIME ) + { + CG_FreeMarkPoly( mp ); + continue; + } + + // fade all marks out with time + t = mp->time + MARK_TOTAL_TIME - cg.time; + if( t < MARK_FADE_TIME ) + { + fade = 255 * t / MARK_FADE_TIME; + if( mp->alphaFade ) + { + for( j = 0; j < mp->poly.numVerts; j++ ) + mp->verts[ j ].modulate[ 3 ] = fade; + } + else + { + for( j = 0; j < mp->poly.numVerts; j++ ) + { + mp->verts[ j ].modulate[ 0 ] = mp->color[ 0 ] * fade; + mp->verts[ j ].modulate[ 1 ] = mp->color[ 1 ] * fade; + mp->verts[ j ].modulate[ 2 ] = mp->color[ 2 ] * fade; + } + } + } + + trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); + } +} + diff --git a/src/cgame/cg_particles.c b/src/cgame/cg_particles.c new file mode 100644 index 0000000..5a302d5 --- /dev/null +++ b/src/cgame/cg_particles.c @@ -0,0 +1,2599 @@ +/* +=========================================================================== +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_particles.c -- the particle system + + +#include "cg_local.h" + +static baseParticleSystem_t baseParticleSystems[ MAX_BASEPARTICLE_SYSTEMS ]; +static baseParticleEjector_t baseParticleEjectors[ MAX_BASEPARTICLE_EJECTORS ]; +static baseParticle_t baseParticles[ MAX_BASEPARTICLES ]; +static int numBaseParticleSystems = 0; +static int numBaseParticleEjectors = 0; +static int numBaseParticles = 0; + +static particleSystem_t particleSystems[ MAX_PARTICLE_SYSTEMS ]; +static particleEjector_t particleEjectors[ MAX_PARTICLE_EJECTORS ]; +static particle_t particles[ MAX_PARTICLES ]; +static particle_t *sortedParticles[ MAX_PARTICLES ]; +static particle_t *radixBuffer[ MAX_PARTICLES ]; + +/* +=============== +CG_LerpValues + +Lerp between two values +=============== +*/ +static float CG_LerpValues( float a, float b, float f ) +{ + if( b == PARTICLES_SAME_AS_INITIAL ) + return a; + else + return ( (a) + (f) * ( (b) - (a) ) ); +} + +/* +=============== +CG_RandomiseValue + +Randomise some value by some variance +=============== +*/ +static float CG_RandomiseValue( float value, float variance ) +{ + if( value != 0.0f ) + return value * ( 1.0f + ( random( ) * variance ) ); + else + return random( ) * variance; +} + +/* +=============== +CG_SpreadVector + +Randomly spread a vector by some amount +=============== +*/ +static void CG_SpreadVector( vec3_t v, float spread ) +{ + vec3_t p, r1, r2; + float randomSpread = crandom( ) * spread; + float randomRotation = random( ) * 360.0f; + + PerpendicularVector( p, v ); + + RotatePointAroundVector( r1, p, v, randomSpread ); + RotatePointAroundVector( r2, v, r1, randomRotation ); + + VectorCopy( r2, v ); +} + +/* +=============== +CG_DestroyParticle + +Destroy an individual particle +=============== +*/ +static void CG_DestroyParticle( particle_t *p, vec3_t impactNormal ) +{ + //this particle has an onDeath particle system attached + if( p->class->onDeathSystemName[ 0 ] != '\0' ) + { + particleSystem_t *ps; + + ps = CG_SpawnNewParticleSystem( p->class->onDeathSystemHandle ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + if( impactNormal ) + CG_SetParticleSystemNormal( ps, impactNormal ); + + CG_SetAttachmentPoint( &ps->attachment, p->origin ); + CG_AttachToPoint( &ps->attachment ); + } + } + + p->valid = qfalse; + + //this gives other systems a couple of + //frames to realise the particle is gone + p->frameWhenInvalidated = cg.clientFrame; +} + +/* +=============== +CG_SpawnNewParticle + +Introduce a new particle into the world +=============== +*/ +static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *parent ) +{ + int i, j; + particle_t *p = NULL; + particleEjector_t *pe = parent; + particleSystem_t *ps = parent->parent; + vec3_t attachmentPoint, attachmentVelocity; + vec3_t transform[ 3 ]; + + for( i = 0; i < MAX_PARTICLES; i++ ) + { + p = &particles[ i ]; + + //FIXME: the + 1 may be unnecessary + if( !p->valid && cg.clientFrame > p->frameWhenInvalidated + 1 ) + { + memset( p, 0, sizeof( particle_t ) ); + + //found a free slot + p->class = bp; + p->parent = pe; + + p->birthTime = cg.time; + p->lifeTime = (int)CG_RandomiseValue( (float)bp->lifeTime, bp->lifeTimeRandFrac ); + + p->radius.delay = (int)CG_RandomiseValue( (float)bp->radius.delay, bp->radius.delayRandFrac ); + p->radius.initial = CG_RandomiseValue( bp->radius.initial, bp->radius.initialRandFrac ); + p->radius.final = CG_RandomiseValue( bp->radius.final, bp->radius.finalRandFrac ); + + p->radius.initial += bp->scaleWithCharge * pe->parent->charge; + + p->alpha.delay = (int)CG_RandomiseValue( (float)bp->alpha.delay, bp->alpha.delayRandFrac ); + p->alpha.initial = CG_RandomiseValue( bp->alpha.initial, bp->alpha.initialRandFrac ); + p->alpha.final = CG_RandomiseValue( bp->alpha.final, bp->alpha.finalRandFrac ); + + p->rotation.delay = (int)CG_RandomiseValue( (float)bp->rotation.delay, bp->rotation.delayRandFrac ); + p->rotation.initial = CG_RandomiseValue( bp->rotation.initial, bp->rotation.initialRandFrac ); + p->rotation.final = CG_RandomiseValue( bp->rotation.final, bp->rotation.finalRandFrac ); + + p->dLightRadius.delay = + (int)CG_RandomiseValue( (float)bp->dLightRadius.delay, bp->dLightRadius.delayRandFrac ); + p->dLightRadius.initial = + CG_RandomiseValue( bp->dLightRadius.initial, bp->dLightRadius.initialRandFrac ); + p->dLightRadius.final = + CG_RandomiseValue( bp->dLightRadius.final, bp->dLightRadius.finalRandFrac ); + + p->colorDelay = CG_RandomiseValue( bp->colorDelay, bp->colorDelayRandFrac ); + + p->bounceMarkRadius = CG_RandomiseValue( bp->bounceMarkRadius, bp->bounceMarkRadiusRandFrac ); + p->bounceMarkCount = + rint( CG_RandomiseValue( (float)bp->bounceMarkCount, bp->bounceMarkCountRandFrac ) ); + p->bounceSoundCount = + rint( CG_RandomiseValue( (float)bp->bounceSoundCount, bp->bounceSoundCountRandFrac ) ); + + if( bp->numModels ) + { + p->model = bp->models[ rand( ) % bp->numModels ]; + + if( bp->modelAnimation.frameLerp < 0 ) + { + bp->modelAnimation.frameLerp = p->lifeTime / bp->modelAnimation.numFrames; + bp->modelAnimation.initialLerp = p->lifeTime / bp->modelAnimation.numFrames; + } + } + + if( !CG_AttachmentPoint( &ps->attachment, attachmentPoint ) ) + return NULL; + + VectorCopy( attachmentPoint, p->origin ); + + if( CG_AttachmentAxis( &ps->attachment, transform ) ) + { + vec3_t transDisplacement; + + VectorMatrixMultiply( bp->displacement, transform, transDisplacement ); + VectorAdd( p->origin, transDisplacement, p->origin ); + } + else + VectorAdd( p->origin, bp->displacement, p->origin ); + + for( j = 0; j <= 2; j++ ) + p->origin[ j ] += ( crandom( ) * bp->randDisplacement[ j ] ); + + switch( bp->velMoveType ) + { + case PMT_STATIC: + if( bp->velMoveValues.dirType == PMD_POINT ) + VectorSubtract( bp->velMoveValues.point, p->origin, p->velocity ); + else if( bp->velMoveValues.dirType == PMD_LINEAR ) + VectorCopy( bp->velMoveValues.dir, p->velocity ); + break; + + case PMT_STATIC_TRANSFORM: + if( !CG_AttachmentAxis( &ps->attachment, transform ) ) + return NULL; + + if( bp->velMoveValues.dirType == PMD_POINT ) + { + vec3_t transPoint; + + VectorMatrixMultiply( bp->velMoveValues.point, transform, transPoint ); + VectorSubtract( transPoint, p->origin, p->velocity ); + } + else if( bp->velMoveValues.dirType == PMD_LINEAR ) + VectorMatrixMultiply( bp->velMoveValues.dir, transform, p->velocity ); + break; + + case PMT_TAG: + case PMT_CENT_ANGLES: + if( bp->velMoveValues.dirType == PMD_POINT ) + VectorSubtract( attachmentPoint, p->origin, p->velocity ); + else if( bp->velMoveValues.dirType == PMD_LINEAR ) + { + if( !CG_AttachmentDir( &ps->attachment, p->velocity ) ) + return NULL; + } + break; + + case PMT_NORMAL: + if( !ps->normalValid ) + { + CG_Printf( S_COLOR_RED "ERROR: a particle with velocityType " + "normal has no normal\n" ); + return NULL; + } + + VectorCopy( ps->normal, p->velocity ); + + //normal displacement + VectorNormalize( p->velocity ); + VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); + break; + } + + VectorNormalize( p->velocity ); + CG_SpreadVector( p->velocity, bp->velMoveValues.dirRandAngle ); + VectorScale( p->velocity, + CG_RandomiseValue( bp->velMoveValues.mag, bp->velMoveValues.magRandFrac ), + p->velocity ); + + if( CG_AttachmentVelocity( &ps->attachment, attachmentVelocity ) ) + { + VectorMA( p->velocity, + CG_RandomiseValue( bp->velMoveValues.parentVelFrac, + bp->velMoveValues.parentVelFracRandFrac ), attachmentVelocity, p->velocity ); + } + + p->lastEvalTime = cg.time; + + p->valid = qtrue; + + //this particle has a child particle system attached + if( bp->childSystemName[ 0 ] != '\0' ) + { + particleSystem_t *ps = CG_SpawnNewParticleSystem( bp->childSystemHandle ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentParticle( &ps->attachment, p ); + CG_AttachToParticle( &ps->attachment ); + } + } + + //this particle has a child trail system attached + if( bp->childTrailSystemName[ 0 ] != '\0' ) + { + trailSystem_t *ts = CG_SpawnNewTrailSystem( bp->childTrailSystemHandle ); + + if( CG_IsTrailSystemValid( &ts ) ) + { + CG_SetAttachmentParticle( &ts->frontAttachment, p ); + CG_AttachToParticle( &ts->frontAttachment ); + } + } + + break; + } + } + + return p; +} + + +/* +=============== +CG_SpawnNewParticles + +Check if there are any ejectors that should be +introducing new particles +=============== +*/ +static void CG_SpawnNewParticles( void ) +{ + int i, j; + particle_t *p; + particleSystem_t *ps; + particleEjector_t *pe; + baseParticleEjector_t *bpe; + float lerpFrac; + int count; + + for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) + { + pe = &particleEjectors[ i ]; + ps = pe->parent; + + if( pe->valid ) + { + //a non attached particle system can't make particles + if( !CG_Attached( &ps->attachment ) ) + continue; + + bpe = particleEjectors[ i ].class; + + //if this system is scheduled for removal don't make any new particles + if( !ps->lazyRemove ) + { + while( pe->nextEjectionTime <= cg.time && + ( pe->count > 0 || pe->totalParticles == PARTICLES_INFINITE ) ) + { + for( j = 0; j < bpe->numParticles; j++ ) + CG_SpawnNewParticle( bpe->particles[ j ], pe ); + + if( pe->count > 0 ) + pe->count--; + + //calculate next ejection time + lerpFrac = 1.0 - ( (float)pe->count / (float)pe->totalParticles ); + pe->nextEjectionTime = cg.time + (int)CG_RandomiseValue( + CG_LerpValues( pe->ejectPeriod.initial, + pe->ejectPeriod.final, + lerpFrac ), + pe->ejectPeriod.randFrac ); + } + } + + if( pe->count == 0 || ps->lazyRemove ) + { + count = 0; + + //wait for child particles to die before declaring this pe invalid + for( j = 0; j < MAX_PARTICLES; j++ ) + { + p = &particles[ j ]; + + if( p->valid && p->parent == pe ) + count++; + } + + if( !count ) + pe->valid = qfalse; + } + } + } +} + + +/* +=============== +CG_SpawnNewParticleEjector + +Allocate a new particle ejector +=============== +*/ +static particleEjector_t *CG_SpawnNewParticleEjector( baseParticleEjector_t *bpe, + particleSystem_t *parent ) +{ + int i; + particleEjector_t *pe = NULL; + particleSystem_t *ps = parent; + + for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) + { + pe = &particleEjectors[ i ]; + + if( !pe->valid ) + { + memset( pe, 0, sizeof( particleEjector_t ) ); + + //found a free slot + pe->class = bpe; + pe->parent = ps; + + pe->ejectPeriod.initial = bpe->eject.initial; + pe->ejectPeriod.final = bpe->eject.final; + pe->ejectPeriod.randFrac = bpe->eject.randFrac; + + pe->nextEjectionTime = cg.time + + (int)CG_RandomiseValue( (float)bpe->eject.delay, bpe->eject.delayRandFrac ); + pe->count = pe->totalParticles = + (int)rint( CG_RandomiseValue( (float)bpe->totalParticles, bpe->totalParticlesRandFrac ) ); + + pe->valid = qtrue; + + if( cg_debugParticles.integer >= 1 ) + CG_Printf( "PE %s created\n", ps->class->name ); + + break; + } + } + + return pe; +} + + +/* +=============== +CG_SpawnNewParticleSystem + +Allocate a new particle system +=============== +*/ +particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle ) +{ + int i, j; + particleSystem_t *ps = NULL; + baseParticleSystem_t *bps = &baseParticleSystems[ psHandle - 1 ]; + + if( !bps->registered ) + { + CG_Printf( S_COLOR_RED "ERROR: a particle system has not been registered yet\n" ); + return NULL; + } + + for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) + { + ps = &particleSystems[ i ]; + + if( !ps->valid ) + { + memset( ps, 0, sizeof( particleSystem_t ) ); + + //found a free slot + ps->class = bps; + + ps->valid = qtrue; + ps->lazyRemove = qfalse; + + for( j = 0; j < bps->numEjectors; j++ ) + CG_SpawnNewParticleEjector( bps->ejectors[ j ], ps ); + + if( cg_debugParticles.integer >= 1 ) + CG_Printf( "PS %s created\n", bps->name ); + + break; + } + } + + return ps; +} + +/* +=============== +CG_RegisterParticleSystem + +Load the shaders required for a particle system +=============== +*/ +qhandle_t CG_RegisterParticleSystem( char *name ) +{ + int i, j, k, l; + baseParticleSystem_t *bps; + baseParticleEjector_t *bpe; + baseParticle_t *bp; + + for( i = 0; i < MAX_BASEPARTICLE_SYSTEMS; i++ ) + { + bps = &baseParticleSystems[ i ]; + + if( !Q_stricmpn( bps->name, name, MAX_QPATH ) ) + { + //already registered + if( bps->registered ) + return i + 1; + + for( j = 0; j < bps->numEjectors; j++ ) + { + bpe = bps->ejectors[ j ]; + + for( l = 0; l < bpe->numParticles; l++ ) + { + bp = bpe->particles[ l ]; + + for( k = 0; k < bp->numFrames; k++ ) + bp->shaders[ k ] = trap_R_RegisterShader( bp->shaderNames[ k ] ); + + for( k = 0; k < bp->numModels; k++ ) + bp->models[ k ] = trap_R_RegisterModel( bp->modelNames[ k ] ); + + if( bp->bounceMarkName[ 0 ] != '\0' ) + bp->bounceMark = trap_R_RegisterShader( bp->bounceMarkName ); + + if( bp->bounceSoundName[ 0 ] != '\0' ) + bp->bounceSound = trap_S_RegisterSound( bp->bounceSoundName, qfalse ); + + //recursively register any children + if( bp->childSystemName[ 0 ] != '\0' ) + { + //don't care about a handle for children since + //the system deals with it + CG_RegisterParticleSystem( bp->childSystemName ); + } + + if( bp->onDeathSystemName[ 0 ] != '\0' ) + { + //don't care about a handle for children since + //the system deals with it + CG_RegisterParticleSystem( bp->onDeathSystemName ); + } + + if( bp->childTrailSystemName[ 0 ] != '\0' ) + bp->childTrailSystemHandle = CG_RegisterTrailSystem( bp->childTrailSystemName ); + } + } + + if( cg_debugParticles.integer >= 1 ) + CG_Printf( "Registered particle system %s\n", name ); + + bps->registered = qtrue; + + //avoid returning 0 + return i + 1; + } + } + + CG_Printf( S_COLOR_RED "ERROR: failed to register particle system %s\n", name ); + return 0; +} + + +/* +=============== +CG_ParseValueAndVariance + +Parse a value and its random variance +=============== +*/ +static void CG_ParseValueAndVariance( char *token, float *value, float *variance, qboolean allowNegative ) +{ + char valueBuffer[ 16 ]; + char *variancePtr = NULL, *varEndPointer = NULL; + float localValue = 0.0f; + float localVariance = 0.0f; + + Q_strncpyz( valueBuffer, token, sizeof( valueBuffer ) ); + + variancePtr = strchr( valueBuffer, '~' ); + + //variance included + if( variancePtr ) + { + variancePtr[ 0 ] = '\0'; + variancePtr++; + + localValue = atof_neg( valueBuffer, allowNegative ); + + varEndPointer = strchr( variancePtr, '%' ); + + if( varEndPointer ) + { + varEndPointer[ 0 ] = '\0'; + localVariance = atof_neg( variancePtr, qfalse ) / 100.0f; + } + else + { + if( localValue != 0.0f ) + localVariance = atof_neg( variancePtr, qfalse ) / localValue; + else + localVariance = atof_neg( variancePtr, qfalse ); + } + } + else + localValue = atof_neg( valueBuffer, allowNegative ); + + if( value != NULL ) + *value = localValue; + + if( variance != NULL ) + *variance = localVariance; +} + +/* +=============== +CG_ParseColor +=============== +*/ +static qboolean CG_ParseColor( byte *c, char **text_p ) +{ + char *token; + int i; + + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( text_p ); + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) ); + } + + return qtrue; +} + +/* +=============== +CG_ParseParticle + +Parse a particle section +=============== +*/ +static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) +{ + char *token; + float number, randFrac; + int i; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "bounce" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "cull" ) ) + { + bp->bounceCull = qtrue; + + bp->bounceFrac = -1.0f; + bp->bounceFracRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->bounceFrac = number; + bp->bounceFracRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "bounceMark" ) ) + { + token = COM_Parse( text_p ); + if( !*token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->bounceMarkCount = number; + bp->bounceMarkCountRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !*token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->bounceMarkRadius = number; + bp->bounceMarkRadiusRandFrac = randFrac; + + token = COM_ParseExt( text_p, qfalse ); + if( !*token ) + break; + + Q_strncpyz( bp->bounceMarkName, token, MAX_QPATH ); + + continue; + } + else if( !Q_stricmp( token, "bounceSound" ) ) + { + token = COM_Parse( text_p ); + if( !*token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->bounceSoundCount = number; + bp->bounceSoundCountRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !*token ) + break; + + Q_strncpyz( bp->bounceSoundName, token, MAX_QPATH ); + + continue; + } + else if( !Q_stricmp( token, "shader" ) ) + { + if( bp->numModels > 0 ) + { + CG_Printf( S_COLOR_RED "ERROR: 'shader' not allowed in " + "conjunction with 'model'\n", token ); + break; + } + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "sync" ) ) + bp->framerate = 0.0f; + else + bp->framerate = atof_neg( token, qfalse ); + + token = COM_ParseExt( text_p, qfalse ); + if( !*token ) + break; + + while( *token && bp->numFrames < MAX_PS_SHADER_FRAMES ) + { + Q_strncpyz( bp->shaderNames[ bp->numFrames++ ], token, MAX_QPATH ); + token = COM_ParseExt( text_p, qfalse ); + } + + continue; + } + else if( !Q_stricmp( token, "model" ) ) + { + if( bp->numFrames > 0 ) + { + CG_Printf( S_COLOR_RED "ERROR: 'model' not allowed in " + "conjunction with 'shader'\n", token ); + break; + } + + token = COM_ParseExt( text_p, qfalse ); + if( !*token ) + break; + + while( *token && bp->numModels < MAX_PS_MODELS ) + { + Q_strncpyz( bp->modelNames[ bp->numModels++ ], token, MAX_QPATH ); + token = COM_ParseExt( text_p, qfalse ); + } + + continue; + } + else if( !Q_stricmp( token, "modelAnimation" ) ) + { + token = COM_Parse( text_p ); + if( !*token ) + break; + + bp->modelAnimation.firstFrame = atoi_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !*token ) + break; + + bp->modelAnimation.numFrames = atoi( token ); + bp->modelAnimation.reversed = qfalse; + bp->modelAnimation.flipflop = qfalse; + + // if numFrames is negative the animation is reversed + if( bp->modelAnimation.numFrames < 0 ) + { + bp->modelAnimation.numFrames = -bp->modelAnimation.numFrames; + bp->modelAnimation.reversed = qtrue; + } + + token = COM_Parse( text_p ); + if( !*token ) + break; + + bp->modelAnimation.loopFrames = atoi( token ); + + token = COM_Parse( text_p ); + if( !*token ) + break; + + if( !Q_stricmp( token, "sync" ) ) + { + bp->modelAnimation.frameLerp = -1; + bp->modelAnimation.initialLerp = -1; + } + else + { + float fps = atof_neg( token, qfalse ); + + if( fps == 0.0f ) + fps = 1.0f; + + bp->modelAnimation.frameLerp = 1000 / fps; + bp->modelAnimation.initialLerp = 1000 / fps; + } + + continue; + } + /// + else if( !Q_stricmp( token, "velocityType" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "static" ) ) + bp->velMoveType = PMT_STATIC; + else if( !Q_stricmp( token, "static_transform" ) ) + bp->velMoveType = PMT_STATIC_TRANSFORM; + else if( !Q_stricmp( token, "tag" ) ) + bp->velMoveType = PMT_TAG; + else if( !Q_stricmp( token, "cent" ) ) + bp->velMoveType = PMT_CENT_ANGLES; + else if( !Q_stricmp( token, "normal" ) ) + bp->velMoveType = PMT_NORMAL; + + continue; + } + else if( !Q_stricmp( token, "velocityDir" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "linear" ) ) + bp->velMoveValues.dirType = PMD_LINEAR; + else if( !Q_stricmp( token, "point" ) ) + bp->velMoveValues.dirType = PMD_POINT; + + continue; + } + else if( !Q_stricmp( token, "velocityMagnitude" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->velMoveValues.mag = number; + bp->velMoveValues.magRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "parentVelocityFraction" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->velMoveValues.parentVelFrac = number; + bp->velMoveValues.parentVelFracRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "velocity" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->velMoveValues.dir[ i ] = atof_neg( token, qtrue ); + } + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + + bp->velMoveValues.dirRandAngle = randFrac; + + continue; + } + else if( !Q_stricmp( token, "velocityPoint" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->velMoveValues.point[ i ] = atof_neg( token, qtrue ); + } + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + + bp->velMoveValues.pointRandAngle = randFrac; + + continue; + } + /// + else if( !Q_stricmp( token, "accelerationType" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "static" ) ) + bp->accMoveType = PMT_STATIC; + else if( !Q_stricmp( token, "static_transform" ) ) + bp->accMoveType = PMT_STATIC_TRANSFORM; + else if( !Q_stricmp( token, "tag" ) ) + bp->accMoveType = PMT_TAG; + else if( !Q_stricmp( token, "cent" ) ) + bp->accMoveType = PMT_CENT_ANGLES; + else if( !Q_stricmp( token, "normal" ) ) + bp->accMoveType = PMT_NORMAL; + + continue; + } + else if( !Q_stricmp( token, "accelerationDir" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "linear" ) ) + bp->accMoveValues.dirType = PMD_LINEAR; + else if( !Q_stricmp( token, "point" ) ) + bp->accMoveValues.dirType = PMD_POINT; + + continue; + } + else if( !Q_stricmp( token, "accelerationMagnitude" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->accMoveValues.mag = number; + bp->accMoveValues.magRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "acceleration" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->accMoveValues.dir[ i ] = atof_neg( token, qtrue ); + } + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + + bp->accMoveValues.dirRandAngle = randFrac; + + continue; + } + else if( !Q_stricmp( token, "accelerationPoint" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->accMoveValues.point[ i ] = atof_neg( token, qtrue ); + } + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse ); + + bp->accMoveValues.pointRandAngle = randFrac; + + continue; + } + /// + else if( !Q_stricmp( token, "displacement" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &bp->displacement[ i ], + &bp->randDisplacement[ i ], qtrue ); + } + + // if there is another token on the same line interpret it as an + // additional displacement in all three directions, for compatibility + // with the old scripts where this was the only option + randFrac = 0; + token = COM_ParseExt( text_p, qfalse ); + if( token ) + CG_ParseValueAndVariance( token, NULL, &randFrac, qtrue ); + + for( i = 0; i < 3; i++ ) + { + // convert randDisplacement from proportions to absolute values + if( bp->displacement[ i ] != 0 ) + bp->randDisplacement[ i ] *= bp->displacement[ i ]; + + bp->randDisplacement[ i ] += randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "normalDisplacement" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->normalDisplacement = atof_neg( token, qtrue ); + + continue; + } + else if( !Q_stricmp( token, "overdrawProtection" ) ) + { + bp->overdrawProtection = qtrue; + + continue; + } + else if( !Q_stricmp( token, "realLight" ) ) + { + bp->realLight = qtrue; + + continue; + } + else if( !Q_stricmp( token, "dynamicLight" ) ) + { + bp->dynamicLight = qtrue; + + token = COM_Parse( text_p ); + if( !*token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->dLightRadius.delay = (int)number; + bp->dLightRadius.delayRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !*token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->dLightRadius.initial = number; + bp->dLightRadius.initialRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !*token ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + bp->dLightRadius.final = PARTICLES_SAME_AS_INITIAL; + bp->dLightRadius.finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->dLightRadius.final = number; + bp->dLightRadius.finalRandFrac = randFrac; + } + + token = COM_Parse( text_p ); + if( !*token ) + break; + + if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseColor( bp->dLightColor, text_p ) ) + break; + + token = COM_Parse( text_p ); + if( Q_stricmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + break; + } + } + + continue; + } + else if( !Q_stricmp( token, "cullOnStartSolid" ) ) + { + bp->cullOnStartSolid = qtrue; + + continue; + } + else if( !Q_stricmp( token, "radius" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->radius.delay = (int)number; + bp->radius.delayRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->radius.initial = number; + bp->radius.initialRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + bp->radius.final = PARTICLES_SAME_AS_INITIAL; + bp->radius.finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->radius.final = number; + bp->radius.finalRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "physicsRadius" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->physicsRadius = atoi( token ); + } + else if( !Q_stricmp( token, "alpha" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->alpha.delay = (int)number; + bp->alpha.delayRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->alpha.initial = number; + bp->alpha.initialRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + bp->alpha.final = PARTICLES_SAME_AS_INITIAL; + bp->alpha.finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->alpha.final = number; + bp->alpha.finalRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "color" ) ) + { + token = COM_Parse( text_p ); + if( !*token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->colorDelay = (int)number; + bp->colorDelayRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !*token ) + break; + + if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseColor( bp->initialColor, text_p ) ) + break; + + token = COM_Parse( text_p ); + if( Q_stricmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + break; + } + + token = COM_Parse( text_p ); + if( !*token ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + bp->finalColor[ 0 ] = bp->initialColor[ 0 ]; + bp->finalColor[ 1 ] = bp->initialColor[ 1 ]; + bp->finalColor[ 2 ] = bp->initialColor[ 2 ]; + } + else if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseColor( bp->finalColor, text_p ) ) + break; + + token = COM_Parse( text_p ); + if( Q_stricmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + break; + } + } + else + { + CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); + break; + } + } + else + { + CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); + break; + } + + continue; + } + else if( !Q_stricmp( token, "rotation" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->rotation.delay = (int)number; + bp->rotation.delayRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qtrue ); + + bp->rotation.initial = number; + bp->rotation.initialRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + bp->rotation.final = PARTICLES_SAME_AS_INITIAL; + bp->rotation.finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac, qtrue ); + + bp->rotation.final = number; + bp->rotation.finalRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "lifeTime" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bp->lifeTime = (int)number; + bp->lifeTimeRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "childSystem" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + Q_strncpyz( bp->childSystemName, token, MAX_QPATH ); + + continue; + } + else if( !Q_stricmp( token, "onDeathSystem" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + Q_strncpyz( bp->onDeathSystemName, token, MAX_QPATH ); + + continue; + } + else if( !Q_stricmp( token, "childTrailSystem" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + Q_strncpyz( bp->childTrailSystemName, token, MAX_QPATH ); + + continue; + } + else if( !Q_stricmp( token, "scaleWithCharge" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->scaleWithCharge = atof( token ); + + continue; + } + else if( !Q_stricmp( token, "}" ) ) + return qtrue; //reached the end of this particle + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle\n", token ); + return qfalse; + } + } + + return qfalse; +} + +/* +=============== +CG_InitialiseBaseParticle +=============== +*/ +static void CG_InitialiseBaseParticle( baseParticle_t *bp ) +{ + memset( bp, 0, sizeof( baseParticle_t ) ); + + memset( bp->initialColor, 0xFF, sizeof( bp->initialColor ) ); + memset( bp->finalColor, 0xFF, sizeof( bp->finalColor ) ); +} + +/* +=============== +CG_ParseParticleEjector + +Parse a particle ejector section +=============== +*/ +static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text_p ) +{ + char *token; + float number, randFrac; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "{" ) ) + { + CG_InitialiseBaseParticle( &baseParticles[ numBaseParticles ] ); + + if( !CG_ParseParticle( &baseParticles[ numBaseParticles ], text_p ) ) + { + CG_Printf( S_COLOR_RED "ERROR: failed to parse particle\n" ); + return qfalse; + } + + if( bpe->numParticles == MAX_PARTICLES_PER_EJECTOR ) + { + CG_Printf( S_COLOR_RED "ERROR: ejector has > %d particles\n", MAX_PARTICLES_PER_EJECTOR ); + return qfalse; + } + else if( numBaseParticles == MAX_BASEPARTICLES ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of particles (%d) reached\n", MAX_BASEPARTICLES ); + return qfalse; + } + else + { + //start parsing particles again + bpe->particles[ bpe->numParticles ] = &baseParticles[ numBaseParticles ]; + bpe->numParticles++; + numBaseParticles++; + } + continue; + } + else if( !Q_stricmp( token, "delay" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bpe->eject.delay = (int)number; + bpe->eject.delayRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "period" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bpe->eject.initial = atoi_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "-" ) ) + bpe->eject.final = PARTICLES_SAME_AS_INITIAL; + else + bpe->eject.final = atoi_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &bpe->eject.randFrac, qfalse ); + + continue; + } + else if( !Q_stricmp( token, "count" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "infinite" ) ) + { + bpe->totalParticles = PARTICLES_INFINITE; + bpe->totalParticlesRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac, qfalse ); + + bpe->totalParticles = (int)number; + bpe->totalParticlesRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "particle" ) ) //acceptable text + continue; + else if( !Q_stricmp( token, "}" ) ) + return qtrue; //reached the end of this particle ejector + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle ejector\n", token ); + return qfalse; + } + } + + return qfalse; +} + + +/* +=============== +CG_ParseParticleSystem + +Parse a particle system section +=============== +*/ +static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p, const char *name ) +{ + char *token; + baseParticleEjector_t *bpe; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseParticleEjector( &baseParticleEjectors[ numBaseParticleEjectors ], text_p ) ) + { + CG_Printf( S_COLOR_RED "ERROR: failed to parse particle ejector\n" ); + return qfalse; + } + + bpe = &baseParticleEjectors[ numBaseParticleEjectors ]; + + //check for infinite count + zero period + if( bpe->totalParticles == PARTICLES_INFINITE && + ( bpe->eject.initial == 0.0f || bpe->eject.final == 0.0f ) ) + { + CG_Printf( S_COLOR_RED "ERROR: ejector with 'count infinite' potentially has zero period\n" ); + return qfalse; + } + + if( bps->numEjectors == MAX_EJECTORS_PER_SYSTEM ) + { + CG_Printf( S_COLOR_RED "ERROR: particle system has > %d ejectors\n", MAX_EJECTORS_PER_SYSTEM ); + return qfalse; + } + else if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of particle ejectors (%d) reached\n", + MAX_BASEPARTICLE_EJECTORS ); + return qfalse; + } + else + { + //start parsing ejectors again + bps->ejectors[ bps->numEjectors ] = &baseParticleEjectors[ numBaseParticleEjectors ]; + bps->numEjectors++; + numBaseParticleEjectors++; + } + continue; + } + else if( !Q_stricmp( token, "thirdPersonOnly" ) ) + bps->thirdPersonOnly = qtrue; + else if( !Q_stricmp( token, "ejector" ) ) //acceptable text + continue; + else if( !Q_stricmp( token, "}" ) ) + { + if( cg_debugParticles.integer >= 1 ) + CG_Printf( "Parsed particle system %s\n", name ); + + return qtrue; //reached the end of this particle system + } + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle system %s\n", token, bps->name ); + return qfalse; + } + } + + return qfalse; +} + +/* +=============== +CG_ParseParticleFile + +Load the particle systems from a particle file +=============== +*/ +static qboolean CG_ParseParticleFile( const char *fileName ) +{ + char *text_p; + int i; + int len; + char *token; + char text[ 32000 ]; + char psName[ MAX_QPATH ]; + qboolean psNameSet = qfalse; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( S_COLOR_RED "ERROR: particle file %s is %s\n", fileName, + len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "{" ) ) + { + if( psNameSet ) + { + //check for name space clashes + for( i = 0; i < numBaseParticleSystems; i++ ) + { + if( !Q_stricmp( baseParticleSystems[ i ].name, psName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: a particle system is already named %s\n", psName ); + return qfalse; + } + } + + Q_strncpyz( baseParticleSystems[ numBaseParticleSystems ].name, psName, MAX_QPATH ); + + if( !CG_ParseParticleSystem( &baseParticleSystems[ numBaseParticleSystems ], &text_p, psName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse particle system %s\n", fileName, psName ); + return qfalse; + } + + //start parsing particle systems again + psNameSet = qfalse; + + if( numBaseParticleSystems == MAX_BASEPARTICLE_SYSTEMS ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of particle systems (%d) reached\n", + MAX_BASEPARTICLE_SYSTEMS ); + return qfalse; + } + else + numBaseParticleSystems++; + + continue; + } + else + { + CG_Printf( S_COLOR_RED "ERROR: unamed particle system\n" ); + return qfalse; + } + } + + if( !psNameSet ) + { + Q_strncpyz( psName, token, sizeof( psName ) ); + psNameSet = qtrue; + } + else + { + CG_Printf( S_COLOR_RED "ERROR: particle system already named\n" ); + return qfalse; + } + } + + return qtrue; +} + + +/* +=============== +CG_LoadParticleSystems + +Load particle systems from .particle files +=============== +*/ +void CG_LoadParticleSystems( void ) +{ + int i, j, numFiles, fileLen; + char fileList[ MAX_PARTICLE_FILES * MAX_QPATH ]; + char fileName[ MAX_QPATH ]; + char *filePtr; + + //clear out the old + numBaseParticleSystems = 0; + numBaseParticleEjectors = 0; + numBaseParticles = 0; + + for( i = 0; i < MAX_BASEPARTICLE_SYSTEMS; i++ ) + { + baseParticleSystem_t *bps = &baseParticleSystems[ i ]; + memset( bps, 0, sizeof( baseParticleSystem_t ) ); + } + + for( i = 0; i < MAX_BASEPARTICLE_EJECTORS; i++ ) + { + baseParticleEjector_t *bpe = &baseParticleEjectors[ i ]; + memset( bpe, 0, sizeof( baseParticleEjector_t ) ); + } + + for( i = 0; i < MAX_BASEPARTICLES; i++ ) + { + baseParticle_t *bp = &baseParticles[ i ]; + memset( bp, 0, sizeof( baseParticle_t ) ); + } + + + //and bring in the new + numFiles = trap_FS_GetFileList( "scripts", ".particle", + fileList, MAX_PARTICLE_FILES * MAX_QPATH ); + filePtr = fileList; + + for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + strcpy( fileName, "scripts/" ); + strcat( fileName, filePtr ); + CG_Printf( "...loading '%s'\n", fileName ); + CG_ParseParticleFile( fileName ); + } + + //connect any child systems to their psHandle + for( i = 0; i < numBaseParticles; i++ ) + { + baseParticle_t *bp = &baseParticles[ i ]; + + if( bp->childSystemName[ 0 ] ) + { + //particle class has a child, resolve the name + for( j = 0; j < numBaseParticleSystems; j++ ) + { + baseParticleSystem_t *bps = &baseParticleSystems[ j ]; + + if( !Q_stricmp( bps->name, bp->childSystemName ) ) + { + //FIXME: add checks for cycles and infinite children + + bp->childSystemHandle = j + 1; + + break; + } + } + + if( j == numBaseParticleSystems ) + { + //couldn't find named particle system + CG_Printf( S_COLOR_YELLOW "WARNING: failed to find child %s\n", bp->childSystemName ); + bp->childSystemName[ 0 ] = '\0'; + } + } + + if( bp->onDeathSystemName[ 0 ] ) + { + //particle class has a child, resolve the name + for( j = 0; j < numBaseParticleSystems; j++ ) + { + baseParticleSystem_t *bps = &baseParticleSystems[ j ]; + + if( !Q_stricmp( bps->name, bp->onDeathSystemName ) ) + { + //FIXME: add checks for cycles and infinite children + + bp->onDeathSystemHandle = j + 1; + + break; + } + } + + if( j == numBaseParticleSystems ) + { + //couldn't find named particle system + CG_Printf( S_COLOR_YELLOW "WARNING: failed to find onDeath system %s\n", bp->onDeathSystemName ); + bp->onDeathSystemName[ 0 ] = '\0'; + } + } + } +} + +/* +=============== +CG_SetParticleSystemNormal +=============== +*/ +void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ) +{ + if( ps == NULL || !ps->valid ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); + return; + } + + ps->normalValid = qtrue; + VectorCopy( normal, ps->normal ); + VectorNormalize( ps->normal ); +} + + +/* +=============== +CG_DestroyParticleSystem + +Destroy a particle system + +This doesn't actually invalidate anything, it just stops +particle ejectors from producing new particles so the +garbage collector will eventually remove this system. +However is does set the pointer to NULL so the user is +unable to manipulate this particle system any longer. +=============== +*/ +void CG_DestroyParticleSystem( particleSystem_t **ps ) +{ + int i; + particleEjector_t *pe; + + if( *ps == NULL || !(*ps)->valid ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: tried to destroy a NULL particle system\n" ); + return; + } + + if( cg_debugParticles.integer >= 1 ) + CG_Printf( "PS destroyed\n" ); + + for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) + { + pe = &particleEjectors[ i ]; + + if( pe->valid && pe->parent == *ps ) + pe->totalParticles = pe->count = 0; + } + + *ps = NULL; +} + +/* +=============== +CG_IsParticleSystemInfinite + +Test a particle system for 'count infinite' ejectors +=============== +*/ +qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps ) +{ + int i; + particleEjector_t *pe; + + if( ps == NULL ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: tried to test a NULL particle system\n" ); + return qfalse; + } + + if( !ps->valid ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: tried to test an invalid particle system\n" ); + return qfalse; + } + + //don't bother checking already invalid systems + if( !ps->valid ) + return qfalse; + + for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) + { + pe = &particleEjectors[ i ]; + + if( pe->valid && pe->parent == ps ) + { + if( pe->totalParticles == PARTICLES_INFINITE ) + return qtrue; + } + } + + return qfalse; +} + +/* +=============== +CG_IsParticleSystemValid + +Test a particle system for validity +=============== +*/ +qboolean CG_IsParticleSystemValid( particleSystem_t **ps ) +{ + if( *ps == NULL || ( *ps && !(*ps)->valid ) ) + { + if( *ps && !(*ps)->valid ) + *ps = NULL; + + return qfalse; + } + + return qtrue; +} + +/* +=============== +CG_GarbageCollectParticleSystems + +Destroy inactive particle systems +=============== +*/ +static void CG_GarbageCollectParticleSystems( void ) +{ + int i, j, count; + particleSystem_t *ps; + particleEjector_t *pe; + int centNum; + + for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) + { + ps = &particleSystems[ i ]; + count = 0; + + //don't bother checking already invalid systems + if( !ps->valid ) + continue; + + for( j = 0; j < MAX_PARTICLE_EJECTORS; j++ ) + { + pe = &particleEjectors[ j ]; + + if( pe->valid && pe->parent == ps ) + count++; + } + + if( !count ) + ps->valid = qfalse; + + //check systems where the parent cent has left the PVS + //( local player entity is always valid ) + if( ( centNum = CG_AttachmentCentNum( &ps->attachment ) ) >= 0 && + centNum != cg.snap->ps.clientNum ) + { + if( !cg_entities[ centNum ].valid ) + ps->lazyRemove = qtrue; + } + + if( cg_debugParticles.integer >= 1 && !ps->valid ) + CG_Printf( "PS %s garbage collected\n", ps->class->name ); + } +} + + +/* +=============== +CG_CalculateTimeFrac + +Calculate the fraction of time passed +=============== +*/ +static float CG_CalculateTimeFrac( int birth, int life, int delay ) +{ + float frac; + + frac = ( (float)cg.time - (float)( birth + delay ) ) / (float)( life - delay ); + + if( frac < 0.0f ) + frac = 0.0f; + else if( frac > 1.0f ) + frac = 1.0f; + + return frac; +} + +/* +=============== +CG_EvaluateParticlePhysics + +Compute the physics on a specific particle +=============== +*/ +static void CG_EvaluateParticlePhysics( particle_t *p ) +{ + particleSystem_t *ps = p->parent->parent; + baseParticle_t *bp = p->class; + vec3_t acceleration, newOrigin; + vec3_t mins, maxs; + float deltaTime, bounce, radius, dot; + trace_t trace; + vec3_t transform[ 3 ]; + + if( p->atRest ) + { + VectorClear( p->velocity ); + return; + } + + switch( bp->accMoveType ) + { + case PMT_STATIC: + if( bp->accMoveValues.dirType == PMD_POINT ) + VectorSubtract( bp->accMoveValues.point, p->origin, acceleration ); + else if( bp->accMoveValues.dirType == PMD_LINEAR ) + VectorCopy( bp->accMoveValues.dir, acceleration ); + + break; + + case PMT_STATIC_TRANSFORM: + if( !CG_AttachmentAxis( &ps->attachment, transform ) ) + return; + + if( bp->accMoveValues.dirType == PMD_POINT ) + { + vec3_t transPoint; + + VectorMatrixMultiply( bp->accMoveValues.point, transform, transPoint ); + VectorSubtract( transPoint, p->origin, acceleration ); + } + else if( bp->accMoveValues.dirType == PMD_LINEAR ) + VectorMatrixMultiply( bp->accMoveValues.dir, transform, acceleration ); + break; + + case PMT_TAG: + case PMT_CENT_ANGLES: + if( bp->accMoveValues.dirType == PMD_POINT ) + { + vec3_t point; + + if( !CG_AttachmentPoint( &ps->attachment, point ) ) + return; + + VectorSubtract( point, p->origin, acceleration ); + } + else if( bp->accMoveValues.dirType == PMD_LINEAR ) + { + if( !CG_AttachmentDir( &ps->attachment, acceleration ) ) + return; + } + break; + + case PMT_NORMAL: + if( !ps->normalValid ) + return; + + VectorCopy( ps->normal, acceleration ); + + break; + } + +#define MAX_ACC_RADIUS 1000.0f + + if( bp->accMoveValues.dirType == PMD_POINT ) + { + //FIXME: so this fall off is a bit... odd -- it works.. + float r2 = DotProduct( acceleration, acceleration ); // = radius^2 + float scale = ( MAX_ACC_RADIUS - r2 ) / MAX_ACC_RADIUS; + + if( scale > 1.0f ) + scale = 1.0f; + else if( scale < 0.1f ) + scale = 0.1f; + + scale *= CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac ); + + VectorNormalize( acceleration ); + CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle ); + VectorScale( acceleration, scale, acceleration ); + } + else if( bp->accMoveValues.dirType == PMD_LINEAR ) + { + VectorNormalize( acceleration ); + CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle ); + VectorScale( acceleration, + CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac ), + acceleration ); + } + + // Some particles have a visual radius that differs from their collision radius + if( bp->physicsRadius ) + radius = bp->physicsRadius; + else + radius = CG_LerpValues( p->radius.initial, p->radius.final, + CG_CalculateTimeFrac( p->birthTime, p->lifeTime, + p->radius.delay ) ); + + VectorSet( mins, -radius, -radius, -radius ); + VectorSet( maxs, radius, radius, radius ); + + bounce = CG_RandomiseValue( bp->bounceFrac, bp->bounceFracRandFrac ); + + deltaTime = (float)( cg.time - p->lastEvalTime ) * 0.001; + VectorMA( p->velocity, deltaTime, acceleration, p->velocity ); + VectorMA( p->origin, deltaTime, p->velocity, newOrigin ); + p->lastEvalTime = cg.time; + + // we're not doing particle physics, but at least cull them in solids + if( !cg_bounceParticles.integer ) + { + int contents = trap_CM_PointContents( newOrigin, 0 ); + + if( ( contents & CONTENTS_SOLID ) || ( contents & CONTENTS_NODROP ) ) + CG_DestroyParticle( p, NULL ); + else + VectorCopy( newOrigin, p->origin ); + return; + } + + CG_Trace( &trace, p->origin, mins, maxs, newOrigin, + CG_AttachmentCentNum( &ps->attachment ), CONTENTS_SOLID ); + + //not hit anything or not a collider + if( trace.fraction == 1.0f || bounce == 0.0f ) + { + VectorCopy( newOrigin, p->origin ); + return; + } + + //remove particles that get into a CONTENTS_NODROP brush + if( ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) || + ( bp->cullOnStartSolid && trace.startsolid ) ) + { + CG_DestroyParticle( p, NULL ); + return; + } + else if( bp->bounceCull ) + { + CG_DestroyParticle( p, trace.plane.normal ); + return; + } + + //reflect the velocity on the trace plane + dot = DotProduct( p->velocity, trace.plane.normal ); + VectorMA( p->velocity, -2.0f * dot, trace.plane.normal, p->velocity ); + + VectorScale( p->velocity, bounce, p->velocity ); + + if( trace.plane.normal[ 2 ] > 0.5f && + ( p->velocity[ 2 ] < 40.0f || + p->velocity[ 2 ] < -cg.frametime * p->velocity[ 2 ] ) ) + p->atRest = qtrue; + + if( bp->bounceMarkName[ 0 ] && p->bounceMarkCount > 0 ) + { + CG_ImpactMark( bp->bounceMark, trace.endpos, trace.plane.normal, + random( ) * 360, 1, 1, 1, 1, qtrue, bp->bounceMarkRadius, qfalse ); + p->bounceMarkCount--; + } + + if( bp->bounceSoundName[ 0 ] && p->bounceSoundCount > 0 ) + { + trap_S_StartSound( trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, bp->bounceSound ); + p->bounceSoundCount--; + } + + VectorCopy( trace.endpos, p->origin ); +} + + +#define GETKEY(x,y) (((x)>>y)&0xFF) + +/* +=============== +CG_Radix +=============== +*/ +static void CG_Radix( int bits, int size, particle_t **source, particle_t **dest ) +{ + int count[ 256 ]; + int index[ 256 ]; + int i; + + memset( count, 0, sizeof( count ) ); + + for( i = 0; i < size; i++ ) + count[ GETKEY( source[ i ]->sortKey, bits ) ]++; + + index[ 0 ] = 0; + + for( i = 1; i < 256; i++ ) + index[ i ] = index[ i - 1 ] + count[ i - 1 ]; + + for( i = 0; i < size; i++ ) + dest[ index[ GETKEY( source[ i ]->sortKey, bits ) ]++ ] = source[ i ]; +} + +/* +=============== +CG_RadixSort + +Radix sort with 4 byte size buckets +=============== +*/ +static void CG_RadixSort( particle_t **source, particle_t **temp, int size ) +{ + CG_Radix( 0, size, source, temp ); + CG_Radix( 8, size, temp, source ); + CG_Radix( 16, size, source, temp ); + CG_Radix( 24, size, temp, source ); +} + +/* +=============== +CG_CompactAndSortParticles + +Depth sort the particles +=============== +*/ +static void CG_CompactAndSortParticles( void ) +{ + int i, j = 0; + int numParticles; + vec3_t delta; + + for( i = 0; i < MAX_PARTICLES; i++ ) + sortedParticles[ i ] = &particles[ i ]; + + if( !cg_depthSortParticles.integer ) + return; + + for( i = MAX_PARTICLES - 1; i >= 0; i-- ) + { + if( sortedParticles[ i ]->valid ) + { + //find the first hole + while( j < MAX_PARTICLES && sortedParticles[ j ]->valid ) + j++; + + //no more holes + if( j >= i ) + break; + + sortedParticles[ j ] = sortedParticles[ i ]; + } + } + + numParticles = i; + + //set sort keys + for( i = 0; i < numParticles; i++ ) + { + VectorSubtract( sortedParticles[ i ]->origin, cg.refdef.vieworg, delta ); + sortedParticles[ i ]->sortKey = (int)DotProduct( delta, delta ); + } + + CG_RadixSort( sortedParticles, radixBuffer, numParticles ); + + //FIXME: wtf? + //reverse order of particles array + for( i = 0; i < numParticles; i++ ) + radixBuffer[ i ] = sortedParticles[ numParticles - i - 1 ]; + + for( i = 0; i < numParticles; i++ ) + sortedParticles[ i ] = radixBuffer[ i ]; +} + +/* +=============== +CG_RenderParticle + +Actually render a particle +=============== +*/ +static void CG_RenderParticle( particle_t *p ) +{ + refEntity_t re; + float timeFrac, scale; + int index; + baseParticle_t *bp = p->class; + particleSystem_t *ps = p->parent->parent; + baseParticleSystem_t *bps = ps->class; + vec3_t alight, dlight, lightdir; + int i; + vec3_t up = { 0.0f, 0.0f, 1.0f }; + + memset( &re, 0, sizeof( refEntity_t ) ); + + timeFrac = CG_CalculateTimeFrac( p->birthTime, p->lifeTime, 0 ); + + scale = CG_LerpValues( p->radius.initial, + p->radius.final, + CG_CalculateTimeFrac( p->birthTime, + p->lifeTime, + p->radius.delay ) ); + + re.shaderTime = p->birthTime / 1000.0f; + + if( bp->numFrames ) //shader based + { + re.reType = RT_SPRITE; + + //apply environmental lighting to the particle + if( bp->realLight ) + { + trap_R_LightForPoint( p->origin, alight, dlight, lightdir ); + for( i = 0; i <= 2; i++ ) + re.shaderRGBA[ i ] = (byte)alight[ i ]; + } + else + { + vec3_t colorRange; + + VectorSubtract( bp->finalColor, + bp->initialColor, colorRange ); + + VectorMA( bp->initialColor, + CG_CalculateTimeFrac( p->birthTime, + p->lifeTime, + p->colorDelay ), + colorRange, re.shaderRGBA ); + } + + re.shaderRGBA[ 3 ] = (byte)( (float)0xFF * + CG_LerpValues( p->alpha.initial, + p->alpha.final, + CG_CalculateTimeFrac( p->birthTime, + p->lifeTime, + p->alpha.delay ) ) ); + + re.radius = scale; + + re.rotation = CG_LerpValues( p->rotation.initial, + p->rotation.final, + CG_CalculateTimeFrac( p->birthTime, + p->lifeTime, + p->rotation.delay ) ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + if( Distance( p->origin, cg.refdef.vieworg ) < re.radius && bp->overdrawProtection ) + return; + + if( bp->framerate == 0.0f ) + { + //sync animation time to lifeTime of particle + index = (int)( timeFrac * ( bp->numFrames + 1 ) ); + + if( index >= bp->numFrames ) + index = bp->numFrames - 1; + + re.customShader = bp->shaders[ index ]; + } + else + { + //looping animation + index = (int)( bp->framerate * timeFrac * p->lifeTime * 0.001 ) % bp->numFrames; + re.customShader = bp->shaders[ index ]; + } + + } + else if( bp->numModels ) //model based + { + re.reType = RT_MODEL; + + re.hModel = p->model; + + if( p->atRest ) + AxisCopy( p->lastAxis, re.axis ); + else + { + // convert direction of travel into axis + VectorNormalize2( p->velocity, re.axis[ 0 ] ); + + if( re.axis[ 0 ][ 0 ] == 0.0f && re.axis[ 0 ][ 1 ] == 0.0f ) + AxisCopy( axisDefault, re.axis ); + else + { + ProjectPointOnPlane( re.axis[ 2 ], up, re.axis[ 0 ] ); + VectorNormalize( re.axis[ 2 ] ); + CrossProduct( re.axis[ 2 ], re.axis[ 0 ], re.axis[ 1 ] ); + } + + AxisCopy( re.axis, p->lastAxis ); + } + + if( scale != 1.0f ) + { + VectorScale( re.axis[ 0 ], scale, re.axis[ 0 ] ); + VectorScale( re.axis[ 1 ], scale, re.axis[ 1 ] ); + VectorScale( re.axis[ 2 ], scale, re.axis[ 2 ] ); + re.nonNormalizedAxes = qtrue; + } + else + re.nonNormalizedAxes = qfalse; + + p->lf.animation = &bp->modelAnimation; + + //run animation + CG_RunLerpFrame( &p->lf, 1.0f ); + + re.oldframe = p->lf.oldFrame; + re.frame = p->lf.frame; + re.backlerp = p->lf.backlerp; + } + + if( bps->thirdPersonOnly && + CG_AttachmentCentNum( &ps->attachment ) == cg.snap->ps.clientNum && + !cg.renderingThirdPerson ) + re.renderfx |= RF_THIRD_PERSON; + + if( bp->dynamicLight && !( re.renderfx & RF_THIRD_PERSON ) ) + { + trap_R_AddLightToScene( p->origin, + CG_LerpValues( p->dLightRadius.initial, p->dLightRadius.final, + CG_CalculateTimeFrac( p->birthTime, p->lifeTime, p->dLightRadius.delay ) ), + (float)bp->dLightColor[ 0 ] / (float)0xFF, + (float)bp->dLightColor[ 1 ] / (float)0xFF, + (float)bp->dLightColor[ 2 ] / (float)0xFF ); + } + + VectorCopy( p->origin, re.origin ); + + trap_R_AddRefEntityToScene( &re ); +} + +/* +=============== +CG_AddParticles + +Add particles to the scene +=============== +*/ +void CG_AddParticles( void ) +{ + int i; + particle_t *p; + int numPS = 0, numPE = 0, numP = 0; + + //remove expired particle systems + CG_GarbageCollectParticleSystems( ); + + //check each ejector and introduce any new particles + CG_SpawnNewParticles( ); + + //sorting + CG_CompactAndSortParticles( ); + + for( i = 0; i < MAX_PARTICLES; i++ ) + { + p = sortedParticles[ i ]; + + if( p->valid ) + { + if( p->birthTime + p->lifeTime > cg.time ) + { + //particle is active + CG_EvaluateParticlePhysics( p ); + CG_RenderParticle( p ); + } + else + CG_DestroyParticle( p, NULL ); + } + } + + if( cg_debugParticles.integer >= 2 ) + { + for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) + if( particleSystems[ i ].valid ) + numPS++; + + for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) + if( particleEjectors[ i ].valid ) + numPE++; + + for( i = 0; i < MAX_PARTICLES; i++ ) + if( particles[ i ].valid ) + numP++; + + CG_Printf( "PS: %d PE: %d P: %d\n", numPS, numPE, numP ); + } +} + +/* +=============== +CG_ParticleSystemEntity + +Particle system entity client code +=============== +*/ +void CG_ParticleSystemEntity( centity_t *cent ) +{ + entityState_t *es; + + es = ¢->currentState; + + if( es->eFlags & EF_NODRAW ) + { + if( CG_IsParticleSystemValid( ¢->entityPS ) && CG_IsParticleSystemInfinite( cent->entityPS ) ) + CG_DestroyParticleSystem( ¢->entityPS ); + + return; + } + + if( !CG_IsParticleSystemValid( ¢->entityPS ) && !cent->entityPSMissing ) + { + cent->entityPS = CG_SpawnNewParticleSystem( cgs.gameParticleSystems[ es->modelindex ] ); + + if( CG_IsParticleSystemValid( ¢->entityPS ) ) + { + CG_SetAttachmentPoint( ¢->entityPS->attachment, cent->lerpOrigin ); + CG_SetAttachmentCent( ¢->entityPS->attachment, cent ); + CG_AttachToPoint( ¢->entityPS->attachment ); + } + else + cent->entityPSMissing = qtrue; + } +} + +static particleSystem_t *testPS; +static qhandle_t testPSHandle; + +/* +=============== +CG_DestroyTestPS_f + +Destroy the test a particle system +=============== +*/ +void CG_DestroyTestPS_f( void ) +{ + if( CG_IsParticleSystemValid( &testPS ) ) + CG_DestroyParticleSystem( &testPS ); +} + +/* +=============== +CG_TestPS_f + +Test a particle system +=============== +*/ +void CG_TestPS_f( void ) +{ + vec3_t origin; + vec3_t up = { 0.0f, 0.0f, 1.0f }; + char psName[ MAX_QPATH ]; + + if( trap_Argc( ) < 2 ) + return; + + Q_strncpyz( psName, CG_Argv( 1 ), MAX_QPATH ); + testPSHandle = CG_RegisterParticleSystem( psName ); + + if( testPSHandle ) + { + CG_DestroyTestPS_f( ); + + testPS = CG_SpawnNewParticleSystem( testPSHandle ); + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], origin ); + + if( CG_IsParticleSystemValid( &testPS ) ) + { + CG_SetAttachmentPoint( &testPS->attachment, origin ); + CG_SetParticleSystemNormal( testPS, up ); + CG_AttachToPoint( &testPS->attachment ); + } + } +} diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c new file mode 100644 index 0000000..03601f6 --- /dev/null +++ b/src/cgame/cg_players.c @@ -0,0 +1,2422 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_players.c -- handle the media and animation for player entities + + +#include "cg_local.h" + +char *cg_customSoundNames[ MAX_CUSTOM_SOUNDS ] = +{ + "*death1.wav", + "*death2.wav", + "*death3.wav", + "*jump1.wav", + "*pain25_1.wav", + "*pain50_1.wav", + "*pain75_1.wav", + "*pain100_1.wav", + "*falling1.wav", + "*gasp.wav", + "*drown.wav", + "*fall1.wav", + "*taunt.wav" +}; + + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) +{ + clientInfo_t *ci; + int i; + + if( soundName[ 0 ] != '*' ) + return trap_S_RegisterSound( soundName, qfalse ); + + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) + clientNum = 0; + + ci = &cgs.clientinfo[ clientNum ]; + + for( i = 0; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[ i ]; i++ ) + { + if( !strcmp( soundName, cg_customSoundNames[ i ] ) ) + return ci->sounds[ i ]; + } + + CG_Error( "Unknown custom sound: %s", soundName ); + return 0; +} + + + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ + +/* +====================== +CG_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) +{ + char *text_p, *prev; + int len; + int i; + char *token; + float fps; + int skip; + char text[ 20000 ]; + fileHandle_t f; + animation_t *animations; + + animations = ci->animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + trap_FS_FCloseFile( f ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + skip = 0; // quite the compiler warning + + ci->footsteps = FOOTSTEP_NORMAL; + VectorClear( ci->headOffset ); + ci->gender = GENDER_MALE; + ci->fixedlegs = qfalse; + ci->fixedtorso = qfalse; + ci->nonsegmented = qfalse; + + // read optional parameters + while( 1 ) + { + prev = text_p; // so we can unget + token = COM_Parse( &text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "footsteps" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) + ci->footsteps = FOOTSTEP_NORMAL; + else if( !Q_stricmp( token, "flesh" ) ) + ci->footsteps = FOOTSTEP_FLESH; + else if( !Q_stricmp( token, "none" ) ) + ci->footsteps = FOOTSTEP_NONE; + else if( !Q_stricmp( token, "custom" ) ) + ci->footsteps = FOOTSTEP_CUSTOM; + else + CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); + + continue; + } + else if( !Q_stricmp( token, "headoffset" ) ) + { + for( i = 0 ; i < 3 ; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + ci->headOffset[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "sex" ) ) + { + token = COM_Parse( &text_p ); + + if( !token ) + break; + + if( token[ 0 ] == 'f' || token[ 0 ] == 'F' ) + ci->gender = GENDER_FEMALE; + else if( token[ 0 ] == 'n' || token[ 0 ] == 'N' ) + ci->gender = GENDER_NEUTER; + else + ci->gender = GENDER_MALE; + + continue; + } + else if( !Q_stricmp( token, "fixedlegs" ) ) + { + ci->fixedlegs = qtrue; + continue; + } + else if( !Q_stricmp( token, "fixedtorso" ) ) + { + ci->fixedtorso = qtrue; + continue; + } + else if( !Q_stricmp( token, "nonsegmented" ) ) + { + ci->nonsegmented = qtrue; + continue; + } + + // if it is a number, start parsing animations + if( token[ 0 ] >= '0' && token[ 0 ] <= '9' ) + { + text_p = prev; // unget the token + break; + } + + Com_Printf( "unknown token '%s' is %s\n", token, filename ); + } + + if( !ci->nonsegmented ) + { + // read information for each frame + for( i = 0; i < MAX_PLAYER_ANIMATIONS; i++ ) + { + token = COM_Parse( &text_p ); + + if( !*token ) + { + if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) + { + animations[ i ].firstFrame = animations[ TORSO_GESTURE ].firstFrame; + animations[ i ].frameLerp = animations[ TORSO_GESTURE ].frameLerp; + animations[ i ].initialLerp = animations[ TORSO_GESTURE ].initialLerp; + animations[ i ].loopFrames = animations[ TORSO_GESTURE ].loopFrames; + animations[ i ].numFrames = animations[ TORSO_GESTURE ].numFrames; + animations[ i ].reversed = qfalse; + animations[ i ].flipflop = qfalse; + continue; + } + + break; + } + + animations[ i ].firstFrame = atoi( token ); + + // leg only frames are adjusted to not count the upper body only frames + if( i == LEGS_WALKCR ) + skip = animations[ LEGS_WALKCR ].firstFrame - animations[ TORSO_GESTURE ].firstFrame; + + if( i >= LEGS_WALKCR && inonsegmented ) + { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + if( !ci->legsSkin ) + Com_Printf( "Leg skin load failure: %s\n", filename ); + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + if( !ci->torsoSkin ) + Com_Printf( "Torso skin load failure: %s\n", filename ); + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head_%s.skin", modelName, skinName ); + ci->headSkin = trap_R_RegisterSkin( filename ); + if( !ci->headSkin ) + Com_Printf( "Head skin load failure: %s\n", filename ); + + if( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) + return qfalse; + } + else + { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/nonseg_%s.skin", modelName, skinName ); + ci->nonSegSkin = trap_R_RegisterSkin( filename ); + if( !ci->nonSegSkin ) + Com_Printf( "Non-segmented skin load failure: %s\n", filename ); + + if( !ci->nonSegSkin ) + return qfalse; + } + + return qtrue; +} + +/* +========================== +CG_RegisterClientModelname +========================== +*/ +static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName ) +{ + char filename[ MAX_QPATH * 2 ]; + + // do this first so the nonsegmented property is set + // load the animations + Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); + if( !CG_ParseAnimationFile( filename, ci ) ) + { + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; + } + + // load cmodels before models so filecache works + + if( !ci->nonsegmented ) + { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if( !ci->legsModel ) + { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if( !ci->torsoModel ) + { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", modelName ); + ci->headModel = trap_R_RegisterModel( filename ); + if( !ci->headModel ) + { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + else + { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/nonseg.md3", modelName ); + ci->nonSegModel = trap_R_RegisterModel( filename ); + if( !ci->nonSegModel ) + { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + // if any skins failed to load, return failure + if( !CG_RegisterClientSkin( ci, modelName, skinName ) ) + { + Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); + return qfalse; + } + + return qtrue; +} + + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits +=================== +*/ +static void CG_LoadClientInfo( clientInfo_t *ci ) +{ + const char *dir; + int i; + const char *s; + int clientNum; + + if( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName ) ) + CG_Error( "CG_RegisterClientModelname( %s, %s ) failed", ci->modelName, ci->skinName ); + + // sounds + dir = ci->modelName; + + for( i = 0; i < MAX_CUSTOM_SOUNDS; i++ ) + { + s = cg_customSoundNames[ i ]; + + if( !s ) + break; + + // fanny about a bit with sounds that are missing + if( !CG_FileExists( va( "sound/player/%s/%s", dir, s + 1 ) ) ) + { + //file doesn't exist + + if( i == 11 || i == 8 ) //fall or falling + { + ci->sounds[ i ] = trap_S_RegisterSound( "sound/null.wav", qfalse ); + } + else + { + if( i == 9 ) //gasp + s = cg_customSoundNames[ 7 ]; //pain100_1 + else if( i == 10 ) //drown + s = cg_customSoundNames[ 0 ]; //death1 + + ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse ); + } + } + else + { + ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse ); + } + } + + if( ci->footsteps == FOOTSTEP_CUSTOM ) + { + for( i = 0; i < 4; i++ ) + { + ci->customFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/%s/step%d.wav", dir, i + 1 ), qfalse ); + if( !ci->customFootsteps[ i ] ) + ci->customFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/footsteps/step%d.wav", i + 1 ), qfalse ); + + ci->customMetalFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/%s/clank%d.wav", dir, i + 1 ), qfalse ); + if( !ci->customMetalFootsteps[ i ] ) + ci->customMetalFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/footsteps/clank%d.wav", i + 1 ), qfalse ); + } + } + + // reset any existing players and bodies, because they might be in bad + // frames for this new model + clientNum = ci - cgs.clientinfo; + for( i = 0; i < MAX_GENTITIES; i++ ) + { + if( cg_entities[ i ].currentState.clientNum == clientNum && + cg_entities[ i ].currentState.eType == ET_PLAYER ) + CG_ResetPlayerEntity( &cg_entities[ i ] ); + } +} + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) +{ + VectorCopy( from->headOffset, to->headOffset ); + to->footsteps = from->footsteps; + to->gender = from->gender; + + to->legsModel = from->legsModel; + to->legsSkin = from->legsSkin; + to->torsoModel = from->torsoModel; + to->torsoSkin = from->torsoSkin; + to->headModel = from->headModel; + to->headSkin = from->headSkin; + to->nonSegModel = from->nonSegModel; + to->nonSegSkin = from->nonSegSkin; + to->nonsegmented = from->nonsegmented; + to->modelIcon = from->modelIcon; + + memcpy( to->animations, from->animations, sizeof( to->animations ) ); + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); + memcpy( to->customFootsteps, from->customFootsteps, sizeof( to->customFootsteps ) ); + memcpy( to->customMetalFootsteps, from->customMetalFootsteps, sizeof( to->customMetalFootsteps ) ); +} + + +/* +====================== +CG_GetCorpseNum +====================== +*/ +static int CG_GetCorpseNum( class_t class ) +{ + int i; + clientInfo_t *match; + char *modelName; + char *skinName; + + modelName = BG_ClassConfig( class )->modelName; + skinName = BG_ClassConfig( class )->skinName; + + for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) + { + match = &cgs.corpseinfo[ i ]; + + if( !match->infoValid ) + continue; + + if( !Q_stricmp( modelName, match->modelName ) && + !Q_stricmp( skinName, match->skinName ) ) + { + // this clientinfo is identical, so use it's handles + return i; + } + } + + //something has gone horribly wrong + return -1; +} + + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) +{ + int i; + clientInfo_t *match; + + for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) + { + match = &cgs.corpseinfo[ i ]; + + if( !match->infoValid ) + continue; + + if( !Q_stricmp( ci->modelName, match->modelName ) && + !Q_stricmp( ci->skinName, match->skinName ) ) + { + // this clientinfo is identical, so use it's handles + CG_CopyClientInfoModel( match, ci ); + + return qtrue; + } + } + + // shouldn't happen + return qfalse; +} + + +/* +====================== +CG_PrecacheClientInfo +====================== +*/ +void CG_PrecacheClientInfo( class_t class, char *model, char *skin ) +{ + clientInfo_t *ci; + clientInfo_t newInfo; + + ci = &cgs.corpseinfo[ class ]; + + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + // model + Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) ); + + // modelName did not include a skin name + if( !skin ) + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + else + Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); + + newInfo.infoValid = qtrue; + + // actually register the models + *ci = newInfo; + CG_LoadClientInfo( ci ); +} + +/* +============= +CG_StatusMessages + +Print messages for player status changes +============= +*/ +static void CG_StatusMessages( clientInfo_t *new, clientInfo_t *old ) +{ + if( !old->infoValid ) + return; + + if( strcmp( new->name, old->name ) ) + CG_Printf( "%s" S_COLOR_WHITE " renamed to %s\n", old->name, new->name ); + + if( old->team != new->team ) + { + if( new->team == TEAM_NONE ) + CG_Printf( "%s" S_COLOR_WHITE " left the %ss\n", new->name, + BG_TeamName( old->team ) ); + else if( old->team == TEAM_NONE ) + CG_Printf( "%s" S_COLOR_WHITE " joined the %ss\n", new->name, + BG_TeamName( new->team ) ); + else + CG_Printf( "%s" S_COLOR_WHITE " left the %ss and joined the %ss\n", + new->name, BG_TeamName( old->team ), BG_TeamName( new->team ) ); + } +} + +/* +====================== +CG_NewClientInfo +====================== +*/ +void CG_NewClientInfo( int clientNum ) +{ + clientInfo_t *ci; + clientInfo_t newInfo; + const char *configstring; + const char *v; + char *slash; + + ci = &cgs.clientinfo[ clientNum ]; + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if( !configstring[ 0 ] ) + { + memset( ci, 0, sizeof( *ci ) ); + return; // player just left + } + + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + // grab our own ignoreList + if( clientNum == cg.predictedPlayerState.clientNum ) + { + v = Info_ValueForKey( configstring, "ig" ); + Com_ClientListParse( &cgs.ignoreList, v ); + } + + // isolate the player's name + v = Info_ValueForKey( configstring, "n" ); + Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + + // team + v = Info_ValueForKey( configstring, "t" ); + newInfo.team = atoi( v ); + + // if this is us, execute team-specific config files + // the spectator config is a little unreliable because it's easy to get on + // to the spectator team without joining it - e.g. when a new game starts. + // It's not a big deal because the spec config is the least important + // slash used anyway. + // I guess it's possible for someone to change teams during a restart and + // for that to then be missed here. But that's rare enough that people can + // just exec the configs manually, I think. + if( clientNum == cg.clientNum && ci->infoValid && + ci->team != newInfo.team ) + { + char config[ MAX_CVAR_VALUE_STRING ]; + + trap_Cvar_VariableStringBuffer( + va( "cg_%sConfig", BG_TeamName( newInfo.team ) ), + config, sizeof( config ) ); + + if( config[ 0 ] ) + trap_SendConsoleCommand( va( "exec \"%s\"\n", config ) ); + } + + // model + v = Info_ValueForKey( configstring, "model" ); + Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + + slash = strchr( newInfo.modelName, '/' ); + + if( !slash ) + { + // modelName didn not include a skin name + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } + else + { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + // truncate modelName + *slash = 0; + } + + // voice + v = Info_ValueForKey( configstring, "v" ); + Q_strncpyz( newInfo.voice, v, sizeof( newInfo.voice ) ); + + CG_StatusMessages( &newInfo, ci ); + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + *ci = newInfo; + + // scan for an existing clientinfo that matches this modelname + // so we can avoid loading checks if possible + if( !CG_ScanForExistingClientInfo( ci ) ) + CG_LoadClientInfo( ci ); +} + + + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + + +/* +=============== +CG_SetLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) +{ + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if( newAnimation < 0 || newAnimation >= MAX_PLAYER_TOTALANIMATIONS ) + CG_Error( "Bad animation number: %i", newAnimation ); + + anim = &ci->animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if( cg_debugAnim.integer ) + CG_Printf( "Anim: %i\n", newAnimation ); +} + +/* +=============== +CG_RunPlayerLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunPlayerLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) +{ + // see if the animation sequence is switching + if( newAnimation != lf->animationNumber || !lf->animation ) + CG_SetLerpFrameAnimation( ci, lf, newAnimation ); + + CG_RunLerpFrame( lf, speedScale ); +} + + +/* +=============== +CG_ClearLerpFrame +=============== +*/ +static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) +{ + lf->frameTime = lf->oldFrameTime = cg.time; + CG_SetLerpFrameAnimation( ci, lf, animationNumber ); + lf->oldFrame = lf->frame = lf->animation->firstFrame; +} + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) +{ + clientInfo_t *ci; + int clientNum; + float speedScale = 1.0f; + + clientNum = cent->currentState.clientNum; + + if( cg_noPlayerAnims.integer ) + { + *legsOld = *legs = *torsoOld = *torso = 0; + return; + } + + ci = &cgs.clientinfo[ clientNum ]; + + // do the shuffle turn frames locally + if( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) + CG_RunPlayerLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); + else + CG_RunPlayerLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); + + *legsOld = cent->pe.legs.oldFrame; + *legs = cent->pe.legs.frame; + *legsBackLerp = cent->pe.legs.backlerp; + + CG_RunPlayerLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); + + *torsoOld = cent->pe.torso.oldFrame; + *torso = cent->pe.torso.frame; + *torsoBackLerp = cent->pe.torso.backlerp; +} + + +/* +=============== +CG_PlayerNonSegAnimation +=============== +*/ +static void CG_PlayerNonSegAnimation( centity_t *cent, int *nonSegOld, + int *nonSeg, float *nonSegBackLerp ) +{ + clientInfo_t *ci; + int clientNum; + float speedScale = 1.0f; + + clientNum = cent->currentState.clientNum; + + if( cg_noPlayerAnims.integer ) + { + *nonSegOld = *nonSeg = 0; + return; + } + + ci = &cgs.clientinfo[ clientNum ]; + + // do the shuffle turn frames locally + if( cent->pe.nonseg.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == NSPA_STAND ) + CG_RunPlayerLerpFrame( ci, ¢->pe.nonseg, NSPA_TURN, speedScale ); + else + CG_RunPlayerLerpFrame( ci, ¢->pe.nonseg, cent->currentState.legsAnim, speedScale ); + + *nonSegOld = cent->pe.nonseg.oldFrame; + *nonSeg = cent->pe.nonseg.frame; + *nonSegBackLerp = cent->pe.nonseg.backlerp; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +/* +================== +CG_SwingAngles +================== +*/ +static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging ) +{ + float swing; + float move; + float scale; + + if( !*swinging ) + { + // see if a swing should be started + swing = AngleSubtract( *angle, destination ); + + if( swing > swingTolerance || swing < -swingTolerance ) + *swinging = qtrue; + } + + if( !*swinging ) + return; + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + + if( scale < swingTolerance * 0.5 ) + scale = 0.5; + else if( scale < swingTolerance ) + scale = 1.0; + else + scale = 2.0; + + // swing towards the destination angle + if( swing >= 0 ) + { + move = cg.frametime * scale * speed; + + if( move >= swing ) + { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + else if( swing < 0 ) + { + move = cg.frametime * scale * -speed; + + if( move <= swing ) + { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if( swing > clampTolerance ) + *angle = AngleMod( destination - ( clampTolerance - 1 ) ); + else if( swing < -clampTolerance ) + *angle = AngleMod( destination + ( clampTolerance - 1 ) ); +} + +/* +================= +CG_AddPainTwitch +================= +*/ +static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) +{ + int t; + float f; + + t = cg.time - cent->pe.painTime; + + if( t >= PAIN_TWITCH_TIME ) + return; + + f = 1.0 - (float)t / PAIN_TWITCH_TIME; + + if( cent->pe.painDirection ) + torsoAngles[ ROLL ] += 20 * f; + else + torsoAngles[ ROLL ] -= 20 * f; +} + + +/* +=============== +CG_PlayerAngles + +Handles seperate torso motion + + legs pivot based on direction of movement + + head always looks exactly at cent->lerpAngles + + if motion < 20 degrees, show in head only + if < 45 degrees, also show in torso +=============== +*/ +static void CG_PlayerAngles( centity_t *cent, vec3_t srcAngles, + vec3_t legs[ 3 ], vec3_t torso[ 3 ], vec3_t head[ 3 ] ) +{ + vec3_t legsAngles, torsoAngles, headAngles; + float dest; + static int movementOffsets[ 8 ] = { 0, 22, 45, -22, 0, 22, -45, -22 }; + vec3_t velocity; + float speed; + int dir, clientNum; + clientInfo_t *ci; + + VectorCopy( srcAngles, headAngles ); + headAngles[ YAW ] = AngleMod( headAngles[ YAW ] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + + // --------- yaw ------------- + + // allow yaw to drift a bit + if( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE || + ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) + { + // if not standing still, always point all in the same direction + cent->pe.torso.yawing = qtrue; // always center + cent->pe.torso.pitching = qtrue; // always center + cent->pe.legs.yawing = qtrue; // always center + } + + // adjust legs for movement dir + if( cent->currentState.eFlags & EF_DEAD ) + { + // don't let dead bodies twitch + dir = 0; + } + else + { + // did use angles2.. now uses time2.. looks a bit funny but time2 isn't used othwise + dir = cent->currentState.time2; + if( dir < 0 || dir > 7 ) + CG_Error( "Bad player movement angle" ); + } + + legsAngles[ YAW ] = headAngles[ YAW ] + movementOffsets[ dir ]; + torsoAngles[ YAW ] = headAngles[ YAW ] + 0.25 * movementOffsets[ dir ]; + + // torso + if( cent->currentState.eFlags & EF_DEAD ) + { + CG_SwingAngles( torsoAngles[ YAW ], 0, 0, cg_swingSpeed.value, + ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); + CG_SwingAngles( legsAngles[ YAW ], 0, 0, cg_swingSpeed.value, + ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + } + else + { + CG_SwingAngles( torsoAngles[ YAW ], 25, 90, cg_swingSpeed.value, + ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); + CG_SwingAngles( legsAngles[ YAW ], 40, 90, cg_swingSpeed.value, + ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + } + + torsoAngles[ YAW ] = cent->pe.torso.yawAngle; + legsAngles[ YAW ] = cent->pe.legs.yawAngle; + + // --------- pitch ------------- + + // only show a fraction of the pitch angle in the torso + if( headAngles[ PITCH ] > 180 ) + dest = ( -360 + headAngles[ PITCH ] ) * 0.75f; + else + dest = headAngles[ PITCH ] * 0.75f; + + CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); + torsoAngles[ PITCH ] = cent->pe.torso.pitchAngle; + + // + clientNum = cent->currentState.clientNum; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) + { + ci = &cgs.clientinfo[ clientNum ]; + if( ci->fixedtorso ) + torsoAngles[ PITCH ] = 0.0f; + } + + // --------- roll ------------- + + + // lean towards the direction of travel + VectorCopy( cent->currentState.pos.trDelta, velocity ); + speed = VectorNormalize( velocity ); + + if( speed ) + { + vec3_t axis[ 3 ]; + float side; + + speed *= 0.05f; + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[ 1 ] ); + legsAngles[ ROLL ] -= side; + + side = speed * DotProduct( velocity, axis[ 0 ] ); + legsAngles[ PITCH ] += side; + } + + // + clientNum = cent->currentState.clientNum; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) + { + ci = &cgs.clientinfo[ clientNum ]; + + if( ci->fixedlegs ) + { + legsAngles[ YAW ] = torsoAngles[ YAW ]; + legsAngles[ PITCH ] = 0.0f; + legsAngles[ ROLL ] = 0.0f; + } + } + + // pain twitch + CG_AddPainTwitch( cent, torsoAngles ); + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( torsoAngles, torso ); + AnglesToAxis( headAngles, head ); +} + +#define MODEL_WWSMOOTHTIME 200 + +/* +=============== +CG_PlayerWWSmoothing + +Smooth the angles of transitioning wall walkers +=============== +*/ +static void CG_PlayerWWSmoothing( centity_t *cent, vec3_t in[ 3 ], vec3_t out[ 3 ] ) +{ + entityState_t *es = ¢->currentState; + int i; + vec3_t surfNormal, rotAxis, temp; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f }; + float stLocal, sFraction, rotAngle; + vec3_t inAxis[ 3 ], lastAxis[ 3 ], outAxis[ 3 ]; + + //set surfNormal + if( !(es->eFlags & EF_WALLCLIMB ) ) + VectorCopy( refNormal, surfNormal ); + else if( !( es->eFlags & EF_WALLCLIMBCEILING ) ) + VectorCopy( es->angles2, surfNormal ); + else + VectorCopy( ceilingNormal, surfNormal ); + + AxisCopy( in, inAxis ); + + if( !VectorCompare( surfNormal, cent->pe.lastNormal ) ) + { + //if we moving from the ceiling to the floor special case + //( x product of colinear vectors is undefined) + if( VectorCompare( ceilingNormal, cent->pe.lastNormal ) && + VectorCompare( refNormal, surfNormal ) ) + { + VectorCopy( in[ 1 ], rotAxis ); + rotAngle = 180.0f; + } + else + { + AxisCopy( cent->pe.lastAxis, lastAxis ); + rotAngle = DotProduct( inAxis[ 0 ], lastAxis[ 0 ] ) + + DotProduct( inAxis[ 1 ], lastAxis[ 1 ] ) + + DotProduct( inAxis[ 2 ], lastAxis[ 2 ] ); + + rotAngle = RAD2DEG( acos( ( rotAngle - 1.0f ) / 2.0f ) ); + + CrossProduct( lastAxis[ 0 ], inAxis[ 0 ], temp ); + VectorCopy( temp, rotAxis ); + CrossProduct( lastAxis[ 1 ], inAxis[ 1 ], temp ); + VectorAdd( rotAxis, temp, rotAxis ); + CrossProduct( lastAxis[ 2 ], inAxis[ 2 ], temp ); + VectorAdd( rotAxis, temp, rotAxis ); + + VectorNormalize( rotAxis ); + } + + //iterate through smooth array + for( i = 0; i < MAXSMOOTHS; i++ ) + { + //found an unused index in the smooth array + if( cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME < cg.time ) + { + //copy to array and stop + VectorCopy( rotAxis, cent->pe.sList[ i ].rotAxis ); + cent->pe.sList[ i ].rotAngle = rotAngle; + cent->pe.sList[ i ].time = cg.time; + break; + } + } + } + + //iterate through ops + for( i = MAXSMOOTHS - 1; i >= 0; i-- ) + { + //if this op has time remaining, perform it + if( cg.time < cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME ) + { + stLocal = 1.0f - ( ( ( cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME ) - cg.time ) / MODEL_WWSMOOTHTIME ); + sFraction = -( cos( stLocal * M_PI ) + 1.0f ) / 2.0f; + + RotatePointAroundVector( outAxis[ 0 ], cent->pe.sList[ i ].rotAxis, + inAxis[ 0 ], sFraction * cent->pe.sList[ i ].rotAngle ); + RotatePointAroundVector( outAxis[ 1 ], cent->pe.sList[ i ].rotAxis, + inAxis[ 1 ], sFraction * cent->pe.sList[ i ].rotAngle ); + RotatePointAroundVector( outAxis[ 2 ], cent->pe.sList[ i ].rotAxis, + inAxis[ 2 ], sFraction * cent->pe.sList[ i ].rotAngle ); + + AxisCopy( outAxis, inAxis ); + } + } + + //outAxis has been copied to inAxis + AxisCopy( inAxis, out ); +} + +/* +=============== +CG_PlayerNonSegAngles + +Resolve angles for non-segmented models +=============== +*/ +static void CG_PlayerNonSegAngles( centity_t *cent, vec3_t srcAngles, vec3_t nonSegAxis[ 3 ] ) +{ + vec3_t localAngles; + vec3_t velocity; + float speed; + int dir; + entityState_t *es = ¢->currentState; + vec3_t surfNormal; + vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f }; + + VectorCopy( srcAngles, localAngles ); + localAngles[ YAW ] = AngleMod( localAngles[ YAW ] ); + localAngles[ PITCH ] = 0.0f; + localAngles[ ROLL ] = 0.0f; + + //set surfNormal + if( !( es->eFlags & EF_WALLCLIMBCEILING ) ) + VectorCopy( es->angles2, surfNormal ); + else + VectorCopy( ceilingNormal, surfNormal ); + + //make sure that WW transitions don't cause the swing stuff to go nuts + if( !VectorCompare( surfNormal, cent->pe.lastNormal ) ) + { + //stop CG_SwingAngles having an eppy + cent->pe.nonseg.yawAngle = localAngles[ YAW ]; + cent->pe.nonseg.yawing = qfalse; + } + + // --------- yaw ------------- + + // allow yaw to drift a bit + if( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != NSPA_STAND ) + { + // if not standing still, always point all in the same direction + cent->pe.nonseg.yawing = qtrue; // always center + } + + // adjust legs for movement dir + if( cent->currentState.eFlags & EF_DEAD ) + { + // don't let dead bodies twitch + dir = 0; + } + else + { + // did use angles2.. now uses time2.. looks a bit funny but time2 isn't used othwise + dir = cent->currentState.time2; + if( dir < 0 || dir > 7 ) + CG_Error( "Bad player movement angle" ); + } + + // torso + if( cent->currentState.eFlags & EF_DEAD ) + { + CG_SwingAngles( localAngles[ YAW ], 0, 0, cg_swingSpeed.value, + ¢->pe.nonseg.yawAngle, ¢->pe.nonseg.yawing ); + } + else + { + CG_SwingAngles( localAngles[ YAW ], 40, 90, cg_swingSpeed.value, + ¢->pe.nonseg.yawAngle, ¢->pe.nonseg.yawing ); + } + + localAngles[ YAW ] = cent->pe.nonseg.yawAngle; + + // --------- pitch ------------- + + //NO PITCH! + + + // --------- roll ------------- + + + // lean towards the direction of travel + VectorCopy( cent->currentState.pos.trDelta, velocity ); + speed = VectorNormalize( velocity ); + + if( speed ) + { + vec3_t axis[ 3 ]; + float side; + + //much less than with the regular model system + speed *= 0.01f; + + AnglesToAxis( localAngles, axis ); + side = speed * DotProduct( velocity, axis[ 1 ] ); + localAngles[ ROLL ] -= side; + + side = speed * DotProduct( velocity, axis[ 0 ] ); + localAngles[ PITCH ] += side; + } + + //FIXME: PAIN[123] animations? + // pain twitch + //CG_AddPainTwitch( cent, torsoAngles ); + + AnglesToAxis( localAngles, nonSegAxis ); +} + + +//========================================================================== + +/* +=============== +CG_PlayerUpgrade +=============== +*/ +static void CG_PlayerUpgrades( centity_t *cent, refEntity_t *torso ) +{ + int held, active; + qboolean jetjump; + refEntity_t jetpack; + refEntity_t battpack; + refEntity_t flash; + entityState_t *es = ¢->currentState; + + held = es->modelindex; + active = es->modelindex2; + + jetjump = ( cent->jetPackJumpTime + 100 > cg.time && !( active & ( 1 << UP_JETPACK ) ) ); + + if( held & ( 1 << UP_JETPACK ) ) + { + memset( &jetpack, 0, sizeof( jetpack ) ); + VectorCopy( torso->lightingOrigin, jetpack.lightingOrigin ); + jetpack.shadowPlane = torso->shadowPlane; + jetpack.renderfx = torso->renderfx; + + jetpack.hModel = cgs.media.jetpackModel; + + //identity matrix + AxisCopy( axisDefault, jetpack.axis ); + + //FIXME: change to tag_back when it exists + CG_PositionRotatedEntityOnTag( &jetpack, torso, torso->hModel, "tag_head" ); + + trap_R_AddRefEntityToScene( &jetpack ); + + if( jetjump || active & ( 1 << UP_JETPACK ) ) + { + if( es->pos.trDelta[ 2 ] > 10.0f ) + { + if( cent->jetPackState != JPS_ASCENDING ) + { + if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) + CG_DestroyParticleSystem( ¢->jetPackPS ); + + cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackAscendPS ); + cent->jetPackState = JPS_ASCENDING; + } + + if( !jetjump ) + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, + vec3_origin, cgs.media.jetpackAscendSound ); + } + else if( es->pos.trDelta[ 2 ] < -10.0f ) + { + if( cent->jetPackState != JPS_DESCENDING ) + { + if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) + CG_DestroyParticleSystem( ¢->jetPackPS ); + + cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackDescendPS ); + cent->jetPackState = JPS_DESCENDING; + } + + if( !jetjump ) + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, + vec3_origin, cgs.media.jetpackDescendSound ); + } + else + { + if( cent->jetPackState != JPS_HOVERING ) + { + if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) + CG_DestroyParticleSystem( ¢->jetPackPS ); + + cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackHoverPS ); + cent->jetPackState = JPS_HOVERING; + } + + if( !jetjump ) + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, + vec3_origin, cgs.media.jetpackIdleSound ); + } + + memset( &flash, 0, sizeof( flash ) ); + VectorCopy( torso->lightingOrigin, flash.lightingOrigin ); + flash.shadowPlane = torso->shadowPlane; + flash.renderfx = torso->renderfx; + + flash.hModel = cgs.media.jetpackFlashModel; + if( !flash.hModel ) + return; + + AxisCopy( axisDefault, flash.axis ); + + CG_PositionRotatedEntityOnTag( &flash, &jetpack, jetpack.hModel, "tag_flash" ); + trap_R_AddRefEntityToScene( &flash ); + + if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) + { + CG_SetAttachmentTag( ¢->jetPackPS->attachment, + jetpack, jetpack.hModel, "tag_flash" ); + CG_SetAttachmentCent( ¢->jetPackPS->attachment, cent ); + CG_AttachToTag( ¢->jetPackPS->attachment ); + } + } + else if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) + { + CG_DestroyParticleSystem( ¢->jetPackPS ); + cent->jetPackState = JPS_OFF; + } + } + else if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) + { + CG_DestroyParticleSystem( ¢->jetPackPS ); + cent->jetPackState = JPS_OFF; + } + + if( held & ( 1 << UP_BATTPACK ) ) + { + memset( &battpack, 0, sizeof( battpack ) ); + VectorCopy( torso->lightingOrigin, battpack.lightingOrigin ); + battpack.shadowPlane = torso->shadowPlane; + battpack.renderfx = torso->renderfx; + + battpack.hModel = cgs.media.battpackModel; + + //identity matrix + AxisCopy( axisDefault, battpack.axis ); + + //FIXME: change to tag_back when it exists + CG_PositionRotatedEntityOnTag( &battpack, torso, torso->hModel, "tag_head" ); + + trap_R_AddRefEntityToScene( &battpack ); + } + + if( es->eFlags & EF_BLOBLOCKED ) + { + vec3_t temp, origin, up = { 0.0f, 0.0f, 1.0f }; + trace_t tr; + float size; + + VectorCopy( es->pos.trBase, temp ); + temp[ 2 ] -= 4096.0f; + + CG_Trace( &tr, es->pos.trBase, NULL, NULL, temp, es->number, MASK_SOLID ); + VectorCopy( tr.endpos, origin ); + + size = 32.0f; + + if( size > 0.0f ) + CG_ImpactMark( cgs.media.creepShader, origin, up, + 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue ); + } +} + + +/* +=============== +CG_PlayerFloatSprite + +Float a sprite over the player's head +=============== +*/ +static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) +{ + int rf; + refEntity_t ent; + + if( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) + rf = RF_THIRD_PERSON; // only show in mirrors + else + rf = 0; + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[ 2 ] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = rf; + ent.shaderRGBA[ 0 ] = 255; + ent.shaderRGBA[ 1 ] = 255; + ent.shaderRGBA[ 2 ] = 255; + ent.shaderRGBA[ 3 ] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) +{ + if( cent->currentState.eFlags & EF_CONNECTION ) + { + CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); + return; + } +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + + should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE 128 +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, class_t class ) +{ + vec3_t end, mins, maxs; + trace_t trace; + float alpha; + entityState_t *es = ¢->currentState; + vec3_t surfNormal = { 0.0f, 0.0f, 1.0f }; + + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); + mins[ 2 ] = 0.0f; + maxs[ 2 ] = 2.0f; + + if( es->eFlags & EF_WALLCLIMB ) + { + if( es->eFlags & EF_WALLCLIMBCEILING ) + VectorSet( surfNormal, 0.0f, 0.0f, -1.0f ); + else + VectorCopy( es->angles2, surfNormal ); + } + + *shadowPlane = 0; + + if( cg_shadows.integer == 0 ) + return qfalse; + + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + VectorMA( cent->lerpOrigin, -SHADOW_DISTANCE, surfNormal, end ); + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) + return qfalse; + + // FIXME: stencil shadows will be broken for walls. + // Unfortunately there isn't much that can be + // done since Q3 references only the Z coord + // of the shadowPlane + if( surfNormal[ 2 ] < 0.0f ) + *shadowPlane = trace.endpos[ 2 ] - 1.0f; + else + *shadowPlane = trace.endpos[ 2 ] + 1.0f; + + if( cg_shadows.integer != 1 ) // no mark for stencil or projection shadows + return qtrue; + + // fade the shadow out with height + alpha = 1.0 - trace.fraction; + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, + cent->pe.legs.yawAngle, 0.0f, 0.0f, 0.0f, alpha, qfalse, + 24.0f * BG_ClassConfig( class )->shadowScale, qtrue ); + + return qtrue; +} + + +/* +=============== +CG_PlayerSplash + +Draw a mark at the water surface +=============== +*/ +static void CG_PlayerSplash( centity_t *cent, class_t class ) +{ + vec3_t start, end; + vec3_t mins, maxs; + trace_t trace; + int contents; + + if( !cg_shadows.integer ) + return; + + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); + + VectorCopy( cent->lerpOrigin, end ); + end[ 2 ] += mins[ 2 ]; + + // if the feet aren't in liquid, don't make a mark + // this won't handle moving water brushes, but they wouldn't draw right anyway... + contents = trap_CM_PointContents( end, 0 ); + + if( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) + return; + + VectorCopy( cent->lerpOrigin, start ); + start[ 2 ] += 32; + + // if the head isn't out of liquid, don't make a mark + contents = trap_CM_PointContents( start, 0 ); + + if( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) + return; + + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, + ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); + + if( trace.fraction == 1.0f ) + return; + + CG_ImpactMark( cgs.media.wakeMarkShader, trace.endpos, trace.plane.normal, + cent->pe.legs.yawAngle, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, + 32.0f * BG_ClassConfig( class )->shadowScale, qtrue ); +} + + +/* +================= +CG_LightVerts +================= +*/ +int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) +{ + int i, j; + float incoming; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + + trap_R_LightForPoint( verts[ 0 ].xyz, ambientLight, directedLight, lightDir ); + + for( i = 0; i < numVerts; i++ ) + { + incoming = DotProduct( normal, lightDir ); + + if( incoming <= 0 ) + { + verts[ i ].modulate[ 0 ] = ambientLight[ 0 ]; + verts[ i ].modulate[ 1 ] = ambientLight[ 1 ]; + verts[ i ].modulate[ 2 ] = ambientLight[ 2 ]; + verts[ i ].modulate[ 3 ] = 255; + continue; + } + + j = ( ambientLight[ 0 ] + incoming * directedLight[ 0 ] ); + + if( j > 255 ) + j = 255; + + verts[ i ].modulate[ 0 ] = j; + + j = ( ambientLight[ 1 ] + incoming * directedLight[ 1 ] ); + + if( j > 255 ) + j = 255; + + verts[ i ].modulate[ 1 ] = j; + + j = ( ambientLight[ 2 ] + incoming * directedLight[ 2 ] ); + + if( j > 255 ) + j = 255; + + verts[ i ].modulate[ 2 ] = j; + + verts[ i ].modulate[ 3 ] = 255; + } + return qtrue; +} + + +/* +================= +CG_LightFromDirection +================= +*/ +int CG_LightFromDirection( vec3_t point, vec3_t direction ) +{ + int j; + float incoming; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + vec3_t result; + + trap_R_LightForPoint( point, ambientLight, directedLight, lightDir ); + + incoming = DotProduct( direction, lightDir ); + + if( incoming <= 0 ) + { + result[ 0 ] = ambientLight[ 0 ]; + result[ 1 ] = ambientLight[ 1 ]; + result[ 2 ] = ambientLight[ 2 ]; + return (int)( (float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f ); + } + + j = ( ambientLight[ 0 ] + incoming * directedLight[ 0 ] ); + + if( j > 255 ) + j = 255; + + result[ 0 ] = j; + + j = ( ambientLight[ 1 ] + incoming * directedLight[ 1 ] ); + + if( j > 255 ) + j = 255; + + result[ 1 ] = j; + + j = ( ambientLight[ 2 ] + incoming * directedLight[ 2 ] ); + + if( j > 255 ) + j = 255; + + result[ 2 ] = j; + + return (int)((float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f ); +} + + +/* +================= +CG_AmbientLight +================= +*/ +int CG_AmbientLight( vec3_t point ) +{ + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + vec3_t result; + + trap_R_LightForPoint( point, ambientLight, directedLight, lightDir ); + + result[ 0 ] = ambientLight[ 0 ]; + result[ 1 ] = ambientLight[ 1 ]; + result[ 2 ] = ambientLight[ 2 ]; + return (int)((float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f ); +} + +#define TRACE_DEPTH 32.0f + +/* +=============== +CG_Player +=============== +*/ +void CG_Player( centity_t *cent ) +{ + clientInfo_t *ci; + + // NOTE: legs is used for nonsegmented models + // this helps reduce code to be changed + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + int clientNum; + int renderfx; + qboolean shadow = qfalse; + float shadowPlane = 0.0f; + entityState_t *es = ¢->currentState; + class_t class = ( es->misc >> 8 ) & 0xFF; + float scale; + vec3_t tempAxis[ 3 ], tempAxis2[ 3 ]; + vec3_t angles; + int held = es->modelindex; + vec3_t surfNormal = { 0.0f, 0.0f, 1.0f }; + + // the client number is stored in clientNum. It can't be derived + // from the entity number, because a single client may have + // multiple corpses on the level using the same clientinfo + clientNum = es->clientNum; + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) + CG_Error( "Bad clientNum on player entity" ); + + ci = &cgs.clientinfo[ clientNum ]; + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if( !ci->infoValid ) + return; + + //don't draw + if( es->eFlags & EF_NODRAW ) + return; + + // get the player model information + renderfx = 0; + if( es->number == cg.snap->ps.clientNum ) + { + if( !cg.renderingThirdPerson ) + renderfx = RF_THIRD_PERSON; // only draw in mirrors + else if( cg_cameraMode.integer ) + return; + } + + if( cg_drawBBOX.integer ) + { + vec3_t mins, maxs; + + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); + CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs ); + } + + memset( &legs, 0, sizeof( legs ) ); + memset( &torso, 0, sizeof( torso ) ); + memset( &head, 0, sizeof( head ) ); + + VectorCopy( cent->lerpAngles, angles ); + AnglesToAxis( cent->lerpAngles, tempAxis ); + + //rotate lerpAngles to floor + if( es->eFlags & EF_WALLCLIMB && + BG_RotateAxis( es->angles2, tempAxis, tempAxis2, qtrue, es->eFlags & EF_WALLCLIMBCEILING ) ) + AxisToAngles( tempAxis2, angles ); + else + VectorCopy( cent->lerpAngles, angles ); + + //normalise the pitch + if( angles[ PITCH ] < -180.0f ) + angles[ PITCH ] += 360.0f; + + // get the rotation information + if( !ci->nonsegmented ) + CG_PlayerAngles( cent, angles, legs.axis, torso.axis, head.axis ); + else + CG_PlayerNonSegAngles( cent, angles, legs.axis ); + + AxisCopy( legs.axis, tempAxis ); + + //rotate the legs axis to back to the wall + if( es->eFlags & EF_WALLCLIMB && + BG_RotateAxis( es->angles2, legs.axis, tempAxis, qfalse, es->eFlags & EF_WALLCLIMBCEILING ) ) + AxisCopy( tempAxis, legs.axis ); + + //smooth out WW transitions so the model doesn't hop around + CG_PlayerWWSmoothing( cent, legs.axis, legs.axis ); + + AxisCopy( tempAxis, cent->pe.lastAxis ); + + // get the animation state (after rotation, to allow feet shuffle) + if( !ci->nonsegmented ) + CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + else + CG_PlayerNonSegAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp ); + + // add the talk baloon or disconnect icon + CG_PlayerSprites( cent ); + + // add the shadow + if( ( es->number == cg.snap->ps.clientNum && cg.renderingThirdPerson ) || + es->number != cg.snap->ps.clientNum ) + shadow = CG_PlayerShadow( cent, &shadowPlane, class ); + + // add a water splash if partially in and out of water + CG_PlayerSplash( cent, class ); + + if( cg_shadows.integer == 3 && shadow ) + renderfx |= RF_SHADOW_PLANE; + + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all + + // + // add the legs + // + if( !ci->nonsegmented ) + { + legs.hModel = ci->legsModel; + + if( held & ( 1 << UP_LIGHTARMOUR ) ) + legs.customSkin = cgs.media.larmourLegsSkin; + else + legs.customSkin = ci->legsSkin; + } + else + { + legs.hModel = ci->nonSegModel; + legs.customSkin = ci->nonSegSkin; + } + + VectorCopy( cent->lerpOrigin, legs.origin ); + + VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); + legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all + + //move the origin closer into the wall with a CapTrace + if( es->eFlags & EF_WALLCLIMB && !( es->eFlags & EF_DEAD ) && !( cg.intermissionStarted ) ) + { + vec3_t start, end, mins, maxs; + trace_t tr; + + if( es->eFlags & EF_WALLCLIMBCEILING ) + VectorSet( surfNormal, 0.0f, 0.0f, -1.0f ); + else + VectorCopy( es->angles2, surfNormal ); + + BG_ClassBoundingBox( class, mins, maxs, NULL, NULL, NULL ); + + VectorMA( legs.origin, -TRACE_DEPTH, surfNormal, end ); + VectorMA( legs.origin, 1.0f, surfNormal, start ); + CG_CapTrace( &tr, start, mins, maxs, end, es->number, MASK_PLAYERSOLID ); + + //if the trace misses completely then just use legs.origin + //apparently capsule traces are "smaller" than box traces + if( tr.fraction != 1.0f ) + VectorMA( legs.origin, tr.fraction * -TRACE_DEPTH, surfNormal, legs.origin ); + + VectorCopy( legs.origin, legs.lightingOrigin ); + VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all + } + + //rescale the model + scale = BG_ClassConfig( class )->modelScale; + + if( scale != 1.0f ) + { + VectorScale( legs.axis[ 0 ], scale, legs.axis[ 0 ] ); + VectorScale( legs.axis[ 1 ], scale, legs.axis[ 1 ] ); + VectorScale( legs.axis[ 2 ], scale, legs.axis[ 2 ] ); + + legs.nonNormalizedAxes = qtrue; + } + + //offset on the Z axis if required + VectorMA( legs.origin, BG_ClassConfig( class )->zOffset, surfNormal, legs.origin ); + VectorCopy( legs.origin, legs.lightingOrigin ); + VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all + + trap_R_AddRefEntityToScene( &legs ); + + // if the model failed, allow the default nullmodel to be displayed + if( !legs.hModel ) + return; + + if( !ci->nonsegmented ) + { + // + // add the torso + // + torso.hModel = ci->torsoModel; + + if( held & ( 1 << UP_LIGHTARMOUR ) ) + torso.customSkin = cgs.media.larmourTorsoSkin; + else + torso.customSkin = ci->torsoSkin; + + if( !torso.hModel ) + return; + + VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" ); + + torso.shadowPlane = shadowPlane; + torso.renderfx = renderfx; + + trap_R_AddRefEntityToScene( &torso ); + + // + // add the head + // + head.hModel = ci->headModel; + + if( held & ( 1 << UP_HELMET_MK1 ) ) + head.customSkin = cgs.media.larmourHeadSkin; + else if( held & ( 1 << UP_HELMET_MK2 ) ) + head.customSkin = cgs.media.larmourMk2HeadSkin; + else + head.customSkin = ci->headSkin; + + if( !head.hModel ) + return; + + VectorCopy( cent->lerpOrigin, head.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head" ); + + head.shadowPlane = shadowPlane; + head.renderfx = renderfx; + + trap_R_AddRefEntityToScene( &head ); + + // if this player has been hit with poison cloud, add an effect PS + if( ( es->eFlags & EF_POISONCLOUDED ) && + ( es->number != cg.snap->ps.clientNum || cg.renderingThirdPerson ) ) + { + if( !CG_IsParticleSystemValid( ¢->poisonCloudedPS ) ) + cent->poisonCloudedPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudedPS ); + + CG_SetAttachmentTag( ¢->poisonCloudedPS->attachment, + head, head.hModel, "tag_head" ); + CG_SetAttachmentCent( ¢->poisonCloudedPS->attachment, cent ); + CG_AttachToTag( ¢->poisonCloudedPS->attachment ); + } + else if( CG_IsParticleSystemValid( ¢->poisonCloudedPS ) ) + CG_DestroyParticleSystem( ¢->poisonCloudedPS ); + } + + // + // add the gun / barrel / flash + // + if( es->weapon != WP_NONE ) + { + if( !ci->nonsegmented ) + CG_AddPlayerWeapon( &torso, NULL, cent ); + else + CG_AddPlayerWeapon( &legs, NULL, cent ); + } + + CG_PlayerUpgrades( cent, &torso ); + + //sanity check that particle systems are stopped when dead + if( es->eFlags & EF_DEAD ) + { + if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) + CG_DestroyParticleSystem( ¢->muzzlePS ); + + if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) + CG_DestroyParticleSystem( ¢->jetPackPS ); + } + + VectorCopy( surfNormal, cent->pe.lastNormal ); +} + +/* +=============== +CG_Corpse +=============== +*/ +void CG_Corpse( centity_t *cent ) +{ + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + entityState_t *es = ¢->currentState; + int corpseNum; + int renderfx; + qboolean shadow = qfalse; + float shadowPlane; + vec3_t origin, liveZ, deadZ; + float scale; + + corpseNum = CG_GetCorpseNum( es->clientNum ); + + if( corpseNum < 0 || corpseNum >= MAX_CLIENTS ) + CG_Error( "Bad corpseNum on corpse entity: %d", corpseNum ); + + ci = &cgs.corpseinfo[ corpseNum ]; + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if( !ci->infoValid ) + return; + + memset( &legs, 0, sizeof( legs ) ); + memset( &torso, 0, sizeof( torso ) ); + memset( &head, 0, sizeof( head ) ); + + VectorCopy( cent->lerpOrigin, origin ); + BG_ClassBoundingBox( es->clientNum, liveZ, NULL, NULL, deadZ, NULL ); + origin[ 2 ] -= ( liveZ[ 2 ] - deadZ[ 2 ] ); + + VectorCopy( es->angles, cent->lerpAngles ); + + // get the rotation information + if( !ci->nonsegmented ) + CG_PlayerAngles( cent, cent->lerpAngles, legs.axis, torso.axis, head.axis ); + else + CG_PlayerNonSegAngles( cent, cent->lerpAngles, legs.axis ); + + //set the correct frame (should always be dead) + if( cg_noPlayerAnims.integer ) + legs.oldframe = legs.frame = torso.oldframe = torso.frame = 0; + else if( !ci->nonsegmented ) + { + memset( ¢->pe.legs, 0, sizeof( lerpFrame_t ) ); + CG_RunPlayerLerpFrame( ci, ¢->pe.legs, es->legsAnim, 1 ); + legs.oldframe = cent->pe.legs.oldFrame; + legs.frame = cent->pe.legs.frame; + legs.backlerp = cent->pe.legs.backlerp; + + memset( ¢->pe.torso, 0, sizeof( lerpFrame_t ) ); + CG_RunPlayerLerpFrame( ci, ¢->pe.torso, es->torsoAnim, 1 ); + torso.oldframe = cent->pe.torso.oldFrame; + torso.frame = cent->pe.torso.frame; + torso.backlerp = cent->pe.torso.backlerp; + } + else + { + memset( ¢->pe.nonseg, 0, sizeof( lerpFrame_t ) ); + CG_RunPlayerLerpFrame( ci, ¢->pe.nonseg, es->legsAnim, 1 ); + legs.oldframe = cent->pe.nonseg.oldFrame; + legs.frame = cent->pe.nonseg.frame; + legs.backlerp = cent->pe.nonseg.backlerp; + } + + // add the shadow + shadow = CG_PlayerShadow( cent, &shadowPlane, es->clientNum ); + + // get the player model information + renderfx = 0; + + if( cg_shadows.integer == 3 && shadow ) + renderfx |= RF_SHADOW_PLANE; + + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all + + // + // add the legs + // + if( !ci->nonsegmented ) + { + legs.hModel = ci->legsModel; + legs.customSkin = ci->legsSkin; + } + else + { + legs.hModel = ci->nonSegModel; + legs.customSkin = ci->nonSegSkin; + } + + VectorCopy( origin, legs.origin ); + + VectorCopy( origin, legs.lightingOrigin ); + legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + legs.origin[ 2 ] += BG_ClassConfig( es->clientNum )->zOffset; + VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all + + //rescale the model + scale = BG_ClassConfig( es->clientNum )->modelScale; + + if( scale != 1.0f ) + { + VectorScale( legs.axis[ 0 ], scale, legs.axis[ 0 ] ); + VectorScale( legs.axis[ 1 ], scale, legs.axis[ 1 ] ); + VectorScale( legs.axis[ 2 ], scale, legs.axis[ 2 ] ); + + legs.nonNormalizedAxes = qtrue; + } + + trap_R_AddRefEntityToScene( &legs ); + + // if the model failed, allow the default nullmodel to be displayed + if( !legs.hModel ) + return; + + if( !ci->nonsegmented ) + { + // + // add the torso + // + torso.hModel = ci->torsoModel; + if( !torso.hModel ) + return; + + torso.customSkin = ci->torsoSkin; + + VectorCopy( origin, torso.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" ); + + torso.shadowPlane = shadowPlane; + torso.renderfx = renderfx; + + trap_R_AddRefEntityToScene( &torso ); + + // + // add the head + // + head.hModel = ci->headModel; + if( !head.hModel ) + return; + + head.customSkin = ci->headSkin; + + VectorCopy( origin, head.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); + + head.shadowPlane = shadowPlane; + head.renderfx = renderfx; + + trap_R_AddRefEntityToScene( &head ); + } +} + + +//===================================================================== + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) +{ + cent->errorTime = -99999; // guarantee no error decay added + cent->extrapolated = qfalse; + + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], + ¢->pe.legs, cent->currentState.legsAnim ); + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], + ¢->pe.torso, cent->currentState.torsoAnim ); + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], + ¢->pe.nonseg, cent->currentState.legsAnim ); + + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + VectorCopy( cent->lerpOrigin, cent->rawOrigin ); + VectorCopy( cent->lerpAngles, cent->rawAngles ); + + memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); + cent->pe.legs.yawAngle = cent->rawAngles[ YAW ]; + cent->pe.legs.yawing = qfalse; + cent->pe.legs.pitchAngle = 0; + cent->pe.legs.pitching = qfalse; + + memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); + cent->pe.torso.yawAngle = cent->rawAngles[ YAW ]; + cent->pe.torso.yawing = qfalse; + cent->pe.torso.pitchAngle = cent->rawAngles[ PITCH ]; + cent->pe.torso.pitching = qfalse; + + memset( ¢->pe.nonseg, 0, sizeof( cent->pe.nonseg ) ); + cent->pe.nonseg.yawAngle = cent->rawAngles[ YAW ]; + cent->pe.nonseg.yawing = qfalse; + cent->pe.nonseg.pitchAngle = cent->rawAngles[ PITCH ]; + cent->pe.nonseg.pitching = qfalse; + + if( cg_debugPosition.integer ) + CG_Printf( "%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); +} + +/* +================== +CG_PlayerDisconnect + +Player disconnecting +================== +*/ +void CG_PlayerDisconnect( vec3_t org ) +{ + particleSystem_t *ps; + + trap_S_StartSound( org, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.disconnectSound ); + + ps = CG_SpawnNewParticleSystem( cgs.media.disconnectPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, org ); + CG_AttachToPoint( &ps->attachment ); + } +} + +centity_t *CG_GetPlayerLocation( void ) +{ + int i; + centity_t *eloc, *best; + float bestlen, len; + vec3_t origin; + + best = NULL; + bestlen = 3.0f * 8192.0f * 8192.0f; + + VectorCopy( cg.predictedPlayerState.origin, origin ); + + for( i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) + { + eloc = &cg_entities[ i ]; + if( !eloc->valid || eloc->currentState.eType != ET_LOCATION ) + continue; + + len = DistanceSquared(origin, eloc->lerpOrigin); + + if( len > bestlen ) + continue; + + if( !trap_R_inPVS( origin, eloc->lerpOrigin ) ) + continue; + + bestlen = len; + best = eloc; + } + + return best; +} diff --git a/src/cgame/cg_playerstate.c b/src/cgame/cg_playerstate.c new file mode 100644 index 0000000..242a3bb --- /dev/null +++ b/src/cgame/cg_playerstate.c @@ -0,0 +1,344 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// following another player or playing back a demo, it will be checked +// when the snapshot transitions like all the other entities + + +#include "cg_local.h" + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) +{ + float left, front, up; + float kick; + int health; + float scale; + vec3_t dir; + vec3_t angles; + float dist; + float yaw, pitch; + + // show the attacking player's head and name in corner + cg.attackerTime = cg.time; + + // the lower on health you are, the greater the view kick will be + health = cg.snap->ps.stats[STAT_HEALTH]; + + if( health < 40 ) + scale = 1; + else + scale = 40.0 / health; + + kick = damage * scale; + + if( kick < 5 ) + kick = 5; + + if( kick > 10 ) + kick = 10; + + // if yaw and pitch are both 255, make the damage always centered (falling, etc) + if( yawByte == 255 && pitchByte == 255 ) + { + cg.damageX = 0; + cg.damageY = 0; + cg.v_dmg_roll = 0; + cg.v_dmg_pitch = -kick; + } + else + { + // positional + pitch = pitchByte / 255.0 * 360; + yaw = yawByte / 255.0 * 360; + + angles[ PITCH ] = pitch; + angles[ YAW ] = yaw; + angles[ ROLL ] = 0; + + AngleVectors( angles, dir, NULL, NULL ); + VectorSubtract( vec3_origin, dir, dir ); + + front = DotProduct( dir, cg.refdef.viewaxis[ 0 ] ); + left = DotProduct( dir, cg.refdef.viewaxis[ 1 ] ); + up = DotProduct( dir, cg.refdef.viewaxis[ 2 ] ); + + dir[ 0 ] = front; + dir[ 1 ] = left; + dir[ 2 ] = 0; + dist = VectorLength( dir ); + + if( dist < 0.1f ) + dist = 0.1f; + + cg.v_dmg_roll = kick * left; + + cg.v_dmg_pitch = -kick * front; + + if( front <= 0.1 ) + front = 0.1f; + + cg.damageX = -left / front; + cg.damageY = up / dist; + } + + // clamp the position + if( cg.damageX > 1.0 ) + cg.damageX = 1.0; + + if( cg.damageX < - 1.0 ) + cg.damageX = -1.0; + + if( cg.damageY > 1.0 ) + cg.damageY = 1.0; + + if( cg.damageY < - 1.0 ) + cg.damageY = -1.0; + + // don't let the screen flashes vary as much + if( kick > 10 ) + kick = 10; + + cg.damageValue = kick; + cg.v_dmg_time = cg.time + DAMAGE_TIME; + cg.damageTime = cg.snap->serverTime; +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) +{ + // no error decay on player movement + cg.thisFrameTeleport = qtrue; + + // display weapons available + cg.weaponSelectTime = cg.time; + + // select the weapon the server says we are using + cg.weaponSelect = cg.snap->ps.weapon; + + CG_ResetPainBlend( ); +} + +/* +============== +CG_CheckPlayerstateEvents + +============== +*/ +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) +{ + int i; + int event; + centity_t *cent; + + if( ps->externalEvent && ps->externalEvent != ops->externalEvent ) + { + cent = &cg_entities[ ps->clientNum ]; + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + + cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + + // go through the predictable events buffer + for( i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++ ) + { + // if we have a new predictable event + if( i >= ops->eventSequence || + // or the server told us to play another event instead of a predicted event we already issued + // or something the server told us changed our prediction causing a different event + ( i > ops->eventSequence - MAX_PS_EVENTS && ps->events[ i & ( MAX_PS_EVENTS - 1 ) ] != + ops->events[ i & ( MAX_PS_EVENTS - 1 ) ] ) ) + { + event = ps->events[ i & ( MAX_PS_EVENTS - 1 ) ]; + + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & ( MAX_PS_EVENTS - 1 ) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event; + + cg.eventSequence++; + } + } +} + + +/* +================== +CG_CheckChangedPredictableEvents +================== +*/ +void CG_CheckChangedPredictableEvents( playerState_t *ps ) +{ + int i; + int event; + centity_t *cent; + + cent = &cg.predictedPlayerEntity; + + for( i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++ ) + { + // + if( i >= cg.eventSequence ) + continue; + + // if this event is not further back in than the maximum predictable events we remember + if( i > cg.eventSequence - MAX_PREDICTED_EVENTS ) + { + // if the new playerstate event is different from a previously predicted one + if( ps->events[ i & ( MAX_PS_EVENTS - 1 ) ] != cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] ) + { + event = ps->events[ i & ( MAX_PS_EVENTS - 1 ) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & ( MAX_PS_EVENTS - 1 ) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event; + + if( cg_showmiss.integer ) + CG_Printf( "WARNING: changed predicted event\n" ); + } + } + } +} + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) +{ + int reward; + + // don't play the sounds if the player just spawned + if( ps->persistant[ PERS_SPECSTATE ] != ops->persistant[ PERS_SPECSTATE ] ) + return; + + // health changes of more than -1 should make pain sounds + if( ps->stats[ STAT_HEALTH ] < ops->stats[ STAT_HEALTH ] - 1 ) + { + if( ps->stats[ STAT_HEALTH ] > 0 ) + CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[ STAT_HEALTH ] ); + } + + if( ( BG_UpgradeIsActive( UP_JETPACK, ps->stats ) || + ( cg.predictedPlayerEntity.jetPackJumpTime + 1000 > cg.time && + cg.predictedPlayerEntity.jetPackJumpTime + 250 < cg.time ) ) && + ps->stats[ STAT_FUEL ] <= JETPACK_FUEL_LOW ) + { + static int last = 0; + + if( last + 740 < cg.time ) + { + trap_S_StartSound( NULL, cg.predictedPlayerState.clientNum, CHAN_AUTO, cgs.media.jetpackLowFuelSound ); + last = cg.time; + } + } + + // if we are going into the intermission, don't start any voices + if( cg.intermissionStarted ) + return; + + // reward sounds + reward = qfalse; +} + + +/* +=============== +CG_TransitionPlayerState + +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) +{ + // check for changing follow mode + if( ps->clientNum != ops->clientNum ) + { + cg.thisFrameTeleport = qtrue; + // make sure we don't get any unwanted transition effects + *ops = *ps; + + CG_ResetPainBlend( ); + } + + // damage events (player is getting wounded) + if( ps->damageEvent != ops->damageEvent && ps->damageCount ) + CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); + + // respawning + if( ps->persistant[ PERS_SPAWN_COUNT ] != ops->persistant[ PERS_SPAWN_COUNT ] ) + CG_Respawn( ); + + if( cg.mapRestart ) + { + CG_Respawn( ); + cg.mapRestart = qfalse; + } + + if( cg.snap->ps.pm_type != PM_INTERMISSION && + ps->persistant[ PERS_SPECSTATE ] == SPECTATOR_NOT ) + CG_CheckLocalSounds( ps, ops ); + + // run events + CG_CheckPlayerstateEvents( ps, ops ); + + // smooth the ducking viewheight change + if( ps->viewheight != ops->viewheight ) + { + cg.duckChange = ps->viewheight - ops->viewheight; + cg.duckTime = cg.time; + } + + // changed team + if( ps->stats[ STAT_TEAM ] != ops->stats[ STAT_TEAM ] ) + { + cg.lastHealthCross = 0; + cg.chargeMeterAlpha = 0.0f; + } + + if( ps->stats[ STAT_FUEL ] < JETPACK_FUEL_JUMP && + ops->stats[ STAT_FUEL ] >= JETPACK_FUEL_JUMP && + cg.predictedPlayerEntity.jetPackJumpTime + 1000 > cg.time ) + { + trap_S_StartSound( NULL, cg.predictedPlayerState.clientNum, CHAN_AUTO, cgs.media.jetpackNoJumpFuelSound ); + } +} + diff --git a/src/cgame/cg_predict.c b/src/cgame/cg_predict.c new file mode 100644 index 0000000..6005475 --- /dev/null +++ b/src/cgame/cg_predict.c @@ -0,0 +1,898 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_predict.c -- this file generates cg.predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// ahead the client's movement. +// It also handles local physics interaction, like fragments bouncing off walls + + +#include "cg_local.h" + +static pmove_t cg_pmove; + +static int cg_numSolidEntities; +static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; +static int cg_numTriggerEntities; +static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +/* +==================== +CG_BuildSolidList + +When a new cg.snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) +{ + int i; + centity_t *cent; + snapshot_t *snap; + entityState_t *ent; + + cg_numSolidEntities = 0; + cg_numTriggerEntities = 0; + + if( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) + snap = cg.nextSnap; + else + snap = cg.snap; + + for( i = 0; i < snap->numEntities; i++ ) + { + cent = &cg_entities[ snap->entities[ i ].number ]; + ent = ¢->currentState; + + if( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) + { + cg_triggerEntities[ cg_numTriggerEntities ] = cent; + cg_numTriggerEntities++; + continue; + } + + if( cent->nextState.solid && ent->eType != ET_MISSILE ) + { + cg_solidEntities[ cg_numSolidEntities ] = cent; + cg_numSolidEntities++; + continue; + } + } +} + +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, + const vec3_t maxs, const vec3_t end, int skipNumber, + int mask, trace_t *tr, traceType_t collisionType ) +{ + int i, j, x, zd, zu; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + vec3_t bmins, bmaxs; + vec3_t origin, angles; + centity_t *cent; + vec3_t dims; + vec3_t size; + + //SUPAR HACK + //this causes a trace to collide with the local player + if( skipNumber == MAGIC_TRACE_HACK ) + j = cg_numSolidEntities + 1; + else + j = cg_numSolidEntities; + + for( i = 0; i < j; i++ ) + { + if( i < cg_numSolidEntities ) + cent = cg_solidEntities[ i ]; + else + cent = &cg.predictedPlayerEntity; + + ent = ¢->currentState; + + if( ent->number == skipNumber ) + continue; + + if( ent->solid == SOLID_BMODEL ) + { + // special value for bmodel + cmodel = trap_CM_InlineModel( ent->modelindex ); + VectorCopy( cent->lerpAngles, angles ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); + } + else if( BG_IsCuboid( ent->modelindex ) ) + { + BG_CuboidBBox( ent->angles, bmins, bmaxs ); + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + } + else + { + // encoded bbox + x = ( ent->solid & 255 ); + zd = ( ( ent->solid >> 8 ) & 255 ); + zu = ( ( ent->solid >> 16 ) & 255 ) - 32; + + bmins[ 0 ] = bmins[ 1 ] = -x; + bmaxs[ 0 ] = bmaxs[ 1 ] = x; + bmins[ 2 ] = -zd; + bmaxs[ 2 ] = zu; + + if( i == cg_numSolidEntities ) + BG_ClassBoundingBox( ( ent->misc >> 8 ) & 0xFF, bmins, bmaxs, NULL, NULL, NULL ); + + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + } + + + if( collisionType == TT_CAPSULE ) + { + trap_CM_TransformedCapsuleTrace ( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles ); + } + else if( collisionType == TT_AABB ) + { + trap_CM_TransformedBoxTrace ( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles ); + } + else if( collisionType == TT_BISPHERE ) + { + trap_CM_TransformedBiSphereTrace( &trace, start, end, + mins[ 0 ], maxs[ 0 ], cmodel, mask, origin ); + } + + if( trace.allsolid || trace.fraction < tr->fraction ) + { + trace.entityNum = ent->number; + + if( tr->lateralFraction < trace.lateralFraction ) + { + float oldLateralFraction = tr->lateralFraction; + *tr = trace; + tr->lateralFraction = oldLateralFraction; + } + else + *tr = trace; + } + else if( trace.startsolid ) + { + tr->startsolid = qtrue; + tr->entityNum = ent->number; + } + + if( tr->allsolid ) + return; + } +} + +/* +================ +CG_Trace +================ +*/ +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) +{ + trace_t t; + + trap_CM_BoxTrace( &t, start, end, mins, maxs, 0, mask ); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_AABB ); + + *result = t; +} + +/* +================ +CG_CapTrace +================ +*/ +void CG_CapTrace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) +{ + trace_t t; + + trap_CM_CapsuleTrace( &t, start, end, mins, maxs, 0, mask ); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_CAPSULE ); + + *result = t; +} + +/* +================ +CG_BiSphereTrace +================ +*/ +void CG_BiSphereTrace( trace_t *result, const vec3_t start, const vec3_t end, + const float startRadius, const float endRadius, int skipNumber, int mask ) +{ + trace_t t; + vec3_t mins, maxs; + + mins[ 0 ] = startRadius; + maxs[ 0 ] = endRadius; + + trap_CM_BiSphereTrace( &t, start, end, startRadius, endRadius, 0, mask ); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_BISPHERE ); + + *result = t; +} + +/* +================ +CG_PointContents +================ +*/ +int CG_PointContents( const vec3_t point, int passEntityNum ) +{ + int i; + entityState_t *ent; + centity_t *cent; + clipHandle_t cmodel; + int contents; + + contents = trap_CM_PointContents (point, 0); + + for( i = 0; i < cg_numSolidEntities; i++ ) + { + cent = cg_solidEntities[ i ]; + + ent = ¢->currentState; + + if( ent->number == passEntityNum ) + continue; + + if( ent->solid != SOLID_BMODEL ) // special value for bmodel + continue; + + cmodel = trap_CM_InlineModel( ent->modelindex ); + + if( !cmodel ) + continue; + + contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + } + + return contents; +} + + +/* +======================== +CG_InterpolatePlayerState + +Generates cg.predictedPlayerState by interpolating between +cg.snap->player_state and cg.nextFrame->player_state +======================== +*/ +static void CG_InterpolatePlayerState( qboolean grabAngles ) +{ + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg.predictedPlayerState; + prev = cg.snap; + next = cg.nextSnap; + + *out = cg.snap->ps; + + // if we are still allowing local input, short circuit the view angles + if( grabAngles ) + { + usercmd_t cmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber( ); + trap_GetUserCmd( cmdNum, &cmd ); + + PM_UpdateViewAngles( out, &cmd ); + } + + // if the next frame is a teleport, we can't lerp to it + if( cg.nextFrameTeleport ) + return; + + if( !next || next->serverTime <= prev->serverTime ) + return; + + f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + + i = next->ps.bobCycle; + if( i < prev->ps.bobCycle ) + i += 256; // handle wraparound + + out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + + for( i = 0; i < 3; i++ ) + { + out->origin[ i ] = prev->ps.origin[ i ] + f * ( next->ps.origin[ i ] - prev->ps.origin[ i ] ); + + if( !grabAngles ) + out->viewangles[ i ] = LerpAngle( prev->ps.viewangles[ i ], next->ps.viewangles[ i ], f ); + + out->velocity[ i ] = prev->ps.velocity[ i ] + + f * (next->ps.velocity[ i ] - prev->ps.velocity[ i ] ); + } +} + + +/* +========================= +CG_TouchTriggerPrediction + +Predict push triggers and items +========================= +*/ +static void CG_TouchTriggerPrediction( void ) +{ + int i; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + centity_t *cent; + qboolean spectator; + + // dead clients don't activate triggers + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) + return; + + spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); + + if( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) + return; + + for( i = 0; i < cg_numTriggerEntities; i++ ) + { + cent = cg_triggerEntities[ i ]; + ent = ¢->currentState; + + if( ent->solid != SOLID_BMODEL ) + continue; + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if( !cmodel ) + continue; + + trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, + cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); + + if( !trace.startsolid ) + continue; + + if( ent->eType == ET_TELEPORT_TRIGGER ) + cg.hyperspace = qtrue; + } +} + +static int CG_IsUnacceptableError( playerState_t *ps, playerState_t *pps ) +{ + vec3_t delta; + int i; + + if( pps->pm_type != ps->pm_type || + pps->pm_flags != ps->pm_flags || + pps->pm_time != ps->pm_time ) + { + return 1; + } + + VectorSubtract( pps->origin, ps->origin, delta ); + if( VectorLengthSquared( delta ) > 0.1f * 0.1f ) + { + if( cg_showmiss.integer ) + { + CG_Printf( "origin delta: %.2f ", VectorLength( delta ) ); + } + return 2; + } + + VectorSubtract( pps->velocity, ps->velocity, delta ); + if( VectorLengthSquared( delta ) > 0.1f * 0.1f ) + { + if( cg_showmiss.integer ) + { + CG_Printf( "velocity delta: %.2f ", VectorLength( delta ) ); + } + return 3; + } + + if( pps->weaponTime != ps->weaponTime || + pps->gravity != ps->gravity || + pps->speed != ps->speed || + pps->delta_angles[ 0 ] != ps->delta_angles[ 0 ] || + pps->delta_angles[ 1 ] != ps->delta_angles[ 1 ] || + pps->delta_angles[ 2 ] != ps->delta_angles[ 2 ] || + pps->groundEntityNum != ps->groundEntityNum ) + { + return 4; + } + + if( pps->legsTimer != ps->legsTimer || + pps->legsAnim != ps->legsAnim || + pps->torsoTimer != ps->torsoTimer || + pps->torsoAnim != ps->torsoAnim || + pps->movementDir != ps->movementDir ) + { + return 5; + } + + VectorSubtract( pps->grapplePoint, ps->grapplePoint, delta ); + if( VectorLengthSquared( delta ) > 0.1f * 0.1f ) + return 6; + + if( pps->eFlags != ps->eFlags ) + return 7; + + if( pps->eventSequence != ps->eventSequence ) + return 8; + + for( i = 0; i < MAX_PS_EVENTS; i++ ) + { + if ( pps->events[ i ] != ps->events[ i ] || + pps->eventParms[ i ] != ps->eventParms[ i ] ) + { + return 9; + } + } + + if( pps->externalEvent != ps->externalEvent || + pps->externalEventParm != ps->externalEventParm || + pps->externalEventTime != ps->externalEventTime ) + { + return 10; + } + + if( pps->clientNum != ps->clientNum || + pps->weapon != ps->weapon || + pps->weaponstate != ps->weaponstate ) + { + return 11; + } + + if( fabs( AngleDelta( ps->viewangles[ 0 ], pps->viewangles[ 0 ] ) ) > 1.0f || + fabs( AngleDelta( ps->viewangles[ 1 ], pps->viewangles[ 1 ] ) ) > 1.0f || + fabs( AngleDelta( ps->viewangles[ 2 ], pps->viewangles[ 2 ] ) ) > 1.0f ) + { + return 12; + } + + if( pps->viewheight != ps->viewheight ) + return 13; + + if( pps->damageEvent != ps->damageEvent || + pps->damageYaw != ps->damageYaw || + pps->damagePitch != ps->damagePitch || + pps->damageCount != ps->damageCount ) + { + return 14; + } + + for( i = 0; i < MAX_STATS; i++ ) + { + if( pps->stats[ i ] != ps->stats[ i ] ) + return 15; + } + + for( i = 0; i < MAX_PERSISTANT; i++ ) + { + if( pps->persistant[ i ] != ps->persistant[ i ] ) + return 16; + } + + if( pps->generic1 != ps->generic1 || + pps->loopSound != ps->loopSound ) + { + return 19; + } + + return 0; +} + + +/* +================= +CG_PredictPlayerState + +Generates cg.predictedPlayerState for the current cg.time +cg.predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one. Would require saving all intermediate +playerState_t during prediction. + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ + +//Refer to G_PMTraceHack documentation for information about this function +void CG_PMTraceHack(trace_t *results,const vec3_t start,const vec3_t mins,const vec3_t maxs,const vec3_t end,int passEntityNum,int contentmask ) +{ + int modelindex; + CG_Trace(results,start,mins,maxs,end,passEntityNum,contentmask); + if(results->entityNum!=ENTITYNUM_NONE) + { + modelindex=cg_entities[results->entityNum].currentState.modelindex; + if(modelindex>=CUBOID_FIRST&&modelindex<=CUBOID_LAST&&(cg_entities[results->entityNum].currentState.eFlags&EF_B_SPAWNED)) + results->surfaceFlags|=BG_CuboidAttributes(modelindex)->surfaceFlags; + } +} + +void CG_PredictPlayerState( void ) +{ + int cmdNum, current, i; + playerState_t oldPlayerState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + int stateIndex = 0, predictCmd = 0; + + cg.hyperspace = qfalse; // will be set if touching a trigger_teleport + + // if this is the first frame we must guarantee + // predictedPlayerState is valid even if there is some + // other error condition + if( !cg.validPPS ) + { + cg.validPPS = qtrue; + cg.predictedPlayerState = cg.snap->ps; + } + + + // demo playback just copies the moves + if( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) + { + CG_InterpolatePlayerState( qfalse ); + return; + } + + // non-predicting local movement will grab the latest angles + if( cg_nopredict.integer || cg_synchronousClients.integer ) + { + CG_InterpolatePlayerState( qtrue ); + return; + } + + // prepare for pmove + cg_pmove.ps = &cg.predictedPlayerState; + cg_pmove.pmext = &cg.pmext; + cg_pmove.trace = CG_PMTraceHack; + cg_pmove.pointcontents = CG_PointContents; + cg_pmove.debugLevel = cg_debugMove.integer; + + if( cg_pmove.ps->pm_type == PM_DEAD ) + cg_pmove.tracemask = MASK_DEADSOLID; + else + cg_pmove.tracemask = MASK_PLAYERSOLID; + + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + cg_pmove.tracemask = MASK_DEADSOLID; // spectators can fly through bodies + + cg_pmove.noFootsteps = 0; + + // save the state before the pmove so we can detect transitions + oldPlayerState = cg.predictedPlayerState; + + current = trap_GetCurrentCmdNumber( ); + + // if we don't have the commands right after the snapshot, we + // can't accurately predict a current position, so just freeze at + // the last good position we had + cmdNum = current - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &oldestCmd ); + + if( oldestCmd.serverTime > cg.snap->ps.commandTime && + oldestCmd.serverTime < cg.time ) + { // special check for map_restart + if( cg_showmiss.integer ) + CG_Printf( "exceeded PACKET_BACKUP on commands\n" ); + + return; + } + + // get the latest command so we can know which commands are from previous map_restarts + trap_GetUserCmd( current, &latestCmd ); + + // get the most recent information we have, even if + // the server time is beyond our current cg.time, + // because predicted player positions are going to + // be ahead of everything else anyway + if( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) + { + cg.predictedPlayerState = cg.nextSnap->ps; + cg.physicsTime = cg.nextSnap->serverTime; + } + else + { + cg.predictedPlayerState = cg.snap->ps; + cg.physicsTime = cg.snap->serverTime; + } + + if( pmove_msec.integer < 8 ) + trap_Cvar_Set( "pmove_msec", "8" ); + else if( pmove_msec.integer > 33 ) + trap_Cvar_Set( "pmove_msec", "33" ); + + cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; + cg_pmove.pmove_msec = pmove_msec.integer; + + // Like the comments described above, a player's state is entirely + // re-predicted from the last valid snapshot every client frame, which + // can be really, really, really slow. Every old command has to be + // run again. For every client frame that is *not* directly after a + // snapshot, this is unnecessary, since we have no new information. + // For those, we'll play back the predictions from the last frame and + // predict only the newest commands. Essentially, we'll be doing + // an incremental predict instead of a full predict. + // + // If we have a new snapshot, we can compare its player state's command + // time to the command times in the queue to find a match. If we find + // a matching state, and the predicted version has not deviated, we can + // use the predicted state as a base - and also do an incremental predict. + // + // With this method, we get incremental predicts on every client frame + // except a frame following a new snapshot in which there was a prediction + // error. This yeilds anywhere from a 15% to 40% performance increase, + // depending on how much of a bottleneck the CPU is. + if( cg_optimizePrediction.integer ) + { + if( cg.nextFrameTeleport || cg.thisFrameTeleport ) + { + // do a full predict + cg.lastPredictedCommand = 0; + cg.stateTail = cg.stateHead; + predictCmd = current - CMD_BACKUP + 1; + } + // cg.physicsTime is the current snapshot's serverTime if it's the same + // as the last one + else if( cg.physicsTime == cg.lastServerTime ) + { + // we have no new information, so do an incremental predict + predictCmd = cg.lastPredictedCommand + 1; + } + else + { + // we have a new snapshot + int i; + int errorcode; + qboolean error = qtrue; + + // loop through the saved states queue + for( i = cg.stateHead; i != cg.stateTail; + i = ( i + 1 ) % NUM_SAVED_STATES ) + { + // if we find a predicted state whose commandTime matches the snapshot + // player state's commandTime + if( cg.savedPmoveStates[ i ].commandTime != + cg.predictedPlayerState.commandTime ) + { + continue; + } + // make sure the state differences are acceptable + errorcode = CG_IsUnacceptableError( &cg.predictedPlayerState, + &cg.savedPmoveStates[ i ] ); + + if( errorcode ) + { + if( cg_showmiss.integer ) + CG_Printf("errorcode %d at %d\n", errorcode, cg.time); + break; + } + + // this one is almost exact, so we'll copy it in as the starting point + *cg_pmove.ps = cg.savedPmoveStates[ i ]; + // advance the head + cg.stateHead = ( i + 1 ) % NUM_SAVED_STATES; + + // set the next command to predict + predictCmd = cg.lastPredictedCommand + 1; + + // a saved state matched, so flag it + error = qfalse; + break; + } + + // if no saved states matched + if( error ) + { + // do a full predict + cg.lastPredictedCommand = 0; + cg.stateTail = cg.stateHead; + predictCmd = current - CMD_BACKUP + 1; + } + } + + // keep track of the server time of the last snapshot so we + // know when we're starting from a new one in future calls + cg.lastServerTime = cg.physicsTime; + stateIndex = cg.stateHead; + } + + // run cmds + moved = qfalse; + + for( cmdNum = current - CMD_BACKUP + 1; cmdNum <= current; cmdNum++ ) + { + // get the command + trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); + + if( cg_pmove.pmove_fixed ) + PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); + + // don't do anything if the time is before the snapshot player time + if( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) + continue; + + // don't do anything if the command was from a previous map_restart + if( cg_pmove.cmd.serverTime > latestCmd.serverTime ) + continue; + + // check for a prediction error from last frame + // on a lan, this will often be the exact value + // from the snapshot, but on a wan we will have + // to predict several commands to get to the point + // we want to compare + if( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) + { + vec3_t delta; + float len; + + if( cg.thisFrameTeleport ) + { + // a teleport will not cause an error decay + VectorClear( cg.predictedError ); + + if( cg_showmiss.integer ) + CG_Printf( "PredictionTeleport\n" ); + + cg.thisFrameTeleport = qfalse; + } + else + { + vec3_t adjusted; + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); + + if( cg_showmiss.integer ) + { + if( !VectorCompare( oldPlayerState.origin, adjusted ) ) + CG_Printf("prediction error\n"); + } + + VectorSubtract( oldPlayerState.origin, adjusted, delta ); + len = VectorLength( delta ); + + if( len > 0.1 ) + { + if( cg_showmiss.integer ) + CG_Printf( "Prediction miss: %f\n", len ); + + if( cg_errorDecay.integer ) + { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + + if( f < 0 ) + f = 0; + + if( f > 0 && cg_showmiss.integer ) + CG_Printf( "Double prediction decay: %f\n", f ); + + VectorScale( cg.predictedError, f, cg.predictedError ); + } + else + VectorClear( cg.predictedError ); + + VectorAdd( delta, cg.predictedError, cg.predictedError ); + cg.predictedErrorTime = cg.oldTime; + } + } + } + + // don't predict gauntlet firing, which is only supposed to happen + // when it actually inflicts damage + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + cg_pmove.autoWeaponHit[ i ] = qfalse; + + if( cg_pmove.pmove_fixed ) + cg_pmove.cmd.serverTime = ( ( cg_pmove.cmd.serverTime + pmove_msec.integer - 1 ) / + pmove_msec.integer ) * pmove_msec.integer; + + if( !cg_optimizePrediction.integer ) + { + Pmove( &cg_pmove ); + } + else if( cg_optimizePrediction.integer && ( cmdNum >= predictCmd || + ( stateIndex + 1 ) % NUM_SAVED_STATES == cg.stateHead ) ) + { + Pmove( &cg_pmove ); + // record the last predicted command + cg.lastPredictedCommand = cmdNum; + + // if we haven't run out of space in the saved states queue + if( ( stateIndex + 1 ) % NUM_SAVED_STATES != cg.stateHead ) + { + // save the state for the false case ( of cmdNum >= predictCmd ) + // in later calls to this function + cg.savedPmoveStates[ stateIndex ] = *cg_pmove.ps; + stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES; + cg.stateTail = stateIndex; + } + } + else + { + *cg_pmove.ps = cg.savedPmoveStates[ stateIndex ]; + stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES; + } + + moved = qtrue; + + // add push trigger movement effects + CG_TouchTriggerPrediction( ); + + // check for predictable events that changed from previous predictions + //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); + } + + // adjust for the movement of the groundentity + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, + cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); + + + // fire events and other transition triggered things + CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); + + +} diff --git a/src/cgame/cg_public.h b/src/cgame/cg_public.h new file mode 100644 index 0000000..9d197ff --- /dev/null +++ b/src/cgame/cg_public.h @@ -0,0 +1,262 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#define CMD_BACKUP 64 +#define CMD_MASK (CMD_BACKUP - 1) +// allow a lot of command backups for very fast systems +// multiple commands may be combined into a single packet, so this +// needs to be larger than PACKET_BACKUP + + +#define MAX_ENTITIES_IN_SNAPSHOT 256 + +// snapshots are a view of the server at a given time + +// Snapshots are generated at regular time intervals by the server, +// but they may not be sent if a client's rate level is exceeded, or +// they may be dropped by the network. +typedef struct +{ + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + byte areamask[ MAX_MAP_AREA_BYTES ]; // portalarea visibility bits + + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + entityState_t entities[ MAX_ENTITIES_IN_SNAPSHOT ]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +} snapshot_t; + +enum +{ + CGAME_EVENT_NONE, + CGAME_EVENT_TEAMMENU, + CGAME_EVENT_SCOREBOARD, + CGAME_EVENT_EDITHUD +}; + +/* +================================================================== + +functions imported from the main executable + +================================================================== +*/ + +#define CGAME_IMPORT_API_VERSION 4 + +typedef enum +{ + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_SETCLIPREGION, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + CG_R_REGISTERFONT, + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + CG_R_REMAP_SHADER, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + + CG_CM_TEMPCAPSULEMODEL, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_GET_ENTITY_TOKEN, + CG_R_ADDPOLYSTOSCENE, + CG_R_INPVS, + CG_FS_SEEK, + CG_FS_GETFILELIST, + CG_LITERAL_ARGS, + CG_CM_BISPHERETRACE, + CG_CM_TRANSFORMEDBISPHERETRACE, + CG_GETDEMOSTATE, + CG_GETDEMOPOS, + CG_GETDEMONAME, + + CG_KEY_KEYNUMTOSTRINGBUF, + CG_KEY_GETBINDINGBUF, + CG_KEY_SETBINDING, + + CG_PARSE_ADD_GLOBAL_DEFINE, + CG_PARSE_LOAD_SOURCE, + CG_PARSE_FREE_SOURCE, + CG_PARSE_READ_TOKEN, + CG_PARSE_SOURCE_FILE_AND_LINE, + + CG_KEY_SETOVERSTRIKEMODE, + CG_KEY_GETOVERSTRIKEMODE, + + CG_S_SOUNDDURATION, + + CG_MEMSET = 200, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS +} cgameImport_t; + + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum +{ + CG_INIT, + // void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, + // void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, + // qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, + // void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, + // int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, + // int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, + // void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, + // void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING, + // void (*CG_EventHandling)(int type); + + CG_CONSOLE_TEXT, + // void (*CG_ConsoleText)( void ); + // pass text that has been printed to the console to cgame + // use Cmd_Argc() / Cmd_Argv() to read it + + CG_VOIP_STRING + // char *(*CG_VoIPString)( void ); + // returns a string of comma-delimited clientnums based on cl_voipSendTarget +} cgameExport_t; + +//---------------------------------------------- diff --git a/src/cgame/cg_scanner.c b/src/cgame/cg_scanner.c new file mode 100644 index 0000000..3941956 --- /dev/null +++ b/src/cgame/cg_scanner.c @@ -0,0 +1,362 @@ +/* +=========================================================================== +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#include "cg_local.h" + +static entityPos_t entityPositions; + +#define HUMAN_SCANNER_UPDATE_PERIOD 700 + +/* +============= +CG_UpdateEntityPositions + +Update this client's perception of entity positions +============= +*/ +void CG_UpdateEntityPositions( void ) +{ + centity_t *cent = NULL; + int i; + + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + if( entityPositions.lastUpdateTime + HUMAN_SCANNER_UPDATE_PERIOD > cg.time ) + return; + } + + VectorCopy( cg.refdef.vieworg, entityPositions.origin ); + VectorCopy( cg.refdefViewAngles, entityPositions.vangles ); + entityPositions.lastUpdateTime = cg.time; + + entityPositions.numAlienBuildables = 0; + entityPositions.numHumanBuildables = 0; + entityPositions.numAlienClients = 0; + entityPositions.numHumanClients = 0; + + for( i = 0; i < cg.snap->numEntities; i++ ) + { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + + if( cent->currentState.eType == ET_BUILDABLE && + !( cent->currentState.eFlags & EF_DEAD ) ) + { + if( BG_Buildable(cent->currentState.modelindex,NULL)->cuboid ) + if( !BG_CuboidAttributes(cent->currentState.modelindex)->detectable ) + continue; //forget about undetectable cuboids + + // add to list of item positions (for creep) + if( cent->currentState.modelindex2 == TEAM_ALIENS ) + { + VectorCopy( cent->lerpOrigin, entityPositions.alienBuildablePos[ + entityPositions.numAlienBuildables ] ); + entityPositions.alienBuildableTimes[ + entityPositions.numAlienBuildables ] = cent->miscTime; + + if( entityPositions.numAlienBuildables < MAX_GENTITIES ) + entityPositions.numAlienBuildables++; + } + else if( cent->currentState.modelindex2 == TEAM_HUMANS ) + { + VectorCopy( cent->lerpOrigin, entityPositions.humanBuildablePos[ + entityPositions.numHumanBuildables ] ); + + if( entityPositions.numHumanBuildables < MAX_GENTITIES ) + entityPositions.numHumanBuildables++; + } + } + else if( cent->currentState.eType == ET_PLAYER ) + { + int team = cent->currentState.misc & 0x00FF; + + if( team == TEAM_ALIENS ) + { + VectorCopy( cent->lerpOrigin, entityPositions.alienClientPos[ + entityPositions.numAlienClients ] ); + + if( entityPositions.numAlienClients < MAX_CLIENTS ) + entityPositions.numAlienClients++; + } + else if( team == TEAM_HUMANS ) + { + VectorCopy( cent->lerpOrigin, entityPositions.humanClientPos[ + entityPositions.numHumanClients ] ); + + if( entityPositions.numHumanClients < MAX_CLIENTS ) + entityPositions.numHumanClients++; + } + } + } +} + +#define STALKWIDTH (2.0f * cgDC.aspectScale) +#define BLIPX (16.0f * cgDC.aspectScale) +#define BLIPY 8.0f +#define FAR_ALPHA 0.8f +#define NEAR_ALPHA 1.2f + +/* +============= +CG_DrawBlips + +Draw blips and stalks for the human scanner +============= +*/ +static void CG_DrawBlips( rectDef_t *rect, vec3_t origin, vec4_t colour ) +{ + vec3_t drawOrigin; + vec3_t up = { 0, 0, 1 }; + float alphaMod = 1.0f; + float timeFractionSinceRefresh = 1.0f - + ( (float)( cg.time - entityPositions.lastUpdateTime ) / + (float)HUMAN_SCANNER_UPDATE_PERIOD ); + vec4_t localColour; + + Vector4Copy( colour, localColour ); + + RotatePointAroundVector( drawOrigin, up, origin, -entityPositions.vangles[ 1 ] - 90 ); + drawOrigin[ 0 ] /= ( 2 * HELMET_RANGE / rect->w ); + drawOrigin[ 1 ] /= ( 2 * HELMET_RANGE / rect->h ); + drawOrigin[ 2 ] /= ( 2 * HELMET_RANGE / rect->w ); + + alphaMod = FAR_ALPHA + + ( ( drawOrigin[ 1 ] + ( rect->h / 2.0f ) ) / rect->h ) * ( NEAR_ALPHA - FAR_ALPHA ); + + localColour[ 3 ] *= alphaMod; + localColour[ 3 ] *= ( 0.5f + ( timeFractionSinceRefresh * 0.5f ) ); + + if( localColour[ 3 ] > 1.0f ) + localColour[ 3 ] = 1.0f; + else if( localColour[ 3 ] < 0.0f ) + localColour[ 3 ] = 0.0f; + + trap_R_SetColor( localColour ); + + if( drawOrigin[ 2 ] > 0 ) + CG_DrawPic( rect->x + ( rect->w / 2 ) - ( STALKWIDTH / 2 ) - drawOrigin[ 0 ], + rect->y + ( rect->h / 2 ) + drawOrigin[ 1 ] - drawOrigin[ 2 ], + STALKWIDTH, drawOrigin[ 2 ], cgs.media.scannerLineShader ); + else + CG_DrawPic( rect->x + ( rect->w / 2 ) - ( STALKWIDTH / 2 ) - drawOrigin[ 0 ], + rect->y + ( rect->h / 2 ) + drawOrigin[ 1 ], + STALKWIDTH, -drawOrigin[ 2 ], cgs.media.scannerLineShader ); + + CG_DrawPic( rect->x + ( rect->w / 2 ) - ( BLIPX / 2 ) - drawOrigin[ 0 ], + rect->y + ( rect->h / 2 ) - ( BLIPY / 2 ) + drawOrigin[ 1 ] - drawOrigin[ 2 ], + BLIPX, BLIPY, cgs.media.scannerBlipShader ); + trap_R_SetColor( NULL ); +} + +#define BLIPX2 (24.0f * cgDC.aspectScale) +#define BLIPY2 24.0f + +/* +============= +CG_DrawDir + +Draw dot marking the direction to an enemy +============= +*/ +static void CG_DrawDir( rectDef_t *rect, vec3_t origin, vec4_t colour ) +{ + vec3_t drawOrigin; + vec3_t noZOrigin; + vec3_t normal, antinormal, normalDiff; + vec3_t view, noZview; + vec3_t up = { 0.0f, 0.0f, 1.0f }; + vec3_t top = { 0.0f, -1.0f, 0.0f }; + float angle; + playerState_t *ps = &cg.snap->ps; + + BG_GetClientNormal( ps, normal ); + + AngleVectors( entityPositions.vangles, view, NULL, NULL ); + + ProjectPointOnPlane( noZOrigin, origin, normal ); + ProjectPointOnPlane( noZview, view, normal ); + VectorNormalize( noZOrigin ); + VectorNormalize( noZview ); + + //calculate the angle between the images of the blip and the view + angle = RAD2DEG( acos( DotProduct( noZOrigin, noZview ) ) ); + CrossProduct( noZOrigin, noZview, antinormal ); + VectorNormalize( antinormal ); + + //decide which way to rotate + VectorSubtract( normal, antinormal, normalDiff ); + if( VectorLength( normalDiff ) < 1.0f ) + angle = 360.0f - angle; + + RotatePointAroundVector( drawOrigin, up, top, angle ); + + trap_R_SetColor( colour ); + CG_DrawPic( rect->x + ( rect->w / 2 ) - ( BLIPX2 / 2 ) - drawOrigin[ 0 ] * ( rect->w / 2 ), + rect->y + ( rect->h / 2 ) - ( BLIPY2 / 2 ) + drawOrigin[ 1 ] * ( rect->h / 2 ), + BLIPX2, BLIPY2, cgs.media.scannerBlipShader ); + trap_R_SetColor( NULL ); +} + +/* +============= +CG_AlienSense +============= +*/ +void CG_AlienSense( rectDef_t *rect ) +{ + int i; + vec3_t origin; + vec3_t relOrigin; + vec4_t buildable = { 1.0f, 0.0f, 0.0f, 0.7f }; + vec4_t client = { 0.0f, 0.0f, 1.0f, 0.7f }; + + VectorCopy( entityPositions.origin, origin ); + + //draw human buildables + for( i = 0; i < entityPositions.numHumanBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < ALIENSENSE_RANGE ) + CG_DrawDir( rect, relOrigin, buildable ); + } + + //draw human clients + for( i = 0; i < entityPositions.numHumanClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < ALIENSENSE_RANGE ) + CG_DrawDir( rect, relOrigin, client ); + } +} + +/* +============= +CG_Scanner +============= +*/ +void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color ) +{ + int i; + vec3_t origin; + vec3_t relOrigin; + vec4_t hIabove; + vec4_t hIbelow; + vec4_t aIabove = { 1.0f, 0.0f, 0.0f, 0.75f }; + vec4_t aIbelow = { 1.0f, 0.0f, 0.0f, 0.5f }; + + Vector4Copy( color, hIabove ); + hIabove[ 3 ] *= 1.5f; + Vector4Copy( color, hIbelow ); + + VectorCopy( entityPositions.origin, origin ); + + //draw human buildables below scanner plane + for( i = 0; i < entityPositions.numHumanBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) ) + CG_DrawBlips( rect, relOrigin, hIbelow ); + } + + //draw alien buildables below scanner plane + for( i = 0; i < entityPositions.numAlienBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) ) + CG_DrawBlips( rect, relOrigin, aIbelow ); + } + + //draw human clients below scanner plane + for( i = 0; i < entityPositions.numHumanClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) ) + CG_DrawBlips( rect, relOrigin, hIbelow ); + } + + //draw alien buildables below scanner plane + for( i = 0; i < entityPositions.numAlienClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) ) + CG_DrawBlips( rect, relOrigin, aIbelow ); + } + + if( !cg_disableScannerPlane.integer ) + { + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + } + + //draw human buildables above scanner plane + for( i = 0; i < entityPositions.numHumanBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) ) + CG_DrawBlips( rect, relOrigin, hIabove ); + } + + //draw alien buildables above scanner plane + for( i = 0; i < entityPositions.numAlienBuildables; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) ) + CG_DrawBlips( rect, relOrigin, aIabove ); + } + + //draw human clients above scanner plane + for( i = 0; i < entityPositions.numHumanClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) ) + CG_DrawBlips( rect, relOrigin, hIabove ); + } + + //draw alien clients above scanner plane + for( i = 0; i < entityPositions.numAlienClients; i++ ) + { + VectorClear( relOrigin ); + VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin ); + + if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) ) + CG_DrawBlips( rect, relOrigin, aIabove ); + } +} diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c new file mode 100644 index 0000000..48a5a60 --- /dev/null +++ b/src/cgame/cg_servercmds.c @@ -0,0 +1,1339 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_servercmds.c -- reliably sequenced text commands sent by the server +// these are processed at snapshot transition time, so there will definately +// be a valid snapshot this frame + + +#include "cg_local.h" + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) +{ + int i; + + cg.numScores = ( trap_Argc( ) - 3 ) / 6; + + if( cg.numScores > MAX_CLIENTS ) + cg.numScores = MAX_CLIENTS; + + cg.teamScores[ 0 ] = atoi( CG_Argv( 1 ) ); + cg.teamScores[ 1 ] = atoi( CG_Argv( 2 ) ); + + memset( cg.scores, 0, sizeof( cg.scores ) ); + + if( cg_debugRandom.integer ) + CG_Printf( "cg.numScores: %d\n", cg.numScores ); + + for( i = 0; i < cg.numScores; i++ ) + { + // + cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 3 ) ); + cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 4 ) ); + cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 5 ) ); + cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 6 ) ); + cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 7 ) ); + cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 8 ) ); + + if( cg.scores[ i ].client < 0 || cg.scores[ i ].client >= MAX_CLIENTS ) + cg.scores[ i ].client = 0; + + cgs.clientinfo[ cg.scores[ i ].client ].score = cg.scores[ i ].score; + + cg.scores[ i ].team = cgs.clientinfo[ cg.scores[ i ].client ].team; + } +} + +/* +================= +CG_ParseTeamInfo + +================= +*/ +static void CG_ParseTeamInfo( void ) +{ + int i; + int count; + int client; + + count = ( trap_Argc( ) - 1 ) / 5; + + cgs.teaminfoReceievedTime = cg.time; + + for( i = 0; i < count; i++ ) + { + client = atoi( CG_Argv( i * 5 + 1 ) ); + if( client < 0 || client >= MAX_CLIENTS ) + { + CG_Printf( "[skipnotify]CG_ParseTeamInfo: bad client number: %d\n", client ); + return; + } + + cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 5 + 2 ) ); + cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 5 + 3 ) ); + cgs.clientinfo[ client ].curWeaponClass = atoi( CG_Argv( i * 5 + 4 ) ); + cgs.clientinfo[ client ].upgrade = atoi( CG_Argv( i * 5 + 5 ) ); + } +} + + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) +{ + const char *info; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + cgs.markDeconstruct = atoi( Info_ValueForKey( info, "g_markDeconstruct" ) ); + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) +{ + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg.warmupTime = warmup; +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) +{ + const char *alienStages = CG_ConfigString( CS_ALIEN_STAGES ); + const char *humanStages = CG_ConfigString( CS_HUMAN_STAGES ); + + if( alienStages[0] ) + { + sscanf( alienStages, "%d %d %d", &cgs.alienStage, &cgs.alienCredits, + &cgs.alienNextStageThreshold ); + } + else + cgs.alienStage = cgs.alienCredits = cgs.alienNextStageThreshold = 0; + + + if( humanStages[0] ) + { + sscanf( humanStages, "%d %d %d", &cgs.humanStage, &cgs.humanCredits, + &cgs.humanNextStageThreshold ); + } + else + cgs.humanStage = cgs.humanCredits = cgs.humanNextStageThreshold = 0; + + cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); + cg.warmupTime = atoi( CG_ConfigString( CS_WARMUP ) ); +} + + +/* +===================== +CG_ShaderStateChanged +===================== +*/ +void CG_ShaderStateChanged( void ) +{ + char originalShader[ MAX_QPATH ]; + char newShader[ MAX_QPATH ]; + char timeOffset[ 16 ]; + const char *o; + char *n, *t; + + o = CG_ConfigString( CS_SHADERSTATE ); + + while( o && *o ) + { + n = strstr( o, "=" ); + + if( n && *n ) + { + strncpy( originalShader, o, n - o ); + originalShader[ n - o ] = 0; + n++; + t = strstr( n, ":" ); + + if( t && *t ) + { + strncpy( newShader, n, t - n ); + newShader[ t - n ] = 0; + } + else + break; + + t++; + o = strstr( t, "@" ); + + if( o ) + { + strncpy( timeOffset, t, o - t ); + timeOffset[ o - t ] = 0; + o++; + trap_R_RemapShader( originalShader, newShader, timeOffset ); + } + } + else + break; + } +} + +/* +================ +CG_AnnounceAlienStageTransistion +================ +*/ +static void CG_AnnounceAlienStageTransistion( stage_t from, stage_t to ) +{ + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_ALIENS ) + return; + + trap_S_StartLocalSound( cgs.media.alienStageTransition, CHAN_ANNOUNCER ); + CG_CenterPrint( "We have evolved!", 200, GIANTCHAR_WIDTH * 4 ); +} + +/* +================ +CG_AnnounceHumanStageTransistion +================ +*/ +static void CG_AnnounceHumanStageTransistion( stage_t from, stage_t to ) +{ + if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return; + + trap_S_StartLocalSound( cgs.media.humanStageTransition, CHAN_ANNOUNCER ); + CG_CenterPrint( "Reinforcements have arrived!", 200, GIANTCHAR_WIDTH * 4 ); +} + +/* +================ +CG_ConfigStringModified + +================ +*/ +static void CG_ConfigStringModified( void ) +{ + const char *str; + int num; + + num = atoi( CG_Argv( 1 ) ); + + // get the gamestate from the client system, which will have the + // new configstring already integrated + trap_GetGameState( &cgs.gameState ); + + // look up the individual string that was modified + str = CG_ConfigString( num ); + + // do something with it if necessary + if( num == CS_MUSIC ) + CG_StartMusic( ); + else if( num == CS_SERVERINFO ) + CG_ParseServerinfo( ); + else if( num == CS_WARMUP ) + CG_ParseWarmup( ); + else if( num == CS_ALIEN_STAGES ) + { + stage_t oldAlienStage = cgs.alienStage; + + if( str[0] ) + { + sscanf( str, "%d %d %d", &cgs.alienStage, &cgs.alienCredits, + &cgs.alienNextStageThreshold ); + + if( cgs.alienStage != oldAlienStage ) + CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage ); + } + else + { + cgs.alienStage = cgs.alienCredits = cgs.alienNextStageThreshold = 0; + } + } + else if( num == CS_HUMAN_STAGES ) + { + stage_t oldHumanStage = cgs.humanStage; + + if( str[0] ) + { + sscanf( str, "%d %d %d", &cgs.humanStage, &cgs.humanCredits, + &cgs.humanNextStageThreshold ); + + if( cgs.humanStage != oldHumanStage ) + CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage ); + } + else + { + cgs.humanStage = cgs.humanCredits = cgs.humanNextStageThreshold = 0; + } + } + else if( num == CS_LEVEL_START_TIME ) + cgs.levelStartTime = atoi( str ); + else if( num >= CS_VOTE_TIME && num < CS_VOTE_TIME + NUM_TEAMS ) + { + cgs.voteTime[ num - CS_VOTE_TIME ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_TIME ] = qtrue; + + if( num - CS_VOTE_TIME == TEAM_NONE ) + trap_Cvar_Set( "ui_voteActive", cgs.voteTime[ TEAM_NONE ] ? "1" : "0" ); + else if( num - CS_VOTE_TIME == TEAM_ALIENS ) + trap_Cvar_Set( "ui_alienTeamVoteActive", + cgs.voteTime[ TEAM_ALIENS ] ? "1" : "0" ); + else if( num - CS_VOTE_TIME == TEAM_HUMANS ) + trap_Cvar_Set( "ui_humanTeamVoteActive", + cgs.voteTime[ TEAM_HUMANS ] ? "1" : "0" ); + } + else if( num >= CS_VOTE_YES && num < CS_VOTE_YES + NUM_TEAMS ) + { + cgs.voteYes[ num - CS_VOTE_YES ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_YES ] = qtrue; + } + else if( num >= CS_VOTE_NO && num < CS_VOTE_NO + NUM_TEAMS ) + { + cgs.voteNo[ num - CS_VOTE_NO ] = atoi( str ); + cgs.voteModified[ num - CS_VOTE_NO ] = qtrue; + } + else if( num >= CS_VOTE_STRING && num < CS_VOTE_STRING + NUM_TEAMS ) + Q_strncpyz( cgs.voteString[ num - CS_VOTE_STRING ], str, + sizeof( cgs.voteString[ num - CS_VOTE_STRING ] ) ); + else if( num >= CS_VOTE_CALLER && num < CS_VOTE_CALLER + NUM_TEAMS ) + Q_strncpyz( cgs.voteCaller[ num - CS_VOTE_CALLER ], str, + sizeof( cgs.voteCaller[ num - CS_VOTE_CALLER ] ) ); + else if( num == CS_INTERMISSION ) + cg.intermissionStarted = atoi( str ); + else if( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) + cgs.gameModels[ num - CS_MODELS ] = trap_R_RegisterModel( str ); + else if( num >= CS_SHADERS && num < CS_SHADERS+MAX_GAME_SHADERS ) + cgs.gameShaders[ num - CS_SHADERS ] = trap_R_RegisterShader( str ); + else if( num >= CS_PARTICLE_SYSTEMS && num < CS_PARTICLE_SYSTEMS+MAX_GAME_PARTICLE_SYSTEMS ) + cgs.gameParticleSystems[ num - CS_PARTICLE_SYSTEMS ] = CG_RegisterParticleSystem( (char *)str ); + else if( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS ) + { + if( str[ 0 ] != '*' ) + { // player specific sounds don't register here + cgs.gameSounds[ num - CS_SOUNDS ] = trap_S_RegisterSound( str, qfalse ); + } + } + else if( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) + { + CG_NewClientInfo( num - CS_PLAYERS ); + CG_BuildSpectatorString( ); + } + else if( num == CS_WINNER ) + { + trap_Cvar_Set( "ui_winner", str ); + } + else if( num == CS_SHADERSTATE ) + { + CG_ShaderStateChanged( ); + } +} + + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A tournement restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +static void CG_MapRestart( void ) +{ + if( cg_showmiss.integer ) + CG_Printf( "CG_MapRestart\n" ); + + CG_InitMarkPolys( ); + + // make sure the "3 frags left" warnings play again + cg.fraglimitWarnings = 0; + + cg.timelimitWarnings = 0; + + cg.intermissionStarted = qfalse; + + cgs.voteTime[ TEAM_NONE ] = 0; + + cg.mapRestart = qtrue; + + CG_StartMusic( ); + + trap_S_ClearLoopingSounds( qtrue ); + + // we really should clear more parts of cg here and stop sounds + + trap_Cvar_Set( "cg_thirdPerson", "0" ); + + cg.splashTime = cg.time; +} + +/* +============== +CG_Menu +============== +*/ +void CG_Menu( int menu, int arg ) +{ + const char *cmd; // command to send + const char *longMsg = NULL; // command parameter + const char *shortMsg = NULL; // non-modal version of message + const char *dialog; + dialogType_t type = 0; // controls which cg_disable var will switch it off + + switch( cg.snap->ps.stats[ STAT_TEAM ] ) + { + case TEAM_ALIENS: + dialog = "menu tremulous_alien_dialog\n"; + break; + case TEAM_HUMANS: + dialog = "menu tremulous_human_dialog\n"; + break; + default: + dialog = "menu tremulous_default_dialog\n"; + } + cmd = dialog; + + switch( menu ) + { + case MN_TEAM: + cmd = "menu tremulous_teamselect\n"; + type = DT_INTERACTIVE; + break; + + case MN_A_CLASS: + cmd = "menu tremulous_alienclass\n"; + type = DT_INTERACTIVE; + break; + + case MN_H_SPAWN: + cmd = "menu tremulous_humanitem\n"; + type = DT_INTERACTIVE; + break; + + case MN_A_BUILD: + cmd = "menu tremulous_alienbuild\n"; + type = DT_INTERACTIVE; + break; + + case MN_H_BUILD: + cmd = "menu tremulous_humanbuild\n"; + type = DT_INTERACTIVE; + break; + + case MN_H_ARMOURY: + cmd = "menu tremulous_humanarmoury\n"; + type = DT_INTERACTIVE; + break; + + case MN_H_UNKNOWNITEM: + shortMsg = "Unknown item"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_TEAMFULL: + longMsg = "The alien team has too many players. Please wait until slots " + "become available or join the human team."; + shortMsg = "The alien team has too many players"; + type = DT_COMMAND; + break; + + case MN_H_TEAMFULL: + longMsg = "The human team has too many players. Please wait until slots " + "become available or join the alien team."; + shortMsg = "The human team has too many players"; + type = DT_COMMAND; + break; + + case MN_A_TEAMLOCKED: + longMsg = "The alien team is locked. You cannot join the aliens " + "at this time."; + shortMsg = "The alien team is locked"; + type = DT_COMMAND; + break; + + case MN_H_TEAMLOCKED: + longMsg = "The human team is locked. You cannot join the humans " + "at this time."; + shortMsg = "The human team is locked"; + type = DT_COMMAND; + break; + + case MN_PLAYERLIMIT: + longMsg = "The maximum number of playing clients has been reached. " + "Please wait until slots become available."; + shortMsg = "No free player slots"; + type = DT_COMMAND; + break; + + case MN_WARMUP: + longMsg = "You must wait until the warmup time is finished " + "before joining a team. "; + shortMsg = "You cannot join a team during warmup."; + type = DT_COMMAND; + break; + + //=============================== + + // Since cheating commands have no default binds, they will often be done + // via console. In light of this, perhaps opening a menu is + // counterintuitive + case MN_CMD_CHEAT: + //longMsg = "This action is considered cheating. It can only be used " + // "in cheat mode, which is not enabled on this server."; + shortMsg = "Cheats are not enabled on this server"; + type = DT_COMMAND; + break; + + case MN_CMD_CHEAT_TEAM: + shortMsg = "Cheats are not enabled on this server, so " + "you may not use this command while on a team"; + type = DT_COMMAND; + break; + + case MN_CMD_TEAM: + //longMsg = "You must be on a team to perform this action. Join the alien" + // "or human team and try again."; + shortMsg = "Join a team first"; + type = DT_COMMAND; + break; + + case MN_CMD_SPEC: + //longMsg = "You may not perform this action while on a team. Become a " + // "spectator before trying again."; + shortMsg = "You can only use this command when spectating"; + type = DT_COMMAND; + break; + + case MN_CMD_ALIEN: + //longMsg = "You must be on the alien team to perform this action."; + shortMsg = "Must be alien to use this command"; + type = DT_COMMAND; + break; + + case MN_CMD_HUMAN: + //longMsg = "You must be on the human team to perform this action."; + shortMsg = "Must be human to use this command"; + type = DT_COMMAND; + break; + + case MN_CMD_LIVING: + //longMsg = "You must be living to perform this action."; + shortMsg = "Must be living to use this command"; + type = DT_COMMAND; + break; + + + //=============================== + + case MN_B_NOROOM: + longMsg = "There is no room to build here. Move until the structure turns " + "translucent green, indicating a valid build location."; + shortMsg = "There is no room to build here"; + type = DT_BUILD; + break; + + case MN_B_NORMAL: + longMsg = "Cannot build on this surface. The surface is too steep or " + "unsuitable for building. Please choose another site for this " + "structure."; + shortMsg = "Cannot build on this surface"; + type = DT_BUILD; + break; + + case MN_B_CANNOT: + longMsg = NULL; + shortMsg = "You cannot build that structure"; + type = DT_BUILD; + break; + + // FIXME: MN_H_ and MN_A_? + case MN_B_LASTSPAWN: + longMsg = "This action would remove your team's last spawn point, " + "which often quickly results in a loss. Try building more " + "spawns."; + shortMsg = "You may not deconstruct the last spawn"; + break; + + case MN_B_SUDDENDEATH: + longMsg = "Neither team has prevailed after a certain time and the " + "game has entered Sudden Death. During Sudden Death " + "building is not allowed."; + shortMsg = "Cannot build during Sudden Death"; + type = DT_BUILD; + break; + + case MN_B_REVOKED: + longMsg = "Your teammates have lost faith in your ability to build " + "for the team. You will not be allowed to build until your " + "team votes to reinstate your building rights."; + shortMsg = "Your building rights have been revoked"; + type = DT_BUILD; + break; + + case MN_B_SURRENDER: + longMsg = "Your team has decided to admit defeat and concede the game:" + "traitors and cowards are not allowed to build."; + // too harsh? + shortMsg = "Building is denied to traitorous cowards"; + break; + + case MN_B_NOSURF: + longMsg = "No surface for building. Find a valid " + "surface and place your structure on it."; + shortMsg = "No surface for building"; + break; + + case MN_B_CUBOID_MODE1: + longMsg = "On this map noone is allowed to build a cuboid before reaching " + "stage 2 to prevent players from cheating."; + shortMsg = "Cuboids are disabled on stage 1"; + break; + + case MN_B_CUBOID_MODE2: + longMsg = "Cuboids are not available on this map. " + "Ask an administrator for more information."; + shortMsg = "Cuboids are disabled on this map"; + break; + + case MN_B_TOODENSE: + longMsg = "There are already too many buildings in your" + "vicinity. Remove some of them or build further."; + shortMsg = "Buildable density is too high here"; + break; + + //=============================== + + case MN_H_NOBP: + if( cgs.markDeconstruct ) + longMsg = "There is no power remaining. Free up power by marking " + "existing buildable objects."; + else + longMsg = "There is no power remaining. Free up power by deconstructing " + "existing buildable objects."; + shortMsg = "There is no power remaining"; + type = DT_BUILD; + break; + + case MN_H_NOTPOWERED: + longMsg = "This buildable is not powered. Build a Reactor and/or Repeater " + "in order to power it."; + shortMsg = "This buildable is not powered"; + type = DT_BUILD; + break; + + case MN_H_ONEREACTOR: + longMsg = "There can only be one Reactor. Deconstruct the existing one if you " + "wish to move it."; + shortMsg = "There can only be one Reactor"; + type = DT_BUILD; + break; + + case MN_H_NOPOWERHERE: + longMsg = "There is no power here. If available, a Repeater may be used to " + "transmit power to this location."; + shortMsg = "There is no power here"; + type = DT_BUILD; + break; + + case MN_H_NODCC: + longMsg = "There is no Defense Computer. A Defense Computer is needed to " + "build this."; + shortMsg = "There is no Defense Computer"; + type = DT_BUILD; + break; + + case MN_H_RPTPOWERHERE: + longMsg = "This area already has power. A Repeater is not required here."; + shortMsg = "This area already has power"; + type = DT_BUILD; + break; + + case MN_H_NOSLOTS: + longMsg = "You have no room to carry this. Please sell any conflicting " + "upgrades before purchasing this item."; + shortMsg = "You have no room to carry this"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_H_NOFUNDS: + longMsg = "Insufficient funds. You do not have enough credits to perform " + "this action."; + shortMsg = "Insufficient funds"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_H_ITEMHELD: + longMsg = "You already hold this item. It is not possible to carry multiple " + "items of the same type."; + shortMsg = "You already hold this item"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_H_NOARMOURYHERE: + longMsg = "You must be near a powered Armoury in order to purchase " + "weapons, upgrades or ammunition."; + shortMsg = "You must be near a powered Armoury"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_H_NOENERGYAMMOHERE: + longMsg = "You must be near a Reactor or a powered Armoury or Repeater " + "in order to purchase energy ammunition."; + shortMsg = "You must be near a Reactor or a powered Armoury or Repeater"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_H_NOROOMBSUITON: + longMsg = "There is not enough room here to put on a Battle Suit. " + "Make sure you have enough head room to climb in."; + shortMsg = "Not enough room here to put on a Battle Suit"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_H_NOROOMBSUITOFF: + longMsg = "There is not enough room here to take off your Battle Suit. " + "Make sure you have enough head room to climb out."; + shortMsg = "Not enough room here to take off your Battle Suit"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_H_ARMOURYBUILDTIMER: + longMsg = "You are not allowed to buy or sell weapons until your " + "build timer has expired."; + shortMsg = "You can not buy or sell weapons until your build timer " + "expires"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_H_DEADTOCLASS: + shortMsg = "You must be dead to use the class command"; + type = DT_COMMAND; + break; + + case MN_H_UNKNOWNSPAWNITEM: + shortMsg = "Unknown starting item"; + type = DT_COMMAND; + break; + + //=============================== + + case MN_A_NOCREEP: + longMsg = "There is no creep here. You must build near existing Eggs or " + "the Overmind. Alien structures will not support themselves."; + shortMsg = "There is no creep here"; + type = DT_BUILD; + break; + + case MN_A_NOOVMND: + longMsg = "There is no Overmind. An Overmind must be built to control " + "the structure you tried to place."; + shortMsg = "There is no Overmind"; + type = DT_BUILD; + break; + + case MN_A_ONEOVERMIND: + longMsg = "There can only be one Overmind. Deconstruct the existing one if you " + "wish to move it."; + shortMsg = "There can only be one Overmind"; + type = DT_BUILD; + break; + + case MN_A_NOBP: + longMsg = "The Overmind cannot control any more structures. Deconstruct existing " + "structures to build more."; + shortMsg = "The Overmind cannot control any more structures"; + type = DT_BUILD; + break; + + case MN_A_NOEROOM: + longMsg = "There is no room to evolve here. Move away from walls or other " + "nearby objects and try again."; + shortMsg = "There is no room to evolve here"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_TOOCLOSE: + longMsg = "This location is too close to the enemy to evolve. Move away " + "from the enemy's presence and try again."; + shortMsg = "This location is too close to the enemy to evolve"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_NOOVMND_EVOLVE: + longMsg = "There is no Overmind. An Overmind must be built to allow " + "you to upgrade."; + shortMsg = "There is no Overmind"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_EVOLVEBUILDTIMER: + longMsg = "You cannot evolve until your build timer has expired."; + shortMsg = "You cannot evolve until your build timer expires"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_INFEST: + trap_Cvar_Set( "ui_currentClass", + va( "%d %d", cg.snap->ps.stats[ STAT_CLASS ], + cg.snap->ps.persistant[ PERS_CREDIT ] ) ); + + cmd = "menu tremulous_alienupgrade\n"; + type = DT_INTERACTIVE; + break; + + case MN_A_CANTEVOLVE: + shortMsg = va( "You cannot evolve into a %s", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_EVOLVEWALLWALK: + shortMsg = "You cannot evolve while wallwalking"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_UNKNOWNCLASS: + shortMsg = "Unknown class"; + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTSPAWN: + shortMsg = va( "You cannot spawn as a %s", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTALLOWED: + shortMsg = va( "The %s is not allowed", + BG_ClassConfig( arg )->humanName ); + type = DT_ARMOURYEVOLVE; + break; + + case MN_A_CLASSNOTATSTAGE: + shortMsg = va( "The %s is not allowed at Stage %d", + BG_ClassConfig( arg )->humanName, + cgs.alienStage + 1 ); + type = DT_ARMOURYEVOLVE; + break; + + default: + longMsg = "An error has occured. Server has not specified" + " the error code. This is propably a bug."; + shortMsg = "Generic error"; + break; + } + + if( type == DT_ARMOURYEVOLVE && cg_disableUpgradeDialogs.integer ) + return; + + if( type == DT_BUILD && cg_disableBuildDialogs.integer ) + return; + + if( type == DT_COMMAND && cg_disableCommandDialogs.integer ) + return; + + if( cmd != dialog ) + { + trap_SendConsoleCommand( cmd ); + } + else if( longMsg && cg_disableWarningDialogs.integer == 0 ) + { + trap_Cvar_Set( "ui_dialog", longMsg ); + trap_SendConsoleCommand( cmd ); + } + else if( shortMsg && cg_disableWarningDialogs.integer < 2 ) + { + CG_Printf( "%s\n", shortMsg ); + } +} + +/* +================= +CG_Say +================= +*/ +static void CG_Say( int clientNum, saymode_t mode, const char *text ) +{ + char *name; + char prefix[ 11 ] = ""; + char *ignore = ""; + char *location = ""; + char *color; + char *maybeColon; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) + { + clientInfo_t *ci = &cgs.clientinfo[ clientNum ]; + char *tcolor = S_COLOR_WHITE; + + name = ci->name; + + if( ci->team == TEAM_ALIENS ) + tcolor = S_COLOR_RED; + else if( ci->team == TEAM_HUMANS ) + tcolor = S_COLOR_CYAN; + + if( cg_chatTeamPrefix.integer ) + Com_sprintf( prefix, sizeof( prefix ), "[%s%c" S_COLOR_WHITE "] ", + tcolor, toupper( *( BG_TeamName( ci->team ) ) ) ); + + if( Com_ClientListContains( &cgs.ignoreList, clientNum ) ) + ignore = "[skipnotify]"; + + if( ( mode == SAY_TEAM || mode == SAY_AREA ) && + cg.snap->ps.pm_type != PM_INTERMISSION ) + { + int locationNum; + + if( clientNum == cg.snap->ps.clientNum ) + { + centity_t *locent; + + locent = CG_GetPlayerLocation( ); + if( locent ) + locationNum = locent->currentState.generic1; + else + locationNum = 0; + } + else + locationNum = ci->location; + + if( locationNum > 0 && locationNum < MAX_LOCATIONS ) + { + const char *s = CG_ConfigString( CS_LOCATIONS + locationNum ); + + if( *s ) + location = va( " (%s" S_COLOR_WHITE ")", s ); + } + } + } + else + name = "console"; + + // IRC-like /me parsing + if( mode != SAY_RAW && Q_stricmpn( text, "/me ", 4 ) == 0 ) + { + text += 4; + Q_strcat( prefix, sizeof( prefix ), "* " ); + maybeColon = ""; + } + else + maybeColon = ":"; + + switch( mode ) + { + case SAY_ALL: + // might already be ignored but in that case no harm is done + if( cg_teamChatsOnly.integer ) + ignore = "[skipnotify]"; + + CG_Printf( "%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_GREEN "%s\n", + ignore, prefix, name, maybeColon, INDENT_MARKER, text ); + break; + case SAY_TEAM: + CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s %c" S_COLOR_CYAN "%s\n", + ignore, prefix, name, location, maybeColon, INDENT_MARKER, text ); + break; + case SAY_ADMINS: + case SAY_ADMINS_PUBLIC: + CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_MAGENTA "%s\n", + ignore, prefix, + ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]", + name, maybeColon, INDENT_MARKER, text ); + break; + case SAY_AREA: + CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s %c" S_COLOR_BLUE "%s\n", + ignore, prefix, name, location, maybeColon, INDENT_MARKER, text ); + break; + case SAY_PRIVMSG: + case SAY_TPRIVMSG: + color = ( mode == SAY_TPRIVMSG ) ? S_COLOR_CYAN : S_COLOR_GREEN; + CG_Printf( "%s%s[%s" S_COLOR_WHITE " -> %s" S_COLOR_WHITE "]%s %c%s%s\n", + ignore, prefix, name, cgs.clientinfo[ cg.clientNum ].name, + maybeColon, INDENT_MARKER, color, text ); + if( !ignore[0] ) + { + CG_CenterPrint( va( "%sPrivate message from: " S_COLOR_WHITE "%s", + color, name ), 200, GIANTCHAR_WIDTH * 4 ); + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) + clientNum = cg.clientNum; + CG_Printf( ">> to reply, say: /m %d [your message] <<\n", clientNum ); + } + break; + case SAY_RAW: + CG_Printf( "%s\n", text ); + break; + } + + switch( mode ) + { + case SAY_TEAM: + case SAY_AREA: + case SAY_TPRIVMSG: + if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND ); + break; + } + else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + trap_S_StartLocalSound( cgs.media.humanTalkSound, CHAN_LOCAL_SOUND ); + break; + } + default: + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } +} + +/* +================= +CG_VoiceTrack + +return the voice indexed voice track or print errors quietly to console +in case someone is on an unpure server and wants to know which voice pak +is missing or incomplete +================= +*/ +static voiceTrack_t *CG_VoiceTrack( char *voice, int cmd, int track ) +{ + voice_t *v; + voiceCmd_t *c; + voiceTrack_t *t; + + v = BG_VoiceByName( cgs.voices, voice ); + if( !v ) + { + CG_Printf( "[skipnotify]WARNING: could not find voice \"%s\"\n", voice ); + return NULL; + } + c = BG_VoiceCmdByNum( v->cmds, cmd ); + if( !c ) + { + CG_Printf( "[skipnotify]WARNING: could not find command %d " + "in voice \"%s\"\n", cmd, voice ); + return NULL; + } + t = BG_VoiceTrackByNum( c->tracks, track ); + if( !t ) + { + CG_Printf( "[skipnotify]WARNING: could not find track %d for command %d in " + "voice \"%s\"\n", track, cmd, voice ); + return NULL; + } + return t; +} + +/* +================= +CG_ParseVoice + +voice clientNum vChan cmdNum trackNum [sayText] +================= +*/ +static void CG_ParseVoice( void ) +{ + int clientNum; + voiceChannel_t vChan; + char sayText[ MAX_SAY_TEXT] = {""}; + voiceTrack_t *track; + clientInfo_t *ci; + + if( trap_Argc() < 5 || trap_Argc() > 6 ) + return; + + if( trap_Argc() == 6 ) + Q_strncpyz( sayText, CG_Argv( 5 ), sizeof( sayText ) ); + + clientNum = atoi( CG_Argv( 1 ) ); + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) + return; + + vChan = atoi( CG_Argv( 2 ) ); + if( vChan < 0 || vChan >= VOICE_CHAN_NUM_CHANS ) + return; + + if( cg_teamChatsOnly.integer && vChan != VOICE_CHAN_TEAM ) + return; + + ci = &cgs.clientinfo[ clientNum ]; + + // this joker is still talking + if( ci->voiceTime > cg.time ) + return; + + track = CG_VoiceTrack( ci->voice, atoi( CG_Argv( 3 ) ), atoi( CG_Argv( 4 ) ) ); + + // keep track of how long the player will be speaking + // assume it takes 3s to say "*unintelligible gibberish*" + if( track ) + ci->voiceTime = cg.time + track->duration; + else + ci->voiceTime = cg.time + 3000; + + if( !sayText[ 0 ] ) + { + if( track ) + Q_strncpyz( sayText, track->text, sizeof( sayText ) ); + else + Q_strncpyz( sayText, "*unintelligible gibberish*", sizeof( sayText ) ); + } + + if( !cg_noVoiceText.integer ) + { + switch( vChan ) + { + case VOICE_CHAN_ALL: + CG_Say( clientNum, SAY_ALL, sayText ); + break; + case VOICE_CHAN_TEAM: + CG_Say( clientNum, SAY_TEAM, sayText ); + break; + default: + break; + } + } + + // playing voice audio tracks disabled + if( cg_noVoiceChats.integer ) + return; + + // no audio track to play + if( !track ) + return; + + // don't play audio track for lamers + if( Com_ClientListContains( &cgs.ignoreList, clientNum ) ) + return; + + switch( vChan ) + { + case VOICE_CHAN_ALL: + trap_S_StartLocalSound( track->track, CHAN_VOICE ); + break; + case VOICE_CHAN_TEAM: + trap_S_StartLocalSound( track->track, CHAN_VOICE ); + break; + case VOICE_CHAN_LOCAL: + trap_S_StartSound( NULL, clientNum, CHAN_VOICE, track->track ); + break; + default: + break; + } +} + +/* +================= +CG_CenterPrint_f +================= +*/ +static void CG_CenterPrint_f( void ) +{ + CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +} + +/* +================= +CG_Print_f +================= +*/ +static void CG_Print_f( void ) +{ + CG_Printf( "%s", CG_Argv( 1 ) ); +} + +/* +================= +CG_Chat_f +================= +*/ +static void CG_Chat_f( void ) +{ + char id[ 3 ]; + char mode[ 3 ]; + + trap_Argv( 1, id, sizeof( id ) ); + trap_Argv( 2, mode, sizeof( mode ) ); + + CG_Say( atoi( id ), atoi( mode ), CG_Argv( 3 ) ); +} + +/* +================= +CG_ClientLevelShot_f +================= +*/ +static void CG_ClientLevelShot_f( void ) +{ + cg.levelShot = qtrue; +} + +/* +================= +CG_ServerMenu_f +================= +*/ +static void CG_ServerMenu_f( void ) +{ + if( !cg.demoPlayback ) + { + if( trap_Argc( ) == 2 ) + CG_Menu( atoi( CG_Argv( 1 ) ), 0 ); + else if( trap_Argc( ) == 3 ) + CG_Menu( atoi( CG_Argv( 1 ) ), atoi( CG_Argv( 2 ) ) ); + } +} + +/* +================= +CG_ServerCloseMenus_f +================= +*/ +static void CG_ServerCloseMenus_f( void ) +{ + trap_SendConsoleCommand( "closemenus\n" ); +} + +/* +================= +CG_PoisonCloud_f +================= +*/ +static void CG_PoisonCloud_f( void ) +{ + cg.poisonedTime = cg.time; + + if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) + { + cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS ); + CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity ); + CG_AttachToCent( &cg.poisonCloudPS->attachment ); + } +} + +static void CG_GameCmds_f( void ) +{ + int i; + int c = trap_Argc( ); + + /* + There is no corresponding trap_RemoveCommand because a server could send + something like + cmds quit + which would result in trap_RemoveCommand( "quit" ), which would be really bad + */ + for( i = 1; i < c; i++ ) + trap_AddCommand( CG_Argv( i ) ); +} + +static consoleCommand_t svcommands[ ] = +{ + { "cb2", CG_Cuboid_Response }, // set local cuboid + { "cb3", CG_Cuboid_Response }, // set local cuboid and print a "limit exceeded" warning + { "chat", CG_Chat_f }, + { "clientLevelShot", CG_ClientLevelShot_f }, + { "cmds", CG_GameCmds_f }, + { "cp", CG_CenterPrint_f }, + { "cs", CG_ConfigStringModified }, + { "map_restart", CG_MapRestart }, + { "poisoncloud", CG_PoisonCloud_f }, + { "print", CG_Print_f }, + { "scores", CG_ParseScores }, + { "serverclosemenus", CG_ServerCloseMenus_f }, + { "servermenu", CG_ServerMenu_f }, + { "tinfo", CG_ParseTeamInfo }, + { "voice", CG_ParseVoice } +}; + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) +{ + const char *cmd; + consoleCommand_t *command; + + cmd = CG_Argv( 0 ); + command = bsearch( cmd, svcommands, sizeof( svcommands ) / + sizeof( svcommands[ 0 ]), sizeof( svcommands[ 0 ] ), + cmdcmp ); + + if( command ) + { + command->function( ); + return; + } + + CG_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) +{ + while( cgs.serverCommandSequence < latestSequence ) + { + if( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) + CG_ServerCommand( ); + } +} diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c new file mode 100644 index 0000000..673558d --- /dev/null +++ b/src/cgame/cg_snapshot.c @@ -0,0 +1,408 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + + +#include "cg_local.h" + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) +{ + // if the previous snapshot this entity was updated in is at least + // an event window back in time then we can reset the previous event + if( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) + cent->previousEvent = 0; + + cent->trailTime = cg.snap->serverTime; + + VectorCopy( cent->currentState.origin, cent->lerpOrigin ); + VectorCopy( cent->currentState.angles, cent->lerpAngles ); + + if( cent->currentState.eType == ET_PLAYER ) + CG_ResetPlayerEntity( cent ); +} + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) +{ + cent->currentState = cent->nextState; + cent->currentValid = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if( !cent->interpolate ) + CG_ResetEntity( cent ); + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) +{ + int i; + centity_t *cent; + entityState_t *state; + + cg.snap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList( ); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn( ); + + for( i = 0; i < cg.snap->numEntities; i++ ) + { + state = &cg.snap->entities[ i ]; + cent = &cg_entities[ state->number ]; + + memcpy( ¢->currentState, state, sizeof( entityState_t ) ); + //cent->currentState = *state; + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) +{ + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if( !cg.snap ) + CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); + + if( !cg.nextSnap ) + CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); + + // execute any server string commands before transitioning entities + CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); + + // clear the currentValid flag for all entities in the existing snapshot + for( i = 0; i < cg.snap->numEntities; i++ ) + { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + cent->currentValid = qfalse; + } + + // move nextSnap to snap and do the transitions + oldFrame = cg.snap; + cg.snap = cg.nextSnap; + + BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; + + for( i = 0; i < cg.snap->numEntities; i++ ) + { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + CG_TransitionEntity( cent ); + + // remember time of snapshot this entity was last updated in + cent->snapShotTime = cg.snap->serverTime; + } + + cg.nextSnap = NULL; + + // check for playerstate transition events + if( oldFrame ) + { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg.snap->ps; + // teleporting checks are irrespective of prediction + if( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) + cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || + cg_nopredict.integer || cg_synchronousClients.integer ) + CG_TransitionPlayerState( ps, ops ); + } +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) +{ + int num; + entityState_t *es; + centity_t *cent; + + cg.nextSnap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + + // check for extrapolation errors + for( num = 0 ; num < snap->numEntities ; num++ ) + { + es = &snap->entities[ num ]; + cent = &cg_entities[ es->number ]; + + memcpy( ¢->nextState, es, sizeof( entityState_t ) ); + //cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate + if( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) + cent->interpolate = qfalse; + else + cent->interpolate = qtrue; + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) + cg.nextFrameTeleport = qtrue; + else + cg.nextFrameTeleport = qfalse; + + // if changing follow mode, don't interpolate + if( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) + cg.nextFrameTeleport = qtrue; + + // if changing server restarts, don't interpolate + if( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) + cg.nextFrameTeleport = qtrue; + + // sort out solid entities + CG_BuildSolidList( ); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) +{ + qboolean r; + snapshot_t *dest; + + if( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) + { + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i\n", + cg.latestSnapshotNum, cgs.processedSnapshotNum ); + } + + while( cgs.processedSnapshotNum < cg.latestSnapshotNum ) + { + // decide which of the two slots to load it into + if( cg.snap == &cg.activeSnapshots[ 0 ] ) + dest = &cg.activeSnapshots[ 1 ]; + else + dest = &cg.activeSnapshots[ 0 ]; + + // try to read the snapshot from the client system + cgs.processedSnapshotNum++; + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); + + // FIXME: why would trap_GetSnapshot return a snapshot with the same server time + if( cg.snap && r && dest->serverTime == cg.snap->serverTime ) + { + //continue; + } + + // if it succeeded, return + if( r ) + { + CG_AddLagometerSnapshotInfo( dest ); + return dest; + } + + // a GetSnapshot will return failure if the snapshot + // never arrived, or is so old that its entities + // have been shoved off the end of the circular + // buffer in the client system. + + // record as a dropped packet + CG_AddLagometerSnapshotInfo( NULL ); + + // If there are additional snapshots, continue trying to + // read them. + } + + // nothing left to read + return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg.snap after exiting this function, +then a 3D game view cannot be rendered. This should only happen +right after the initial connection. After cg.snap has been valid +once, it will never turn invalid. + +Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) +{ + snapshot_t *snap; + int n; + + // see what the latest snapshot the client system has is + trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); + + if( n != cg.latestSnapshotNum ) + { + if( n < cg.latestSnapshotNum ) + { + // this should never happen + CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); + } + + cg.latestSnapshotNum = n; + } + + // If we have yet to receive a snapshot, check for it. + // Once we have gotten the first snapshot, cg.snap will + // always have valid data for the rest of the game + while( !cg.snap ) + { + snap = CG_ReadNextSnapshot( ); + + if( !snap ) + { + // we can't continue until we get a snapshot + return; + } + + // set our weapon selection to what + // the playerstate is currently using + if( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) + CG_SetInitialSnapshot( snap ); + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg.time to interpolate towards, or we run + // out of available snapshots + do + { + // if we don't have a nextframe, try and read a new one in + if( !cg.nextSnap ) + { + snap = CG_ReadNextSnapshot( ); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if( !snap ) + break; + + CG_SetNextSnap( snap ); + + // if time went backwards, we have a level restart + if( cg.nextSnap->serverTime < cg.snap->serverTime ) + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); + } + + // if our time is < nextFrame's, we have a nice interpolating state + if( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) + break; + + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot( ); + } while( 1 ); + + // assert our valid conditions upon exiting + if( cg.snap == NULL ) + CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); + + if( cg.time < cg.snap->serverTime ) + { + // this can happen right after a vid_restart + cg.time = cg.snap->serverTime; + } + + if( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) + CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); +} + diff --git a/src/cgame/cg_syscalls.asm b/src/cgame/cg_syscalls.asm new file mode 100644 index 0000000..2537c91 --- /dev/null +++ b/src/cgame/cg_syscalls.asm @@ -0,0 +1,121 @@ +code + +equ trap_Print -1 +equ trap_Error -2 +equ trap_Milliseconds -3 +equ trap_Cvar_Register -4 +equ trap_Cvar_Update -5 +equ trap_Cvar_Set -6 +equ trap_Cvar_VariableStringBuffer -7 +equ trap_Argc -8 +equ trap_Argv -9 +equ trap_Args -10 +equ trap_FS_FOpenFile -11 +equ trap_FS_Read -12 +equ trap_FS_Write -13 +equ trap_FS_FCloseFile -14 +equ trap_SendConsoleCommand -15 +equ trap_AddCommand -16 +equ trap_SendClientCommand -17 +equ trap_UpdateScreen -18 +equ trap_CM_LoadMap -19 +equ trap_CM_NumInlineModels -20 +equ trap_CM_InlineModel -21 +equ trap_CM_LoadModel -22 +equ trap_CM_TempBoxModel -23 +equ trap_CM_PointContents -24 +equ trap_CM_TransformedPointContents -25 +equ trap_CM_BoxTrace -26 +equ trap_CM_TransformedBoxTrace -27 +equ trap_CM_MarkFragments -28 +equ trap_S_StartSound -29 +equ trap_S_StartLocalSound -30 +equ trap_S_ClearLoopingSounds -31 +equ trap_S_AddLoopingSound -32 +equ trap_S_UpdateEntityPosition -33 +equ trap_S_Respatialize -34 +equ trap_S_RegisterSound -35 +equ trap_S_StartBackgroundTrack -36 +equ trap_R_LoadWorldMap -37 +equ trap_R_RegisterModel -38 +equ trap_R_RegisterSkin -39 +equ trap_R_RegisterShader -40 +equ trap_R_ClearScene -41 +equ trap_R_AddRefEntityToScene -42 +equ trap_R_AddPolyToScene -43 +equ trap_R_AddLightToScene -44 +equ trap_R_RenderScene -45 +equ trap_R_SetColor -46 +equ trap_R_SetClipRegion -47 +equ trap_R_DrawStretchPic -48 +equ trap_R_ModelBounds -49 +equ trap_R_LerpTag -50 +equ trap_GetGlconfig -51 +equ trap_GetGameState -52 +equ trap_GetCurrentSnapshotNumber -53 +equ trap_GetSnapshot -54 +equ trap_GetServerCommand -55 +equ trap_GetCurrentCmdNumber -56 +equ trap_GetUserCmd -57 +equ trap_SetUserCmdValue -58 +equ trap_R_RegisterShaderNoMip -59 +equ trap_MemoryRemaining -60 +equ trap_R_RegisterFont -61 +equ trap_Key_IsDown -62 +equ trap_Key_GetCatcher -63 +equ trap_Key_SetCatcher -64 +equ trap_Key_GetKey -65 +equ trap_S_StopBackgroundTrack -66 +equ trap_RealTime -67 +equ trap_SnapVector -68 +equ trap_RemoveCommand -69 +equ trap_R_LightForPoint -70 +equ trap_CIN_PlayCinematic -71 +equ trap_CIN_StopCinematic -72 +equ trap_CIN_RunCinematic -73 +equ trap_CIN_DrawCinematic -74 +equ trap_CIN_SetExtents -75 +equ trap_R_RemapShader -76 +equ trap_S_AddRealLoopingSound -77 +equ trap_S_StopLoopingSound -78 +equ trap_CM_TempCapsuleModel -79 +equ trap_CM_CapsuleTrace -80 +equ trap_CM_TransformedCapsuleTrace -81 +equ trap_R_AddAdditiveLightToScene -82 +equ trap_GetEntityToken -83 +equ trap_R_AddPolysToScene -84 +equ trap_R_inPVS -85 +equ trap_FS_Seek -86 +equ trap_FS_GetFileList -87 +equ trap_LiteralArgs -88 +equ trap_CM_BiSphereTrace -89 +equ trap_CM_TransformedBiSphereTrace -90 +equ trap_GetDemoState -91 +equ trap_GetDemoPos -92 +equ trap_GetDemoName -93 +equ trap_Key_KeynumToStringBuf -94 +equ trap_Key_GetBindingBuf -95 +equ trap_Key_SetBinding -96 + +equ trap_Parse_AddGlobalDefine -97 +equ trap_Parse_LoadSource -98 +equ trap_Parse_FreeSource -99 +equ trap_Parse_ReadToken -100 +equ trap_Parse_SourceFileAndLine -101 +equ trap_Key_SetOverstrikeMode -102 +equ trap_Key_GetOverstrikeMode -103 + +equ trap_S_SoundDuration -104 + +equ memset -201 +equ memcpy -202 +equ strncpy -203 +equ sin -204 +equ cos -205 +equ atan2 -206 +equ sqrt -207 +equ floor -208 +equ ceil -209 +equ testPrintInt -210 +equ testPrintFloat -211 + diff --git a/src/cgame/cg_syscalls.c b/src/cgame/cg_syscalls.c new file mode 100644 index 0000000..0c0b722 --- /dev/null +++ b/src/cgame/cg_syscalls.c @@ -0,0 +1,592 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_syscalls.c -- this file is only included when building a dll +// cg_syscalls.asm is included instead when building a qvm + + +#include "cg_local.h" + +static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; + + +Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) +{ + syscall = syscallptr; +} + + +int PASSFLOAT( float x ) +{ + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_Print( const char *fmt ) +{ + syscall( CG_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) +{ + syscall( CG_ERROR, fmt ); +} + +int trap_Milliseconds( void ) +{ + return syscall( CG_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) +{ + syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) +{ + syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) +{ + syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) +{ + syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Argc( void ) +{ + return syscall( CG_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) +{ + syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void trap_Args( char *buffer, int bufferLength ) +{ + syscall( CG_ARGS, buffer, bufferLength ); +} + +void trap_LiteralArgs( char *buffer, int bufferLength ) +{ + syscall( CG_LITERAL_ARGS, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) +{ + return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) +{ + syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) +{ + syscall( CG_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) +{ + syscall( CG_FS_FCLOSEFILE, f ); +} + +void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ) +{ + syscall( CG_FS_SEEK, f, offset, origin ); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) +{ + return syscall( CG_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +void trap_SendConsoleCommand( const char *text ) +{ + syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void trap_AddCommand( const char *cmdName ) +{ + syscall( CG_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) +{ + syscall( CG_REMOVECOMMAND, cmdName ); +} + +void trap_SendClientCommand( const char *s ) +{ + syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void trap_UpdateScreen( void ) +{ + syscall( CG_UPDATESCREEN ); +} + +void trap_CM_LoadMap( const char *mapname ) +{ + syscall( CG_CM_LOADMAP, mapname ); +} + +int trap_CM_NumInlineModels( void ) +{ + return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) +{ + return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) +{ + return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) +{ + return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); +} + +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) +{ + return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, + const vec3_t angles ) +{ + return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) +{ + syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) +{ + syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) +{ + syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) +{ + syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_BiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask ) +{ + syscall( CG_CM_BISPHERETRACE, results, start, end, + PASSFLOAT( startRad ), PASSFLOAT( endRad ), model, mask ); +} + +void trap_CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask, + const vec3_t origin ) +{ + syscall( CG_CM_TRANSFORMEDBISPHERETRACE, results, start, end, PASSFLOAT( startRad ), + PASSFLOAT( endRad ), model, mask, origin ); +} + +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ) +{ + return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, + pointBuffer, maxFragments, fragmentBuffer ); +} + +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) +{ + syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) +{ + syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void trap_S_ClearLoopingSounds( qboolean killall ) +{ + syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); +} + +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) +{ + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) +{ + syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_StopLoopingSound( int entityNum ) +{ + syscall( CG_S_STOPLOOPINGSOUND, entityNum ); +} + +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) +{ + syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) +{ + return syscall( CG_S_REGISTERSOUND, sample, compressed ); +} + +int trap_S_SoundDuration( sfxHandle_t handle ) +{ + return syscall( CG_S_SOUNDDURATION, handle ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) +{ + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +void trap_R_LoadWorldMap( const char *mapname ) +{ + syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) +{ + return syscall( CG_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) +{ + return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) +{ + return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) +{ + return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ) +{ + syscall(CG_R_REGISTERFONT, fontName, pointSize, font ); +} + +void trap_R_ClearScene( void ) +{ + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) +{ + syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) +{ + syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) +{ + return syscall( CG_R_INPVS, p1, p2 ); +} + +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) +{ + syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); +} + +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) +{ + return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) +{ + syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) +{ + syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) +{ + syscall( CG_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) +{ + syscall( CG_R_SETCOLOR, rgba ); +} + +void trap_R_SetClipRegion( const float *region ) +{ + syscall( CG_R_SETCLIPREGION, region ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) +{ + syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), + PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ) +{ + return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) +{ + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) +{ + syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) +{ + syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) +{ + syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) +{ + return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean trap_GetServerCommand( int serverCommandNumber ) +{ + return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) +{ + return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) +{ + return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) +{ + syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT( sensitivityScale ) ); +} + +void testPrintInt( char *string, int i ) +{ + syscall( CG_TESTPRINTINT, string, i ); +} + +void testPrintFloat( char *string, float f ) +{ + syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); +} + +int trap_MemoryRemaining( void ) +{ + return syscall( CG_MEMORY_REMAINING ); +} + +qboolean trap_Key_IsDown( int keynum ) +{ + return syscall( CG_KEY_ISDOWN, keynum ); +} + +int trap_Key_GetCatcher( void ) +{ + return syscall( CG_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) +{ + syscall( CG_KEY_SETCATCHER, catcher ); +} + +int trap_Key_GetKey( const char *binding ) +{ + return syscall( CG_KEY_GETKEY, binding ); +} + +int trap_Parse_AddGlobalDefine( char *define ) +{ + return syscall( CG_PARSE_ADD_GLOBAL_DEFINE, define ); +} + +int trap_Parse_LoadSource( const char *filename ) +{ + return syscall( CG_PARSE_LOAD_SOURCE, filename ); +} + +int trap_Parse_FreeSource( int handle ) +{ + return syscall( CG_PARSE_FREE_SOURCE, handle ); +} + +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ) +{ + return syscall( CG_PARSE_READ_TOKEN, handle, pc_token ); +} + +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) +{ + return syscall( CG_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_S_StopBackgroundTrack( void ) +{ + syscall( CG_S_STOPBACKGROUNDTRACK ); +} + +int trap_RealTime(qtime_t *qtime) +{ + return syscall( CG_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) +{ + syscall( CG_SNAPVECTOR, v ); +} + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ) +{ + return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic( int handle ) +{ + return syscall(CG_CIN_STOPCINEMATIC, handle); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic( int handle ) +{ + return syscall(CG_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic( int handle ) +{ + syscall(CG_CIN_DRAWCINEMATIC, handle); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents( int handle, int x, int y, int w, int h ) +{ + syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); +} + +int trap_GetDemoState( void ) +{ + return syscall( CG_GETDEMOSTATE ); +} + +int trap_GetDemoPos( void ) +{ + return syscall( CG_GETDEMOPOS ); +} + +void trap_GetDemoName( char *buffer, int size ) +{ + syscall( CG_GETDEMONAME, buffer, size ); +} + +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { + syscall( CG_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen ); +} + +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + syscall( CG_KEY_GETBINDINGBUF, keynum, buf, buflen ); +} + +void trap_Key_SetBinding( int keynum, const char *binding ) { + syscall( CG_KEY_SETBINDING, keynum, binding ); +} + +void trap_Key_SetOverstrikeMode( qboolean state ) { + syscall( CG_KEY_SETOVERSTRIKEMODE, state ); +} + +qboolean trap_Key_GetOverstrikeMode( void ) { + return syscall( CG_KEY_GETOVERSTRIKEMODE ); +} diff --git a/src/cgame/cg_trails.c b/src/cgame/cg_trails.c new file mode 100644 index 0000000..05f5ef5 --- /dev/null +++ b/src/cgame/cg_trails.c @@ -0,0 +1,1531 @@ +/* +=========================================================================== +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_trails.c -- the trail system + + +#include "cg_local.h" + +static baseTrailSystem_t baseTrailSystems[ MAX_BASETRAIL_SYSTEMS ]; +static baseTrailBeam_t baseTrailBeams[ MAX_BASETRAIL_BEAMS ]; +static int numBaseTrailSystems = 0; +static int numBaseTrailBeams = 0; + +static trailSystem_t trailSystems[ MAX_TRAIL_SYSTEMS ]; +static trailBeam_t trailBeams[ MAX_TRAIL_BEAMS ]; + +/* +=============== +CG_CalculateBeamNodeProperties + +Fills in trailBeamNode_t.textureCoord +=============== +*/ +static void CG_CalculateBeamNodeProperties( trailBeam_t *tb ) +{ + trailBeamNode_t *i = NULL; + trailSystem_t *ts; + baseTrailBeam_t *btb; + float nodeDistances[ MAX_TRAIL_BEAM_NODES ]; + float totalDistance = 0.0f, position = 0.0f; + int j, numNodes = 0; + float TCRange, widthRange, alphaRange; + vec3_t colorRange; + float fadeAlpha = 1.0f; + + if( !tb || !tb->nodes ) + return; + + ts = tb->parent; + btb = tb->class; + + if( ts->destroyTime > 0 && btb->fadeOutTime ) + { + fadeAlpha -= (float)( cg.time - ts->destroyTime ) / btb->fadeOutTime; + + if( fadeAlpha < 0.0f ) + fadeAlpha = 0.0f; + } + + TCRange = tb->class->backTextureCoord - + tb->class->frontTextureCoord; + widthRange = tb->class->backWidth - + tb->class->frontWidth; + alphaRange = tb->class->backAlpha - + tb->class->frontAlpha; + VectorSubtract( tb->class->backColor, + tb->class->frontColor, colorRange ); + + for( i = tb->nodes; i && i->next; i = i->next ) + { + nodeDistances[ numNodes++ ] = + Distance( i->position, i->next->position ); + } + + for( j = 0; j < numNodes; j++ ) + totalDistance += nodeDistances[ j ]; + + for( j = 0, i = tb->nodes; i; i = i->next, j++ ) + { + if( tb->class->textureType == TBTT_STRETCH ) + { + i->textureCoord = tb->class->frontTextureCoord + + ( ( position / totalDistance ) * TCRange ); + } + else if( tb->class->textureType == TBTT_REPEAT ) + { + if( tb->class->clampToBack ) + i->textureCoord = ( totalDistance - position ) / + tb->class->repeatLength; + else + i->textureCoord = position / tb->class->repeatLength; + } + + i->halfWidth = ( tb->class->frontWidth + + ( ( position / totalDistance ) * widthRange ) ) / 2.0f; + i->alpha = (byte)( (float)0xFF * ( tb->class->frontAlpha + + ( ( position / totalDistance ) * alphaRange ) ) * fadeAlpha ); + VectorMA( tb->class->frontColor, ( position / totalDistance ), + colorRange, i->color ); + + position += nodeDistances[ j ]; + } +} + +/* +=============== +CG_LightVertex + +Lights a particular vertex +=============== +*/ +static void CG_LightVertex( vec3_t point, byte alpha, byte *rgba ) +{ + int i; + vec3_t alight, dlight, lightdir; + + trap_R_LightForPoint( point, alight, dlight, lightdir ); + for( i = 0; i <= 2; i++ ) + rgba[ i ] = (int)alight[ i ]; + + rgba[ 3 ] = alpha; +} + +/* +=============== +CG_RenderBeam + +Renders a beam +=============== +*/ +static void CG_RenderBeam( trailBeam_t *tb ) +{ + trailBeamNode_t *i = NULL; + trailBeamNode_t *prev = NULL; + trailBeamNode_t *next = NULL; + vec3_t up; + polyVert_t verts[ ( MAX_TRAIL_BEAM_NODES - 1 ) * 4 ]; + int numVerts = 0; + baseTrailBeam_t *btb; + trailSystem_t *ts; + baseTrailSystem_t *bts; + + if( !tb || !tb->nodes ) + return; + + btb = tb->class; + ts = tb->parent; + bts = ts->class; + + if( bts->thirdPersonOnly && + ( CG_AttachmentCentNum( &ts->frontAttachment ) == cg.snap->ps.clientNum || + CG_AttachmentCentNum( &ts->backAttachment ) == cg.snap->ps.clientNum ) && + !cg.renderingThirdPerson ) + return; + + CG_CalculateBeamNodeProperties( tb ); + + i = tb->nodes; + + do + { + prev = i->prev; + next = i->next; + + if( prev && next ) + { + //this node has two neighbours + GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up ); + } + else if( !prev && next ) + { + //this is the front + GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up ); + } + else if( prev && !next ) + { + //this is the back + GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up ); + } + else + break; + + if( prev ) + { + VectorMA( i->position, i->halfWidth, up, verts[ numVerts ].xyz ); + verts[ numVerts ].st[ 0 ] = i->textureCoord; + verts[ numVerts ].st[ 1 ] = 1.0f; + + if( btb->realLight ) + CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); + else + { + VectorCopy( i->color, verts[ numVerts ].modulate ); + verts[ numVerts ].modulate[ 3 ] = i->alpha; + } + + numVerts++; + + VectorMA( i->position, -i->halfWidth, up, verts[ numVerts ].xyz ); + verts[ numVerts ].st[ 0 ] = i->textureCoord; + verts[ numVerts ].st[ 1 ] = 0.0f; + + if( btb->realLight ) + CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); + else + { + VectorCopy( i->color, verts[ numVerts ].modulate ); + verts[ numVerts ].modulate[ 3 ] = i->alpha; + } + + numVerts++; + } + + if( next ) + { + VectorMA( i->position, -i->halfWidth, up, verts[ numVerts ].xyz ); + verts[ numVerts ].st[ 0 ] = i->textureCoord; + verts[ numVerts ].st[ 1 ] = 0.0f; + + if( btb->realLight ) + CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); + else + { + VectorCopy( i->color, verts[ numVerts ].modulate ); + verts[ numVerts ].modulate[ 3 ] = i->alpha; + } + + numVerts++; + + VectorMA( i->position, i->halfWidth, up, verts[ numVerts ].xyz ); + verts[ numVerts ].st[ 0 ] = i->textureCoord; + verts[ numVerts ].st[ 1 ] = 1.0f; + + if( btb->realLight ) + CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); + else + { + VectorCopy( i->color, verts[ numVerts ].modulate ); + verts[ numVerts ].modulate[ 3 ] = i->alpha; + } + + numVerts++; + } + + i = i->next; + } while( i ); + + trap_R_AddPolysToScene( tb->class->shader, 4, &verts[ 0 ], numVerts / 4 ); +} + +/* +=============== +CG_AllocateBeamNode + +Allocates a trailBeamNode_t from a trailBeam_t's nodePool +=============== +*/ +static trailBeamNode_t *CG_AllocateBeamNode( trailBeam_t *tb ) +{ + baseTrailBeam_t *btb = tb->class; + int i; + trailBeamNode_t *tbn; + + for( i = 0; i < MAX_TRAIL_BEAM_NODES; i++ ) + { + tbn = &tb->nodePool[ i ]; + if( !tbn->used ) + { + tbn->timeLeft = btb->segmentTime; + tbn->prev = NULL; + tbn->next = NULL; + tbn->used = qtrue; + return tbn; + } + } + + // no space left + return NULL; +} + +/* +=============== +CG_DestroyBeamNode + +Removes a node from a beam +Returns the new head +=============== +*/ +static trailBeamNode_t *CG_DestroyBeamNode( trailBeamNode_t *tbn ) +{ + trailBeamNode_t *newHead = NULL; + + if( tbn->prev ) + { + if( tbn->next ) + { + // node is in the middle + tbn->prev->next = tbn->next; + tbn->next->prev = tbn->prev; + } + else // node is at the back + tbn->prev->next = NULL; + + // find the new head (shouldn't have changed) + newHead = tbn->prev; + + while( newHead->prev ) + newHead = newHead->prev; + } + else if( tbn->next ) + { + //node is at the front + tbn->next->prev = NULL; + newHead = tbn->next; + } + + tbn->prev = NULL; + tbn->next = NULL; + tbn->used = qfalse; + + return newHead; +} + +/* +=============== +CG_FindLastBeamNode + +Returns the last beam node in a beam +=============== +*/ +static trailBeamNode_t *CG_FindLastBeamNode( trailBeam_t *tb ) +{ + trailBeamNode_t *i = tb->nodes; + + while( i && i->next ) + i = i->next; + + return i; +} + +/* +=============== +CG_CountBeamNodes + +Returns the number of nodes in a beam +=============== +*/ +static int CG_CountBeamNodes( trailBeam_t *tb ) +{ + trailBeamNode_t *i = tb->nodes; + int numNodes = 0; + + while( i ) + { + numNodes++; + i = i->next; + } + + return numNodes; +} + +/* +=============== +CG_PrependBeamNode + +Prepend a new beam node to the front of a beam +Returns the new node +=============== +*/ +static trailBeamNode_t *CG_PrependBeamNode( trailBeam_t *tb ) +{ + trailBeamNode_t *i; + + if( tb->nodes ) + { + // prepend another node + i = CG_AllocateBeamNode( tb ); + + if( i ) + { + i->next = tb->nodes; + tb->nodes->prev = i; + tb->nodes = i; + } + } + else //add first node + { + i = CG_AllocateBeamNode( tb ); + + if( i ) + tb->nodes = i; + } + + return i; +} + +/* +=============== +CG_AppendBeamNode + +Append a new beam node to the back of a beam +Returns the new node +=============== +*/ +static trailBeamNode_t *CG_AppendBeamNode( trailBeam_t *tb ) +{ + trailBeamNode_t *last, *i; + + if( tb->nodes ) + { + // append another node + last = CG_FindLastBeamNode( tb ); + i = CG_AllocateBeamNode( tb ); + + if( i ) + { + last->next = i; + i->prev = last; + i->next = NULL; + } + } + else //add first node + { + i = CG_AllocateBeamNode( tb ); + + if( i ) + tb->nodes = i; + } + + return i; +} + +/* +=============== +CG_ApplyJitters +=============== +*/ +static void CG_ApplyJitters( trailBeam_t *tb ) +{ + trailBeamNode_t *i = NULL; + int j; + baseTrailBeam_t *btb; + trailSystem_t *ts; + trailBeamNode_t *start; + trailBeamNode_t *end; + + if( !tb || !tb->nodes ) + return; + + btb = tb->class; + ts = tb->parent; + + for( j = 0; j < btb->numJitters; j++ ) + { + if( tb->nextJitterTimes[ j ] <= cg.time ) + { + for( i = tb->nodes; i; i = i->next ) + { + i->jitters[ j ][ 0 ] = ( crandom( ) * btb->jitters[ j ].magnitude ); + i->jitters[ j ][ 1 ] = ( crandom( ) * btb->jitters[ j ].magnitude ); + } + + tb->nextJitterTimes[ j ] = cg.time + btb->jitters[ j ].period; + } + } + + start = tb->nodes; + end = CG_FindLastBeamNode( tb ); + + if( !btb->jitterAttachments ) + { + if( CG_Attached( &ts->frontAttachment ) && start->next ) + start = start->next; + + if( CG_Attached( &ts->backAttachment ) && end->prev ) + end = end->prev; + } + + for( i = start; i; i = i->next ) + { + vec3_t forward, right, up; + trailBeamNode_t *prev; + trailBeamNode_t *next; + float upJitter = 0.0f, rightJitter = 0.0f; + + prev = i->prev; + next = i->next; + + if( prev && next ) + { + //this node has two neighbours + GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up ); + VectorSubtract( next->position, prev->position, forward ); + } + else if( !prev && next ) + { + //this is the front + GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up ); + VectorSubtract( next->position, i->position, forward ); + } + else if( prev && !next ) + { + //this is the back + GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up ); + VectorSubtract( i->position, prev->position, forward ); + } + + VectorNormalize( forward ); + CrossProduct( forward, up, right ); + VectorNormalize( right ); + + for( j = 0; j < btb->numJitters; j++ ) + { + upJitter += i->jitters[ j ][ 0 ]; + rightJitter += i->jitters[ j ][ 1 ]; + } + + VectorMA( i->position, upJitter, up, i->position ); + VectorMA( i->position, rightJitter, right, i->position ); + + if( i == end ) + break; + } +} + +/* +=============== +CG_UpdateBeam + +Updates a beam +=============== +*/ +static void CG_UpdateBeam( trailBeam_t *tb ) +{ + baseTrailBeam_t *btb; + trailSystem_t *ts; + trailBeamNode_t *i; + int deltaTime; + int nodesToAdd; + int j; + int numNodes; + + if( !tb ) + return; + + btb = tb->class; + ts = tb->parent; + + deltaTime = cg.time - tb->lastEvalTime; + tb->lastEvalTime = cg.time; + + // first make sure this beam has enough nodes + if( ts->destroyTime <= 0 || btb->fadeOutTime > 0 ) + { + nodesToAdd = btb->numSegments - CG_CountBeamNodes( tb ) + 1; + + while( nodesToAdd-- ) + { + i = CG_AppendBeamNode( tb ); + + if( !tb->nodes->next && CG_Attached( &ts->frontAttachment ) ) + { + // this is the first node to be added + if( !CG_AttachmentPoint( &ts->frontAttachment, i->refPosition ) ) + CG_DestroyTrailSystem( &ts ); + } + else + VectorCopy( i->prev->refPosition, i->refPosition ); + } + } + + numNodes = CG_CountBeamNodes( tb ); + + for( i = tb->nodes; i; i = i->next ) + VectorCopy( i->refPosition, i->position ); + + if( CG_Attached( &ts->frontAttachment ) && CG_Attached( &ts->backAttachment ) ) + { + // beam between two attachments + vec3_t dir, front, back; + + if( ts->destroyTime > 0 && ( cg.time - ts->destroyTime ) >= btb->fadeOutTime ) + { + tb->valid = qfalse; + return; + } + + if( !CG_AttachmentPoint( &ts->frontAttachment, front ) ) + CG_DestroyTrailSystem( &ts ); + + if( !CG_AttachmentPoint( &ts->backAttachment, back ) ) + CG_DestroyTrailSystem( &ts ); + + VectorSubtract( back, front, dir ); + + for( j = 0, i = tb->nodes; i; i = i->next, j++ ) + { + float scale = (float)j / (float)( numNodes - 1 ); + + VectorMA( front, scale, dir, i->position ); + } + } + else if( CG_Attached( &ts->frontAttachment ) ) + { + // beam from one attachment + + // cull the trail tail + i = CG_FindLastBeamNode( tb ); + + if( i && i->timeLeft >= 0 ) + { + i->timeLeft -= deltaTime; + + if( i->timeLeft < 0 ) + { + tb->nodes = CG_DestroyBeamNode( i ); + + if( !tb->nodes ) + { + tb->valid = qfalse; + return; + } + + // if the ts has been destroyed, stop creating new nodes + if( ts->destroyTime <= 0 ) + CG_PrependBeamNode( tb ); + } + else if( i->timeLeft >= 0 && i->prev ) + { + vec3_t dir; + float length; + + VectorSubtract( i->refPosition, i->prev->refPosition, dir ); + length = VectorNormalize( dir ) * + ( (float)i->timeLeft / (float)tb->class->segmentTime ); + + VectorMA( i->prev->refPosition, length, dir, i->position ); + } + } + + if( tb->nodes ) + { + if( !CG_AttachmentPoint( &ts->frontAttachment, tb->nodes->refPosition ) ) + CG_DestroyTrailSystem( &ts ); + + VectorCopy( tb->nodes->refPosition, tb->nodes->position ); + } + } + + CG_ApplyJitters( tb ); +} + +/* +=============== +CG_ParseTrailBeamColor +=============== +*/ +static qboolean CG_ParseTrailBeamColor( byte *c, char **text_p ) +{ + char *token; + int i; + + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( text_p ); + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) ); + } + + return qtrue; +} + +/* +=============== +CG_ParseTrailBeam + +Parse a trail beam +=============== +*/ +static qboolean CG_ParseTrailBeam( baseTrailBeam_t *btb, char **text_p ) +{ + char *token; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "segments" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->numSegments = atoi_neg( token, qfalse ); + + if( btb->numSegments >= MAX_TRAIL_BEAM_NODES ) + { + btb->numSegments = MAX_TRAIL_BEAM_NODES - 1; + CG_Printf( S_COLOR_YELLOW "WARNING: too many segments in trail beam\n" ); + } + continue; + } + else if( !Q_stricmp( token, "width" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->frontWidth = atof_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "-" ) ) + btb->backWidth = btb->frontWidth; + else + btb->backWidth = atof_neg( token, qfalse ); + continue; + } + else if( !Q_stricmp( token, "alpha" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->frontAlpha = atof_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "-" ) ) + btb->backAlpha = btb->frontAlpha; + else + btb->backAlpha = atof_neg( token, qfalse ); + continue; + } + else if( !Q_stricmp( token, "color" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseTrailBeamColor( btb->frontColor, text_p ) ) + break; + + token = COM_Parse( text_p ); + if( Q_stricmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + break; + } + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + btb->backColor[ 0 ] = btb->frontColor[ 0 ]; + btb->backColor[ 1 ] = btb->frontColor[ 1 ]; + btb->backColor[ 2 ] = btb->frontColor[ 2 ]; + } + else if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseTrailBeamColor( btb->backColor, text_p ) ) + break; + + token = COM_Parse( text_p ); + if( Q_stricmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + break; + } + } + else + { + CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); + break; + } + } + else + { + CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); + break; + } + + continue; + } + else if( !Q_stricmp( token, "segmentTime" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->segmentTime = atoi_neg( token, qfalse ); + continue; + } + else if( !Q_stricmp( token, "fadeOutTime" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->fadeOutTime = atoi_neg( token, qfalse ); + continue; + } + else if( !Q_stricmp( token, "shader" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + Q_strncpyz( btb->shaderName, token, MAX_QPATH ); + + continue; + } + else if( !Q_stricmp( token, "textureType" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "stretch" ) ) + { + btb->textureType = TBTT_STRETCH; + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->frontTextureCoord = atof_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->backTextureCoord = atof_neg( token, qfalse ); + } + else if( !Q_stricmp( token, "repeat" ) ) + { + btb->textureType = TBTT_REPEAT; + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "front" ) ) + btb->clampToBack = qfalse; + else if( !Q_stricmp( token, "back" ) ) + btb->clampToBack = qtrue; + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown textureType clamp \"%s\"\n", token ); + break; + } + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->repeatLength = atof_neg( token, qfalse ); + } + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown textureType \"%s\"\n", token ); + break; + } + + continue; + } + else if( !Q_stricmp( token, "realLight" ) ) + { + btb->realLight = qtrue; + + continue; + } + else if( !Q_stricmp( token, "jitter" ) ) + { + if( btb->numJitters == MAX_TRAIL_BEAM_JITTERS ) + { + CG_Printf( S_COLOR_RED "ERROR: too many jitters\n", token ); + break; + } + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->jitters[ btb->numJitters ].magnitude = atof_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->jitters[ btb->numJitters ].period = atoi_neg( token, qfalse ); + + btb->numJitters++; + + continue; + } + else if( !Q_stricmp( token, "jitterAttachments" ) ) + { + btb->jitterAttachments = qtrue; + + continue; + } + else if( !Q_stricmp( token, "}" ) ) + return qtrue; //reached the end of this trail beam + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail beam\n", token ); + return qfalse; + } + } + + return qfalse; +} + +/* +=============== +CG_InitialiseBaseTrailBeam +=============== +*/ +static void CG_InitialiseBaseTrailBeam( baseTrailBeam_t *btb ) +{ + memset( btb, 0, sizeof( baseTrailBeam_t ) ); + + btb->numSegments = 1; + btb->frontWidth = btb->backWidth = 1.0f; + btb->frontAlpha = btb->backAlpha = 1.0f; + memset( btb->frontColor, 0xFF, sizeof( btb->frontColor ) ); + memset( btb->backColor, 0xFF, sizeof( btb->backColor ) ); + + btb->segmentTime = 100; + + btb->textureType = TBTT_STRETCH; + btb->frontTextureCoord = 0.0f; + btb->backTextureCoord = 1.0f; +} + +/* +=============== +CG_ParseTrailSystem + +Parse a trail system section +=============== +*/ +static qboolean CG_ParseTrailSystem( baseTrailSystem_t *bts, char **text_p, const char *name ) +{ + char *token; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "{" ) ) + { + CG_InitialiseBaseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ] ); + + if( !CG_ParseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ], text_p ) ) + { + CG_Printf( S_COLOR_RED "ERROR: failed to parse trail beam\n" ); + return qfalse; + } + + if( bts->numBeams == MAX_BEAMS_PER_SYSTEM ) + { + CG_Printf( S_COLOR_RED "ERROR: trail system has > %d beams\n", MAX_BEAMS_PER_SYSTEM ); + return qfalse; + } + else if( numBaseTrailBeams == MAX_BASETRAIL_BEAMS ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of trail beams (%d) reached\n", + MAX_BASETRAIL_BEAMS ); + return qfalse; + } + else + { + //start parsing beams again + bts->beams[ bts->numBeams ] = &baseTrailBeams[ numBaseTrailBeams ]; + bts->numBeams++; + numBaseTrailBeams++; + } + continue; + } + else if( !Q_stricmp( token, "thirdPersonOnly" ) ) + bts->thirdPersonOnly = qtrue; + else if( !Q_stricmp( token, "lifeTime" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + bts->lifeTime = atoi_neg( token, qfalse ); + continue; + } + else if( !Q_stricmp( token, "beam" ) ) //acceptable text + continue; + else if( !Q_stricmp( token, "}" ) ) + { + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "Parsed trail system %s\n", name ); + + return qtrue; //reached the end of this trail system + } + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail system %s\n", token, bts->name ); + return qfalse; + } + } + + return qfalse; +} + +/* +=============== +CG_ParseTrailFile + +Load the trail systems from a trail file +=============== +*/ +static qboolean CG_ParseTrailFile( const char *fileName ) +{ + char *text_p; + int i; + int len; + char *token; + char text[ 32000 ]; + char tsName[ MAX_QPATH ]; + qboolean tsNameSet = qfalse; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + if( len <= 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( S_COLOR_RED "ERROR: trail file %s is %s\n", fileName, + len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "{" ) ) + { + if( tsNameSet ) + { + //check for name space clashes + for( i = 0; i < numBaseTrailSystems; i++ ) + { + if( !Q_stricmp( baseTrailSystems[ i ].name, tsName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: a trail system is already named %s\n", tsName ); + return qfalse; + } + } + + Q_strncpyz( baseTrailSystems[ numBaseTrailSystems ].name, tsName, MAX_QPATH ); + + if( !CG_ParseTrailSystem( &baseTrailSystems[ numBaseTrailSystems ], &text_p, tsName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse trail system %s\n", fileName, tsName ); + return qfalse; + } + + //start parsing trail systems again + tsNameSet = qfalse; + + if( numBaseTrailSystems == MAX_BASETRAIL_SYSTEMS ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of trail systems (%d) reached\n", + MAX_BASETRAIL_SYSTEMS ); + return qfalse; + } + else + numBaseTrailSystems++; + + continue; + } + else + { + CG_Printf( S_COLOR_RED "ERROR: unamed trail system\n" ); + return qfalse; + } + } + + if( !tsNameSet ) + { + Q_strncpyz( tsName, token, sizeof( tsName ) ); + tsNameSet = qtrue; + } + else + { + CG_Printf( S_COLOR_RED "ERROR: trail system already named\n" ); + return qfalse; + } + } + + return qtrue; +} + +/* +=============== +CG_LoadTrailSystems + +Load trail system templates +=============== +*/ +void CG_LoadTrailSystems( void ) +{ + int i, numFiles, fileLen; + char fileList[ MAX_TRAIL_FILES * MAX_QPATH ]; + char fileName[ MAX_QPATH ]; + char *filePtr; + + //clear out the old + numBaseTrailSystems = 0; + numBaseTrailBeams = 0; + + for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ ) + { + baseTrailSystem_t *bts = &baseTrailSystems[ i ]; + memset( bts, 0, sizeof( baseTrailSystem_t ) ); + } + + for( i = 0; i < MAX_BASETRAIL_BEAMS; i++ ) + { + baseTrailBeam_t *btb = &baseTrailBeams[ i ]; + memset( btb, 0, sizeof( baseTrailBeam_t ) ); + } + + //and bring in the new + numFiles = trap_FS_GetFileList( "scripts", ".trail", + fileList, MAX_TRAIL_FILES * MAX_QPATH ); + filePtr = fileList; + + for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + strcpy( fileName, "scripts/" ); + strcat( fileName, filePtr ); + CG_Printf( "...loading '%s'\n", fileName ); + CG_ParseTrailFile( fileName ); + } +} + +/* +=============== +CG_RegisterTrailSystem + +Load the media that a trail system needs +=============== +*/ +qhandle_t CG_RegisterTrailSystem( char *name ) +{ + int i, j; + baseTrailSystem_t *bts; + baseTrailBeam_t *btb; + + for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ ) + { + bts = &baseTrailSystems[ i ]; + + if( !Q_stricmp( bts->name, name ) ) + { + //already registered + if( bts->registered ) + return i + 1; + + for( j = 0; j < bts->numBeams; j++ ) + { + btb = bts->beams[ j ]; + + btb->shader = trap_R_RegisterShader( btb->shaderName ); + } + + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "Registered trail system %s\n", name ); + + bts->registered = qtrue; + + //avoid returning 0 + return i + 1; + } + } + + CG_Printf( S_COLOR_RED "ERROR: failed to register trail system %s\n", name ); + return 0; +} + + +/* +=============== +CG_SpawnNewTrailBeam + +Allocate a new trail beam +=============== +*/ +static trailBeam_t *CG_SpawnNewTrailBeam( baseTrailBeam_t *btb, + trailSystem_t *parent ) +{ + int i; + trailBeam_t *tb = NULL; + trailSystem_t *ts = parent; + + for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) + { + tb = &trailBeams[ i ]; + + if( !tb->valid ) + { + memset( tb, 0, sizeof( trailBeam_t ) ); + + //found a free slot + tb->class = btb; + tb->parent = ts; + + tb->valid = qtrue; + + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "TB %s created\n", ts->class->name ); + + return tb; + } + } + + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "MAX_TRAIL_BEAMS\n" ); + + return NULL; +} + + +/* +=============== +CG_SpawnNewTrailSystem + +Spawns a new trail system +=============== +*/ +trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle ) +{ + int i, j; + trailSystem_t *ts = NULL; + baseTrailSystem_t *bts = &baseTrailSystems[ psHandle - 1 ]; + + if( !bts->registered ) + { + CG_Printf( S_COLOR_RED "ERROR: a trail system has not been registered yet\n" ); + return NULL; + } + + for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) + { + ts = &trailSystems[ i ]; + + if( !ts->valid ) + { + memset( ts, 0, sizeof( trailSystem_t ) ); + + //found a free slot + ts->class = bts; + + ts->valid = qtrue; + ts->destroyTime = -1; + ts->birthTime = cg.time; + + for( j = 0; j < bts->numBeams; j++ ) + CG_SpawnNewTrailBeam( bts->beams[ j ], ts ); + + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "TS %s created\n", bts->name ); + + return ts; + } + } + + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "MAX_TRAIL_SYSTEMS\n" ); + + return NULL; +} + +/* +=============== +CG_DestroyTrailSystem + +Destroy a trail system +=============== +*/ +void CG_DestroyTrailSystem( trailSystem_t **ts ) +{ + (*ts)->destroyTime = cg.time; + + if( CG_Attached( &(*ts)->frontAttachment ) && + !CG_Attached( &(*ts)->backAttachment ) ) + { + vec3_t v; + + // attach the trail head to a static point + CG_AttachmentPoint( &(*ts)->frontAttachment, v ); + CG_SetAttachmentPoint( &(*ts)->frontAttachment, v ); + CG_AttachToPoint( &(*ts)->frontAttachment ); + + (*ts)->frontAttachment.centValid = qfalse; // a bit naughty + } + + ts = NULL; +} + +/* +=============== +CG_IsTrailSystemValid + +Test a trail system for validity +=============== +*/ +qboolean CG_IsTrailSystemValid( trailSystem_t **ts ) +{ + if( *ts == NULL || ( *ts && !(*ts)->valid ) ) + { + if( *ts && !(*ts)->valid ) + *ts = NULL; + + return qfalse; + } + + return qtrue; +} + +/* +=============== +CG_GarbageCollectTrailSystems + +Destroy inactive trail systems +=============== +*/ +static void CG_GarbageCollectTrailSystems( void ) +{ + int i, j, count; + trailSystem_t *ts; + trailBeam_t *tb; + int centNum; + + for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) + { + ts = &trailSystems[ i ]; + count = 0; + + //don't bother checking already invalid systems + if( !ts->valid ) + continue; + + for( j = 0; j < MAX_TRAIL_BEAMS; j++ ) + { + tb = &trailBeams[ j ]; + + if( tb->valid && tb->parent == ts ) + count++; + } + + if( !count ) + ts->valid = qfalse; + + //check systems where the parent cent has left the PVS + //( local player entity is always valid ) + if( ( centNum = CG_AttachmentCentNum( &ts->frontAttachment ) ) >= 0 && + centNum != cg.snap->ps.clientNum ) + { + trailSystem_t *tempTS = ts; + + if( !cg_entities[ centNum ].valid ) + CG_DestroyTrailSystem( &tempTS ); + } + + if( ( centNum = CG_AttachmentCentNum( &ts->backAttachment ) ) >= 0 && + centNum != cg.snap->ps.clientNum ) + { + trailSystem_t *tempTS = ts; + + if( !cg_entities[ centNum ].valid ) + CG_DestroyTrailSystem( &tempTS ); + } + + // lifetime expired + if( ts->destroyTime <= 0 && ts->class->lifeTime && + ts->birthTime + ts->class->lifeTime < cg.time ) + { + trailSystem_t *tempTS = ts; + + CG_DestroyTrailSystem( &tempTS ); + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "TS %s expired (born %d, lives %d, now %d)\n", + ts->class->name, ts->birthTime, ts->class->lifeTime, + cg.time ); + } + + if( cg_debugTrails.integer >= 1 && !ts->valid ) + CG_Printf( "TS %s garbage collected\n", ts->class->name ); + } +} + +/* +=============== +CG_AddTrails + +Add trails to the scene +=============== +*/ +void CG_AddTrails( void ) +{ + int i; + trailBeam_t *tb; + int numTS = 0, numTB = 0; + + //remove expired trail systems + CG_GarbageCollectTrailSystems( ); + + for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) + { + tb = &trailBeams[ i ]; + + if( tb->valid ) + { + CG_UpdateBeam( tb ); + CG_RenderBeam( tb ); + } + } + + if( cg_debugTrails.integer >= 2 ) + { + for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) + if( trailSystems[ i ].valid ) + numTS++; + + for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) + if( trailBeams[ i ].valid ) + numTB++; + + CG_Printf( "TS: %d TB: %d\n", numTS, numTB ); + } +} + +static trailSystem_t *testTS; +static qhandle_t testTSHandle; + +/* +=============== +CG_DestroyTestTS_f + +Destroy the test a trail system +=============== +*/ +void CG_DestroyTestTS_f( void ) +{ + if( CG_IsTrailSystemValid( &testTS ) ) + CG_DestroyTrailSystem( &testTS ); +} + +/* +=============== +CG_TestTS_f + +Test a trail system +=============== +*/ +void CG_TestTS_f( void ) +{ + char tsName[ MAX_QPATH ]; + + if( trap_Argc( ) < 2 ) + return; + + Q_strncpyz( tsName, CG_Argv( 1 ), MAX_QPATH ); + testTSHandle = CG_RegisterTrailSystem( tsName ); + + if( testTSHandle ) + { + CG_DestroyTestTS_f( ); + + testTS = CG_SpawnNewTrailSystem( testTSHandle ); + + if( CG_IsTrailSystemValid( &testTS ) ) + { + CG_SetAttachmentCent( &testTS->frontAttachment, &cg_entities[ 0 ] ); + CG_AttachToCent( &testTS->frontAttachment ); + } + } +} diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c new file mode 100644 index 0000000..f68d070 --- /dev/null +++ b/src/cgame/cg_tutorial.c @@ -0,0 +1,864 @@ +/* +=========================================================================== +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_tutorial.c -- the tutorial system + +#include "cg_local.h" + +#define CG_AxisName(a) ((a)==0?"X (forward-backward)":((a)==1?"Y (left-right)":"Z (up-down)")) + +typedef struct +{ + char *command; + char *humanName; + keyNum_t keys[ 2 ]; +} bind_t; + +static bind_t bindings[ ] = +{ + { "+button2", "Activate Upgrade", { -1, -1 } }, + { "+speed", "Run/Walk", { -1, -1 } }, + { "+button8", "Sprint", { -1, -1 } }, + { "+moveup", "Jump", { -1, -1 } }, + { "+movedown", "Crouch", { -1, -1 } }, + { "+attack", "Primary Attack", { -1, -1 } }, + { "+button5", "Secondary Attack", { -1, -1 } }, + { "reload", "Reload", { -1, -1 } }, + { "buy ammo", "Buy Ammo / Refuel", { -1, -1 } }, + { "itemact medkit", "Use Medkit", { -1, -1 } }, + { "+button7", "Use Structure/Evolve", { -1, -1 } }, + { "deconstruct", "Deconstruct Structure", { -1, -1 } }, + { "weapprev", "Previous Upgrade", { -1, -1 } }, + { "weapnext", "Next Upgrade", { -1, -1 } }, + { "cuboidAxis next","Next Cuboid axis", { -1, -1 } }, + { "cuboidRotate", "Rotate Cuboid", { -1, -1 } } +}; + +static const int numBindings = sizeof( bindings ) / sizeof( bind_t ); + +/* +================= +CG_GetBindings +================= +*/ +static void CG_GetBindings( void ) +{ + int i, j, numKeys; + char buffer[ MAX_STRING_CHARS ]; + + for( i = 0; i < numBindings; i++ ) + { + bindings[ i ].keys[ 0 ] = bindings[ i ].keys[ 1 ] = K_NONE; + numKeys = 0; + + for( j = 0; j < K_LAST_KEY; j++ ) + { + trap_Key_GetBindingBuf( j, buffer, MAX_STRING_CHARS ); + + if( buffer[ 0 ] == 0 ) + continue; + + if( !Q_stricmp( buffer, bindings[ i ].command ) ) + { + bindings[ i ].keys[ numKeys++ ] = j; + + if( numKeys > 1 ) + break; + } + } + } +} + +/* +=============== +CG_KeyNameForCommand +=============== +*/ +static const char *CG_KeyNameForCommand( const char *command ) +{ + int i, j; + static char buffer[ MAX_STRING_CHARS ]; + int firstKeyLength; + + buffer[ 0 ] = '\0'; + + for( i = 0; i < numBindings; i++ ) + { + if( !Q_stricmp( command, bindings[ i ].command ) ) + { + if( bindings[ i ].keys[ 0 ] != K_NONE ) + { + trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 0 ], + buffer, MAX_STRING_CHARS ); + firstKeyLength = strlen( buffer ); + + for( j = 0; j < firstKeyLength; j++ ) + buffer[ j ] = toupper( buffer[ j ] ); + + if( bindings[ i ].keys[ 1 ] != K_NONE ) + { + Q_strcat( buffer, MAX_STRING_CHARS, " or " ); + trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 1 ], + buffer + strlen( buffer ), MAX_STRING_CHARS - strlen( buffer ) ); + + for( j = firstKeyLength + 4; j < strlen( buffer ); j++ ) + buffer[ j ] = toupper( buffer[ j ] ); + } + } + else + { + Com_sprintf( buffer, MAX_STRING_CHARS, "\"%s\" (unbound)", + bindings[ i ].humanName ); + } + + return buffer; + } + } + + return ""; +} + +#define MAX_TUTORIAL_TEXT 4096 + +/* +=============== +CG_BuildableInRange +=============== +*/ +static entityState_t *CG_BuildableInRange( playerState_t *ps, float *healthFraction ) +{ + vec3_t view, point; + trace_t trace; + entityState_t *es; + int health; + + AngleVectors( cg.refdefViewAngles, view, NULL, NULL ); + VectorMA( cg.refdef.vieworg, 64, view, point ); + CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL, + point, ps->clientNum, MASK_SHOT ); + + es = &cg_entities[ trace.entityNum ].currentState; + + if( healthFraction ) + { + health = es->generic1; + *healthFraction = (float)health / BG_Buildable( es->modelindex, es->angles )->health; + } + + if( es->eType == ET_BUILDABLE && + ps->stats[ STAT_TEAM ] == BG_Buildable( es->modelindex, NULL )->team ) + return es; + else + return NULL; +} + +/* +=============== +CG_AlienBuilderText +=============== +*/ +static void CG_AlienBuilderText( char *text, playerState_t *ps ) +{ + buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + entityState_t *es; + + if( buildable > BA_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to place the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_Buildable( buildable, NULL )->humanName ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to cancel placing the %s\n", + CG_KeyNameForCommand( "+button5" ), + BG_Buildable( buildable, NULL )->humanName ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to build a structure\n", + CG_KeyNameForCommand( "+attack" ) ) ); + } + + if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + { + if( cgs.markDeconstruct ) + { + if( es->eFlags & EF_B_MARKED ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to unmark this structure for replacement\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to mark this structure for replacement\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to destroy this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } + + if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to swipe\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_BUILDER0_UPG ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to launch a projectile\n", + CG_KeyNameForCommand( "+button2" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to walk on walls\n", + CG_KeyNameForCommand( "+movedown" ) ) ); + } +} + +/* +=============== +CG_AlienLevel0Text +=============== +*/ +static void CG_AlienLevel0Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Touch humans to damage them\n" ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to walk on walls\n", + CG_KeyNameForCommand( "+movedown" ) ) ); +} + +/* +=============== +CG_AlienLevel1Text +=============== +*/ +static void CG_AlienLevel1Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Touch humans to grab them\n" ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to swipe\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL1_UPG ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to spray poisonous gas\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to walk on walls\n", + CG_KeyNameForCommand( "+movedown" ) ) ); +} + +/* +=============== +CG_AlienLevel2Text +=============== +*/ +static void CG_AlienLevel2Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to bite\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL2_UPG ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to invoke an electrical attack\n", + CG_KeyNameForCommand( "+button5" ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold down %s then touch a wall to wall jump\n", + CG_KeyNameForCommand( "+moveup" ) ) ); +} + +/* +=============== +CG_AlienLevel3Text +=============== +*/ +static void CG_AlienLevel3Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to bite\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + if( ps->stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL3_UPG ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to launch a projectile\n", + CG_KeyNameForCommand( "+button2" ) ) ); + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold down and release %s to pounce\n", + CG_KeyNameForCommand( "+button5" ) ) ); +} + +/* +=============== +CG_AlienLevel4Text +=============== +*/ +static void CG_AlienLevel4Text( char *text, playerState_t *ps ) +{ + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to swipe\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold down and release %s to trample\n", + CG_KeyNameForCommand( "+button5" ) ) ); +} + +/* +=============== +CG_HumanCkitText +=============== +*/ +static void CG_HumanCkitText( char *text, playerState_t *ps ) +{ + buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + entityState_t *es; + + if( buildable > BA_NONE ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to place the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_Buildable( buildable, NULL )->humanName ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to cancel placing the %s\n", + CG_KeyNameForCommand( "+button5" ), + BG_Buildable( buildable, NULL )->humanName ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to build a structure\n", + CG_KeyNameForCommand( "+attack" ) ) ); + } + + if( ( es = CG_BuildableInRange( ps, NULL ) ) ) + { + if( cgs.markDeconstruct ) + { + if( es->eFlags & EF_B_MARKED ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to unmark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to mark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to destroy this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } +} + +/* +=============== +CG_HumanText +=============== +*/ +static void CG_HumanText( char *text, playerState_t *ps ) +{ + char *name; + upgrade_t upgrade = UP_NONE; + + if( cg.weaponSelect < 32 ) + name = cg_weapons[ cg.weaponSelect ].humanName; + else + { + name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; + upgrade = cg.weaponSelect - 32; + } + + if( !ps->ammo && !ps->clips && !BG_Weapon( ps->weapon )->infiniteAmmo ) + { + //no ammo + switch( ps->weapon ) + { + case WP_MACHINEGUN: + case WP_CHAINGUN: + case WP_SHOTGUN: + case WP_FLAMER: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Find an Armoury and press %s for more ammo\n", + CG_KeyNameForCommand( "buy ammo" ) ) ); + break; + + case WP_LAS_GUN: + case WP_PULSE_RIFLE: + case WP_MASS_DRIVER: + case WP_LUCIFER_CANNON: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Find an Armoury, Reactor, or Repeater and press %s for more ammo\n", + CG_KeyNameForCommand( "buy ammo" ) ) ); + break; + + default: + break; + } + } + else + { + switch( ps->weapon ) + { + case WP_BLASTER: + case WP_MACHINEGUN: + case WP_SHOTGUN: + case WP_LAS_GUN: + case WP_CHAINGUN: + case WP_PULSE_RIFLE: + case WP_FLAMER: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to fire the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_Weapon( ps->weapon )->humanName ) ); + break; + + case WP_MASS_DRIVER: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to fire the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_Weapon( ps->weapon )->humanName ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold %s to zoom\n", + CG_KeyNameForCommand( "+button5" ) ) ); + break; + + case WP_PAIN_SAW: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold %s to activate the %s\n", + CG_KeyNameForCommand( "+attack" ), + BG_Weapon( ps->weapon )->humanName ) ); + break; + + case WP_LUCIFER_CANNON: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Hold and release %s to fire a charged shot\n", + CG_KeyNameForCommand( "+attack" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to fire the %s\n", + CG_KeyNameForCommand( "+button5" ), + BG_Weapon( ps->weapon )->humanName ) ); + break; + + case WP_HBUILD: + CG_HumanCkitText( text, ps ); + break; + + default: + break; + } + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s and ", + CG_KeyNameForCommand( "weapprev" ) ) ); + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "%s to select an upgrade\n", + CG_KeyNameForCommand( "weapnext" ) ) ); + + if( upgrade == UP_NONE || + ( upgrade > UP_NONE && BG_Upgrade( upgrade )->usable ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to use the %s\n", + CG_KeyNameForCommand( "+button2" ), + name ) ); + } + + if( ps->stats[ STAT_HEALTH ] <= 35 && + BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to use your %s\n", + CG_KeyNameForCommand( "itemact medkit" ), + BG_Upgrade( UP_MEDKIT )->humanName ) ); + } + + if( ps->stats[ STAT_STAMINA ] <= STAMINA_BLACKOUT_LEVEL ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + "You are blacking out. Stop sprinting to recover stamina\n" ); + } + else if( ps->stats[ STAT_STAMINA ] <= STAMINA_SLOW_LEVEL ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Your stamina is low. Stop sprinting to recover\n" ); + } + + switch( cg.nearUsableBuildable ) + { + case BA_H_ARMOURY: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to buy equipment upgrades at the %s. Sell your old weapon first!\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable, NULL )->humanName ) ); + break; + case BA_H_REPEATER: + case BA_H_REACTOR: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to refill your energy weapon's ammo at the %s\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable, NULL )->humanName ) ); + break; + case BA_NONE: + break; + default: + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to use the %s\n", + CG_KeyNameForCommand( "+button7" ), + BG_Buildable( cg.nearUsableBuildable, NULL )->humanName ) ); + break; + } + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s and any direction to sprint\n", + CG_KeyNameForCommand( "+button8" ) ) ); +} + +/* +=============== +CG_SpectatorText +=============== +*/ +static void CG_SpectatorText( char *text, playerState_t *ps ) +{ + if( cgs.clientinfo[ cg.clientNum ].team != TEAM_NONE ) + { + if( ps->pm_flags & PMF_QUEUED ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to leave spawn queue\n", + CG_KeyNameForCommand( "+attack" ) ) ); + else + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to spawn\n", + CG_KeyNameForCommand( "+attack" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to join a team\n", + CG_KeyNameForCommand( "+attack" ) ) ); + } + + if( ps->pm_flags & PMF_FOLLOW ) + { + if( !cg.chaseFollow ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to switch to chase-cam spectator mode\n", + CG_KeyNameForCommand( "+button2" ) ) ); + else if( cgs.clientinfo[ cg.clientNum ].team == TEAM_NONE ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to return to free spectator mode\n", + CG_KeyNameForCommand( "+button2" ) ) ); + else + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to stop following\n", + CG_KeyNameForCommand( "+button2" ) ) ); + + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s or ", + CG_KeyNameForCommand( "weapprev" ) ) ); + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "%s to change player\n", + CG_KeyNameForCommand( "weapnext" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to follow a player\n", + CG_KeyNameForCommand( "+button2" ) ) ); + } +} + +#define BINDING_REFRESH_INTERVAL 30 + +/* +=============== +CG_TutorialText + +Returns context help for the current class/weapon +=============== +*/ +static void CG_NewText( char *text, playerState_t *ps ); + +const char *CG_TutorialText( void ) +{ + playerState_t *ps; + static char text[ MAX_TUTORIAL_TEXT ]; + static int refreshBindings = 0; + static qboolean checkedUpdate = qfalse; + + // force mod tutorial if new version + if( !checkedUpdate ) + { + trap_Cvar_Update( &cg_lastModVersion ); + + if( cg_lastModVersion.integer < MODVER_CURRENT ) + { + trap_Cvar_Set( "cg_modTutorial", "1" ); + trap_Cvar_Set( "cg_modTutorialReference", va( "%i", cg_lastModVersion.integer ) ); + trap_Cvar_Set( "cg_lastModVersion", va( "%i", MODVER_CURRENT ) ); + } + checkedUpdate = qtrue; + } + + if( refreshBindings == 0 ) + CG_GetBindings( ); + + refreshBindings = ( refreshBindings + 1 ) % BINDING_REFRESH_INTERVAL; + + text[ 0 ] = '\0'; + ps = &cg.snap->ps; + + if( cg_modTutorial.integer ) + { + CG_NewText( text, ps ); + + if( !cg_tutorial.integer ) + return text; + } + + if( !cg.intermissionStarted && !cg.demoPlayback ) + { + if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT || + ps->pm_flags & PMF_FOLLOW ) + { + CG_SpectatorText( text, ps ); + } + else if( ps->stats[ STAT_HEALTH ] > 0 ) + { + switch( ps->stats[ STAT_CLASS ] ) + { + case PCL_ALIEN_BUILDER0: + case PCL_ALIEN_BUILDER0_UPG: + CG_AlienBuilderText( text, ps ); + break; + + case PCL_ALIEN_LEVEL0: + CG_AlienLevel0Text( text, ps ); + break; + + case PCL_ALIEN_LEVEL1: + case PCL_ALIEN_LEVEL1_UPG: + CG_AlienLevel1Text( text, ps ); + break; + + case PCL_ALIEN_LEVEL2: + case PCL_ALIEN_LEVEL2_UPG: + CG_AlienLevel2Text( text, ps ); + break; + + case PCL_ALIEN_LEVEL3: + case PCL_ALIEN_LEVEL3_UPG: + CG_AlienLevel3Text( text, ps ); + break; + + case PCL_ALIEN_LEVEL4: + CG_AlienLevel4Text( text, ps ); + break; + + case PCL_HUMAN: + case PCL_HUMAN_BSUIT: + CG_HumanText( text, ps ); + break; + + default: + break; + } + + if( ps->stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + if( BG_AlienCanEvolve( ps->stats[ STAT_CLASS ], + ps->persistant[ PERS_CREDIT ], + cgs.alienStage ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to evolve\n", + CG_KeyNameForCommand( "+button7" ) ) ); + } + } + } + } + else if( !cg.demoPlayback ) + { + if( !CG_ClientIsReady( ps->clientNum ) ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s when ready to continue\n", + CG_KeyNameForCommand( "+attack" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, "Waiting for other players to be ready\n" ); + } + } + + if( !cg.demoPlayback ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, "Press ESC for the menu" ); + } + + return text; +} + +/* +=============== +CG_NewText + +Informs the player about new mod features. cg_tutorial2 contains a +mod version (table below). CG_NewText will explain all features +introduced in that version and later. + +0 N/A vanilla GPP +1 Sep 03 alpha 1 + +=============== +*/ + +static void CG_CuboidText( char *text, playerState_t *ps ) +{ + buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + + if ( buildable <= BA_NONE ) + return; + + if( !BG_Buildable( buildable, NULL )->cuboid ) + return; + + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Cuboid requires new keybindings. They can be set in Options -> Controls -> Cuboid\n" ); + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s and ", + CG_KeyNameForCommand( "weapnext" ) ) ); + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "%s to resize the %s on %s axis.\n", + CG_KeyNameForCommand( "weapprev" ), + BG_Buildable( buildable, NULL )->humanName, + CG_AxisName( cg_cuboidResizeAxis.integer ) ) ); + Q_strcat(text, MAX_TUTORIAL_TEXT, + va( "Press %s to rotate %s around %s axis\n", + CG_KeyNameForCommand( "cuboidRotate" ), + BG_Buildable( buildable, NULL )->humanName, + CG_AxisName( cg_cuboidResizeAxis.integer ) ) ); + Q_strcat(text, MAX_TUTORIAL_TEXT, + va( "Press %s to select another axis.\n", + CG_KeyNameForCommand( "cuboidAxis next" ) ) ); +} + +static void CG_NewText( char *text, playerState_t *ps ) +{ + int reference; + + reference = cg_modTutorialReference.integer; + + if( !cg.intermissionStarted && !cg.demoPlayback ) + { + if( ps->persistant[ PERS_SPECSTATE ] == SPECTATOR_NOT && + !( ps->pm_flags & PMF_FOLLOW ) && + ps->stats[ STAT_HEALTH ] > 0 ) + { + if( reference >= MODVER_C2_0_1_0 ) + { + if( BG_InventoryContainsUpgrade( UP_JETPACK, ps->stats ) ) + { + if( ps->stats[ STAT_FUEL ] > JETPACK_FUEL_JUMP ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to perform a jetpack-aided jump. It uses fuel instead of stamina\n", + CG_KeyNameForCommand( "+moveup" ) ) ); + } + if( ps->stats[ STAT_FUEL ] <= JETPACK_FUEL_LOW ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "You are running low on jet fuel. Find an Armoury and press %s to refuel\n", + CG_KeyNameForCommand( "buy ammo" ) ) ); + } + else if( ps->stats[ STAT_FUEL ] <= 0 ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "You are out of jet fuel. You can no longer fly. Find an Armoury and press %s to refuel\n", + CG_KeyNameForCommand( "buy ammo" ) ) ); + } + } + } + + switch( ps->weapon ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + if( reference >= MODVER_C2_0_1_0 ) + { + CG_CuboidText( text, ps ); + if( ps->weapon == WP_ABUILD2 ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + "Spitting at a human has a chance of impregnating him\n" + "with an alien egg. After a certain amount of time,\n" + "the egg can be spawned from by aliens, killing the human.\n" ); + } + break; + + case WP_ALEVEL4: + if( reference >= MODVER_C2_0_1_0 ) + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to drop a Tyrant Bomb. It can be regerated by touching a Booster.\n", + CG_KeyNameForCommand( "+button2" ) ) ); + break; + } + } + } +} + diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c new file mode 100644 index 0000000..cd5e384 --- /dev/null +++ b/src/cgame/cg_view.c @@ -0,0 +1,1500 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering + + +#include "cg_local.h" + + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f( void ) +{ + vec3_t angles; + + memset( &cg.testModelEntity, 0, sizeof( cg.testModelEntity ) ); + memset( &cg.testModelBarrelEntity, 0, sizeof( cg.testModelBarrelEntity ) ); + + if( trap_Argc( ) < 2 ) + return; + + Q_strncpyz( cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + + Q_strncpyz( cg.testModelBarrelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelBarrelName[ strlen( cg.testModelBarrelName ) - 4 ] = '\0'; + Q_strcat( cg.testModelBarrelName, MAX_QPATH, "_barrel.md3" ); + cg.testModelBarrelEntity.hModel = trap_R_RegisterModel( cg.testModelBarrelName ); + + if( trap_Argc( ) == 3 ) + { + cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg.testModelEntity.frame = 1; + cg.testModelEntity.oldframe = 0; + } + + if( !cg.testModelEntity.hModel ) + { + CG_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], cg.testModelEntity.origin ); + + angles[ PITCH ] = 0; + angles[ YAW ] = 180 + cg.refdefViewAngles[ 1 ]; + angles[ ROLL ] = 0; + + AnglesToAxis( angles, cg.testModelEntity.axis ); + cg.testGun = qfalse; + + if( cg.testModelBarrelEntity.hModel ) + { + angles[ YAW ] = 0; + angles[ PITCH ] = 0; + angles[ ROLL ] = 0; + AnglesToAxis( angles, cg.testModelBarrelEntity.axis ); + } +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f( void ) +{ + CG_TestModel_f( ); + cg.testGun = qtrue; + cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f( void ) +{ + cg.testModelEntity.frame++; + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f( void ) +{ + cg.testModelEntity.frame--; + + if( cg.testModelEntity.frame < 0 ) + cg.testModelEntity.frame = 0; + + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f( void ) +{ + cg.testModelEntity.skinNum++; + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f( void ) +{ + cg.testModelEntity.skinNum--; + + if( cg.testModelEntity.skinNum < 0 ) + cg.testModelEntity.skinNum = 0; + + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel( void ) +{ + int i; + + // re-register the model, because the level may have changed + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + cg.testModelBarrelEntity.hModel = trap_R_RegisterModel( cg.testModelBarrelName ); + + if( !cg.testModelEntity.hModel ) + { + CG_Printf( "Can't register model\n" ); + return; + } + + // if testing a gun, set the origin reletive to the view origin + if( cg.testGun ) + { + VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); + VectorCopy( cg.refdef.viewaxis[ 0 ], cg.testModelEntity.axis[ 0 ] ); + VectorCopy( cg.refdef.viewaxis[ 1 ], cg.testModelEntity.axis[ 1 ] ); + VectorCopy( cg.refdef.viewaxis[ 2 ], cg.testModelEntity.axis[ 2 ] ); + + // allow the position to be adjusted + for( i = 0; i < 3; i++ ) + { + cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 0 ][ i ] * cg_gun_x.value; + cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 1 ][ i ] * cg_gun_y.value; + cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 2 ][ i ] * cg_gun_z.value; + } + } + + trap_R_AddRefEntityToScene( &cg.testModelEntity ); + + if( cg.testModelBarrelEntity.hModel ) + { + CG_PositionEntityOnTag( &cg.testModelBarrelEntity, &cg.testModelEntity, + cg.testModelEntity.hModel, "tag_barrel" ); + + trap_R_AddRefEntityToScene( &cg.testModelBarrelEntity ); + } +} + + + +//============================================================================ + + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +static void CG_CalcVrect( void ) +{ + int size; + + // the intermission should allways be full screen + if( cg.snap->ps.pm_type == PM_INTERMISSION ) + size = 100; + else + size = cg_viewsize.integer; + + cg.refdef.width = cgs.glconfig.vidWidth * size / 100; + cg.refdef.width &= ~1; + + cg.refdef.height = cgs.glconfig.vidHeight * size / 100; + cg.refdef.height &= ~1; + + cg.refdef.x = ( cgs.glconfig.vidWidth - cg.refdef.width ) / 2; + cg.refdef.y = ( cgs.glconfig.vidHeight - cg.refdef.height ) / 2; +} + +//============================================================================== + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*/ +void CG_OffsetThirdPersonView( void ) +{ + int i; + vec3_t forward, right, up; + vec3_t view; + trace_t trace; + static vec3_t mins = { -8, -8, -8 }; + static vec3_t maxs = { 8, 8, 8 }; + vec3_t focusPoint; + vec3_t surfNormal; + int cmdNum; + usercmd_t cmd, oldCmd; + float range; + vec3_t mouseInputAngles; + vec3_t rotationAngles; + vec3_t axis[ 3 ], rotaxis[ 3 ]; + float deltaPitch; + static float pitch; + static vec3_t killerPos = { 0, 0, 0 }; + + // If cg_thirdpersonShoulderViewMode == 2, do shoulder view instead + // If cg_thirdpersonShoulderViewMode == 1, do shoulder view when chasing + // a wallwalker because it's really erratic to watch + if( ( cg_thirdPersonShoulderViewMode.integer == 2 ) || + ( ( cg_thirdPersonShoulderViewMode.integer == 1 ) && + ( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) && + ( cg.snap->ps.stats[ STAT_HEALTH ] > 0 ) ) ) + { + CG_OffsetShoulderView( ); + return; + } + + BG_GetClientNormal( &cg.predictedPlayerState, surfNormal ); + // Set the view origin to the class's view height + VectorMA( cg.refdef.vieworg, cg.predictedPlayerState.viewheight, surfNormal, cg.refdef.vieworg ); + + // Set the focus point where the camera will look (at the player's vieworg) + VectorCopy( cg.refdef.vieworg, focusPoint ); + + // If player is dead, we want the player to be between us and the killer + // so pretend that the player was looking at the killer, then place cam behind them. + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) + { + int killerEntNum = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ]; + + // already looking at ourself + if( killerEntNum != cg.snap->ps.clientNum ) + { + vec3_t lookDirection; + if( cg.wasDeadLastFrame == qfalse || !cg_staticDeathCam.integer ) + { + VectorCopy( cg_entities[ killerEntNum ].lerpOrigin, killerPos ); + cg.wasDeadLastFrame = qtrue; + } + VectorSubtract( killerPos, cg.refdef.vieworg, lookDirection ); + vectoangles( lookDirection, cg.refdefViewAngles ); + } + } + + // get and rangecheck cg_thirdPersonRange + range = cg_thirdPersonRange.value; + if( range > 150.0f ) range = 150.0f; + if( range < 30.0f ) range = 30.0f; + + // Calculate the angle of the camera's position around the player. + // Unless in demo, PLAYING in third person, or in dead-third-person cam, allow the player + // to control camera position offsets using the mouse position. + if( cg.demoPlayback || + ( ( cg.snap->ps.pm_flags & PMF_FOLLOW ) && + ( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) ) ) + { + // Collect our input values from the mouse. + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldCmd ); + + // Prevent pitch from wrapping and clamp it within a [-75, 90] range. + // Cgame has no access to ps.delta_angles[] here, so we need to reproduce + // it ourselves. + deltaPitch = SHORT2ANGLE( cmd.angles[ PITCH ] - oldCmd.angles[ PITCH ] ); + if( fabs(deltaPitch) < 200.0f ) + { + pitch += deltaPitch; + } + + mouseInputAngles[ PITCH ] = pitch; + mouseInputAngles[ YAW ] = -1.0f * SHORT2ANGLE( cmd.angles[ YAW ] ); // yaw is inverted + mouseInputAngles[ ROLL ] = 0.0f; + + for( i = 0; i < 3; i++ ) + mouseInputAngles[ i ] = AngleNormalize180( mouseInputAngles[ i ] ); + + // Set the rotation angles to be the view angles offset by the mouse input + // Ignore the original pitch though; it's too jerky otherwise + if( !cg_thirdPersonPitchFollow.integer ) + cg.refdefViewAngles[ PITCH ] = 0.0f; + + for( i = 0; i < 3; i++ ) + { + rotationAngles[ i ] = AngleNormalize180(cg.refdefViewAngles[ i ]) + mouseInputAngles[ i ]; + AngleNormalize180( rotationAngles[ i ] ); + } + + // Don't let pitch go too high/too low or the camera flips around and + // that's really annoying. + // However, when we're not on the floor or ceiling (wallwalk) pitch + // may not be pitch, so just let it go. + if( surfNormal[ 2 ] > 0.5f || surfNormal[ 2 ] < -0.5f ) + { + if( rotationAngles[ PITCH ] > 85.0f ) + rotationAngles[ PITCH ] = 85.0f; + else if( rotationAngles[ PITCH ] < -85.0f ) + rotationAngles[ PITCH ] = -85.0f; + } + + // Perform the rotations specified by rotationAngles. + AnglesToAxis( rotationAngles, axis ); + if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || + !BG_RotateAxis( cg.snap->ps.grapplePoint, axis, rotaxis, qfalse, + cg.snap->ps.eFlags & EF_WALLCLIMBCEILING ) ) + AxisCopy( axis, rotaxis ); + + // Convert the new axis back to angles. + AxisToAngles( rotaxis, rotationAngles ); + } + else + { + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) + { + // If we're playing the game in third person, the viewangles already + // take care of our mouselook, so just use them. + for( i = 0; i < 3; i++ ) + rotationAngles[ i ] = cg.refdefViewAngles[ i ]; + } + else // dead + { + rotationAngles[ PITCH ] = 20.0f; + rotationAngles[ YAW ] = cg.refdefViewAngles[ YAW ]; + } + } + + rotationAngles[ YAW ] -= cg_thirdPersonAngle.value; + + // Move the camera range distance back. + AngleVectors( rotationAngles, forward, right, up ); + VectorCopy( cg.refdef.vieworg, view ); + VectorMA( view, -range, forward, view ); + + // Ensure that the current camera position isn't out of bounds and that there + // is nothing between the camera and the player. + if( !cg_cameraMode.integer ) + { + // Trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if( trace.fraction != 1.0f ) + { + VectorCopy( trace.endpos, view ); + view[ 2 ] += ( 1.0f - trace.fraction ) * 32; + // Try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out. + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + VectorCopy( trace.endpos, view ); + } + } + + // Set the camera position to what we calculated. + VectorCopy( view, cg.refdef.vieworg ); + + // The above checks may have moved the camera such that the existing viewangles + // may not still face the player. Recalculate them to do so. + // but if we're dead, don't bother because we'd rather see what killed us + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 ) + { + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + vectoangles( focusPoint, cg.refdefViewAngles ); + } +} + + +/* +=============== +CG_OffsetShoulderView + +=============== +*/ +void CG_OffsetShoulderView( void ) +{ + int i; + int cmdNum; + usercmd_t cmd, oldCmd; + vec3_t rotationAngles; + vec3_t axis[ 3 ], rotaxis[ 3 ]; + float deltaMousePitch; + static float mousePitch; + vec3_t forward, right, up; + classConfig_t* classConfig; + + // Ignore following pitch; it's too jerky otherwise. + if( !cg_thirdPersonPitchFollow.integer ) + cg.refdefViewAngles[ PITCH ] = 0.0f; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + classConfig = BG_ClassConfig( cg.snap->ps.stats[ STAT_CLASS ] ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 0 ], forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 1 ], right, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, classConfig->shoulderOffsets[ 2 ], up, cg.refdef.vieworg ); + + // If someone is playing like this, the rest is already taken care of + // so just get the firstperson effects and leave. + if( !cg.demoPlayback && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + { + CG_OffsetFirstPersonView(); + return; + } + + // Get mouse input for camera rotation. + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldCmd ); + + // Prevent pitch from wrapping and clamp it within a [30, -50] range. + // Cgame has no access to ps.delta_angles[] here, so we need to reproduce + // it ourselves here. + deltaMousePitch = SHORT2ANGLE( cmd.angles[ PITCH ] - oldCmd.angles[ PITCH ] ); + if( fabs(deltaMousePitch) < 200.0f ) + mousePitch += deltaMousePitch; + + // Handle pitch. + rotationAngles[ PITCH ] = mousePitch; + + rotationAngles[ PITCH ] = AngleNormalize180( rotationAngles[ PITCH ] + AngleNormalize180( cg.refdefViewAngles[ PITCH ] ) ); + if( rotationAngles [ PITCH ] < -90.0f ) rotationAngles [ PITCH ] = -90.0f; + if( rotationAngles [ PITCH ] > 90.0f ) rotationAngles [ PITCH ] = 90.0f; + + // Yaw and Roll are much easier. + rotationAngles[ YAW ] = SHORT2ANGLE( cmd.angles[ YAW ] ) + cg.refdefViewAngles[ YAW ]; + rotationAngles[ ROLL ] = 0.0f; + + // Perform the rotations. + AnglesToAxis( rotationAngles, axis ); + if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || + !BG_RotateAxis( cg.snap->ps.grapplePoint, axis, rotaxis, qfalse, + cg.snap->ps.eFlags & EF_WALLCLIMBCEILING ) ) + AxisCopy( axis, rotaxis ); + + AxisToAngles( rotaxis, rotationAngles ); + + // Actually set the viewangles. + for( i = 0; i < 3; i++ ) + cg.refdefViewAngles[ i ] = rotationAngles[ i ]; + + // Now run the first person stuff so we get various effects added. + CG_OffsetFirstPersonView( ); +} + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) +{ + float steptime; + int timeDelta; + vec3_t normal; + playerState_t *ps = &cg.predictedPlayerState; + + BG_GetClientNormal( ps, normal ); + + steptime = BG_Class( ps->stats[ STAT_CLASS ] )->steptime; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + if( timeDelta < steptime ) + { + float stepChange = cg.stepChange + * (steptime - timeDelta) / steptime; + + VectorMA( cg.refdef.vieworg, -stepChange, normal, cg.refdef.vieworg ); + } +} + +#define PCLOUD_ROLL_AMPLITUDE 25.0f +#define PCLOUD_ROLL_FREQUENCY 0.4f +#define PCLOUD_ZOOM_AMPLITUDE 15 +#define PCLOUD_ZOOM_FREQUENCY 0.625f // 2.5s / 4 +#define PCLOUD_DISORIENT_DURATION 2500 + + +/* +=============== +CG_OffsetFirstPersonView + +=============== +*/ +void CG_OffsetFirstPersonView( void ) +{ + float *origin; + float *angles; + float bob; + float ratio; + float delta; + float speed; + float f; + vec3_t predictedVelocity; + int timeDelta; + float bob2; + vec3_t normal, baseOrigin; + playerState_t *ps = &cg.predictedPlayerState; + + BG_GetClientNormal( ps, normal ); + + if( cg.snap->ps.pm_type == PM_INTERMISSION ) + return; + + origin = cg.refdef.vieworg; + angles = cg.refdefViewAngles; + + VectorCopy( origin, baseOrigin ); + + // if dead, fix the angle and don't add any kick + if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) + { + angles[ ROLL ] = 40; + angles[ PITCH ] = -15; + angles[ YAW ] = cg.snap->ps.stats[ STAT_VIEWLOCK ]; + origin[ 2 ] += cg.predictedPlayerState.viewheight; + return; + } + + // add angles based on damage kick + if( cg.damageTime ) + { + ratio = cg.time - cg.damageTime; + if( ratio < DAMAGE_DEFLECT_TIME ) + { + ratio /= DAMAGE_DEFLECT_TIME; + angles[ PITCH ] += ratio * cg.v_dmg_pitch; + angles[ ROLL ] += ratio * cg.v_dmg_roll; + } + else + { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if( ratio > 0 ) + { + angles[ PITCH ] += ratio * cg.v_dmg_pitch; + angles[ ROLL ] += ratio * cg.v_dmg_roll; + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg.time - cg.landTime) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * cg.fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 0 ] ); + angles[ PITCH ] += delta * cg_runpitch.value; + + delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 1 ] ); + angles[ ROLL ] -= delta * cg_runroll.value; + + // add angles based on bob + // bob amount is class dependant + + if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + bob2 = 0.0f; + else + bob2 = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; + + +#define LEVEL4_FEEDBACK 10.0f + + //give a charging player some feedback + if( ps->weapon == WP_ALEVEL4 ) + { + if( ps->stats[ STAT_MISC ] > 0 ) + { + float fraction = (float)ps->stats[ STAT_MISC ] / + LEVEL4_TRAMPLE_CHARGE_MAX; + + if( fraction > 1.0f ) + fraction = 1.0f; + + bob2 *= ( 1.0f + fraction * LEVEL4_FEEDBACK ); + } + } + + if( bob2 != 0.0f ) + { + // make sure the bob is visible even at low speeds + speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + + delta = cg.bobfracsin * ( bob2 ) * speed; + if( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) + delta *= 3; // crouching + + angles[ PITCH ] += delta; + delta = cg.bobfracsin * ( bob2 ) * speed; + if( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) + delta *= 3; // crouching accentuates roll + + if( cg.bobcycle & 1 ) + delta = -delta; + + angles[ ROLL ] += delta; + } + +#define LEVEL3_FEEDBACK 20.0f + + //provide some feedback for pouncing + if( ( cg.predictedPlayerState.weapon == WP_ALEVEL3 || + cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG ) && + cg.predictedPlayerState.stats[ STAT_MISC ] > 0 ) + { + float fraction1, fraction2; + vec3_t forward; + + AngleVectors( angles, forward, NULL, NULL ); + VectorNormalize( forward ); + + fraction1 = (float)cg.predictedPlayerState.stats[ STAT_MISC ] / + LEVEL3_POUNCE_TIME_UPG; + if( fraction1 > 1.0f ) + fraction1 = 1.0f; + + fraction2 = -sin( fraction1 * M_PI / 2 ); + + VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin ); + } + +#define STRUGGLE_DIST 5.0f +#define STRUGGLE_TIME 250 + + //allow the player to struggle a little whilst grabbed + if( cg.predictedPlayerState.pm_type == PM_GRABBED ) + { + vec3_t forward, right, up; + usercmd_t cmd; + int cmdNum; + float fFraction, rFraction, uFraction; + float fFraction2, rFraction2, uFraction2; + + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + + AngleVectors( angles, forward, right, up ); + + fFraction = (float)( cg.time - cg.forwardMoveTime ) / STRUGGLE_TIME; + rFraction = (float)( cg.time - cg.rightMoveTime ) / STRUGGLE_TIME; + uFraction = (float)( cg.time - cg.upMoveTime ) / STRUGGLE_TIME; + + if( fFraction > 1.0f ) + fFraction = 1.0f; + if( rFraction > 1.0f ) + rFraction = 1.0f; + if( uFraction > 1.0f ) + uFraction = 1.0f; + + fFraction2 = -sin( fFraction * M_PI / 2 ); + rFraction2 = -sin( rFraction * M_PI / 2 ); + uFraction2 = -sin( uFraction * M_PI / 2 ); + + if( cmd.forwardmove > 0 ) + VectorMA( origin, STRUGGLE_DIST * fFraction, forward, origin ); + else if( cmd.forwardmove < 0 ) + VectorMA( origin, -STRUGGLE_DIST * fFraction, forward, origin ); + else + cg.forwardMoveTime = cg.time; + + if( cmd.rightmove > 0 ) + VectorMA( origin, STRUGGLE_DIST * rFraction, right, origin ); + else if( cmd.rightmove < 0 ) + VectorMA( origin, -STRUGGLE_DIST * rFraction, right, origin ); + else + cg.rightMoveTime = cg.time; + + if( cmd.upmove > 0 ) + VectorMA( origin, STRUGGLE_DIST * uFraction, up, origin ); + else if( cmd.upmove < 0 ) + VectorMA( origin, -STRUGGLE_DIST * uFraction, up, origin ); + else + cg.upMoveTime = cg.time; + } + + if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && + ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && + !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + { + float scale, fraction, pitchFraction; + + scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / + BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); + if( scale < 0.0f ) + scale = 0.0f; + + fraction = sin( ( cg.time - cg.poisonedTime ) / 500.0f * M_PI * PCLOUD_ROLL_FREQUENCY ) * + scale; + pitchFraction = sin( ( cg.time - cg.poisonedTime ) / 200.0f * M_PI * PCLOUD_ROLL_FREQUENCY ) * + scale; + + angles[ ROLL ] += fraction * PCLOUD_ROLL_AMPLITUDE; + angles[ YAW ] += fraction * PCLOUD_ROLL_AMPLITUDE; + angles[ PITCH ] += pitchFraction * PCLOUD_ROLL_AMPLITUDE / 2.0f; + } + + // this *feels* more realisitic for humans + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS && + ( cg.predictedPlayerState.pm_type == PM_NORMAL || + cg.predictedPlayerState.pm_type == PM_JETPACK ) ) + { + angles[PITCH] += cg.bobfracsin * bob2 * 0.5; + + // heavy breathing effects //FIXME: sound + if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < STAMINA_BREATHING_LEVEL ) + { + float deltaBreath = ( cg.predictedPlayerState.stats[ STAT_STAMINA ] - + STAMINA_BREATHING_LEVEL ) / -250.0; + float deltaAngle = cos( (float)cg.time/150.0 ) * deltaBreath; + + deltaAngle += ( deltaAngle < 0 ? -deltaAngle : deltaAngle ) * 0.5; + + angles[ PITCH ] -= deltaAngle; + } + } + +//=================================== + + // add view height + VectorMA( origin, ps->viewheight, normal, origin ); + + // smooth out duck height changes + timeDelta = cg.time - cg.duckTime; + if( timeDelta < DUCK_TIME) + { + cg.refdef.vieworg[ 2 ] -= cg.duckChange + * ( DUCK_TIME - timeDelta ) / DUCK_TIME; + } + + // add bob height + bob = cg.bobfracsin * cg.xyspeed * bob2; + + if( bob > 6 ) + bob = 6; + + VectorMA( origin, bob, normal, origin ); + + // add fall height + delta = cg.time - cg.landTime; + + if( delta < LAND_DEFLECT_TIME ) + { + f = delta / LAND_DEFLECT_TIME; + cg.refdef.vieworg[ 2 ] += cg.landChange * f; + } + else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) + { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + cg.refdef.vieworg[ 2 ] += cg.landChange * f; + } + + // add step offset + CG_StepOffset( ); +} + +//====================================================================== + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1.0f +#define WAVE_FREQUENCY 0.4f + +#define FOVWARPTIME 400.0f +#define BASE_FOV_Y 73.739792f // atan2( 3, 4 / tan( 90 ) ) +#define MAX_FOV_Y 120.0f +#define MAX_FOV_WARP_Y 127.5f + +static int CG_CalcFov( void ) +{ + float y; + float phase; + float v; + int contents; + float fov_x, fov_y; + float zoomFov; + float f; + int inwater; + int attribFov; + usercmd_t cmd; + usercmd_t oldcmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber( ); + trap_GetUserCmd( cmdNum, &cmd ); + trap_GetUserCmd( cmdNum - 1, &oldcmd ); + + // switch follow modes if necessary: cycle between free -> follow -> third-person follow + if( cmd.buttons & BUTTON_USE_HOLDABLE && !( oldcmd.buttons & BUTTON_USE_HOLDABLE ) ) + { + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + if( !cg.chaseFollow ) + cg.chaseFollow = qtrue; + else + { + cg.chaseFollow = qfalse; + trap_SendClientCommand( "follow\n" ); + } + } + else if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + trap_SendClientCommand( "follow\n" ); + } + + if( cg.predictedPlayerState.pm_type == PM_INTERMISSION || + ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) || + ( cg.renderingThirdPerson ) ) + { + // if in intermission or third person, use a fixed value + fov_y = BASE_FOV_Y; + } + else + { + // don't lock the fov globally - we need to be able to change it + attribFov = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fov * 0.75f; + fov_y = attribFov; + + if ( fov_y < 1.0f ) + fov_y = 1.0f; + else if ( fov_y > MAX_FOV_Y ) + fov_y = MAX_FOV_Y; + + if( cg.spawnTime > ( cg.time - FOVWARPTIME ) && + BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_FOVWARPS ) ) + { + float fraction = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME; + + fov_y = MAX_FOV_WARP_Y - ( ( MAX_FOV_WARP_Y - fov_y ) * fraction ); + } + + // account for zooms + zoomFov = BG_Weapon( cg.predictedPlayerState.weapon )->zoomFov * 0.75f; + if ( zoomFov < 1.0f ) + zoomFov = 1.0f; + else if ( zoomFov > attribFov ) + zoomFov = attribFov; + + // only do all the zoom stuff if the client CAN zoom + // FIXME: zoom control is currently hard coded to BUTTON_ATTACK2 + if( BG_Weapon( cg.predictedPlayerState.weapon )->canZoom ) + { + if ( cg.zoomed ) + { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + + if ( f > 1.0f ) + fov_y = zoomFov; + else + fov_y = fov_y + f * ( zoomFov - fov_y ); + + // BUTTON_ATTACK2 isn't held so unzoom next time + if( !( cmd.buttons & BUTTON_ATTACK2 ) ) + { + cg.zoomed = qfalse; + cg.zoomTime = MIN( cg.time, + cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); + } + } + else + { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + + if ( f > 1.0f ) + fov_y = fov_y; + else + fov_y = zoomFov + f * ( fov_y - zoomFov ); + + // BUTTON_ATTACK2 is held so zoom next time + if( cmd.buttons & BUTTON_ATTACK2 ) + { + cg.zoomed = qtrue; + cg.zoomTime = MIN( cg.time, + cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); + } + } + } + } + + y = cg.refdef.height / tan( 0.5f * DEG2RAD( fov_y ) ); + fov_x = atan2( cg.refdef.width, y ); + fov_x = 2.0f * RAD2DEG( fov_x ); + + // warp if underwater + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + + if( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) + { + phase = cg.time / 1000.0f * WAVE_FREQUENCY * M_PI * 2.0f; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else + inwater = qfalse; + + if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && + ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && + cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 && + !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + { + float scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / + BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); + + phase = ( cg.time - cg.poisonedTime ) / 1000.0f * PCLOUD_ZOOM_FREQUENCY * M_PI * 2.0f; + v = PCLOUD_ZOOM_AMPLITUDE * sin( phase ) * scale; + fov_x += v; + fov_y += v; + } + + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + if( !cg.zoomed ) + cg.zoomSensitivity = 1.0f; + else + cg.zoomSensitivity = cg.refdef.fov_y / 75.0f; + + return inwater; +} + + + +#define NORMAL_HEIGHT 64.0f +#define NORMAL_WIDTH 6.0f + +/* +=============== +CG_DrawSurfNormal + +Draws a vector against +the surface player is looking at +=============== +*/ +static void CG_DrawSurfNormal( void ) +{ + trace_t tr; + vec3_t end, temp; + polyVert_t normal[ 4 ]; + vec4_t color = { 0.0f, 255.0f, 0.0f, 128.0f }; + + VectorMA( cg.refdef.vieworg, 8192, cg.refdef.viewaxis[ 0 ], end ); + + CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, end, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + VectorCopy( tr.endpos, normal[ 0 ].xyz ); + normal[ 0 ].st[ 0 ] = 0; + normal[ 0 ].st[ 1 ] = 0; + Vector4Copy( color, normal[ 0 ].modulate ); + + VectorMA( tr.endpos, NORMAL_WIDTH, cg.refdef.viewaxis[ 1 ], temp ); + VectorCopy( temp, normal[ 1 ].xyz); + normal[ 1 ].st[ 0 ] = 0; + normal[ 1 ].st[ 1 ] = 1; + Vector4Copy( color, normal[ 1 ].modulate ); + + VectorMA( tr.endpos, NORMAL_HEIGHT, tr.plane.normal, temp ); + VectorMA( temp, NORMAL_WIDTH, cg.refdef.viewaxis[ 1 ], temp ); + VectorCopy( temp, normal[ 2 ].xyz ); + normal[ 2 ].st[ 0 ] = 1; + normal[ 2 ].st[ 1 ] = 1; + Vector4Copy( color, normal[ 2 ].modulate ); + + VectorMA( tr.endpos, NORMAL_HEIGHT, tr.plane.normal, temp ); + VectorCopy( temp, normal[ 3 ].xyz ); + normal[ 3 ].st[ 0 ] = 1; + normal[ 3 ].st[ 1 ] = 0; + Vector4Copy( color, normal[ 3 ].modulate ); + + trap_R_AddPolyToScene( cgs.media.outlineShader, 4, normal ); +} + +/* +=============== +CG_addSmoothOp +=============== +*/ +void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ) +{ + int i; + + //iterate through smooth array + for( i = 0; i < MAXSMOOTHS; i++ ) + { + //found an unused index in the smooth array + if( cg.sList[ i ].time + cg_wwSmoothTime.integer < cg.time ) + { + //copy to array and stop + VectorCopy( rotAxis, cg.sList[ i ].rotAxis ); + cg.sList[ i ].rotAngle = rotAngle; + cg.sList[ i ].time = cg.time; + cg.sList[ i ].timeMod = timeMod; + return; + } + } + + //no free indices in the smooth array +} + +/* +=============== +CG_smoothWWTransitions +=============== +*/ +static void CG_smoothWWTransitions( playerState_t *ps, const vec3_t in, vec3_t out ) +{ + vec3_t surfNormal, rotAxis, temp; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f }; + int i; + float stLocal, sFraction, rotAngle; + float smoothTime, timeMod; + qboolean performed = qfalse; + vec3_t inAxis[ 3 ], lastAxis[ 3 ], outAxis[ 3 ]; + + if( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + VectorCopy( in, out ); + return; + } + + //set surfNormal + BG_GetClientNormal( ps, surfNormal ); + + AnglesToAxis( in, inAxis ); + + //if we are moving from one surface to another smooth the transition + if( !VectorCompare( surfNormal, cg.lastNormal ) ) + { + //if we moving from the ceiling to the floor special case + //( x product of colinear vectors is undefined) + if( VectorCompare( ceilingNormal, cg.lastNormal ) && + VectorCompare( refNormal, surfNormal ) ) + { + AngleVectors( in, temp, NULL, NULL ); + ProjectPointOnPlane( rotAxis, temp, refNormal ); + VectorNormalize( rotAxis ); + rotAngle = 180.0f; + timeMod = 1.5f; + } + else + { + AnglesToAxis( cg.lastVangles, lastAxis ); + rotAngle = DotProduct( inAxis[ 0 ], lastAxis[ 0 ] ) + + DotProduct( inAxis[ 1 ], lastAxis[ 1 ] ) + + DotProduct( inAxis[ 2 ], lastAxis[ 2 ] ); + + rotAngle = RAD2DEG( acos( ( rotAngle - 1.0f ) / 2.0f ) ); + + CrossProduct( lastAxis[ 0 ], inAxis[ 0 ], temp ); + VectorCopy( temp, rotAxis ); + CrossProduct( lastAxis[ 1 ], inAxis[ 1 ], temp ); + VectorAdd( rotAxis, temp, rotAxis ); + CrossProduct( lastAxis[ 2 ], inAxis[ 2 ], temp ); + VectorAdd( rotAxis, temp, rotAxis ); + + VectorNormalize( rotAxis ); + + timeMod = 1.0f; + } + + //add the op + CG_addSmoothOp( rotAxis, rotAngle, timeMod ); + } + + //iterate through ops + for( i = MAXSMOOTHS - 1; i >= 0; i-- ) + { + smoothTime = (int)( cg_wwSmoothTime.integer * cg.sList[ i ].timeMod ); + + //if this op has time remaining, perform it + if( cg.time < cg.sList[ i ].time + smoothTime ) + { + stLocal = 1.0f - ( ( ( cg.sList[ i ].time + smoothTime ) - cg.time ) / smoothTime ); + sFraction = -( cos( stLocal * M_PI ) + 1.0f ) / 2.0f; + + RotatePointAroundVector( outAxis[ 0 ], cg.sList[ i ].rotAxis, + inAxis[ 0 ], sFraction * cg.sList[ i ].rotAngle ); + RotatePointAroundVector( outAxis[ 1 ], cg.sList[ i ].rotAxis, + inAxis[ 1 ], sFraction * cg.sList[ i ].rotAngle ); + RotatePointAroundVector( outAxis[ 2 ], cg.sList[ i ].rotAxis, + inAxis[ 2 ], sFraction * cg.sList[ i ].rotAngle ); + + AxisCopy( outAxis, inAxis ); + performed = qtrue; + } + } + + //if we performed any ops then return the smoothed angles + //otherwise simply return the in angles + if( performed ) + AxisToAngles( outAxis, out ); + else + VectorCopy( in, out ); + + //copy the current normal to the lastNormal + VectorCopy( in, cg.lastVangles ); + VectorCopy( surfNormal, cg.lastNormal ); +} + +/* +=============== +CG_smoothWJTransitions +=============== +*/ +static void CG_smoothWJTransitions( playerState_t *ps, const vec3_t in, vec3_t out ) +{ + int i; + float stLocal, sFraction; + qboolean performed = qfalse; + vec3_t inAxis[ 3 ], outAxis[ 3 ]; + + if( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + VectorCopy( in, out ); + return; + } + + AnglesToAxis( in, inAxis ); + + //iterate through ops + for( i = MAXSMOOTHS - 1; i >= 0; i-- ) + { + //if this op has time remaining, perform it + if( cg.time < cg.sList[ i ].time + cg_wwSmoothTime.integer ) + { + stLocal = ( ( cg.sList[ i ].time + cg_wwSmoothTime.integer ) - cg.time ) / cg_wwSmoothTime.integer; + sFraction = 1.0f - ( ( cos( stLocal * M_PI * 2.0f ) + 1.0f ) / 2.0f ); + + RotatePointAroundVector( outAxis[ 0 ], cg.sList[ i ].rotAxis, + inAxis[ 0 ], sFraction * cg.sList[ i ].rotAngle ); + RotatePointAroundVector( outAxis[ 1 ], cg.sList[ i ].rotAxis, + inAxis[ 1 ], sFraction * cg.sList[ i ].rotAngle ); + RotatePointAroundVector( outAxis[ 2 ], cg.sList[ i ].rotAxis, + inAxis[ 2 ], sFraction * cg.sList[ i ].rotAngle ); + + AxisCopy( outAxis, inAxis ); + performed = qtrue; + } + } + + //if we performed any ops then return the smoothed angles + //otherwise simply return the in angles + if( performed ) + AxisToAngles( outAxis, out ); + else + VectorCopy( in, out ); +} + + +/* +=============== +CG_CalcViewValues + +Sets cg.refdef view values +=============== +*/ +static int CG_CalcViewValues( void ) +{ + playerState_t *ps; + + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + + // calculate size of 3D view + CG_CalcVrect( ); + + ps = &cg.predictedPlayerState; + + // intermission view + if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_FREEZE || + ps->pm_type == PM_SPECTATOR ) + { + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + return CG_CalcFov( ); + } + + cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg.xyspeed = sqrt( ps->velocity[ 0 ] * ps->velocity[ 0 ] + + ps->velocity[ 1 ] * ps->velocity[ 1 ] ); + + // the bob velocity should't get too fast to avoid jerking + if( cg.xyspeed > 300.0f ) + cg.xyspeed = 300.0f; + + VectorCopy( ps->origin, cg.refdef.vieworg ); + + if( BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) ) + CG_smoothWWTransitions( ps, ps->viewangles, cg.refdefViewAngles ); + else if( BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) + CG_smoothWJTransitions( ps, ps->viewangles, cg.refdefViewAngles ); + else + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + + //clumsy logic, but it needs to be this way round because the CS propogation + //delay screws things up otherwise + if( !BG_ClassHasAbility( ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) + { + if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) + VectorSet( cg.lastNormal, 0.0f, 0.0f, 1.0f ); + } + + // add error decay + if( cg_errorDecay.value > 0 ) + { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + + if( f > 0 && f < 1 ) + VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); + else + cg.predictedErrorTime = 0; + } + + //shut off the poison cloud effect if it's still on the go + if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) + { + if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) + CG_DestroyParticleSystem( &cg.poisonCloudPS ); + } + else + cg.wasDeadLastFrame = qfalse; + + if( cg.renderingThirdPerson ) + { + // back away from character + CG_OffsetThirdPersonView( ); + } + else + { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView( ); + } + + // position eye reletive to origin + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + if( cg.hyperspace ) + cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + + //draw the surface normal looking at + if( cg_drawSurfNormal.integer ) + CG_DrawSurfNormal( ); + + // field of view + return CG_CalcFov( ); +} + +/* +===================== +CG_AddBufferedSound +===================== +*/ +void CG_AddBufferedSound( sfxHandle_t sfx ) +{ + if( !sfx ) + return; + + cg.soundBuffer[ cg.soundBufferIn ] = sfx; + cg.soundBufferIn = ( cg.soundBufferIn + 1 ) % MAX_SOUNDBUFFER; + + if( cg.soundBufferIn == cg.soundBufferOut ) + cg.soundBufferOut++; +} + +/* +===================== +CG_PlayBufferedSounds +===================== +*/ +static void CG_PlayBufferedSounds( void ) +{ + if( cg.soundTime < cg.time ) + { + if( cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[ cg.soundBufferOut ] ) + { + trap_S_StartLocalSound( cg.soundBuffer[ cg.soundBufferOut ], CHAN_ANNOUNCER ); + cg.soundBuffer[ cg.soundBufferOut ] = 0; + cg.soundBufferOut = ( cg.soundBufferOut + 1 ) % MAX_SOUNDBUFFER; + cg.soundTime = cg.time + 750; + } + } +} + +//========================================================================= + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) +{ + int inwater; + + cg.time = serverTime; + cg.demoPlayback = demoPlayback; + + // update cvars + CG_UpdateCvars( ); + + // if we are only updating the screen as a loading + // pacifier, don't even try to read snapshots + if( cg.infoScreenText[ 0 ] != 0 ) + { + CG_DrawLoadingScreen( ); + return; + } + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds( qfalse ); + + // clear all the render lists + trap_R_ClearScene( ); + + // set up cg.snap and possibly cg.nextSnap + CG_ProcessSnapshots( ); + + // if we haven't received any snapshots yet, all + // we can draw is the information screen + if( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) + { + CG_DrawLoadingScreen( ); + return; + } + + // let the client system know what our weapon and zoom settings are + trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); + + // this counter will be bumped for every valid scene we generate + cg.clientFrame++; + + // update cg.predictedPlayerState + CG_PredictPlayerState( ); + + // decide on third person view + cg.renderingThirdPerson = ( cg_thirdPerson.integer || ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) || + ( cg.chaseFollow && cg.snap->ps.pm_flags & PMF_FOLLOW) ); + + // update speedometer + CG_AddSpeed( ); + + // build cg.refdef + inwater = CG_CalcViewValues( ); + + // build the render lists + if( !cg.hyperspace ) + { + CG_AddPacketEntities( ); // after calcViewValues, so predicted player state is correct + CG_AddMarks( ); + } + + CG_AddViewWeapon( &cg.predictedPlayerState ); + + //after CG_AddViewWeapon + if( !cg.hyperspace ) + { + CG_AddParticles( ); + CG_DrawCuboidParticles( ); + CG_AddTrails( ); + } + + // add buffered sounds + CG_PlayBufferedSounds( ); + + // finish up the rest of the refdef + if( cg.testModelEntity.hModel ) + CG_AddTestModel( ); + + cg.refdef.time = cg.time; + memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); + + //remove expired console lines + if( cg.consoleLines[ 0 ].time + cg_consoleLatency.integer < cg.time && cg_consoleLatency.integer > 0 ) + CG_RemoveNotifyLine( ); + + // update audio positions + trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if( stereoView != STEREO_RIGHT ) + { + cg.frametime = cg.time - cg.oldTime; + + if( cg.frametime < 0 ) + cg.frametime = 0; + + cg.oldTime = cg.time; + CG_AddLagometerFrameInfo( ); + } + + if( cg_timescale.value != cg_timescaleFadeEnd.value ) + { + if( cg_timescale.value < cg_timescaleFadeEnd.value ) + { + cg_timescale.value += cg_timescaleFadeSpeed.value * ( (float)cg.frametime ) / 1000; + if( cg_timescale.value > cg_timescaleFadeEnd.value ) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + else + { + cg_timescale.value -= cg_timescaleFadeSpeed.value * ( (float)cg.frametime ) / 1000; + if( cg_timescale.value < cg_timescaleFadeEnd.value ) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + + if( cg_timescaleFadeSpeed.value ) + trap_Cvar_Set( "timescale", va( "%f", cg_timescale.value ) ); + } + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + if( cg_stats.integer ) + CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); +} + diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c new file mode 100644 index 0000000..2bb23ff --- /dev/null +++ b/src/cgame/cg_weapons.c @@ -0,0 +1,2075 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cg_weapons.c -- events and effects dealing with weapons + + +#include "cg_local.h" + +/* +================= +CG_RegisterUpgrade + +The server says this item is used on this level +================= +*/ +void CG_RegisterUpgrade( int upgradeNum ) +{ + upgradeInfo_t *upgradeInfo; + char *icon; + + if( upgradeNum <= UP_NONE || upgradeNum >= UP_NUM_UPGRADES ) + { + CG_Error( "CG_RegisterUpgrade: out of range: %d", upgradeNum ); + return; + } + + upgradeInfo = &cg_upgrades[ upgradeNum ]; + + if( upgradeInfo->registered ) + { + CG_Printf( "CG_RegisterUpgrade: already registered: (%d) %s\n", upgradeNum, + BG_Upgrade( upgradeNum )->name ); + return; + } + + upgradeInfo->registered = qtrue; + + if( !BG_Upgrade( upgradeNum )->name[ 0 ] ) + CG_Error( "Couldn't find upgrade %i", upgradeNum ); + + upgradeInfo->humanName = BG_Upgrade( upgradeNum )->humanName; + + //la la la la la, i'm not listening! + if( upgradeNum == UP_GRENADE ) + upgradeInfo->upgradeIcon = cg_weapons[ WP_GRENADE ].weaponIcon; + else if( ( icon = BG_Upgrade( upgradeNum )->icon ) ) + upgradeInfo->upgradeIcon = trap_R_RegisterShader( icon ); +} + +/* +=============== +CG_InitUpgrades + +Precaches upgrades +=============== +*/ +void CG_InitUpgrades( void ) +{ + int i; + + Com_Memset( cg_upgrades, 0, sizeof( cg_upgrades ) ); + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + CG_RegisterUpgrade( i ); +} + + +/* +=============== +CG_ParseWeaponModeSection + +Parse a weapon mode section +=============== +*/ +static qboolean CG_ParseWeaponModeSection( weaponInfoMode_t *wim, char **text_p ) +{ + char *token; + int i; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "missileModel" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileModel = trap_R_RegisterModel( token ); + + if( !wim->missileModel ) + CG_Printf( S_COLOR_RED "ERROR: missile model not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "missileSprite" ) ) + { + int size = 0; + + token = COM_Parse( text_p ); + if( !token ) + break; + + size = atoi( token ); + + if( size < 0 ) + size = 0; + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileSprite = trap_R_RegisterShader( token ); + wim->missileSpriteSize = size; + wim->usesSpriteMissle = qtrue; + + if( !wim->missileSprite ) + CG_Printf( S_COLOR_RED "ERROR: missile sprite not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "missileSpriteCharge" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileSpriteCharge = atof( token ); + + continue; + } + else if( !Q_stricmp( token, "missileRotates" ) ) + { + wim->missileRotates = qtrue; + + continue; + } + else if( !Q_stricmp( token, "missileAnimates" ) ) + { + wim->missileAnimates = qtrue; + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileAnimStartFrame = atoi( token ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileAnimNumFrames = atoi( token ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileAnimFrameRate = atoi( token ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileAnimLooping = atoi( token ); + + continue; + } + else if( !Q_stricmp( token, "missileParticleSystem" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileParticleSystem = CG_RegisterParticleSystem( token ); + + if( !wim->missileParticleSystem ) + CG_Printf( S_COLOR_RED "ERROR: missile particle system not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "missileTrailSystem" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileTrailSystem = CG_RegisterTrailSystem( token ); + + if( !wim->missileTrailSystem ) + CG_Printf( S_COLOR_RED "ERROR: missile trail system not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "muzzleParticleSystem" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->muzzleParticleSystem = CG_RegisterParticleSystem( token ); + + if( !wim->muzzleParticleSystem ) + CG_Printf( S_COLOR_RED "ERROR: muzzle particle system not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "impactParticleSystem" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->impactParticleSystem = CG_RegisterParticleSystem( token ); + + if( !wim->impactParticleSystem ) + CG_Printf( S_COLOR_RED "ERROR: impact particle system not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "impactMark" ) ) + { + int size = 0; + + token = COM_Parse( text_p ); + if( !token ) + break; + + size = atoi( token ); + + if( size < 0 ) + size = 0; + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->impactMark = trap_R_RegisterShader( token ); + wim->impactMarkSize = size; + + if( !wim->impactMark ) + CG_Printf( S_COLOR_RED "ERROR: impact mark shader not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "impactSound" ) ) + { + int index = 0; + + token = COM_Parse( text_p ); + if( !token ) + break; + + index = atoi( token ); + + if( index < 0 ) + index = 0; + else if( index > 3 ) + index = 3; + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->impactSound[ index ] = trap_S_RegisterSound( token, qfalse ); + + continue; + } + else if( !Q_stricmp( token, "impactFleshSound" ) ) + { + int index = 0; + + token = COM_Parse( text_p ); + if( !token ) + break; + + index = atoi( token ); + + if( index < 0 ) + index = 0; + else if( index > 3 ) + index = 3; + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->impactFleshSound[ index ] = trap_S_RegisterSound( token, qfalse ); + + continue; + } + else if( !Q_stricmp( token, "alwaysImpact" ) ) + { + wim->alwaysImpact = qtrue; + + continue; + } + else if( !Q_stricmp( token, "flashDLightColor" ) ) + { + for( i = 0 ; i < 3 ; i++ ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->flashDlightColor[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "continuousFlash" ) ) + { + wim->continuousFlash = qtrue; + + continue; + } + else if( !Q_stricmp( token, "missileDlightColor" ) ) + { + for( i = 0 ; i < 3 ; i++ ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileDlightColor[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "missileDlight" ) ) + { + int size = 0; + + token = COM_Parse( text_p ); + if( !token ) + break; + + size = atoi( token ); + + if( size < 0 ) + size = 0; + + wim->missileDlight = size; + + continue; + } + else if( !Q_stricmp( token, "firingSound" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->firingSound = trap_S_RegisterSound( token, qfalse ); + + continue; + } + else if( !Q_stricmp( token, "missileSound" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileSound = trap_S_RegisterSound( token, qfalse ); + + continue; + } + else if( !Q_stricmp( token, "flashSound" ) ) + { + int index = 0; + + token = COM_Parse( text_p ); + if( !token ) + break; + + index = atoi( token ); + + if( index < 0 ) + index = 0; + else if( index > 3 ) + index = 3; + + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->flashSound[ index ] = trap_S_RegisterSound( token, qfalse ); + + continue; + } + else if( !Q_stricmp( token, "}" ) ) + return qtrue; //reached the end of this weapon section + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in weapon section\n", token ); + return qfalse; + } + } + + return qfalse; +} + + +/* +====================== +CG_ParseWeaponFile + +Parses a configuration file describing a weapon +====================== +*/ +static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi ) +{ + char *text_p; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + weaponMode_t weaponMode = WPM_NONE; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "{" ) ) + { + if( weaponMode == WPM_NONE ) + { + CG_Printf( S_COLOR_RED "ERROR: weapon mode section started without a declaration\n" ); + return qfalse; + } + else if( !CG_ParseWeaponModeSection( &wi->wim[ weaponMode ], &text_p ) ) + { + CG_Printf( S_COLOR_RED "ERROR: failed to parse weapon mode section\n" ); + return qfalse; + } + + //start parsing ejectors again + weaponMode = WPM_NONE; + + continue; + } + else if( !Q_stricmp( token, "primary" ) ) + { + weaponMode = WPM_PRIMARY; + continue; + } + else if( !Q_stricmp( token, "secondary" ) ) + { + weaponMode = WPM_SECONDARY; + continue; + } + else if( !Q_stricmp( token, "tertiary" ) ) + { + weaponMode = WPM_TERTIARY; + continue; + } + else if( !Q_stricmp( token, "weaponModel" ) ) + { + char path[ MAX_QPATH ]; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + wi->weaponModel = trap_R_RegisterModel( token ); + + if( !wi->weaponModel ) + CG_Printf( S_COLOR_RED "ERROR: weapon model not found %s\n", token ); + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_flash.md3" ); + wi->flashModel = trap_R_RegisterModel( path ); + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_barrel.md3" ); + wi->barrelModel = trap_R_RegisterModel( path ); + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_hand.md3" ); + wi->handsModel = trap_R_RegisterModel( path ); + + continue; + } + else if( !Q_stricmp( token, "weaponModel3rdPerson" ) ) + { + char path[ MAX_QPATH ]; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + wi->weaponModel3rdPerson = trap_R_RegisterModel( token ); + + if( !wi->weaponModel3rdPerson ) + { + CG_Printf( S_COLOR_RED "ERROR: 3rd person weapon " + "model not found %s\n", token ); + } + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_flash.md3" ); + wi->flashModel3rdPerson = trap_R_RegisterModel( path ); + + strcpy( path, token ); + COM_StripExtension( path, path, MAX_QPATH ); + strcat( path, "_barrel.md3" ); + wi->barrelModel3rdPerson = trap_R_RegisterModel( path ); + + continue; + } + else if( !Q_stricmp( token, "idleSound" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + wi->readySound = trap_S_RegisterSound( token, qfalse ); + + continue; + } + else if( !Q_stricmp( token, "icon" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + wi->weaponIcon = wi->ammoIcon = trap_R_RegisterShader( token ); + + if( !wi->weaponIcon ) + CG_Printf( S_COLOR_RED "ERROR: weapon icon not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "crosshair" ) ) + { + int size = 0; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + size = atoi( token ); + + if( size < 0 ) + size = 0; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + wi->crossHair = trap_R_RegisterShader( token ); + wi->crossHairSize = size; + + if( !wi->crossHair ) + CG_Printf( S_COLOR_RED "ERROR: weapon crosshair not found %s\n", token ); + + continue; + } + else if( !Q_stricmp( token, "disableIn3rdPerson" ) ) + { + wi->disableIn3rdPerson = qtrue; + + continue; + } + + Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); + return qfalse; + } + + return qtrue; +} + +/* +================= +CG_RegisterWeapon +================= +*/ +void CG_RegisterWeapon( int weaponNum ) +{ + weaponInfo_t *weaponInfo; + char path[ MAX_QPATH ]; + vec3_t mins, maxs; + int i; + + if( weaponNum <= WP_NONE || weaponNum >= WP_NUM_WEAPONS ) + { + CG_Error( "CG_RegisterWeapon: out of range: %d", weaponNum ); + return; + } + + weaponInfo = &cg_weapons[ weaponNum ]; + + if( weaponInfo->registered ) + { + CG_Printf( "CG_RegisterWeapon: already registered: (%d) %s\n", weaponNum, + BG_Weapon( weaponNum )->name ); + return; + } + + weaponInfo->registered = qtrue; + + if( !BG_Weapon( weaponNum )->name[ 0 ] ) + CG_Error( "Couldn't find weapon %i", weaponNum ); + + Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_Weapon( weaponNum )->name ); + + weaponInfo->humanName = BG_Weapon( weaponNum )->humanName; + + if( !CG_ParseWeaponFile( path, weaponInfo ) ) + Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path ); + + // calc midpoint for rotation + trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); + for( i = 0 ; i < 3 ; i++ ) + weaponInfo->weaponMidpoint[ i ] = mins[ i ] + 0.5 * ( maxs[ i ] - mins[ i ] ); +} + +/* +=============== +CG_InitWeapons + +Precaches weapons +=============== +*/ +void CG_InitWeapons( void ) +{ + int i; + + Com_Memset( cg_weapons, 0, sizeof( cg_weapons ) ); + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + CG_RegisterWeapon( i ); + + cgs.media.level2ZapTS = CG_RegisterTrailSystem( "models/weapons/lev2zap/lightning" ); +} + + +/* +======================================================================================== + +VIEW WEAPON + +======================================================================================== +*/ + +/* +=============== +CG_SetWeaponLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetWeaponLerpFrameAnimation( weapon_t weapon, lerpFrame_t *lf, int newAnimation ) +{ + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if( newAnimation < 0 || newAnimation >= MAX_WEAPON_ANIMATIONS ) + CG_Error( "Bad animation number: %i", newAnimation ); + + anim = &cg_weapons[ weapon ].animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if( cg_debugAnim.integer ) + CG_Printf( "Anim: %i\n", newAnimation ); +} + +/* +=============== +CG_WeaponAnimation +=============== +*/ +static void CG_WeaponAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ + lerpFrame_t *lf = ¢->pe.weapon; + entityState_t *es = ¢->currentState; + + // see if the animation sequence is switching + if( es->weaponAnim != lf->animationNumber || !lf->animation ) + CG_SetWeaponLerpFrameAnimation( es->weapon, lf, es->weaponAnim ); + + CG_RunLerpFrame( lf, 1.0f ); + + *old = lf->oldFrame; + *now = lf->frame; + *backLerp = lf->backlerp; +} + +/* +================= +CG_MapTorsoToWeaponFrame + +================= +*/ +static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) +{ + + // change weapon + if( frame >= ci->animations[ TORSO_DROP ].firstFrame && + frame < ci->animations[ TORSO_DROP ].firstFrame + 9 ) + return frame - ci->animations[ TORSO_DROP ].firstFrame + 6; + + // stand attack + if( frame >= ci->animations[ TORSO_ATTACK ].firstFrame && + frame < ci->animations[ TORSO_ATTACK ].firstFrame + 6 ) + return 1 + frame - ci->animations[ TORSO_ATTACK ].firstFrame; + + // stand attack 2 + if( frame >= ci->animations[ TORSO_ATTACK2 ].firstFrame && + frame < ci->animations[ TORSO_ATTACK2 ].firstFrame + 6 ) + return 1 + frame - ci->animations[ TORSO_ATTACK2 ].firstFrame; + + return 0; +} + + +/* +============== +CG_CalculateWeaponPosition +============== +*/ +static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) +{ + float scale; + int delta; + float fracsin; + float bob; + weaponInfo_t *weapon; + + weapon = &cg_weapons[ cg.predictedPlayerState.weapon ]; + + VectorCopy( cg.refdef.vieworg, origin ); + VectorCopy( cg.refdefViewAngles, angles ); + + // on odd legs, invert some angles + if( cg.bobcycle & 1 ) + scale = -cg.xyspeed; + else + scale = cg.xyspeed; + + // gun angles from bobbing + // bob amount is class dependant + bob = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; + + if( bob != 0 ) + { + angles[ ROLL ] += scale * cg.bobfracsin * 0.005; + angles[ YAW ] += scale * cg.bobfracsin * 0.01; + angles[ PITCH ] += cg.xyspeed * cg.bobfracsin * 0.005; + } + + // drop the weapon when landing + if( !weapon->noDrift ) + { + delta = cg.time - cg.landTime; + if( delta < LAND_DEFLECT_TIME ) + origin[ 2 ] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; + else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) + origin[ 2 ] += cg.landChange*0.25 * + ( LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta ) / LAND_RETURN_TIME; + + // idle drift + scale = cg.xyspeed + 40; + fracsin = sin( cg.time * 0.001 ); + angles[ ROLL ] += scale * fracsin * 0.01; + angles[ YAW ] += scale * fracsin * 0.01; + angles[ PITCH ] += scale * fracsin * 0.01; + } +} + + +/* +====================== +CG_MachinegunSpinAngle +====================== +*/ +#define SPIN_SPEED 0.9 +#define COAST_TIME 1000 +static float CG_MachinegunSpinAngle( centity_t *cent, qboolean firing ) +{ + int delta; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + if( cent->pe.barrelSpinning ) + angle = cent->pe.barrelAngle + delta * SPIN_SPEED; + else + { + if( delta > COAST_TIME ) + delta = COAST_TIME; + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = cent->pe.barrelAngle + delta * speed; + } + + if( cent->pe.barrelSpinning == !firing ) + { + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = firing; + } + + return angle; +} + + +/* +============= +CG_AddPlayerWeapon + +Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) +The main player will have this called for BOTH cases, so effects like light and +sound should only be done on the world model case. +============= +*/ +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ) +{ + refEntity_t gun; + refEntity_t barrel; + refEntity_t flash; + vec3_t angles; + weapon_t weaponNum; + weaponMode_t weaponMode; + weaponInfo_t *weapon; + qboolean noGunModel; + qboolean firing; + + weaponNum = cent->currentState.weapon; + weaponMode = cent->currentState.generic1; + + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) + weaponMode = WPM_PRIMARY; + + if( ( ( cent->currentState.eFlags & EF_FIRING ) && weaponMode == WPM_PRIMARY ) || + ( ( cent->currentState.eFlags & EF_FIRING2 ) && weaponMode == WPM_SECONDARY ) || + ( ( cent->currentState.eFlags & EF_FIRING3 ) && weaponMode == WPM_TERTIARY ) ) + firing = qtrue; + else + firing = qfalse; + + weapon = &cg_weapons[ weaponNum ]; + if( !weapon->registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddPlayerWeapon: weapon %d (%s) " + "is not registered\n", weaponNum, BG_Weapon( weaponNum )->name ); + return; + } + + // add the weapon + Com_Memset( &gun, 0, sizeof( gun ) ); + Com_Memset( &barrel, 0, sizeof( barrel ) ); + Com_Memset( &flash, 0, sizeof( flash ) ); + + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); + gun.shadowPlane = parent->shadowPlane; + gun.renderfx = parent->renderfx; + + if( ps ) + { + gun.shaderRGBA[ 0 ] = 255; + gun.shaderRGBA[ 1 ] = 255; + gun.shaderRGBA[ 2 ] = 255; + gun.shaderRGBA[ 3 ] = 255; + + //set weapon[1/2]Time when respective buttons change state + if( cg.weapon1Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING ) ) + { + cg.weapon1Time = cg.time; + cg.weapon1Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING ); + } + + if( cg.weapon2Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING2 ) ) + { + cg.weapon2Time = cg.time; + cg.weapon2Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING2 ); + } + + if( cg.weapon3Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING3 ) ) + { + cg.weapon3Time = cg.time; + cg.weapon3Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING3 ); + } + } + + if( !ps ) + { + gun.hModel = weapon->weaponModel3rdPerson; + + if( !gun.hModel ) + gun.hModel = weapon->weaponModel; + } + else + gun.hModel = weapon->weaponModel; + + noGunModel = ( ( !ps || cg.renderingThirdPerson ) && weapon->disableIn3rdPerson ) || !gun.hModel; + + if( !ps ) + { + // add weapon ready sound + if( firing && weapon->wim[ weaponMode ].firingSound ) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + weapon->wim[ weaponMode ].firingSound ); + } + else if( weapon->readySound ) + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); + } + + // Lucifer cannon charge warning beep + if( weaponNum == WP_LUCIFER_CANNON && + ( cent->currentState.eFlags & EF_WARN_CHARGE ) && + cg.snap->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, + vec3_origin, ps ? cgs.media.lCannonWarningSound : + cgs.media.lCannonWarningSound2 ); + } + + if( !noGunModel ) + { + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon" ); + CG_WeaponAnimation( cent, &gun.oldframe, &gun.frame, &gun.backlerp ); + + trap_R_AddRefEntityToScene( &gun ); + + if( !ps ) + { + barrel.hModel = weapon->barrelModel3rdPerson; + + if( !barrel.hModel ) + barrel.hModel = weapon->barrelModel; + } + else + barrel.hModel = weapon->barrelModel; + + // add the spinning barrel + if( barrel.hModel ) + { + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + angles[ YAW ] = 0; + angles[ PITCH ] = 0; + angles[ ROLL ] = CG_MachinegunSpinAngle( cent, firing ); + AnglesToAxis( angles, barrel.axis ); + + CG_PositionRotatedEntityOnTag( &barrel, &gun, gun.hModel, "tag_barrel" ); + + trap_R_AddRefEntityToScene( &barrel ); + } + } + + if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) + { + if( ps || cg.renderingThirdPerson || + cent->currentState.number != cg.predictedPlayerState.clientNum ) + { + if( noGunModel ) + CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); + else + CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, gun.hModel, "tag_flash" ); + } + + //if the PS is infinite disable it when not firing + if( !firing && CG_IsParticleSystemInfinite( cent->muzzlePS ) ) + CG_DestroyParticleSystem( ¢->muzzlePS ); + } + + // add the flash + if( !weapon->wim[ weaponMode ].continuousFlash || !firing ) + { + // impulse flash + if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ) + return; + } + + VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); + flash.shadowPlane = parent->shadowPlane; + flash.renderfx = parent->renderfx; + + if( !ps ) + { + flash.hModel = weapon->flashModel3rdPerson; + + if( !flash.hModel ) + flash.hModel = weapon->flashModel; + } + else + flash.hModel = weapon->flashModel; + + if( flash.hModel ) + { + angles[ YAW ] = 0; + angles[ PITCH ] = 0; + angles[ ROLL ] = crandom( ) * 10; + AnglesToAxis( angles, flash.axis ); + + if( noGunModel ) + CG_PositionRotatedEntityOnTag( &flash, parent, parent->hModel, "tag_weapon" ); + else + CG_PositionRotatedEntityOnTag( &flash, &gun, gun.hModel, "tag_flash" ); + + trap_R_AddRefEntityToScene( &flash ); + } + + if( ps || cg.renderingThirdPerson || + cent->currentState.number != cg.predictedPlayerState.clientNum ) + { + if( weapon->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger ) + { + cent->muzzlePS = CG_SpawnNewParticleSystem( weapon->wim[ weaponMode ].muzzleParticleSystem ); + + if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) + { + if( noGunModel ) + CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); + else + CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, gun.hModel, "tag_flash" ); + + CG_SetAttachmentCent( ¢->muzzlePS->attachment, cent ); + CG_AttachToTag( ¢->muzzlePS->attachment ); + } + + cent->muzzlePsTrigger = qfalse; + } + + // make a dlight for the flash + if( weapon->wim[ weaponMode ].flashDlightColor[ 0 ] || + weapon->wim[ weaponMode ].flashDlightColor[ 1 ] || + weapon->wim[ weaponMode ].flashDlightColor[ 2 ] ) + { + trap_R_AddLightToScene( flash.origin, 300 + ( rand( ) & 31 ), + weapon->wim[ weaponMode ].flashDlightColor[ 0 ], + weapon->wim[ weaponMode ].flashDlightColor[ 1 ], + weapon->wim[ weaponMode ].flashDlightColor[ 2 ] ); + } + } +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ + +#define WEAPON_CLICK_REPEAT 500 + +void CG_AddViewWeapon( playerState_t *ps ) +{ + refEntity_t hand; + centity_t *cent; + clientInfo_t *ci; + float fovOffset; + vec3_t angles; + weaponInfo_t *wi; + weapon_t weapon = ps->weapon; + weaponMode_t weaponMode = ps->generic1; + vec3_t cuboidSize; + + // no weapon carried - can't draw it + if( weapon == WP_NONE ) + return; + + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) + weaponMode = WPM_PRIMARY; + + wi = &cg_weapons[ weapon ]; + if( !wi->registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_AddViewWeapon: weapon %d (%s) " + "is not registered\n", weapon, BG_Weapon( weapon )->name ); + return; + } + cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; + + if( ps->persistant[PERS_SPECSTATE] != SPECTATOR_NOT ) + return; + + if( ps->pm_type == PM_INTERMISSION ) + return; + + // draw a prospective buildable infront of the player + if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) + CG_GhostBuildable( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, cg.cuboidSelection ); + + // no gun if in third person view + if( cg.renderingThirdPerson ) + return; + + // allow the gun to be completely removed + if( !cg_drawGun.integer ) + { + vec3_t origin; + + VectorCopy( cg.refdef.vieworg, origin ); + VectorMA( origin, -8, cg.refdef.viewaxis[ 2 ], origin ); + + if( cent->muzzlePS ) + CG_SetAttachmentPoint( ¢->muzzlePS->attachment, origin ); + + //check for particle systems + if( wi->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger ) + { + cent->muzzlePS = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].muzzleParticleSystem ); + + if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) + { + CG_SetAttachmentPoint( ¢->muzzlePS->attachment, origin ); + CG_SetAttachmentCent( ¢->muzzlePS->attachment, cent ); + CG_AttachToPoint( ¢->muzzlePS->attachment ); + } + cent->muzzlePsTrigger = qfalse; + } + + return; + } + + // don't draw if testing a gun model + if( cg.testGun ) + return; + + // drop gun lower at higher fov + if( cg.refdef.fov_y > 90 ) + fovOffset = -0.4 * ( cg.refdef.fov_y - 90 ); + else + fovOffset = 0; + + Com_Memset( &hand, 0, sizeof( hand ) ); + + // set up gun position + CG_CalculateWeaponPosition( hand.origin, angles ); + + VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[ 0 ], hand.origin ); + VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[ 1 ], hand.origin ); + VectorMA( hand.origin, ( cg_gun_z.value + fovOffset ), cg.refdef.viewaxis[ 2 ], hand.origin ); + + // Lucifer Cannon vibration effect + if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 ) + { + float fraction; + + fraction = (float)ps->stats[ STAT_MISC ] / LCANNON_CHARGE_TIME_MAX; + VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 0 ], + hand.origin ); + VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 1 ], + hand.origin ); + } + + AnglesToAxis( angles, hand.axis ); + + // map torso animations to weapon animations + if( cg_gun_frame.integer ) + { + // development tool + hand.frame = hand.oldframe = cg_gun_frame.integer; + hand.backlerp = 0; + } + else + { + // get clientinfo for animation map + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); + hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); + hand.backlerp = cent->pe.torso.backlerp; + } + + hand.hModel = wi->handsModel; + hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; + + // add everything onto the hand + CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity ); +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( weapon_t weapon ) +{ + if( !BG_InventoryContainsWeapon( weapon, cg.snap->ps.stats ) ) + return qfalse; + + return qtrue; +} + + +/* +=============== +CG_UpgradeSelectable +=============== +*/ +static qboolean CG_UpgradeSelectable( upgrade_t upgrade ) +{ + if( !BG_InventoryContainsUpgrade( upgrade, cg.snap->ps.stats ) ) + return qfalse; + + return BG_Upgrade( upgrade )->usable; +} + + +#define ICON_BORDER 4 + +/* +=================== +CG_DrawItemSelect +=================== +*/ +void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ) +{ + int i; + float x = rect->x; + float y = rect->y; + float width = rect->w; + float height = rect->h; + float iconWidth; + float iconHeight; + int items[ 64 ]; + int colinfo[ 64 ]; + int numItems = 0, selectedItem = 0; + int length; + qboolean vertical; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[ cg.snap->ps.clientNum ]; + ps = &cg.snap->ps; + + // don't display if dead + if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) + return; + + if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) + { + // first make sure that whatever it selected is actually selectable + if( cg.weaponSelect < 32 ) + { + if( !CG_WeaponSelectable( cg.weaponSelect ) ) + CG_NextWeapon_f( ); + } + else + { + if( !CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) + CG_NextWeapon_f( ); + } + } + + // showing weapon select clears pickup item display, but not the blend blob + cg.itemPickupTime = 0; + + // put all weapons in the items list + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) ) + continue; + + if( !ps->ammo && !ps->clips && !BG_Weapon( i )->infiniteAmmo ) + colinfo[ numItems ] = 1; + else + colinfo[ numItems ] = 0; + + if( i == cg.weaponSelect ) + selectedItem = numItems; + + if( !cg_weapons[ i ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: weapon %d (%s) " + "is not registered\n", i, BG_Weapon( i )->name ); + continue; + } + items[ numItems ] = i; + numItems++; + } + + // put all upgrades in the weapons list + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( !BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) ) + continue; + colinfo[ numItems ] = 0; + if( !BG_Upgrade( i )->usable ) + colinfo[ numItems ] = 2; + + if( i == cg.weaponSelect - 32 ) + selectedItem = numItems; + + if( !cg_upgrades[ i ].registered ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: upgrade %d (%s) " + "is not registered\n", i, BG_Upgrade( i )->name ); + continue; + } + items[ numItems ] = i + 32; + numItems++; + } + + // compute the length of the display window and determine orientation + vertical = height > width; + if( vertical ) + { + iconWidth = width * cgDC.aspectScale; + iconHeight = width; + length = height / ( width * cgDC.aspectScale ); + } + else + { + iconWidth = height * cgDC.aspectScale; + iconHeight = height; + length = width / ( height * cgDC.aspectScale ); + } + + // render icon ring + for( i = 0; i < length; i++ ) + { + int item = i - length / 2 + selectedItem; + + if( item < 0 ) + item += length; + else if( item >= length ) + item -= length; + if( item >= 0 && item < numItems ) + { + switch( colinfo[ item ] ) + { + case 0: + color = colorCyan; + break; + case 1: + color = colorRed; + break; + case 2: + color = colorMdGrey; + break; + } + color[3] = 0.5; + trap_R_SetColor( color ); + + if( items[ item ] < 32 ) + CG_DrawPic( x, y, iconWidth, iconHeight, + cg_weapons[ items[ item ] ].weaponIcon ); + else + CG_DrawPic( x, y, iconWidth, iconHeight, + cg_upgrades[ items[ item ] - 32 ].upgradeIcon ); + } + if( vertical ) + y += iconHeight; + else + x += iconWidth; + } + trap_R_SetColor( NULL ); +} + + +/* +=================== +CG_DrawItemSelectText +=================== +*/ +void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ) +{ + int x, w; + char *name; + float *color; + + color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); + if( !color ) + return; + + trap_R_SetColor( color ); + + // draw the selected name + if( cg.weaponSelect < 32 ) + { + if( cg_weapons[ cg.weaponSelect ].registered && + BG_InventoryContainsWeapon( cg.weaponSelect, cg.snap->ps.stats ) ) + { + if( ( name = cg_weapons[ cg.weaponSelect ].humanName ) ) + { + w = UI_Text_Width( name, scale ); + x = rect->x + rect->w / 2; + UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + } + } + } + else + { + if( cg_upgrades[ cg.weaponSelect - 32 ].registered && + BG_InventoryContainsUpgrade( cg.weaponSelect - 32, cg.snap->ps.stats ) ) + { + if( ( name = cg_upgrades[ cg.weaponSelect - 32 ].humanName ) ) + { + w = UI_Text_Width( name, scale ); + x = rect->x + rect->w / 2; + UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + } + } + } + + trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_NextWeapon_f +=============== +*/ +void CG_NextWeapon_f( void ) +{ + int i; + int original; + + if( !cg.snap ) + return; + + if( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + trap_SendClientCommand( "followprev\n" ); + return; + } + + if( BG_Buildable(cg.snap->ps.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid ) + { + CG_CuboidResize(qtrue); + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + for( i = 0; i < 64; i++ ) + { + cg.weaponSelect++; + if( cg.weaponSelect == 64 ) + cg.weaponSelect = 0; + + if( cg.weaponSelect < 32 ) + { + if( CG_WeaponSelectable( cg.weaponSelect ) ) + break; + } + else + { + if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) + break; + } + } + + if( i == 64 ) + cg.weaponSelect = original; +} + +/* +=============== +CG_PrevWeapon_f +=============== +*/ +void CG_PrevWeapon_f( void ) +{ + int i; + int original; + + if( !cg.snap ) + return; + + if( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + trap_SendClientCommand( "follownext\n" ); + return; + } + + if( BG_Buildable(cg.snap->ps.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid ) + { + CG_CuboidResize(qfalse); + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + for( i = 0; i < 64; i++ ) + { + cg.weaponSelect--; + if( cg.weaponSelect == -1 ) + cg.weaponSelect = 63; + + if( cg.weaponSelect < 32 ) + { + if( CG_WeaponSelectable( cg.weaponSelect ) ) + break; + } + else + { + if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) + break; + } + } + + if( i == 64 ) + cg.weaponSelect = original; +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) +{ + int num; + + if( !cg.snap ) + return; + + if( cg.snap->ps.pm_flags & PMF_FOLLOW ) + return; + + num = atoi( CG_Argv( 1 ) ); + + if( num < 1 || num > 31 ) + return; + + cg.weaponSelectTime = cg.time; + + if( !BG_InventoryContainsWeapon( num, cg.snap->ps.stats ) ) + return; // don't have the weapon + + cg.weaponSelect = num; +} + + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode ) +{ + entityState_t *es; + int c; + weaponInfo_t *wi; + weapon_t weaponNum; + + es = ¢->currentState; + + weaponNum = es->weapon; + + if( weaponNum == WP_NONE ) + return; + + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) + weaponMode = WPM_PRIMARY; + + if( weaponNum >= WP_NUM_WEAPONS ) + { + CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); + return; + } + + wi = &cg_weapons[ weaponNum ]; + + // mark the entity as muzzle flashing, so when it is added it will + // append the flash to the weapon model + cent->muzzleFlashTime = cg.time; + + if( wi->wim[ weaponMode ].muzzleParticleSystem ) + { + if( !CG_IsParticleSystemValid( ¢->muzzlePS ) || + !CG_IsParticleSystemInfinite( cent->muzzlePS ) ) + cent->muzzlePsTrigger = qtrue; + } + + // play a sound + for( c = 0; c < 4; c++ ) + { + if( !wi->wim[ weaponMode ].flashSound[ c ] ) + break; + } + + if( c > 0 ) + { + c = rand( ) % c; + if( wi->wim[ weaponMode ].flashSound[ c ] ) + trap_S_StartSound( NULL, es->number, CHAN_WEAPON, wi->wim[ weaponMode ].flashSound[ c ] ); + } +} + + +/* +================= +CG_MissileHitWall + +Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing +================= +*/ +void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientNum, + vec3_t origin, vec3_t dir, impactSound_t soundType, int charge ) +{ + qhandle_t mark = 0; + qhandle_t ps = 0; + int c; + float radius = 1.0f; + weaponInfo_t *weapon = &cg_weapons[ weaponNum ]; + + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) + weaponMode = WPM_PRIMARY; + + mark = weapon->wim[ weaponMode ].impactMark; + radius = weapon->wim[ weaponMode ].impactMarkSize; + ps = weapon->wim[ weaponMode ].impactParticleSystem; + + if( soundType == IMPACTSOUND_FLESH ) + { + //flesh sound + for( c = 0; c < 4; c++ ) + { + if( !weapon->wim[ weaponMode ].impactFleshSound[ c ] ) + break; + } + + if( c > 0 ) + { + c = rand( ) % c; + if( weapon->wim[ weaponMode ].impactFleshSound[ c ] ) + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactFleshSound[ c ] ); + } + } + else + { + //normal sound + for( c = 0; c < 4; c++ ) + { + if( !weapon->wim[ weaponMode ].impactSound[ c ] ) + break; + } + + if( c > 0 ) + { + c = rand( ) % c; + if( weapon->wim[ weaponMode ].impactSound[ c ] ) + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactSound[ c ] ); + } + } + + //create impact particle system + if( ps ) + { + particleSystem_t *partSystem = CG_SpawnNewParticleSystem( ps ); + + if( CG_IsParticleSystemValid( &partSystem ) ) + { + CG_SetAttachmentPoint( &partSystem->attachment, origin ); + CG_SetParticleSystemNormal( partSystem, dir ); + CG_AttachToPoint( &partSystem->attachment ); + partSystem->charge = charge; + } + } + + // + // impact mark + // + if( radius > 0.0f ) + CG_ImpactMark( mark, origin, dir, random( ) * 360, 1, 1, 1, 1, qfalse, radius, qfalse ); +} + + +/* +================= +CG_MissileHitEntity +================= +*/ +void CG_MissileHitEntity( weapon_t weaponNum, weaponMode_t weaponMode, + vec3_t origin, vec3_t dir, int entityNum, int charge ) +{ + vec3_t normal; + weaponInfo_t *weapon = &cg_weapons[ weaponNum ]; + + VectorCopy( dir, normal ); + VectorInverse( normal ); + + CG_Bleed( origin, normal, entityNum ); + + if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES ) + weaponMode = WPM_PRIMARY; + + // always impact! + //if( weapon->wim[ weaponMode ].alwaysImpact ) + { + int sound; + + if( cg_entities[ entityNum ].currentState.eType == ET_PLAYER ) + { + // Players + sound = IMPACTSOUND_FLESH; + } + else if( cg_entities[ entityNum ].currentState.eType == ET_BUILDABLE && + BG_Buildable( cg_entities[ entityNum ].currentState.modelindex, NULL )->team == TEAM_ALIENS ) + { + // Alien buildables + sound = IMPACTSOUND_FLESH; + } + else + sound = IMPACTSOUND_DEFAULT; + + CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, sound, charge ); + } +} + + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + +/* +=============== +CG_Tracer +=============== +*/ +void CG_Tracer( vec3_t source, vec3_t dest ) +{ + vec3_t forward, right; + polyVert_t verts[ 4 ]; + vec3_t line; + float len, begin, end; + vec3_t start, finish; + vec3_t midpoint; + + // tracer + VectorSubtract( dest, source, forward ); + len = VectorNormalize( forward ); + + // start at least a little ways from the muzzle + if( len < 100 ) + return; + + begin = 50 + random( ) * ( len - 60 ); + end = begin + cg_tracerLength.value; + if( end > len ) + end = len; + + VectorMA( source, begin, forward, start ); + VectorMA( source, end, forward, finish ); + + line[ 0 ] = DotProduct( forward, cg.refdef.viewaxis[ 1 ] ); + line[ 1 ] = DotProduct( forward, cg.refdef.viewaxis[ 2 ] ); + + VectorScale( cg.refdef.viewaxis[ 1 ], line[ 1 ], right ); + VectorMA( right, -line[ 0 ], cg.refdef.viewaxis[ 2 ], right ); + VectorNormalize( right ); + + VectorMA( finish, cg_tracerWidth.value, right, verts[ 0 ].xyz ); + verts[ 0 ].st[ 0 ] = 0; + verts[ 0 ].st[ 1 ] = 1; + verts[ 0 ].modulate[ 0 ] = 255; + verts[ 0 ].modulate[ 1 ] = 255; + verts[ 0 ].modulate[ 2 ] = 255; + verts[ 0 ].modulate[ 3 ] = 255; + + VectorMA( finish, -cg_tracerWidth.value, right, verts[ 1 ].xyz ); + verts[ 1 ].st[ 0 ] = 1; + verts[ 1 ].st[ 1 ] = 0; + verts[ 1 ].modulate[ 0 ] = 255; + verts[ 1 ].modulate[ 1 ] = 255; + verts[ 1 ].modulate[ 2 ] = 255; + verts[ 1 ].modulate[ 3 ] = 255; + + VectorMA( start, -cg_tracerWidth.value, right, verts[ 2 ].xyz ); + verts[ 2 ].st[ 0 ] = 1; + verts[ 2 ].st[ 1 ] = 1; + verts[ 2 ].modulate[ 0 ] = 255; + verts[ 2 ].modulate[ 1 ] = 255; + verts[ 2 ].modulate[ 2 ] = 255; + verts[ 2 ].modulate[ 3 ] = 255; + + VectorMA( start, cg_tracerWidth.value, right, verts[ 3 ].xyz ); + verts[ 3 ].st[ 0 ] = 0; + verts[ 3 ].st[ 1 ] = 0; + verts[ 3 ].modulate[ 0 ] = 255; + verts[ 3 ].modulate[ 1 ] = 255; + verts[ 3 ].modulate[ 2 ] = 255; + verts[ 3 ].modulate[ 3 ] = 255; + + trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); + + midpoint[ 0 ] = ( start[ 0 ] + finish[ 0 ] ) * 0.5; + midpoint[ 1 ] = ( start[ 1 ] + finish[ 1 ] ) * 0.5; + midpoint[ 2 ] = ( start[ 2 ] + finish[ 2 ] ) * 0.5; + + // add the tracer sound + trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); +} + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) +{ + vec3_t forward; + centity_t *cent; + int anim; + + if( entityNum == cg.snap->ps.clientNum ) + { + VectorCopy( cg.snap->ps.origin, muzzle ); + muzzle[ 2 ] += cg.snap->ps.viewheight; + AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 14, forward, muzzle ); + return qtrue; + } + + cent = &cg_entities[entityNum]; + + if( !cent->currentValid ) + return qfalse; + + VectorCopy( cent->currentState.pos.trBase, muzzle ); + + AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); + anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + + if( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) + muzzle[ 2 ] += CROUCH_VIEWHEIGHT; + else + muzzle[ 2 ] += DEFAULT_VIEWHEIGHT; + + VectorMA( muzzle, 14, forward, muzzle ); + + return qtrue; + +} + + +/* +====================== +CG_Bullet + +Renders bullet effects. +====================== +*/ +void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) +{ + vec3_t start; + + // if the shooter is currently valid, calc a source point and possibly + // do trail effects + if( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) + { + if( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) + { + // draw a tracer + if( random( ) < cg_tracerChance.value ) + CG_Tracer( start, end ); + } + } + + // impact splash and mark + if( flesh ) + CG_Bleed( end, normal, fleshEntityNum ); + else + CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT, 0 ); +} + +/* +============================================================================ + +SHOTGUN TRACING + +============================================================================ +*/ + +/* +================ +CG_ShotgunPattern + +Perform the same traces the server did to locate the +hit splashes +================ +*/ +static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum ) +{ + int i; + float r, u; + vec3_t end; + vec3_t forward, right, up; + trace_t tr; + + // derive the right and up vectors from the forward vector, because + // the client won't have any other information + VectorNormalize2( origin2, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + // generate the "random" spread pattern + for( i = 0; i < SHOTGUN_PELLETS; i++ ) + { + r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; + u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; + VectorMA( origin, 8192 * 16, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + CG_Trace( &tr, origin, NULL, NULL, end, otherEntNum, MASK_SHOT ); + + if( !( tr.surfaceFlags & SURF_NOIMPACT ) ) + { + if( cg_entities[ tr.entityNum ].currentState.eType == ET_PLAYER || + cg_entities[ tr.entityNum ].currentState.eType == ET_BUILDABLE ) + CG_MissileHitEntity( WP_SHOTGUN, WPM_PRIMARY, tr.endpos, tr.plane.normal, tr.entityNum, 0 ); + else if( tr.surfaceFlags & SURF_METALSTEPS ) + CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL, 0 ); + else + CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT, 0 ); + } + } +} + +/* +============== +CG_ShotgunFire +============== +*/ +void CG_ShotgunFire( entityState_t *es ) +{ + vec3_t v; + + VectorSubtract( es->origin2, es->pos.trBase, v ); + VectorNormalize( v ); + VectorScale( v, 32, v ); + VectorAdd( es->pos.trBase, v, v ); + + CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); +} + +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum ) +{ + team_t team; + qhandle_t bleedPS; + particleSystem_t *ps; + + if( !cg_blood.integer ) + return; + + if( cg_entities[ entityNum ].currentState.eType == ET_PLAYER ) + { + team = cgs.clientinfo[ entityNum ].team; + if( team == TEAM_ALIENS ) + bleedPS = cgs.media.alienBleedPS; + else if( team == TEAM_HUMANS ) + bleedPS = cgs.media.humanBleedPS; + else + return; + } + else if( cg_entities[ entityNum ].currentState.eType == ET_BUILDABLE ) + { + //ew + team = BG_Buildable( cg_entities[ entityNum ].currentState.modelindex, NULL )->team; + if( team == TEAM_ALIENS ) + bleedPS = cgs.media.alienBuildableBleedPS; + else if( team == TEAM_HUMANS ) + bleedPS = cgs.media.humanBuildableBleedPS; + else + return; + } + else + return; + + ps = CG_SpawnNewParticleSystem( bleedPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetAttachmentCent( &ps->attachment, &cg_entities[ entityNum ] ); + CG_AttachToPoint( &ps->attachment ); + + CG_SetParticleSystemNormal( ps, normal ); + } +} + diff --git a/src/client/cl_avi.c b/src/client/cl_avi.c new file mode 100644 index 0000000..7f673fa --- /dev/null +++ b/src/client/cl_avi.c @@ -0,0 +1,676 @@ +/* +=========================================================================== +Copyright (C) 2005-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_local.h" + +#define INDEX_FILE_EXTENSION ".index.dat" + +#define MAX_RIFF_CHUNKS 16 + +typedef struct audioFormat_s +{ + int rate; + int format; + int channels; + int bits; + + int sampleSize; + int totalBytes; +} audioFormat_t; + +typedef struct aviFileData_s +{ + qboolean fileOpen; + fileHandle_t f; + char fileName[ MAX_QPATH ]; + int fileSize; + int moviOffset; + int moviSize; + + fileHandle_t idxF; + int numIndices; + + int frameRate; + int framePeriod; + int width, height; + int numVideoFrames; + int maxRecordSize; + qboolean motionJpeg; + + qboolean audio; + audioFormat_t a; + int numAudioFrames; + + int chunkStack[ MAX_RIFF_CHUNKS ]; + int chunkStackTop; + + byte *cBuffer, *eBuffer; +} aviFileData_t; + +static aviFileData_t afd; + +#define MAX_AVI_BUFFER 2048 + +static byte buffer[ MAX_AVI_BUFFER ]; +static int bufIndex; + +/* +=============== +SafeFS_Write +=============== +*/ +static ID_INLINE void SafeFS_Write( const void *buffer, int len, fileHandle_t f ) +{ + if( FS_Write( buffer, len, f ) < len ) + Com_Error( ERR_DROP, "Failed to write avi file\n" ); +} + +/* +=============== +WRITE_STRING +=============== +*/ +static ID_INLINE void WRITE_STRING( const char *s ) +{ + Com_Memcpy( &buffer[ bufIndex ], s, strlen( s ) ); + bufIndex += strlen( s ); +} + +/* +=============== +WRITE_4BYTES +=============== +*/ +static ID_INLINE void WRITE_4BYTES( int x ) +{ + buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF ); + buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF ); + buffer[ bufIndex + 2 ] = (byte)( ( x >> 16 ) & 0xFF ); + buffer[ bufIndex + 3 ] = (byte)( ( x >> 24 ) & 0xFF ); + bufIndex += 4; +} + +/* +=============== +WRITE_2BYTES +=============== +*/ +static ID_INLINE void WRITE_2BYTES( int x ) +{ + buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF ); + buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF ); + bufIndex += 2; +} + +/* +=============== +WRITE_1BYTES +=============== +*/ +static ID_INLINE void WRITE_1BYTES( int x ) +{ + buffer[ bufIndex ] = x; + bufIndex += 1; +} + +/* +=============== +START_CHUNK +=============== +*/ +static ID_INLINE void START_CHUNK( const char *s ) +{ + if( afd.chunkStackTop == MAX_RIFF_CHUNKS ) + { + Com_Error( ERR_DROP, "ERROR: Top of chunkstack breached\n" ); + } + + afd.chunkStack[ afd.chunkStackTop ] = bufIndex; + afd.chunkStackTop++; + WRITE_STRING( s ); + WRITE_4BYTES( 0 ); +} + +/* +=============== +END_CHUNK +=============== +*/ +static ID_INLINE void END_CHUNK( void ) +{ + int endIndex = bufIndex; + + if( afd.chunkStackTop <= 0 ) + { + Com_Error( ERR_DROP, "ERROR: Bottom of chunkstack breached\n" ); + } + + afd.chunkStackTop--; + bufIndex = afd.chunkStack[ afd.chunkStackTop ]; + bufIndex += 4; + WRITE_4BYTES( endIndex - bufIndex - 4 ); + bufIndex = endIndex; + bufIndex = PAD( bufIndex, 2 ); +} + +/* +=============== +CL_WriteAVIHeader +=============== +*/ +void CL_WriteAVIHeader( void ) +{ + bufIndex = 0; + afd.chunkStackTop = 0; + + START_CHUNK( "RIFF" ); + { + WRITE_STRING( "AVI " ); + { + START_CHUNK( "LIST" ); + { + WRITE_STRING( "hdrl" ); + WRITE_STRING( "avih" ); + WRITE_4BYTES( 56 ); //"avih" "chunk" size + WRITE_4BYTES( afd.framePeriod ); //dwMicroSecPerFrame + WRITE_4BYTES( afd.maxRecordSize * + afd.frameRate ); //dwMaxBytesPerSec + WRITE_4BYTES( 0 ); //dwReserved1 + WRITE_4BYTES( 0x110 ); //dwFlags bits HAS_INDEX and IS_INTERLEAVED + WRITE_4BYTES( afd.numVideoFrames ); //dwTotalFrames + WRITE_4BYTES( 0 ); //dwInitialFrame + + if( afd.audio ) //dwStreams + WRITE_4BYTES( 2 ); + else + WRITE_4BYTES( 1 ); + + WRITE_4BYTES( afd.maxRecordSize ); //dwSuggestedBufferSize + WRITE_4BYTES( afd.width ); //dwWidth + WRITE_4BYTES( afd.height ); //dwHeight + WRITE_4BYTES( 0 ); //dwReserved[ 0 ] + WRITE_4BYTES( 0 ); //dwReserved[ 1 ] + WRITE_4BYTES( 0 ); //dwReserved[ 2 ] + WRITE_4BYTES( 0 ); //dwReserved[ 3 ] + + START_CHUNK( "LIST" ); + { + WRITE_STRING( "strl" ); + WRITE_STRING( "strh" ); + WRITE_4BYTES( 56 ); //"strh" "chunk" size + WRITE_STRING( "vids" ); + + if( afd.motionJpeg ) + WRITE_STRING( "MJPG" ); + else + WRITE_4BYTES( 0 ); // BI_RGB + + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( 0 ); //dwPriority + WRITE_4BYTES( 0 ); //dwInitialFrame + + WRITE_4BYTES( 1 ); //dwTimescale + WRITE_4BYTES( afd.frameRate ); //dwDataRate + WRITE_4BYTES( 0 ); //dwStartTime + WRITE_4BYTES( afd.numVideoFrames ); //dwDataLength + + WRITE_4BYTES( afd.maxRecordSize ); //dwSuggestedBufferSize + WRITE_4BYTES( -1 ); //dwQuality + WRITE_4BYTES( 0 ); //dwSampleSize + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( afd.width ); //rcFrame + WRITE_2BYTES( afd.height ); //rcFrame + + WRITE_STRING( "strf" ); + WRITE_4BYTES( 40 ); //"strf" "chunk" size + WRITE_4BYTES( 40 ); //biSize + WRITE_4BYTES( afd.width ); //biWidth + WRITE_4BYTES( afd.height ); //biHeight + WRITE_2BYTES( 1 ); //biPlanes + WRITE_2BYTES( 24 ); //biBitCount + + if( afd.motionJpeg ) //biCompression + { + WRITE_STRING( "MJPG" ); + WRITE_4BYTES( afd.width * + afd.height ); //biSizeImage + } + else + { + WRITE_4BYTES( 0 ); // BI_RGB + WRITE_4BYTES( afd.width * + afd.height * 3 ); //biSizeImage + } + + WRITE_4BYTES( 0 ); //biXPelsPetMeter + WRITE_4BYTES( 0 ); //biYPelsPetMeter + WRITE_4BYTES( 0 ); //biClrUsed + WRITE_4BYTES( 0 ); //biClrImportant + } + END_CHUNK( ); + + if( afd.audio ) + { + START_CHUNK( "LIST" ); + { + WRITE_STRING( "strl" ); + WRITE_STRING( "strh" ); + WRITE_4BYTES( 56 ); //"strh" "chunk" size + WRITE_STRING( "auds" ); + WRITE_4BYTES( 0 ); //FCC + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( 0 ); //dwPriority + WRITE_4BYTES( 0 ); //dwInitialFrame + + WRITE_4BYTES( afd.a.sampleSize ); //dwTimescale + WRITE_4BYTES( afd.a.sampleSize * + afd.a.rate ); //dwDataRate + WRITE_4BYTES( 0 ); //dwStartTime + WRITE_4BYTES( afd.a.totalBytes / + afd.a.sampleSize ); //dwDataLength + + WRITE_4BYTES( 0 ); //dwSuggestedBufferSize + WRITE_4BYTES( -1 ); //dwQuality + WRITE_4BYTES( afd.a.sampleSize ); //dwSampleSize + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + + WRITE_STRING( "strf" ); + WRITE_4BYTES( 18 ); //"strf" "chunk" size + WRITE_2BYTES( afd.a.format ); //wFormatTag + WRITE_2BYTES( afd.a.channels ); //nChannels + WRITE_4BYTES( afd.a.rate ); //nSamplesPerSec + WRITE_4BYTES( afd.a.sampleSize * + afd.a.rate ); //nAvgBytesPerSec + WRITE_2BYTES( afd.a.sampleSize ); //nBlockAlign + WRITE_2BYTES( afd.a.bits ); //wBitsPerSample + WRITE_2BYTES( 0 ); //cbSize + } + END_CHUNK( ); + } + } + END_CHUNK( ); + + afd.moviOffset = bufIndex; + + START_CHUNK( "LIST" ); + { + WRITE_STRING( "movi" ); + } + } + } +} + +/* +=============== +CL_OpenAVIForWriting + +Creates an AVI file and gets it into a state where +writing the actual data can begin +=============== +*/ +qboolean CL_OpenAVIForWriting( const char *fileName ) +{ + if( afd.fileOpen ) + return qfalse; + + Com_Memset( &afd, 0, sizeof( aviFileData_t ) ); + + // Don't start if a framerate has not been chosen + if( cl_aviFrameRate->integer <= 0 ) + { + Com_Printf( S_COLOR_RED "cl_aviFrameRate must be >= 1\n" ); + return qfalse; + } + + if( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 ) + return qfalse; + + if( ( afd.idxF = FS_FOpenFileWrite( + va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 ) + { + FS_FCloseFile( afd.f ); + return qfalse; + } + + Q_strncpyz( afd.fileName, fileName, MAX_QPATH ); + + afd.frameRate = cl_aviFrameRate->integer; + afd.framePeriod = (int)( 1000000.0f / afd.frameRate ); + afd.width = cls.glconfig.vidWidth; + afd.height = cls.glconfig.vidHeight; + + if( cl_aviMotionJpeg->integer ) + afd.motionJpeg = qtrue; + else + afd.motionJpeg = qfalse; + + // Buffers only need to store RGB pixels. + // Allocate a bit more space for the capture buffer to account for possible + // padding at the end of pixel lines, and padding for alignment + #define MAX_PACK_LEN 16 + afd.cBuffer = Z_Malloc((afd.width * 3 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1); + // raw avi files have pixel lines start on 4-byte boundaries + afd.eBuffer = Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height); + + afd.a.rate = dma.speed; + afd.a.format = WAV_FORMAT_PCM; + afd.a.channels = dma.channels; + afd.a.bits = dma.samplebits; + afd.a.sampleSize = ( afd.a.bits / 8 ) * afd.a.channels; + + if( afd.a.rate % afd.frameRate ) + { + int suggestRate = afd.frameRate; + + while( ( afd.a.rate % suggestRate ) && suggestRate >= 1 ) + suggestRate--; + + Com_Printf( S_COLOR_YELLOW "WARNING: cl_aviFrameRate is not a divisor " + "of the audio rate, suggest %d\n", suggestRate ); + } + + if( !Cvar_VariableIntegerValue( "s_initsound" ) ) + { + afd.audio = qfalse; + } + else if( Q_stricmp( Cvar_VariableString( "s_backend" ), "OpenAL" ) ) + { + if( afd.a.bits != 16 || afd.a.channels != 2 ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: Audio format of %d bit/%d channels not supported", + afd.a.bits, afd.a.channels ); + afd.audio = qfalse; + } + else + afd.audio = qtrue; + } + else + { + afd.audio = qfalse; + Com_Printf( S_COLOR_YELLOW "WARNING: Audio capture is not supported " + "with OpenAL. Set s_useOpenAL to 0 for audio capture\n" ); + } + + // This doesn't write a real header, but allocates the + // correct amount of space at the beginning of the file + CL_WriteAVIHeader( ); + + SafeFS_Write( buffer, bufIndex, afd.f ); + afd.fileSize = bufIndex; + + bufIndex = 0; + START_CHUNK( "idx1" ); + SafeFS_Write( buffer, bufIndex, afd.idxF ); + + afd.moviSize = 4; // For the "movi" + afd.fileOpen = qtrue; + + return qtrue; +} + +/* +=============== +CL_CheckFileSize +=============== +*/ +static qboolean CL_CheckFileSize( int bytesToAdd ) +{ + unsigned int newFileSize; + + newFileSize = + afd.fileSize + // Current file size + bytesToAdd + // What we want to add + ( afd.numIndices * 16 ) + // The index + 4; // The index size + + // I assume all the operating systems + // we target can handle a 2Gb file + if( newFileSize > INT_MAX ) + { + // Close the current file... + CL_CloseAVI( ); + + // ...And open a new one + CL_OpenAVIForWriting( va( "%s_", afd.fileName ) ); + + return qtrue; + } + + return qfalse; +} + +/* +=============== +CL_WriteAVIVideoFrame +=============== +*/ +void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size ) +{ + int chunkOffset = afd.fileSize - afd.moviOffset - 8; + int chunkSize = 8 + size; + int paddingSize = PADLEN(size, 2); + byte padding[ 4 ] = { 0 }; + + if( !afd.fileOpen ) + return; + + // Chunk header + contents + padding + if( CL_CheckFileSize( 8 + size + 2 ) ) + return; + + bufIndex = 0; + WRITE_STRING( "00dc" ); + WRITE_4BYTES( size ); + + SafeFS_Write( buffer, 8, afd.f ); + SafeFS_Write( imageBuffer, size, afd.f ); + SafeFS_Write( padding, paddingSize, afd.f ); + afd.fileSize += ( chunkSize + paddingSize ); + + afd.numVideoFrames++; + afd.moviSize += ( chunkSize + paddingSize ); + + if( size > afd.maxRecordSize ) + afd.maxRecordSize = size; + + // Index + bufIndex = 0; + WRITE_STRING( "00dc" ); //dwIdentifier + WRITE_4BYTES( 0x00000010 ); //dwFlags (all frames are KeyFrames) + WRITE_4BYTES( chunkOffset ); //dwOffset + WRITE_4BYTES( size ); //dwLength + SafeFS_Write( buffer, 16, afd.idxF ); + + afd.numIndices++; +} + +#define PCM_BUFFER_SIZE 44100 + +/* +=============== +CL_WriteAVIAudioFrame +=============== +*/ +void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size ) +{ + static byte pcmCaptureBuffer[ PCM_BUFFER_SIZE ] = { 0 }; + static int bytesInBuffer = 0; + + if( !afd.audio ) + return; + + if( !afd.fileOpen ) + return; + + // Chunk header + contents + padding + if( CL_CheckFileSize( 8 + bytesInBuffer + size + 2 ) ) + return; + + if( bytesInBuffer + size > PCM_BUFFER_SIZE ) + { + Com_Printf( S_COLOR_YELLOW + "WARNING: Audio capture buffer overflow -- truncating\n" ); + size = PCM_BUFFER_SIZE - bytesInBuffer; + } + + Com_Memcpy( &pcmCaptureBuffer[ bytesInBuffer ], pcmBuffer, size ); + bytesInBuffer += size; + + // Only write if we have a frame's worth of audio + if( bytesInBuffer >= (int)ceil( (float)afd.a.rate / (float)afd.frameRate ) * + afd.a.sampleSize ) + { + int chunkOffset = afd.fileSize - afd.moviOffset - 8; + int chunkSize = 8 + bytesInBuffer; + int paddingSize = PADLEN(bytesInBuffer, 2); + byte padding[ 4 ] = { 0 }; + + bufIndex = 0; + WRITE_STRING( "01wb" ); + WRITE_4BYTES( bytesInBuffer ); + + SafeFS_Write( buffer, 8, afd.f ); + SafeFS_Write( pcmCaptureBuffer, bytesInBuffer, afd.f ); + SafeFS_Write( padding, paddingSize, afd.f ); + afd.fileSize += ( chunkSize + paddingSize ); + + afd.numAudioFrames++; + afd.moviSize += ( chunkSize + paddingSize ); + afd.a.totalBytes += bytesInBuffer; + + // Index + bufIndex = 0; + WRITE_STRING( "01wb" ); //dwIdentifier + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( chunkOffset ); //dwOffset + WRITE_4BYTES( bytesInBuffer ); //dwLength + SafeFS_Write( buffer, 16, afd.idxF ); + + afd.numIndices++; + + bytesInBuffer = 0; + } +} + +/* +=============== +CL_TakeVideoFrame +=============== +*/ +void CL_TakeVideoFrame( void ) +{ + // AVI file isn't open + if( !afd.fileOpen ) + return; + + re.TakeVideoFrame( afd.width, afd.height, + afd.cBuffer, afd.eBuffer, afd.motionJpeg ); +} + +/* +=============== +CL_CloseAVI + +Closes the AVI file and writes an index chunk +=============== +*/ +qboolean CL_CloseAVI( void ) +{ + int indexRemainder; + int indexSize = afd.numIndices * 16; + const char *idxFileName = va( "%s" INDEX_FILE_EXTENSION, afd.fileName ); + + // AVI file isn't open + if( !afd.fileOpen ) + return qfalse; + + afd.fileOpen = qfalse; + + FS_Seek( afd.idxF, 4, FS_SEEK_SET ); + bufIndex = 0; + WRITE_4BYTES( indexSize ); + SafeFS_Write( buffer, bufIndex, afd.idxF ); + FS_FCloseFile( afd.idxF ); + + // Write index + + // Open the temp index file + if( ( indexSize = FS_FOpenFileRead( idxFileName, + &afd.idxF, qtrue ) ) <= 0 ) + { + FS_FCloseFile( afd.f ); + return qfalse; + } + + indexRemainder = indexSize; + + // Append index to end of avi file + while( indexRemainder > MAX_AVI_BUFFER ) + { + FS_Read( buffer, MAX_AVI_BUFFER, afd.idxF ); + SafeFS_Write( buffer, MAX_AVI_BUFFER, afd.f ); + afd.fileSize += MAX_AVI_BUFFER; + indexRemainder -= MAX_AVI_BUFFER; + } + FS_Read( buffer, indexRemainder, afd.idxF ); + SafeFS_Write( buffer, indexRemainder, afd.f ); + afd.fileSize += indexRemainder; + FS_FCloseFile( afd.idxF ); + + // Remove temp index file + FS_HomeRemove( idxFileName ); + + // Write the real header + FS_Seek( afd.f, 0, FS_SEEK_SET ); + CL_WriteAVIHeader( ); + + bufIndex = 4; + WRITE_4BYTES( afd.fileSize - 8 ); // "RIFF" size + + bufIndex = afd.moviOffset + 4; // Skip "LIST" + WRITE_4BYTES( afd.moviSize ); + + SafeFS_Write( buffer, bufIndex, afd.f ); + + Z_Free( afd.cBuffer ); + Z_Free( afd.eBuffer ); + FS_FCloseFile( afd.f ); + + Com_Printf( "Wrote %d:%d frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName ); + + return qtrue; +} + +/* +=============== +CL_VideoRecording +=============== +*/ +qboolean CL_VideoRecording( void ) +{ + return afd.fileOpen; +} diff --git a/src/client/cl_cgame.c b/src/client/cl_cgame.c new file mode 100644 index 0000000..babc524 --- /dev/null +++ b/src/client/cl_cgame.c @@ -0,0 +1,1155 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cl_cgame.c -- client system interaction with client game + +#include "client.h" +#include "libmumblelink.h" + +extern qboolean loadCamera(const char *name); +extern void startCamera(int time); +extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles); + +/* +==================== +CL_GetGameState +==================== +*/ +void CL_GetGameState( gameState_t *gs ) { + *gs = cl.gameState; +} + +/* +==================== +CL_GetGlconfig +==================== +*/ +void CL_GetGlconfig( glconfig_t *glconfig ) { + *glconfig = cls.glconfig; +} + + +/* +==================== +CL_GetUserCmd +==================== +*/ +qboolean CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + // cmds[cmdNumber] is the last properly generated command + + // can't return anything that we haven't created yet + if ( cmdNumber > cl.cmdNumber ) { + Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber ); + } + + // the usercmd has been overwritten in the wrapping + // buffer because it is too far out of date + if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP ) { + return qfalse; + } + + *ucmd = cl.cmds[ cmdNumber & CMD_MASK ]; + + return qtrue; +} + +int CL_GetCurrentCmdNumber( void ) { + return cl.cmdNumber; +} + + +/* +==================== +CL_GetParseEntityState +==================== +*/ +qboolean CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) { + // can't return anything that hasn't been parsed yet + if ( parseEntityNumber >= cl.parseEntitiesNum ) { + Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i", + parseEntityNumber, cl.parseEntitiesNum ); + } + + // can't return anything that has been overwritten in the circular buffer + if ( parseEntityNumber <= cl.parseEntitiesNum - MAX_PARSE_ENTITIES ) { + return qfalse; + } + + *state = cl.parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ]; + return qtrue; +} + +/* +==================== +CL_GetCurrentSnapshotNumber +==================== +*/ +void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + *snapshotNumber = cl.snap.messageNum; + *serverTime = cl.snap.serverTime; +} + +/* +==================== +CL_GetSnapshot +==================== +*/ +qboolean CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + clSnapshot_t *clSnap; + int i, count; + + if ( snapshotNumber > cl.snap.messageNum ) { + Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" ); + } + + // if the frame has fallen out of the circular buffer, we can't return it + if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP ) { + return qfalse; + } + + // if the frame is not valid, we can't return it + clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK]; + if ( !clSnap->valid ) { + return qfalse; + } + + // if the entities in the frame have fallen out of their + // circular buffer, we can't return it + if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) { + return qfalse; + } + + // write the snapshot + snapshot->snapFlags = clSnap->snapFlags; + snapshot->serverCommandSequence = clSnap->serverCommandNum; + snapshot->ping = clSnap->ping; + snapshot->serverTime = clSnap->serverTime; + Com_Memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) ); + snapshot->ps = clSnap->ps; + count = clSnap->numEntities; + if ( count > MAX_ENTITIES_IN_SNAPSHOT ) { + Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT ); + count = MAX_ENTITIES_IN_SNAPSHOT; + } + snapshot->numEntities = count; + for ( i = 0 ; i < count ; i++ ) { + snapshot->entities[i] = + cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ]; + } + + // FIXME: configstring changes and server commands!!! + + return qtrue; +} + +/* +===================== +CL_SetUserCmdValue +===================== +*/ +void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale ) { + cl.cgameUserCmdValue = userCmdValue; + cl.cgameSensitivity = sensitivityScale; +} + +/* +===================== +CL_AddCgameCommand +===================== +*/ +void CL_AddCgameCommand( const char *cmdName ) { + Cmd_AddCommand( cmdName, NULL ); +} + +/* +===================== +CL_CgameError +===================== +*/ +void CL_CgameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + + +/* +===================== +CL_ConfigstringModified +===================== +*/ +void CL_ConfigstringModified( void ) { + char *old, *s; + int i, index; + char *dup; + gameState_t oldGs; + int len; + + index = atoi( Cmd_Argv(1) ); + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + // get everything after "cs " + s = Cmd_ArgsFrom(2); + + old = cl.gameState.stringData + cl.gameState.stringOffsets[ index ]; + if ( !strcmp( old, s ) ) { + return; // unchanged + } + + // build the new gameState_t + oldGs = cl.gameState; + + Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + + // leave the first 0 for uninitialized strings + cl.gameState.dataCount = 1; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( i == index ) { + dup = s; + } else { + dup = oldGs.stringData + oldGs.stringOffsets[ i ]; + } + if ( !dup[0] ) { + continue; // leave with the default empty string + } + + len = strlen( dup ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 ); + cl.gameState.dataCount += len + 1; + } + + if ( index == CS_SYSTEMINFO ) { + // parse serverId and other cvars + CL_SystemInfoChanged(); + } + +} + + +/* +=================== +CL_GetServerCommand + +Set up argc/argv for the given command +=================== +*/ +qboolean CL_GetServerCommand( int serverCommandNumber ) { + char *s; + char *cmd; + static char bigConfigString[BIG_INFO_STRING]; + int argc; + + // if we have irretrievably lost a reliable command, drop the connection + if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { + // when a demo record was started after the client got a whole bunch of + // reliable commands then the client never got those first reliable commands + if ( clc.demoplaying ) + return qfalse; + Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); + return qfalse; + } + + if ( serverCommandNumber > clc.serverCommandSequence ) { + Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); + return qfalse; + } + + s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + clc.lastExecutedServerCommand = serverCommandNumber; + + Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); + +rescan: + Cmd_TokenizeString( s ); + cmd = Cmd_Argv(0); + argc = Cmd_Argc(); + + if ( !strcmp( cmd, "disconnect" ) ) { + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552 + // allow server to indicate why they were disconnected + if ( argc >= 2 ) + Com_Error( ERR_SERVERDISCONNECT, "Server disconnected - %s", Cmd_Argv( 1 ) ); + else + Com_Error( ERR_SERVERDISCONNECT, "Server disconnected\n" ); + } + + if ( !strcmp( cmd, "bcs0" ) ) { + Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs1" ) ) { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs2" ) ) { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + strcat( bigConfigString, "\"" ); + s = bigConfigString; + goto rescan; + } + + if ( !strcmp( cmd, "cs" ) ) { + CL_ConfigstringModified(); + // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() + Cmd_TokenizeString( s ); + return qtrue; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + // clear notify lines and outgoing commands before passing + // the restart to the cgame + Con_ClearNotify(); + // reparse the string, because Con_ClearNotify() may have done another Cmd_TokenizeString() + Cmd_TokenizeString( s ); + Com_Memset( cl.cmds, 0, sizeof( cl.cmds ) ); + return qtrue; + } + + // the clientLevelShot command is used during development + // to generate 128*128 screenshots from the intermission + // point of levels for the menu system to use + // we pass it along to the cgame to make apropriate adjustments, + // but we also clear the console and notify lines here + if ( !strcmp( cmd, "clientLevelShot" ) ) { + // don't do it if we aren't running the server locally, + // otherwise malicious remote servers could overwrite + // the existing thumbnails + if ( !com_sv_running->integer ) { + return qfalse; + } + // close the console + Con_Close(); + // take a special screenshot next frame + Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" ); + return qtrue; + } + + // we may want to put a "connect to other server" command here + + // cgame can now act on the command + return qtrue; +} + + +/* +==================== +CL_CM_LoadMap + +Just adds default parameters that cgame doesn't need to know about +==================== +*/ +void CL_CM_LoadMap( const char *mapname ) { + int checksum; + + CM_LoadMap( mapname, qtrue, &checksum ); +} + +/* +==================== +CL_ShutdonwCGame + +==================== +*/ +void CL_ShutdownCGame( void ) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME ); + cls.cgameStarted = qfalse; + if ( !cgvm ) { + return; + } + VM_Call( cgvm, CG_SHUTDOWN ); + VM_Free( cgvm ); + cgvm = NULL; +} + +static int FloatAsInt( float f ) { + floatint_t fi; + fi.f = f; + return fi.i; +} + +/* +==================== +CL_CgameSystemCalls + +The cgame module is making a system call +==================== +*/ +intptr_t CL_CgameSystemCalls( intptr_t *args ) { + switch( args[0] ) { + case CG_PRINT: + Com_Printf( "%s", (const char*)VMA(1) ); + return 0; + case CG_ERROR: + Com_Error( ERR_DROP, "%s", (const char*)VMA(1) ); + return 0; + case CG_MILLISECONDS: + return Sys_Milliseconds(); + case CG_CVAR_REGISTER: + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + return 0; + case CG_CVAR_UPDATE: + Cvar_Update( VMA(1) ); + return 0; + case CG_CVAR_SET: + Cvar_SetSafe( VMA(1), VMA(2) ); + return 0; + case CG_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); + return 0; + case CG_ARGC: + return Cmd_Argc(); + case CG_ARGV: + Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); + return 0; + case CG_ARGS: + Cmd_ArgsBuffer( VMA(1), args[2] ); + return 0; + case CG_LITERAL_ARGS: + Cmd_LiteralArgsBuffer( VMA(1), args[2] ); + return 0; + case CG_FS_FOPENFILE: + return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); + case CG_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + case CG_FS_SEEK: + return FS_Seek( args[1], args[2], args[3] ); + case CG_FS_GETFILELIST: + return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); + case CG_SENDCONSOLECOMMAND: + Cbuf_AddText( VMA(1) ); + return 0; + case CG_ADDCOMMAND: + CL_AddCgameCommand( VMA(1) ); + return 0; + case CG_REMOVECOMMAND: + Cmd_RemoveCommandSafe( VMA(1) ); + return 0; + case CG_SENDCLIENTCOMMAND: + CL_AddReliableCommand(VMA(1), qfalse); + return 0; + case CG_UPDATESCREEN: + // this is used during lengthy level loading, so pump message loop +// Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN! +// We can't call Com_EventLoop here, a restart will crash and this _does_ happen +// if there is a map change while we are downloading at pk3. +// ZOID + SCR_UpdateScreen(); + return 0; + case CG_CM_LOADMAP: + CL_CM_LoadMap( VMA(1) ); + return 0; + case CG_CM_NUMINLINEMODELS: + return CM_NumInlineModels(); + case CG_CM_INLINEMODEL: + return CM_InlineModel( args[1] ); + case CG_CM_TEMPBOXMODEL: + return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qfalse ); + case CG_CM_TEMPCAPSULEMODEL: + return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qtrue ); + case CG_CM_POINTCONTENTS: + return CM_PointContents( VMA(1), args[2] ); + case CG_CM_TRANSFORMEDPOINTCONTENTS: + return CM_TransformedPointContents( VMA(1), args[2], VMA(3), VMA(4) ); + case CG_CM_BOXTRACE: + CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], TT_AABB ); + return 0; + case CG_CM_CAPSULETRACE: + CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], TT_CAPSULE ); + return 0; + case CG_CM_TRANSFORMEDBOXTRACE: + CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), + args[6], args[7], VMA(8), VMA(9), TT_AABB ); + return 0; + case CG_CM_TRANSFORMEDCAPSULETRACE: + CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), + args[6], args[7], VMA(8), VMA(9), TT_CAPSULE ); + return 0; + case CG_CM_BISPHERETRACE: + CM_BiSphereTrace( VMA(1), VMA(2), VMA(3), VMF(4), VMF(5), args[6], args[7] ); + return 0; + case CG_CM_TRANSFORMEDBISPHERETRACE: + CM_TransformedBiSphereTrace( VMA(1), VMA(2), VMA(3), VMF(4), VMF(5), + args[6], args[7], VMA(8) ); + return 0; + case CG_CM_MARKFRAGMENTS: + return re.MarkFragments( args[1], VMA(2), VMA(3), args[4], VMA(5), args[6], VMA(7) ); + case CG_S_STARTSOUND: + S_StartSound( VMA(1), args[2], args[3], args[4] ); + return 0; + case CG_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + case CG_S_CLEARLOOPINGSOUNDS: + S_ClearLoopingSounds(args[1]); + return 0; + case CG_S_ADDLOOPINGSOUND: + S_AddLoopingSound( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_ADDREALLOOPINGSOUND: + S_AddRealLoopingSound( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_STOPLOOPINGSOUND: + S_StopLoopingSound( args[1] ); + return 0; + case CG_S_UPDATEENTITYPOSITION: + S_UpdateEntityPosition( args[1], VMA(2) ); + return 0; + case CG_S_RESPATIALIZE: + S_Respatialize( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_REGISTERSOUND: + return S_RegisterSound( VMA(1), args[2] ); + case CG_S_SOUNDDURATION: + return S_SoundDuration( args[1] ); + case CG_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( VMA(1), VMA(2) ); + return 0; + case CG_R_LOADWORLDMAP: + re.LoadWorld( VMA(1) ); + return 0; + case CG_R_REGISTERMODEL: + return re.RegisterModel( VMA(1) ); + case CG_R_REGISTERSKIN: + return re.RegisterSkin( VMA(1) ); + case CG_R_REGISTERSHADER: + return re.RegisterShader( VMA(1) ); + case CG_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( VMA(1) ); + case CG_R_REGISTERFONT: + re.RegisterFont( VMA(1), args[2], VMA(3)); + case CG_R_CLEARSCENE: + re.ClearScene(); + return 0; + case CG_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( VMA(1) ); + return 0; + case CG_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); + return 0; + case CG_R_ADDPOLYSTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), args[4] ); + return 0; + case CG_R_LIGHTFORPOINT: + return re.LightForPoint( VMA(1), VMA(2), VMA(3), VMA(4) ); + case CG_R_ADDLIGHTTOSCENE: + re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_ADDADDITIVELIGHTTOSCENE: + re.AddAdditiveLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_RENDERSCENE: + re.RenderScene( VMA(1) ); + return 0; + case CG_R_SETCOLOR: + re.SetColor( VMA(1) ); + return 0; + case CG_R_SETCLIPREGION: + re.SetClipRegion( VMA(1) ); + return 0; + case CG_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); + return 0; + case CG_R_MODELBOUNDS: + re.ModelBounds( args[1], VMA(2), VMA(3) ); + return 0; + case CG_R_LERPTAG: + return re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); + case CG_GETGLCONFIG: + CL_GetGlconfig( VMA(1) ); + return 0; + case CG_GETGAMESTATE: + CL_GetGameState( VMA(1) ); + return 0; + case CG_GETCURRENTSNAPSHOTNUMBER: + CL_GetCurrentSnapshotNumber( VMA(1), VMA(2) ); + return 0; + case CG_GETSNAPSHOT: + return CL_GetSnapshot( args[1], VMA(2) ); + case CG_GETSERVERCOMMAND: + return CL_GetServerCommand( args[1] ); + case CG_GETCURRENTCMDNUMBER: + return CL_GetCurrentCmdNumber(); + case CG_GETUSERCMD: + return CL_GetUserCmd( args[1], VMA(2) ); + case CG_SETUSERCMDVALUE: + CL_SetUserCmdValue( args[1], VMF(2) ); + return 0; + case CG_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + case CG_KEY_ISDOWN: + return Key_IsDown( args[1] ); + case CG_KEY_GETCATCHER: + return Key_GetCatcher(); + case CG_KEY_SETCATCHER: + // Don't allow the cgame module to close the console + Key_SetCatcher( args[1] | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ); + return 0; + case CG_KEY_GETKEY: + return Key_GetKey( VMA(1) ); + + case CG_GETDEMOSTATE: + return CL_DemoState( ); + case CG_GETDEMOPOS: + return CL_DemoPos( ); + case CG_GETDEMONAME: + CL_DemoName( VMA(1), args[2] ); + return 0; + + case CG_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf( args[1], VMA(2), args[3] ); + return 0; + case CG_KEY_GETBINDINGBUF: + Key_GetBindingBuf( args[1], VMA(2), args[3] ); + return 0; + case CG_KEY_SETBINDING: + Key_SetBinding( args[1], VMA(2) ); + return 0; + + case CG_PARSE_ADD_GLOBAL_DEFINE: + return Parse_AddGlobalDefine( VMA(1) ); + case CG_PARSE_LOAD_SOURCE: + return Parse_LoadSourceHandle( VMA(1) ); + case CG_PARSE_FREE_SOURCE: + return Parse_FreeSourceHandle( args[1] ); + case CG_PARSE_READ_TOKEN: + return Parse_ReadTokenHandle( args[1], VMA(2) ); + case CG_PARSE_SOURCE_FILE_AND_LINE: + return Parse_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + + case CG_KEY_SETOVERSTRIKEMODE: + Key_SetOverstrikeMode( args[1] ); + return 0; + case CG_KEY_GETOVERSTRIKEMODE: + return Key_GetOverstrikeMode( ); + + case CG_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + case CG_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + case CG_STRNCPY: + strncpy( VMA(1), VMA(2), args[3] ); + return args[1]; + case CG_SIN: + return FloatAsInt( sin( VMF(1) ) ); + case CG_COS: + return FloatAsInt( cos( VMF(1) ) ); + case CG_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + case CG_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + case CG_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + case CG_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + case CG_ACOS: + return FloatAsInt( Q_acos( VMF(1) ) ); + + case CG_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + + case CG_REAL_TIME: + return Com_RealTime( VMA(1) ); + case CG_SNAPVECTOR: + Sys_SnapVector( VMA(1) ); + return 0; + + case CG_CIN_PLAYCINEMATIC: + return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case CG_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case CG_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case CG_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case CG_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case CG_R_REMAP_SHADER: + re.RemapShader( VMA(1), VMA(2), VMA(3) ); + return 0; + +/* + case CG_LOADCAMERA: + return loadCamera(VMA(1)); + + case CG_STARTCAMERA: + startCamera(args[1]); + return 0; + + case CG_GETCAMERAINFO: + return getCameraInfo(args[1], VMA(2), VMA(3)); +*/ + case CG_GET_ENTITY_TOKEN: + return re.GetEntityToken( VMA(1), args[2] ); + case CG_R_INPVS: + return re.inPVS( VMA(1), VMA(2) ); + + default: + assert(0); + Com_Error( ERR_DROP, "Bad cgame system trap: %ld", (long int) args[0] ); + } + return 0; +} + + +/* +==================== +CL_InitCGame + +Should only be called by CL_StartHunkUsers +==================== +*/ +void CL_InitCGame( void ) { + const char *info; + const char *mapname; + int t1, t2; + vmInterpret_t interpret; + + t1 = Sys_Milliseconds(); + + // put away the console + Con_Close(); + + // find the current mapname + info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname ); + + // load the dll or bytecode + if ( cl_connectedToPureServer != 0 ) { + // if sv_pure is set we only allow qvms to be loaded + interpret = VMI_COMPILED; + } + else { + interpret = Cvar_VariableValue( "vm_cgame" ); + } + cgvm = VM_Create( "cgame", CL_CgameSystemCalls, interpret ); + if ( !cgvm ) { + Com_Error( ERR_DROP, "VM_Create on cgame failed" ); + } + cls.state = CA_LOADING; + + // init for this gamestate + // use the lastExecutedServerCommand instead of the serverCommandSequence + // otherwise server commands sent just before a gamestate are dropped + VM_Call( cgvm, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum ); + + // reset any CVAR_CHEAT cvars registered by cgame + if ( !clc.demoplaying && !cl_connectedToCheatServer ) + Cvar_SetCheatState(); + + // we will send a usercmd this frame, which + // will cause the server to send us the first snapshot + cls.state = CA_PRIMED; + + t2 = Sys_Milliseconds(); + + Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 ); + + // have the renderer touch all its images, so they are present + // on the card even if the driver does deferred loading + re.EndRegistration(); + + // make sure everything is paged in + if (!Sys_LowPhysicalMemory()) { + Com_TouchMemory(); + } + + // clear anything that got printed + Con_ClearNotify (); +} + + +/* +==================== +CL_GameCommand + +See if the current console command is claimed by the cgame +==================== +*/ +qboolean CL_GameCommand( void ) { + if ( !cgvm ) { + return qfalse; + } + + return VM_Call( cgvm, CG_CONSOLE_COMMAND ); +} + +/* +==================== +CL_GameConsoleText +==================== +*/ +void CL_GameConsoleText( void ) { + if ( !cgvm ) { + return; + } + + VM_Call( cgvm, CG_CONSOLE_TEXT ); +} + + +/* +===================== +CL_CGameRendering +===================== +*/ +void CL_CGameRendering( stereoFrame_t stereo ) { + VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying ); + VM_Debug( 0 ); +} + + +/* +================= +CL_AdjustTimeDelta + +Adjust the clients view of server time. + +We attempt to have cl.serverTime exactly equal the server's view +of time plus the timeNudge, but with variable latencies over +the internet it will often need to drift a bit to match conditions. + +Our ideal time would be to have the adjusted time approach, but not pass, +the very latest snapshot. + +Adjustments are only made when a new snapshot arrives with a rational +latency, which keeps the adjustment process framerate independent and +prevents massive overadjustment during times of significant packet loss +or bursted delayed packets. +================= +*/ + +#define RESET_TIME 500 + +void CL_AdjustTimeDelta( void ) { + int resetTime; + int newDelta; + int deltaDelta; + + cl.newSnapshots = qfalse; + + // the delta never drifts when replaying a demo + if ( clc.demoplaying ) { + return; + } + + // if the current time is WAY off, just correct to the current value + if ( com_sv_running->integer ) { + resetTime = 100; + } else { + resetTime = RESET_TIME; + } + + newDelta = cl.snap.serverTime - cls.realtime; + deltaDelta = abs( newDelta - cl.serverTimeDelta ); + + if ( deltaDelta > RESET_TIME ) { + cl.serverTimeDelta = newDelta; + cl.oldServerTime = cl.snap.serverTime; // FIXME: is this a problem for cgame? + cl.serverTime = cl.snap.serverTime; + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + } else if ( deltaDelta > 100 ) { + // fast adjust, cut the difference in half + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1; + } else { + // slow drift adjust, only move 1 or 2 msec + + // if any of the frames between this and the previous snapshot + // had to be extrapolated, nudge our sense of time back a little + // the granularity of +1 / -2 is too high for timescale modified frametimes + if ( com_timescale->value == 0 || com_timescale->value == 1 ) { + if ( cl.extrapolatedSnapshot ) { + cl.extrapolatedSnapshot = qfalse; + cl.serverTimeDelta -= 2; + } else { + // otherwise, move our sense of time forward to minimize total latency + cl.serverTimeDelta++; + } + } + } + + if ( cl_showTimeDelta->integer ) { + Com_Printf( "%i ", cl.serverTimeDelta ); + } +} + + +/* +================== +CL_FirstSnapshot +================== +*/ +void CL_FirstSnapshot( void ) { + // ignore snapshots that don't have entities + if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) { + return; + } + cls.state = CA_ACTIVE; + + // set the timedelta so we are exactly on this first frame + cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; + cl.oldServerTime = cl.snap.serverTime; + + clc.timeDemoBaseTime = cl.snap.serverTime; + + // if this is the first frame of active play, + // execute the contents of activeAction now + // this is to allow scripting a timedemo to start right + // after loading + if ( cl_activeAction->string[0] ) { + Cbuf_AddText( cl_activeAction->string ); + Cvar_Set( "activeAction", "" ); + } + +#ifdef USE_MUMBLE + if ((cl_useMumble->integer) && !mumble_islinked()) { + int ret = mumble_link(CLIENT_WINDOW_TITLE); + Com_Printf("Mumble: Linking to Mumble application %s\n", ret==0?"ok":"failed"); + } +#endif + +#ifdef USE_VOIP + if (!clc.speexInitialized) { + int i; + speex_bits_init(&clc.speexEncoderBits); + speex_bits_reset(&clc.speexEncoderBits); + + clc.speexEncoder = speex_encoder_init(&speex_nb_mode); + + speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_FRAME_SIZE, + &clc.speexFrameSize); + speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_SAMPLING_RATE, + &clc.speexSampleRate); + + clc.speexPreprocessor = speex_preprocess_state_init(clc.speexFrameSize, + clc.speexSampleRate); + + i = 1; + speex_preprocess_ctl(clc.speexPreprocessor, + SPEEX_PREPROCESS_SET_DENOISE, &i); + + i = 1; + speex_preprocess_ctl(clc.speexPreprocessor, + SPEEX_PREPROCESS_SET_AGC, &i); + + for (i = 0; i < MAX_CLIENTS; i++) { + speex_bits_init(&clc.speexDecoderBits[i]); + speex_bits_reset(&clc.speexDecoderBits[i]); + clc.speexDecoder[i] = speex_decoder_init(&speex_nb_mode); + clc.voipIgnore[i] = qfalse; + clc.voipGain[i] = 1.0f; + } + clc.speexInitialized = qtrue; + clc.voipMuteAll = qfalse; + Cmd_AddCommand ("voip", CL_Voip_f); + Cvar_Set("cl_voipSendTarget", "all"); + clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0x7FFFFFFF; + } +#endif +} + +/* +================== +CL_SetCGameTime +================== +*/ +void CL_SetCGameTime( void ) { + // getting a valid frame message ends the connection process + if ( cls.state != CA_ACTIVE ) { + if ( cls.state != CA_PRIMED ) { + return; + } + if ( clc.demoplaying ) { + // we shouldn't get the first snapshot on the same frame + // as the gamestate, because it causes a bad time skip + if ( !clc.firstDemoFrameSkipped ) { + clc.firstDemoFrameSkipped = qtrue; + return; + } + CL_ReadDemoMessage(); + } + if ( cl.newSnapshots ) { + cl.newSnapshots = qfalse; + CL_FirstSnapshot(); + } + if ( cls.state != CA_ACTIVE ) { + return; + } + } + + // if we have gotten to this point, cl.snap is guaranteed to be valid + if ( !cl.snap.valid ) { + Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" ); + } + + // allow pause in single player + if ( sv_paused->integer && CL_CheckPaused() && com_sv_running->integer ) { + // paused + return; + } + + if ( cl.snap.serverTime < cl.oldFrameServerTime ) { + Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" ); + } + cl.oldFrameServerTime = cl.snap.serverTime; + + + // get our current view of time + + if ( clc.demoplaying && cl_freezeDemo->integer ) { + // cl_freezeDemo is used to lock a demo in place for single frame advances + + } else { + // cl_timeNudge is a user adjustable cvar that allows more + // or less latency to be added in the interest of better + // smoothness or better responsiveness. + int tn; + + tn = cl_timeNudge->integer; + if (tn<-30) { + tn = -30; + } else if (tn>30) { + tn = 30; + } + + cl.serverTime = cls.realtime + cl.serverTimeDelta - tn; + + // guarantee that time will never flow backwards, even if + // serverTimeDelta made an adjustment or cl_timeNudge was changed + if ( cl.serverTime < cl.oldServerTime ) { + cl.serverTime = cl.oldServerTime; + } + cl.oldServerTime = cl.serverTime; + + // note if we are almost past the latest frame (without timeNudge), + // so we will try and adjust back a bit when the next snapshot arrives + if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) { + cl.extrapolatedSnapshot = qtrue; + } + } + + // if we have gotten new snapshots, drift serverTimeDelta + // don't do this every frame, or a period of packet loss would + // make a huge adjustment + if ( cl.newSnapshots ) { + CL_AdjustTimeDelta(); + } + + if ( !clc.demoplaying ) { + return; + } + + // if we are playing a demo back, we can just keep reading + // messages from the demo file until the cgame definately + // has valid snapshots to interpolate between + + // a timedemo will always use a deterministic set of time samples + // no matter what speed machine it is run on, + // while a normal demo may have different time samples + // each time it is played back + if ( cl_timedemo->integer ) { + int now = Sys_Milliseconds( ); + int frameDuration; + + if (!clc.timeDemoStart) { + clc.timeDemoStart = clc.timeDemoLastFrame = now; + clc.timeDemoMinDuration = INT_MAX; + clc.timeDemoMaxDuration = 0; + } + + frameDuration = now - clc.timeDemoLastFrame; + clc.timeDemoLastFrame = now; + + // Ignore the first measurement as it'll always be 0 + if( clc.timeDemoFrames > 0 ) + { + if( frameDuration > clc.timeDemoMaxDuration ) + clc.timeDemoMaxDuration = frameDuration; + + if( frameDuration < clc.timeDemoMinDuration ) + clc.timeDemoMinDuration = frameDuration; + + // 255 ms = about 4fps + if( frameDuration > UCHAR_MAX ) + frameDuration = UCHAR_MAX; + + clc.timeDemoDurations[ ( clc.timeDemoFrames - 1 ) % + MAX_TIMEDEMO_DURATIONS ] = frameDuration; + } + + clc.timeDemoFrames++; + cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50; + } + + while ( cl.serverTime >= cl.snap.serverTime ) { + // feed another messag, which should change + // the contents of cl.snap + CL_ReadDemoMessage(); + if ( cls.state != CA_ACTIVE ) { + return; // end of demo + } + } + +} + + + diff --git a/src/client/cl_cin.c b/src/client/cl_cin.c new file mode 100644 index 0000000..213b7a4 --- /dev/null +++ b/src/client/cl_cin.c @@ -0,0 +1,1697 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: cl_cin.c + * + * desc: video and cinematic playback + * + * $Archive: /MissionPack/code/client/cl_cin.c $ + * + * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 + * + *****************************************************************************/ + +#include "client.h" +#include "snd_local.h" + +#define MAXSIZE 8 +#define MINSIZE 4 + +#define DEFAULT_CIN_WIDTH 512 +#define DEFAULT_CIN_HEIGHT 512 + +#define ROQ_QUAD 0x1000 +#define ROQ_QUAD_INFO 0x1001 +#define ROQ_CODEBOOK 0x1002 +#define ROQ_QUAD_VQ 0x1011 +#define ROQ_QUAD_JPEG 0x1012 +#define ROQ_QUAD_HANG 0x1013 +#define ROQ_PACKET 0x1030 +#define ZA_SOUND_MONO 0x1020 +#define ZA_SOUND_STEREO 0x1021 + +#define MAX_VIDEO_HANDLES 16 + +extern glconfig_t glConfig; + + +static void RoQ_init( void ); + +/****************************************************************************** +* +* Class: trFMV +* +* Description: RoQ/RnR manipulation routines +* not entirely complete for first run +* +******************************************************************************/ + +static long ROQ_YY_tab[256]; +static long ROQ_UB_tab[256]; +static long ROQ_UG_tab[256]; +static long ROQ_VG_tab[256]; +static long ROQ_VR_tab[256]; +static unsigned short vq2[256*16*4]; +static unsigned short vq4[256*64*4]; +static unsigned short vq8[256*256*4]; + + +typedef struct { + byte linbuf[DEFAULT_CIN_WIDTH*DEFAULT_CIN_HEIGHT*4*2]; + byte file[65536]; + short sqrTable[256]; + + int mcomp[256]; + byte *qStatus[2][32768]; + + long oldXOff, oldYOff, oldysize, oldxsize; + + int currentHandle; +} cinematics_t; + +typedef struct { + char fileName[MAX_OSPATH]; + int CIN_WIDTH, CIN_HEIGHT; + int xpos, ypos, width, height; + qboolean looping, holdAtEnd, dirty, alterGameState, silent, shader; + fileHandle_t iFile; + e_status status; + unsigned int startTime; + unsigned int lastTime; + long tfps; + long RoQPlayed; + long ROQSize; + unsigned int RoQFrameSize; + long onQuad; + long numQuads; + long samplesPerLine; + unsigned int roq_id; + long screenDelta; + + void ( *VQ0)(byte *status, void *qdata ); + void ( *VQ1)(byte *status, void *qdata ); + void ( *VQNormal)(byte *status, void *qdata ); + void ( *VQBuffer)(byte *status, void *qdata ); + + long samplesPerPixel; // defaults to 2 + byte* gray; + unsigned int xsize, ysize, maxsize, minsize; + + qboolean half, smootheddouble, inMemory; + long normalBuffer0; + long roq_flags; + long roqF0; + long roqF1; + long t[2]; + long roqFPS; + int playonwalls; + byte* buf; + long drawX, drawY; +} cin_cache; + +static cinematics_t cin; +static cin_cache cinTable[MAX_VIDEO_HANDLES]; +static int currentHandle = -1; +static int CL_handle = -1; + +extern int s_soundtime; // sample PAIRS + + +void CIN_CloseAllVideos(void) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if (cinTable[i].fileName[0] != 0 ) { + CIN_StopCinematic(i); + } + } +} + + +static int CIN_HandleForVideo(void) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if ( cinTable[i].fileName[0] == 0 ) { + return i; + } + } + Com_Error( ERR_DROP, "CIN_HandleForVideo: none free" ); + return -1; +} + + +extern int CL_ScaledMilliseconds(void); + +//----------------------------------------------------------------------------- +// RllSetupTable +// +// Allocates and initializes the square table. +// +// Parameters: None +// +// Returns: Nothing +//----------------------------------------------------------------------------- +static void RllSetupTable( void ) +{ + int z; + + for (z=0;z<128;z++) { + cin.sqrTable[z] = (short)(z*z); + cin.sqrTable[z+128] = (short)(-cin.sqrTable[z]); + } +} + + + +//----------------------------------------------------------------------------- +// RllDecodeMonoToMono +// +// Decode mono source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of shorts of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput ,unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z=0;z buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/4 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z = 0; z < size; z++) { + prev = (short)(prev + cin.sqrTable[from[z]]); + to[z*2+0] = to[z*2+1] = (short)(prev); + } + + return size; // * 2 * sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToStereo +// +// Decode stereo source data into a stereo buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/2 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) +{ + unsigned int z; + unsigned char *zz = from; + int prevL, prevR; + + if (signedOutput) { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) - 0x8000; + } else { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z=0;z>1); //*sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToMono +// +// Decode stereo source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) +{ + unsigned int z; + int prevL,prevR; + + if (signedOutput) { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) -0x8000; + } else { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z=0;z> 2) ); + } +} + +#define VQ2TO4(a,b,c,d) { \ + *c++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *c++ = a[1]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *c++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *c++ = b[1]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + a += 2; b += 2; } + +#define VQ2TO2(a,b,c,d) { \ + *c++ = *a; \ + *d++ = *a; \ + *d++ = *a; \ + *c++ = *b; \ + *d++ = *b; \ + *d++ = *b; \ + *d++ = *a; \ + *d++ = *a; \ + *d++ = *b; \ + *d++ = *b; \ + a++; b++; } + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static unsigned short yuv_to_rgb( long y, long u, long v ) +{ + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 9; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8; + b = (YY + ROQ_UB_tab[u]) >> 9; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 31) r = 31; if (g > 63) g = 63; if (b > 31) b = 31; + + return (unsigned short)((r<<11)+(g<<5)+(b)); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +static unsigned int yuv_to_rgb24( long y, long u, long v ) +{ + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 6; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; + b = (YY + ROQ_UB_tab[u]) >> 6; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; + + return LittleLong ((r)|(g<<8)|(b<<16)|(255<<24)); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void decodeCodeBook( byte *input, unsigned short roq_flags ) +{ + long i, j, two, four; + unsigned short *aptr, *bptr, *cptr, *dptr; + long y0,y1,y2,y3,cr,cb; + byte *bbptr, *baptr, *bcptr, *bdptr; + union { + unsigned int *i; + unsigned short *s; + } iaptr, ibptr, icptr, idptr; + + if (!roq_flags) { + two = four = 256; + } else { + two = roq_flags>>8; + if (!two) two = 256; + four = roq_flags&0xff; + } + + four *= 2; + + bptr = (unsigned short *)vq2; + + if (!cinTable[currentHandle].half) { + if (!cinTable[currentHandle].smootheddouble) { +// +// normal height +// + if (cinTable[currentHandle].samplesPerPixel==2) { + for(i=0;i cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH; + if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT; + + if ( (startX >= lowx) && (startX+quadSize) <= (bigx) && (startY+quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) { + useY = startY; + scroff = cin.linbuf + (useY+((cinTable[currentHandle].CIN_HEIGHT-bigy)>>1)+yOff)*(cinTable[currentHandle].samplesPerLine) + (((startX+xOff))*cinTable[currentHandle].samplesPerPixel); + + cin.qStatus[0][cinTable[currentHandle].onQuad ] = scroff; + cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff+offset; + } + + if ( quadSize != MINSIZE ) { + quadSize >>= 1; + recurseQuad( startX, startY , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY , quadSize, xOff, yOff ); + recurseQuad( startX, startY+quadSize , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY+quadSize , quadSize, xOff, yOff ); + } +} + + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void setupQuad( long xOff, long yOff ) +{ + long numQuadCels, i,x,y; + byte *temp; + + if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && cinTable[currentHandle].xsize == cin.oldxsize) { + return; + } + + cin.oldXOff = xOff; + cin.oldYOff = yOff; + cin.oldysize = cinTable[currentHandle].ysize; + cin.oldxsize = cinTable[currentHandle].xsize; + + numQuadCels = (cinTable[currentHandle].CIN_WIDTH*cinTable[currentHandle].CIN_HEIGHT) / (16); + numQuadCels += numQuadCels/4 + numQuadCels/16; + numQuadCels += 64; // for overflow + + numQuadCels = (cinTable[currentHandle].xsize*cinTable[currentHandle].ysize) / (16); + numQuadCels += numQuadCels/4; + numQuadCels += 64; // for overflow + + cinTable[currentHandle].onQuad = 0; + + for(y=0;y<(long)cinTable[currentHandle].ysize;y+=16) + for(x=0;x<(long)cinTable[currentHandle].xsize;x+=16) + recurseQuad( x, y, 16, xOff, yOff ); + + temp = NULL; + + for(i=(numQuadCels-64);i256) { + cinTable[currentHandle].drawX = 256; + } + if (cinTable[currentHandle].drawY>256) { + cinTable[currentHandle].drawY = 256; + } + if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256) { + Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n"); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQPrepMcomp( long xoff, long yoff ) +{ + long i, j, x, y, temp, temp2; + + i=cinTable[currentHandle].samplesPerLine; j=cinTable[currentHandle].samplesPerPixel; + if ( cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize*4) && !cinTable[currentHandle].half ) { j = j+j; i = i+i; } + + for(y=0;y<16;y++) { + temp2 = (y+yoff-8)*i; + for(x=0;x<16;x++) { + temp = (x+xoff-8)*j; + cin.mcomp[(x*16)+y] = cinTable[currentHandle].normalBuffer0-(temp2+temp); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void initRoQ( void ) +{ + if (currentHandle < 0) return; + + cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].samplesPerPixel = 4; + ROQ_GenYUVTables(); + RllSetupTable(); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +/* +static byte* RoQFetchInterlaced( byte *source ) { + int x, *src, *dst; + + if (currentHandle < 0) return NULL; + + src = (int *)source; + dst = (int *)cinTable[currentHandle].buf2; + + for(x=0;x<256*256;x++) { + *dst = *src; + dst++; src += 2; + } + return cinTable[currentHandle].buf2; +} +*/ +static void RoQReset( void ) { + + if (currentHandle < 0) return; + + FS_FCloseFile( cinTable[currentHandle].iFile ); + FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); + // let the background thread start reading ahead + FS_Read (cin.file, 16, cinTable[currentHandle].iFile); + RoQ_init(); + cinTable[currentHandle].status = FMV_LOOPED; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQInterrupt(void) +{ + byte *framedata; + short sbuf[32768]; + int ssize; + + if (currentHandle < 0) return; + + FS_Read( cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile ); + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if (cinTable[currentHandle].holdAtEnd==qfalse) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata = cin.file; +// +// new frame is ready +// +redump: + switch(cinTable[currentHandle].roq_id) + { + case ROQ_QUAD_VQ: + if ((cinTable[currentHandle].numQuads&1)) { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ1( (byte *)cin.qStatus[1], framedata); + cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta; + } else { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ0( (byte *)cin.qStatus[0], framedata ); + cinTable[currentHandle].buf = cin.linbuf; + } + if (cinTable[currentHandle].numQuads == 0) { // first frame + Com_Memcpy(cin.linbuf+cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine*cinTable[currentHandle].ysize); + } + cinTable[currentHandle].numQuads++; + cinTable[currentHandle].dirty = qtrue; + break; + case ROQ_CODEBOOK: + decodeCodeBook( framedata, (unsigned short)cinTable[currentHandle].roq_flags ); + break; + case ZA_SOUND_MONO: + if (!cinTable[currentHandle].silent) { + ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples( 0, ssize, 22050, 2, 1, (byte *)sbuf, 1.0f ); + } + break; + case ZA_SOUND_STEREO: + if (!cinTable[currentHandle].silent) { + if (cinTable[currentHandle].numQuads == -1) { + S_Update(); + s_rawend[0] = s_soundtime; + } + ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples( 0, ssize, 22050, 2, 2, (byte *)sbuf, 1.0f ); + } + break; + case ROQ_QUAD_INFO: + if (cinTable[currentHandle].numQuads == -1) { + readQuadInfo( framedata ); + setupQuad( 0, 0 ); + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; + } + if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0; + break; + case ROQ_PACKET: + cinTable[currentHandle].inMemory = cinTable[currentHandle].roq_flags; + cinTable[currentHandle].RoQFrameSize = 0; // for header + break; + case ROQ_QUAD_HANG: + cinTable[currentHandle].RoQFrameSize = 0; + break; + case ROQ_QUAD_JPEG: + break; + default: + cinTable[currentHandle].status = FMV_EOF; + break; + } +// +// read in next frame data +// + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if (cinTable[currentHandle].holdAtEnd==qfalse) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata += cinTable[currentHandle].RoQFrameSize; + cinTable[currentHandle].roq_id = framedata[0] + framedata[1]*256; + cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3]*256 + framedata[4]*65536; + cinTable[currentHandle].roq_flags = framedata[6] + framedata[7]*256; + cinTable[currentHandle].roqF0 = (signed char)framedata[7]; + cinTable[currentHandle].roqF1 = (signed char)framedata[6]; + + if (cinTable[currentHandle].RoQFrameSize>65536||cinTable[currentHandle].roq_id==0x1084) { + Com_DPrintf("roq_size>65536||roq_id==0x1084\n"); + cinTable[currentHandle].status = FMV_EOF; + if (cinTable[currentHandle].looping) { + RoQReset(); + } + return; + } + if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF)) { cinTable[currentHandle].inMemory--; framedata += 8; goto redump; } +// +// one more frame hits the dust +// +// assert(cinTable[currentHandle].RoQFrameSize <= 65536); +// r = FS_Read( cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile ); + cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize+8; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQ_init( void ) +{ + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; + + cinTable[currentHandle].RoQPlayed = 24; + +/* get frame rate */ + cinTable[currentHandle].roqFPS = cin.file[ 6] + cin.file[ 7]*256; + + if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30; + + cinTable[currentHandle].numQuads = -1; + + cinTable[currentHandle].roq_id = cin.file[ 8] + cin.file[ 9]*256; + cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11]*256 + cin.file[12]*65536; + cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15]*256; + + if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize) { + return; + } + +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQShutdown( void ) { + if (!cinTable[currentHandle].buf) { + return; + } + + if ( cinTable[currentHandle].status == FMV_IDLE ) { + return; + } + Com_DPrintf("finished cinematic\n"); + cinTable[currentHandle].status = FMV_IDLE; + + if (cinTable[currentHandle].iFile) { + FS_FCloseFile( cinTable[currentHandle].iFile ); + cinTable[currentHandle].iFile = 0; + } + + if (cinTable[currentHandle].alterGameState) { + cls.state = CA_DISCONNECTED; + CL_handle = -1; + } + cinTable[currentHandle].fileName[0] = 0; + currentHandle = -1; +} + +/* +================== +SCR_StopCinematic +================== +*/ +e_status CIN_StopCinematic(int handle) { + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + currentHandle = handle; + + Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName); + + if (!cinTable[currentHandle].buf) { + return FMV_EOF; + } + + if (cinTable[currentHandle].alterGameState) { + if ( cls.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + cinTable[currentHandle].status = FMV_EOF; + RoQShutdown(); + + return FMV_EOF; +} + +/* +================== +SCR_RunCinematic + +Fetch and decompress the pending frame +================== +*/ + + +e_status CIN_RunCinematic (int handle) +{ + int start = 0; + int thisTime = 0; + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + + if (cin.currentHandle != handle) { + currentHandle = handle; + cin.currentHandle = currentHandle; + cinTable[currentHandle].status = FMV_EOF; + RoQReset(); + } + + if (cinTable[handle].playonwalls < -1) + { + return cinTable[handle].status; + } + + currentHandle = handle; + + if (cinTable[currentHandle].alterGameState) { + if ( cls.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + + if (cinTable[currentHandle].status == FMV_IDLE) { + return cinTable[currentHandle].status; + } + + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + thisTime = CL_ScaledMilliseconds()*com_timescale->value; + if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime))>100) { + cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime; + } + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - cinTable[currentHandle].startTime)*3)/100); + + start = cinTable[currentHandle].startTime; + while( (cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads) + && (cinTable[currentHandle].status == FMV_PLAY) ) + { + RoQInterrupt(); + if (start != cinTable[currentHandle].startTime) { + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) + - cinTable[currentHandle].startTime)*3)/100); + start = cinTable[currentHandle].startTime; + } + } + + cinTable[currentHandle].lastTime = thisTime; + + if (cinTable[currentHandle].status == FMV_LOOPED) { + cinTable[currentHandle].status = FMV_PLAY; + } + + if (cinTable[currentHandle].status == FMV_EOF) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + RoQShutdown(); + } + } + + return cinTable[currentHandle].status; +} + +/* +================== +CL_PlayCinematic + +================== +*/ +int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBits ) { + unsigned short RoQID; + char name[MAX_OSPATH]; + int i; + + if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL) { + Com_sprintf (name, sizeof(name), "video/%s", arg); + } else { + Com_sprintf (name, sizeof(name), "%s", arg); + } + + if (!(systemBits & CIN_system)) { + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if (!strcmp(cinTable[i].fileName, name) ) { + return i; + } + } + } + + Com_DPrintf("SCR_PlayCinematic( %s )\n", arg); + + Com_Memset(&cin, 0, sizeof(cinematics_t) ); + currentHandle = CIN_HandleForVideo(); + + cin.currentHandle = currentHandle; + + strcpy(cinTable[currentHandle].fileName, name); + + cinTable[currentHandle].ROQSize = 0; + cinTable[currentHandle].ROQSize = FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); + + if (cinTable[currentHandle].ROQSize<=0) { + Com_DPrintf("play(%s), ROQSize<=0\n", arg); + cinTable[currentHandle].fileName[0] = 0; + return -1; + } + + CIN_SetExtents(currentHandle, x, y, w, h); + CIN_SetLooping(currentHandle, (systemBits & CIN_loop)!=0); + + cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT; + cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH; + cinTable[currentHandle].holdAtEnd = (systemBits & CIN_hold) != 0; + cinTable[currentHandle].alterGameState = (systemBits & CIN_system) != 0; + cinTable[currentHandle].playonwalls = 1; + cinTable[currentHandle].silent = (systemBits & CIN_silent) != 0; + cinTable[currentHandle].shader = (systemBits & CIN_shader) != 0; + + if (cinTable[currentHandle].alterGameState) { + // close the menu + if ( uivm ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + } else { + cinTable[currentHandle].playonwalls = cl_inGameVideo->integer; + } + + initRoQ(); + + FS_Read (cin.file, 16, cinTable[currentHandle].iFile); + + RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1])*256; + if (RoQID == 0x1084) + { + RoQ_init(); +// FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile); + + cinTable[currentHandle].status = FMV_PLAY; + Com_DPrintf("trFMV::play(), playing %s\n", arg); + + if (cinTable[currentHandle].alterGameState) { + cls.state = CA_CINEMATIC; + } + + Con_Close(); + + s_rawend[0] = s_soundtime; + + return currentHandle; + } + Com_DPrintf("trFMV::play(), invalid RoQ ID\n"); + + RoQShutdown(); + return -1; +} + +void CIN_SetExtents (int handle, int x, int y, int w, int h) { + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].xpos = x; + cinTable[handle].ypos = y; + cinTable[handle].width = w; + cinTable[handle].height = h; + cinTable[handle].dirty = qtrue; +} + +void CIN_SetLooping(int handle, qboolean loop) { + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].looping = loop; +} + +/* +================== +CIN_ResampleCinematic + +Resample cinematic to 256x256 and store in buf2 +================== +*/ +void CIN_ResampleCinematic(int handle, int *buf2) { + int ix, iy, *buf3, xm, ym, ll; + byte *buf; + + buf = cinTable[handle].buf; + + xm = cinTable[handle].CIN_WIDTH/256; + ym = cinTable[handle].CIN_HEIGHT/256; + ll = 8; + if (cinTable[handle].CIN_WIDTH==512) { + ll = 9; + } + + buf3 = (int*)buf; + if (xm==2 && ym==2) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy<256; iy++) { + iiy = iy<<12; + for (ix = 0; ix<2048; ix+=8) { + for(ic = ix;ic<(ix+4);ic++) { + *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic]+bc3[iiy+2048+ic]+bc3[iiy+2048+4+ic])>>2; + bc2++; + } + } + } + } else if (xm==2 && ym==1) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy<256; iy++) { + iiy = iy<<11; + for (ix = 0; ix<2048; ix+=8) { + for(ic = ix;ic<(ix+4);ic++) { + *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic])>>1; + bc2++; + } + } + } + } else { + for (iy = 0; iy<256; iy++) { + for (ix = 0; ix<256; ix++) { + buf2[(iy<<8)+ix] = buf3[((iy*ym)<= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + + if (!cinTable[handle].buf) { + return; + } + + x = cinTable[handle].xpos; + y = cinTable[handle].ypos; + w = cinTable[handle].width; + h = cinTable[handle].height; + buf = cinTable[handle].buf; + SCR_AdjustFrom640( &x, &y, &w, &h ); + + if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) { + int *buf2; + + buf2 = Hunk_AllocateTempMemory( 256*256*4 ); + + CIN_ResampleCinematic(handle, buf2); + + re.DrawStretchRaw( x, y, w, h, 256, 256, (byte *)buf2, handle, qtrue); + cinTable[handle].dirty = qfalse; + Hunk_FreeTempMemory(buf2); + return; + } + + re.DrawStretchRaw( x, y, w, h, cinTable[handle].drawX, cinTable[handle].drawY, buf, handle, cinTable[handle].dirty); + cinTable[handle].dirty = qfalse; +} + +void CL_PlayCinematic_f(void) { + char *arg, *s; + qboolean holdatend; + int bits = CIN_system; + + Com_DPrintf("CL_PlayCinematic_f\n"); + if (cls.state == CA_CINEMATIC) { + SCR_StopCinematic(); + } + + arg = Cmd_Argv( 1 ); + s = Cmd_Argv(2); + + holdatend = qfalse; + if ((s && s[0] == '1') || Q_stricmp(arg,"demoend.roq")==0 || Q_stricmp(arg,"end.roq")==0) { + bits |= CIN_hold; + } + if (s && s[0] == '2') { + bits |= CIN_loop; + } + + S_StopAllSounds (); + + CL_handle = CIN_PlayCinematic( arg, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, bits ); + if (CL_handle >= 0) { + do { + SCR_RunCinematic(); + } while (cinTable[currentHandle].buf == NULL && cinTable[currentHandle].status == FMV_PLAY); // wait for first frame (load codebook and sound) + } +} + + +void SCR_DrawCinematic (void) { + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_DrawCinematic(CL_handle); + } +} + +void SCR_RunCinematic (void) +{ + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_RunCinematic(CL_handle); + } +} + +void SCR_StopCinematic(void) { + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_StopCinematic(CL_handle); + S_StopAllSounds (); + CL_handle = -1; + } +} + +void CIN_UploadCinematic(int handle) { + if (handle >= 0 && handle < MAX_VIDEO_HANDLES) { + if (!cinTable[handle].buf) { + return; + } + if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty) { + if (cinTable[handle].playonwalls == 0) { + cinTable[handle].playonwalls = -1; + } else { + if (cinTable[handle].playonwalls == -1) { + cinTable[handle].playonwalls = -2; + } else { + cinTable[handle].dirty = qfalse; + } + } + } + + // Resample the video if needed + if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) { + int *buf2; + + buf2 = Hunk_AllocateTempMemory( 256*256*4 ); + + CIN_ResampleCinematic(handle, buf2); + + re.UploadCinematic( cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, 256, 256, (byte *)buf2, handle, qtrue); + cinTable[handle].dirty = qfalse; + Hunk_FreeTempMemory(buf2); + } else { + // Upload video at normal resolution + re.UploadCinematic( cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, cinTable[handle].drawX, cinTable[handle].drawY, + cinTable[handle].buf, handle, cinTable[handle].dirty); + cinTable[handle].dirty = qfalse; + } + + if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) { + cinTable[handle].playonwalls--; + } + else if (cl_inGameVideo->integer != 0 && cinTable[handle].playonwalls != 1) { + cinTable[handle].playonwalls = 1; + } + } +} + diff --git a/src/client/cl_console.c b/src/client/cl_console.c new file mode 100644 index 0000000..c7d01bc --- /dev/null +++ b/src/client/cl_console.c @@ -0,0 +1,624 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// console.c + +#include "client.h" + + +int g_console_field_width = 78; + + +#define NUM_CON_TIMES 4 + +#define CON_TEXTSIZE 163840 +typedef struct { + qboolean initialized; + + short text[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line + + int linewidth; // characters across screen + int totallines; // total lines in console scrollback + + float xadjust; // for wide aspect screens + + float displayFrac; // aproaches finalFrac at scr_conspeed + float finalFrac; // 0.0 to 1.0 lines of console to display + + int vislines; // in scanlines + + vec4_t color; +} console_t; + +extern console_t con; + +console_t con; + +cvar_t *con_conspeed; + +#define DEFAULT_CONSOLE_WIDTH 78 + +vec4_t console_color = {1.0, 1.0, 1.0, 1.0}; + + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f (void) { + // Can't toggle the console when it's the only thing available + if ( cls.state == CA_DISCONNECTED && Key_GetCatcher( ) == KEYCATCH_CONSOLE ) { + return; + } + + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_CONSOLE ); +} + +/* +================ +Con_Clear_f +================ +*/ +void Con_Clear_f (void) { + int i; + + for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { + con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; + } + + Con_Bottom(); // go to end +} + + +/* +================ +Con_Dump_f + +Save the console contents out to a file +================ +*/ +void Con_Dump_f (void) +{ + int l, x, i; + short *line; + fileHandle_t f; + char buffer[1024]; + + if (Cmd_Argc() != 2) + { + Com_Printf ("usage: condump \n"); + return; + } + + Com_Printf ("Dumped console text to %s.\n", Cmd_Argv(1) ); + + f = FS_FOpenFileWrite( Cmd_Argv( 1 ) ); + if (!f) + { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + + // skip empty lines + for (l = con.current - con.totallines + 1 ; l <= con.current ; l++) + { + line = con.text + (l%con.totallines)*con.linewidth; + for (x=0 ; x=0 ; x--) + { + if (buffer[x] == ' ') + buffer[x] = 0; + else + break; + } + strcat( buffer, "\n" ); + FS_Write(buffer, strlen(buffer), f); + } + + FS_FCloseFile( f ); +} + + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify( void ) { + Cmd_TokenizeString( NULL ); + CL_GameConsoleText( ); +} + + + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize (void) +{ + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + short tbuf[CON_TEXTSIZE]; + + width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; + + if (width == con.linewidth) + return; + + if (width < 1) // video hasn't been initialized yet + { + width = DEFAULT_CONSOLE_WIDTH; + con.linewidth = width; + con.totallines = CON_TEXTSIZE / con.linewidth; + for(i=0; iinteger ) { + return; + } + + if (!con.initialized) { + con.color[0] = + con.color[1] = + con.color[2] = + con.color[3] = 1.0f; + con.linewidth = -1; + Con_CheckResize (); + con.initialized = qtrue; + } + + if( !skipnotify && !( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) { + Cmd_SaveCmdContext( ); + + // feed the text to cgame + Cmd_TokenizeString( txt ); + CL_GameConsoleText( ); + + Cmd_RestoreCmdContext( ); + } + + color = ColorIndex(COLOR_WHITE); + + while ( (c = *((unsigned char *) txt)) != 0 ) { + if ( Q_IsColorString( txt ) ) { + color = ColorIndex( *(txt+1) ); + txt += 2; + continue; + } + + // count word length + for (l=0 ; l< con.linewidth ; l++) { + if ( txt[l] <= ' ') { + break; + } + + } + + // word wrap + if (l != con.linewidth && (con.x + l >= con.linewidth) ) { + Con_Linefeed(skipnotify); + + } + + txt++; + + switch (c) + { + case INDENT_MARKER: + break; + case '\n': + Con_Linefeed (skipnotify); + break; + case '\r': + con.x = 0; + break; + default: // display character and advance + y = con.current % con.totallines; + con.text[y*con.linewidth+con.x] = (color << 8) | c; + con.x++; + if(con.x >= con.linewidth) + Con_Linefeed(skipnotify); + break; + } + } +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +Con_DrawInput + +Draw the editline after a ] prompt +================ +*/ +void Con_DrawInput (void) { + int y; + + if ( cls.state != CA_DISCONNECTED && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) { + return; + } + + y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); + + re.SetColor( con.color ); + + SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); + + Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y, + SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue, qtrue ); +} + +/* +================ +Con_DrawSolidConsole + +Draws the console with the solid background +================ +*/ +void Con_DrawSolidConsole( float frac ) { + int i, x, y; + int rows; + short *text; + int row; + int lines; +// qhandle_t conShader; + int currentColor; + vec4_t color; + + lines = cls.glconfig.vidHeight * frac; + if (lines <= 0) + return; + + if (lines > cls.glconfig.vidHeight ) + lines = cls.glconfig.vidHeight; + + // on wide screens, we will center the text + con.xadjust = 0; + SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); + + // draw the background + y = frac * SCREEN_HEIGHT; + if ( y < 1 ) { + y = 0; + } + else { + SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader ); + } + + color[0] = 1; + color[1] = 0; + color[2] = 0; + color[3] = 1; + SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color ); + + + // draw the version number + + re.SetColor( g_color_table[ColorIndex(COLOR_RED)] ); + + i = strlen( Q3_VERSION ); + + for (x=0 ; x= con.totallines) { + // past scrollback wrap point + continue; + } + + text = con.text + (row % con.totallines)*con.linewidth; + + for (x=0 ; x>8)&7 ) != currentColor ) { + currentColor = (text[x]>>8)&7; + re.SetColor( g_color_table[currentColor] ); + } + SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); + } + } + + // draw the input prompt, user text, and cursor if desired + Con_DrawInput (); + + re.SetColor( NULL ); +} + + + +/* +================== +Con_DrawConsole +================== +*/ +void Con_DrawConsole( void ) { + // check for console width changes from a vid mode change + Con_CheckResize (); + + // if disconnected, render console full screen + if ( cls.state == CA_DISCONNECTED ) { + if ( !( Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME)) ) { + Con_DrawSolidConsole( 1.0 ); + return; + } + } + + if ( con.displayFrac ) { + Con_DrawSolidConsole( con.displayFrac ); + } + + if( Key_GetCatcher( ) & ( KEYCATCH_UI | KEYCATCH_CGAME ) ) + return; +} + +//================================================================ + +/* +================== +Con_RunConsole + +Scroll it up or down +================== +*/ +void Con_RunConsole (void) { + // decide on the destination height of the console + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) + con.finalFrac = 0.5; // half screen + else + con.finalFrac = 0; // none visible + + // scroll towards the destination height + if (con.finalFrac < con.displayFrac) + { + con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001; + if (con.finalFrac > con.displayFrac) + con.displayFrac = con.finalFrac; + + } + else if (con.finalFrac > con.displayFrac) + { + con.displayFrac += con_conspeed->value*cls.realFrametime*0.001; + if (con.finalFrac < con.displayFrac) + con.displayFrac = con.finalFrac; + } + +} + + +void Con_PageUp( void ) { + con.display -= 2; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_PageDown( void ) { + con.display += 2; + if (con.display > con.current) { + con.display = con.current; + } +} + +void Con_Top( void ) { + con.display = con.totallines; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_Bottom( void ) { + con.display = con.current; +} + + +void Con_Close( void ) { + if ( !com_cl_running->integer ) { + return; + } + Field_Clear( &g_consoleField ); + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CONSOLE ); + con.finalFrac = 0; // none visible + con.displayFrac = 0; +} diff --git a/src/client/cl_curl.c b/src/client/cl_curl.c new file mode 100644 index 0000000..3a59f19 --- /dev/null +++ b/src/client/cl_curl.c @@ -0,0 +1,339 @@ +/* +=========================================================================== +Copyright (C) 2006 Tony J. White (tjw@tjw.org) +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifdef USE_CURL +#include "client.h" +cvar_t *cl_cURLLib; + +#ifdef USE_CURL_DLOPEN +#include "../sys/sys_loadlib.h" + +char* (*qcurl_version)(void); + +CURL* (*qcurl_easy_init)(void); +CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...); +CURLcode (*qcurl_easy_perform)(CURL *curl); +void (*qcurl_easy_cleanup)(CURL *curl); +CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...); +CURL* (*qcurl_easy_duphandle)(CURL *curl); +void (*qcurl_easy_reset)(CURL *curl); +const char *(*qcurl_easy_strerror)(CURLcode); + +CURLM* (*qcurl_multi_init)(void); +CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, + CURL *curl_handle); +CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, + CURL *curl_handle); +CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *exc_fd_set, + int *max_fd); +CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, + int *running_handles); +CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle); +CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, + int *msgs_in_queue); +const char *(*qcurl_multi_strerror)(CURLMcode); + +static void *cURLLib = NULL; + +/* +================= +GPA +================= +*/ +static void *GPA(char *str) +{ + void *rv; + + rv = Sys_LoadFunction(cURLLib, str); + if(!rv) + { + Com_Printf("Can't load symbol %s\n", str); + clc.cURLEnabled = qfalse; + return NULL; + } + else + { + Com_DPrintf("Loaded symbol %s (0x%p)\n", str, rv); + return rv; + } +} +#endif /* USE_CURL_DLOPEN */ + +/* +================= +CL_cURL_Init +================= +*/ +qboolean CL_cURL_Init() +{ +#ifdef USE_CURL_DLOPEN + if(cURLLib) + return qtrue; + + + Com_Printf("Loading \"%s\"...", cl_cURLLib->string); + if( (cURLLib = Sys_LoadLibrary(cl_cURLLib->string)) == 0 ) + { +#ifdef _WIN32 + return qfalse; +#else + char fn[1024]; + + Q_strncpyz( fn, Sys_Cwd( ), sizeof( fn ) ); + strncat(fn, "/", sizeof(fn)-strlen(fn)-1); + strncat(fn, cl_cURLLib->string, sizeof(fn)-strlen(fn)-1); + + if((cURLLib = Sys_LoadLibrary(fn)) == 0) + { +#ifdef ALTERNATE_CURL_LIB + // On some linux distributions there is no libcurl.so.3, but only libcurl.so.4. That one works too. + if( (cURLLib = Sys_LoadLibrary(ALTERNATE_CURL_LIB)) == 0 ) + { + return qfalse; + } +#else + return qfalse; +#endif + } +#endif /* _WIN32 */ + } + + clc.cURLEnabled = qtrue; + + qcurl_version = GPA("curl_version"); + + qcurl_easy_init = GPA("curl_easy_init"); + qcurl_easy_setopt = GPA("curl_easy_setopt"); + qcurl_easy_perform = GPA("curl_easy_perform"); + qcurl_easy_cleanup = GPA("curl_easy_cleanup"); + qcurl_easy_getinfo = GPA("curl_easy_getinfo"); + qcurl_easy_duphandle = GPA("curl_easy_duphandle"); + qcurl_easy_reset = GPA("curl_easy_reset"); + qcurl_easy_strerror = GPA("curl_easy_strerror"); + + qcurl_multi_init = GPA("curl_multi_init"); + qcurl_multi_add_handle = GPA("curl_multi_add_handle"); + qcurl_multi_remove_handle = GPA("curl_multi_remove_handle"); + qcurl_multi_fdset = GPA("curl_multi_fdset"); + qcurl_multi_perform = GPA("curl_multi_perform"); + qcurl_multi_cleanup = GPA("curl_multi_cleanup"); + qcurl_multi_info_read = GPA("curl_multi_info_read"); + qcurl_multi_strerror = GPA("curl_multi_strerror"); + + if(!clc.cURLEnabled) + { + CL_cURL_Shutdown(); + Com_Printf("FAIL One or more symbols not found\n"); + return qfalse; + } + Com_Printf("OK\n"); + + return qtrue; +#else + clc.cURLEnabled = qtrue; + return qtrue; +#endif /* USE_CURL_DLOPEN */ +} + +/* +================= +CL_cURL_Shutdown +================= +*/ +void CL_cURL_Shutdown( void ) +{ + CL_cURL_Cleanup(); +#ifdef USE_CURL_DLOPEN + if(cURLLib) + { + Sys_UnloadLibrary(cURLLib); + cURLLib = NULL; + } + qcurl_easy_init = NULL; + qcurl_easy_setopt = NULL; + qcurl_easy_perform = NULL; + qcurl_easy_cleanup = NULL; + qcurl_easy_getinfo = NULL; + qcurl_easy_duphandle = NULL; + qcurl_easy_reset = NULL; + + qcurl_multi_init = NULL; + qcurl_multi_add_handle = NULL; + qcurl_multi_remove_handle = NULL; + qcurl_multi_fdset = NULL; + qcurl_multi_perform = NULL; + qcurl_multi_cleanup = NULL; + qcurl_multi_info_read = NULL; + qcurl_multi_strerror = NULL; +#endif /* USE_CURL_DLOPEN */ +} + +void CL_cURL_Cleanup(void) +{ + if(clc.downloadCURLM) { + if(clc.downloadCURL) { + qcurl_multi_remove_handle(clc.downloadCURLM, + clc.downloadCURL); + qcurl_easy_cleanup(clc.downloadCURL); + } + qcurl_multi_cleanup(clc.downloadCURLM); + clc.downloadCURLM = NULL; + clc.downloadCURL = NULL; + } + else if(clc.downloadCURL) { + qcurl_easy_cleanup(clc.downloadCURL); + clc.downloadCURL = NULL; + } +} + +static int CL_cURL_CallbackProgress( void *dummy, double dltotal, double dlnow, + double ultotal, double ulnow ) +{ + clc.downloadSize = (int)dltotal; + Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); + clc.downloadCount = (int)dlnow; + Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); + return 0; +} + +static size_t CL_cURL_CallbackWrite(void *buffer, size_t size, size_t nmemb, + void *stream) +{ + FS_Write( buffer, size*nmemb, ((fileHandle_t*)stream)[0] ); + return size*nmemb; +} + +void CL_cURL_BeginDownload( const char *localName, const char *remoteURL ) +{ + clc.cURLUsed = qtrue; + Com_Printf("URL: %s\n", remoteURL); + Com_DPrintf("***** CL_cURL_BeginDownload *****\n" + "Localname: %s\n" + "RemoteURL: %s\n" + "****************************\n", localName, remoteURL); + CL_cURL_Cleanup(); + Q_strncpyz(clc.downloadURL, remoteURL, sizeof(clc.downloadURL)); + Q_strncpyz(clc.downloadName, localName, sizeof(clc.downloadName)); + Com_sprintf(clc.downloadTempName, sizeof(clc.downloadTempName), + "%s.tmp", localName); + + // Set so UI gets access to it + Cvar_Set("cl_downloadName", localName); + Cvar_Set("cl_downloadSize", "0"); + Cvar_Set("cl_downloadCount", "0"); + Cvar_SetValue("cl_downloadTime", cls.realtime); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + clc.downloadCURL = qcurl_easy_init(); + if(!clc.downloadCURL) { + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_easy_init() " + "failed\n"); + return; + } + clc.download = FS_SV_FOpenFileWrite(clc.downloadTempName); + if(!clc.download) { + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: failed to open " + "%s for writing\n", clc.downloadTempName); + return; + } + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, clc.download); + if(com_developer->integer) + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_VERBOSE, 1); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_URL, clc.downloadURL); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_TRANSFERTEXT, 0); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_REFERER, + va("Tremulous://%s", NET_AdrToString(clc.serverAddress))); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_USERAGENT, va("%s %s", + Q3_VERSION, qcurl_version())); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEFUNCTION, + CL_cURL_CallbackWrite); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, &clc.download); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_NOPROGRESS, 0); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSFUNCTION, + CL_cURL_CallbackProgress); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSDATA, NULL); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FAILONERROR, 1); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FOLLOWLOCATION, 1); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_MAXREDIRS, 5); + clc.downloadCURLM = qcurl_multi_init(); + if(!clc.downloadCURLM) { + qcurl_easy_cleanup(clc.downloadCURL); + clc.downloadCURL = NULL; + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_multi_init() " + "failed\n"); + return; + } + qcurl_multi_add_handle(clc.downloadCURLM, clc.downloadCURL); + + if(!(clc.sv_allowDownload & DLF_NO_DISCONNECT) && + !clc.cURLDisconnected) { + + CL_AddReliableCommand("disconnect", qtrue); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + clc.cURLDisconnected = qtrue; + } +} + +void CL_cURL_PerformDownload(void) +{ + CURLMcode res; + CURLMsg *msg; + int c; + int i = 0; + + res = qcurl_multi_perform(clc.downloadCURLM, &c); + while(res == CURLM_CALL_MULTI_PERFORM && i < 100) { + res = qcurl_multi_perform(clc.downloadCURLM, &c); + i++; + } + if(res == CURLM_CALL_MULTI_PERFORM) + return; + msg = qcurl_multi_info_read(clc.downloadCURLM, &c); + if(msg == NULL) { + return; + } + FS_FCloseFile(clc.download); + if(msg->msg == CURLMSG_DONE && msg->data.result == CURLE_OK) { + FS_SV_Rename(clc.downloadTempName, clc.downloadName); + clc.downloadRestart = qtrue; + } + else { + long code; + + qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, + &code); + Com_Error(ERR_DROP, "Download Error: %s Code: %ld URL: %s", + qcurl_easy_strerror(msg->data.result), + code, clc.downloadURL); + } + + CL_NextDownload(); +} +#endif /* USE_CURL */ diff --git a/src/client/cl_curl.h b/src/client/cl_curl.h new file mode 100644 index 0000000..7333ae2 --- /dev/null +++ b/src/client/cl_curl.h @@ -0,0 +1,103 @@ +/* +=========================================================================== +Copyright (C) 2006 Tony J. White (tjw@tjw.org) +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#ifndef __QCURL_H__ +#define __QCURL_H__ + +extern cvar_t *cl_cURLLib; + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +#ifdef WIN32 +#define DEFAULT_CURL_LIB "libcurl-3.dll" +#elif defined(MACOS_X) +#define DEFAULT_CURL_LIB "libcurl.dylib" +#else +#define DEFAULT_CURL_LIB "libcurl.so.4" +#define ALTERNATE_CURL_LIB "libcurl.so.3" +#endif + +#ifdef USE_LOCAL_HEADERS + #include "../libcurl/curl/curl.h" +#else + #include +#endif + + +#ifdef USE_CURL_DLOPEN +extern char* (*qcurl_version)(void); + +extern CURL* (*qcurl_easy_init)(void); +extern CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...); +extern CURLcode (*qcurl_easy_perform)(CURL *curl); +extern void (*qcurl_easy_cleanup)(CURL *curl); +extern CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...); +extern void (*qcurl_easy_reset)(CURL *curl); +extern const char *(*qcurl_easy_strerror)(CURLcode); + +extern CURLM* (*qcurl_multi_init)(void); +extern CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, + CURL *curl_handle); +extern CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, + CURL *curl_handle); +extern CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *exc_fd_set, + int *max_fd); +extern CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, + int *running_handles); +extern CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle); +extern CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, + int *msgs_in_queue); +extern const char *(*qcurl_multi_strerror)(CURLMcode); +#else +#define qcurl_version curl_version + +#define qcurl_easy_init curl_easy_init +#define qcurl_easy_setopt curl_easy_setopt +#define qcurl_easy_perform curl_easy_perform +#define qcurl_easy_cleanup curl_easy_cleanup +#define qcurl_easy_getinfo curl_easy_getinfo +#define qcurl_easy_duphandle curl_easy_duphandle +#define qcurl_easy_reset curl_easy_reset +#define qcurl_easy_strerror curl_easy_strerror + +#define qcurl_multi_init curl_multi_init +#define qcurl_multi_add_handle curl_multi_add_handle +#define qcurl_multi_remove_handle curl_multi_remove_handle +#define qcurl_multi_fdset curl_multi_fdset +#define qcurl_multi_perform curl_multi_perform +#define qcurl_multi_cleanup curl_multi_cleanup +#define qcurl_multi_info_read curl_multi_info_read +#define qcurl_multi_strerror curl_multi_strerror +#endif + +qboolean CL_cURL_Init( void ); +void CL_cURL_Shutdown( void ); +void CL_cURL_BeginDownload( const char *localName, const char *remoteURL ); +void CL_cURL_PerformDownload( void ); +void CL_cURL_Cleanup( void ); +#endif // __QCURL_H__ diff --git a/src/client/cl_input.c b/src/client/cl_input.c new file mode 100644 index 0000000..ee5b478 --- /dev/null +++ b/src/client/cl_input.c @@ -0,0 +1,1010 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl.input.c -- builds an intended movement command to send to the server + +#include "client.h" + +unsigned frame_msec; +int old_com_frameTime; + +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as argv(1) so it can be matched up with the release. + +argv(2) will be set to the time the event happened, which allows exact +control even at low framerates when the down and up events may both get qued +at the same time. + +=============================================================================== +*/ + + +kbutton_t in_left, in_right, in_forward, in_back; +kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; +kbutton_t in_strafe, in_speed; +kbutton_t in_up, in_down; + +#ifdef USE_VOIP +kbutton_t in_voiprecord; +#endif + +kbutton_t in_buttons[16]; + + +qboolean in_mlooking; + + +void IN_MLookDown( void ) { + in_mlooking = qtrue; +} + +void IN_MLookUp( void ) { + in_mlooking = qfalse; + if ( !cl_freelook->integer ) { + IN_CenterView (); + } +} + +void IN_KeyDown( kbutton_t *b ) { + int k; + char *c; + + c = Cmd_Argv(1); + if ( c[0] ) { + k = atoi(c); + } else { + k = -1; // typed manually at the console for continuous down + } + + if ( k == b->down[0] || k == b->down[1] ) { + return; // repeating key + } + + if ( !b->down[0] ) { + b->down[0] = k; + } else if ( !b->down[1] ) { + b->down[1] = k; + } else { + Com_Printf ("Three keys down for a button!\n"); + return; + } + + if ( b->active ) { + return; // still down + } + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + b->downtime = atoi(c); + + b->active = qtrue; + b->wasPressed = qtrue; +} + +void IN_KeyUp( kbutton_t *b ) { + int k; + char *c; + unsigned uptime; + + c = Cmd_Argv(1); + if ( c[0] ) { + k = atoi(c); + } else { + // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->active = qfalse; + return; + } + + if ( b->down[0] == k ) { + b->down[0] = 0; + } else if ( b->down[1] == k ) { + b->down[1] = 0; + } else { + return; // key up without coresponding down (menu pass through) + } + if ( b->down[0] || b->down[1] ) { + return; // some other key is still holding it down + } + + b->active = qfalse; + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + uptime = atoi(c); + if ( uptime ) { + b->msec += uptime - b->downtime; + } else { + b->msec += frame_msec / 2; + } + + b->active = qfalse; +} + + + +/* +=============== +CL_KeyState + +Returns the fraction of the frame that the key was down +=============== +*/ +float CL_KeyState( kbutton_t *key ) { + float val; + int msec; + + msec = key->msec; + key->msec = 0; + + if ( key->active ) { + // still down + if ( !key->downtime ) { + msec = com_frameTime; + } else { + msec += com_frameTime - key->downtime; + } + key->downtime = com_frameTime; + } + +#if 0 + if (msec) { + Com_Printf ("%i ", msec); + } +#endif + + val = (float)msec / frame_msec; + if ( val < 0 ) { + val = 0; + } + if ( val > 1 ) { + val = 1; + } + + return val; +} + + + +void IN_UpDown(void) {IN_KeyDown(&in_up);} +void IN_UpUp(void) {IN_KeyUp(&in_up);} +void IN_DownDown(void) {IN_KeyDown(&in_down);} +void IN_DownUp(void) {IN_KeyUp(&in_down);} +void IN_LeftDown(void) {IN_KeyDown(&in_left);} +void IN_LeftUp(void) {IN_KeyUp(&in_left);} +void IN_RightDown(void) {IN_KeyDown(&in_right);} +void IN_RightUp(void) {IN_KeyUp(&in_right);} +void IN_ForwardDown(void) {IN_KeyDown(&in_forward);} +void IN_ForwardUp(void) {IN_KeyUp(&in_forward);} +void IN_BackDown(void) {IN_KeyDown(&in_back);} +void IN_BackUp(void) {IN_KeyUp(&in_back);} +void IN_LookupDown(void) {IN_KeyDown(&in_lookup);} +void IN_LookupUp(void) {IN_KeyUp(&in_lookup);} +void IN_LookdownDown(void) {IN_KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {IN_KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) {IN_KeyDown(&in_moveleft);} +void IN_MoveleftUp(void) {IN_KeyUp(&in_moveleft);} +void IN_MoverightDown(void) {IN_KeyDown(&in_moveright);} +void IN_MoverightUp(void) {IN_KeyUp(&in_moveright);} + +void IN_SpeedDown(void) {IN_KeyDown(&in_speed);} +void IN_SpeedUp(void) {IN_KeyUp(&in_speed);} +void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);} +void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);} + +#ifdef USE_VOIP +void IN_VoipRecordDown(void) +{ + IN_KeyDown(&in_voiprecord); + Cvar_Set("cl_voipSend", "1"); +} + +void IN_VoipRecordUp(void) +{ + IN_KeyUp(&in_voiprecord); + Cvar_Set("cl_voipSend", "0"); +} +#endif + +void IN_Button0Down(void) {IN_KeyDown(&in_buttons[0]);} +void IN_Button0Up(void) {IN_KeyUp(&in_buttons[0]);} +void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);} +void IN_Button1Up(void) {IN_KeyUp(&in_buttons[1]);} +void IN_Button2Down(void) {IN_KeyDown(&in_buttons[2]);} +void IN_Button2Up(void) {IN_KeyUp(&in_buttons[2]);} +void IN_Button3Down(void) {IN_KeyDown(&in_buttons[3]);} +void IN_Button3Up(void) {IN_KeyUp(&in_buttons[3]);} +void IN_Button4Down(void) {IN_KeyDown(&in_buttons[4]);} +void IN_Button4Up(void) {IN_KeyUp(&in_buttons[4]);} +void IN_Button5Down(void) {IN_KeyDown(&in_buttons[5]);} +void IN_Button5Up(void) {IN_KeyUp(&in_buttons[5]);} +void IN_Button6Down(void) {IN_KeyDown(&in_buttons[6]);} +void IN_Button6Up(void) {IN_KeyUp(&in_buttons[6]);} +void IN_Button7Down(void) {IN_KeyDown(&in_buttons[7]);} +void IN_Button7Up(void) {IN_KeyUp(&in_buttons[7]);} +void IN_Button8Down(void) {IN_KeyDown(&in_buttons[8]);} +void IN_Button8Up(void) {IN_KeyUp(&in_buttons[8]);} +void IN_Button9Down(void) {IN_KeyDown(&in_buttons[9]);} +void IN_Button9Up(void) {IN_KeyUp(&in_buttons[9]);} +void IN_Button10Down(void) {IN_KeyDown(&in_buttons[10]);} +void IN_Button10Up(void) {IN_KeyUp(&in_buttons[10]);} +void IN_Button11Down(void) {IN_KeyDown(&in_buttons[11]);} +void IN_Button11Up(void) {IN_KeyUp(&in_buttons[11]);} +void IN_Button12Down(void) {IN_KeyDown(&in_buttons[12]);} +void IN_Button12Up(void) {IN_KeyUp(&in_buttons[12]);} +void IN_Button13Down(void) {IN_KeyDown(&in_buttons[13]);} +void IN_Button13Up(void) {IN_KeyUp(&in_buttons[13]);} +void IN_Button14Down(void) {IN_KeyDown(&in_buttons[14]);} +void IN_Button14Up(void) {IN_KeyUp(&in_buttons[14]);} +void IN_Button15Down(void) {IN_KeyDown(&in_buttons[15]);} +void IN_Button15Up(void) {IN_KeyUp(&in_buttons[15]);} + +void IN_ButtonDown (void) { + IN_KeyDown(&in_buttons[1]);} +void IN_ButtonUp (void) { + IN_KeyUp(&in_buttons[1]);} + +void IN_CenterView (void) { + cl.viewangles[PITCH] = -SHORT2ANGLE(cl.snap.ps.delta_angles[PITCH]); +} + + +//========================================================================== + +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_sidespeed; + +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; + +cvar_t *cl_run; + +cvar_t *cl_anglespeedkey; + + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles( void ) { + float speed; + + if ( in_speed.active ) { + speed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + speed = 0.001 * cls.frametime; + } + + if ( !in_strafe.active ) { + cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + } + + cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_lookup); + cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown); +} + +/* +================ +CL_KeyMove + +Sets the usercmd_t based on key states +================ +*/ +void CL_KeyMove( usercmd_t *cmd ) { + int movespeed; + int forward, side, up; + + // + // adjust for speed key / running + // the walking flag is to keep animations consistant + // even during acceleration and develeration + // + if ( in_speed.active ^ cl_run->integer ) { + movespeed = 127; + cmd->buttons &= ~BUTTON_WALKING; + } else { + cmd->buttons |= BUTTON_WALKING; + movespeed = 64; + } + + forward = 0; + side = 0; + up = 0; + if ( in_strafe.active ) { + side += movespeed * CL_KeyState (&in_right); + side -= movespeed * CL_KeyState (&in_left); + } + + side += movespeed * CL_KeyState (&in_moveright); + side -= movespeed * CL_KeyState (&in_moveleft); + + + up += movespeed * CL_KeyState (&in_up); + up -= movespeed * CL_KeyState (&in_down); + + forward += movespeed * CL_KeyState (&in_forward); + forward -= movespeed * CL_KeyState (&in_back); + + cmd->forwardmove = ClampChar( forward ); + cmd->rightmove = ClampChar( side ); + cmd->upmove = ClampChar( up ); +} + +/* +================= +CL_MouseEvent +================= +*/ +void CL_MouseEvent( int dx, int dy, int time ) { + if ( Key_GetCatcher( ) & KEYCATCH_UI ) { + VM_Call( uivm, UI_MOUSE_EVENT, dx, dy ); + } else if (Key_GetCatcher( ) & KEYCATCH_CGAME) { + VM_Call (cgvm, CG_MOUSE_EVENT, dx, dy); + } else { + cl.mouseDx[cl.mouseIndex] += dx; + cl.mouseDy[cl.mouseIndex] += dy; + } +} + +/* +================= +CL_JoystickEvent + +Joystick values stay set until changed +================= +*/ +void CL_JoystickEvent( int axis, int value, int time ) { + if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) { + Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis ); + } + cl.joystickAxis[axis] = value; +} + +/* +================= +CL_JoystickMove +================= +*/ +void CL_JoystickMove( usercmd_t *cmd ) { + int movespeed; + float anglespeed; + + if ( in_speed.active ^ cl_run->integer ) { + movespeed = 2; + } else { + movespeed = 1; + cmd->buttons |= BUTTON_WALKING; + } + + if ( in_speed.active ) { + anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + anglespeed = 0.001 * cls.frametime; + } + + if ( !in_strafe.active ) { + cl.viewangles[YAW] += anglespeed * j_yaw->value * cl.joystickAxis[j_yaw_axis->integer]; + cmd->rightmove = ClampChar( cmd->rightmove + (int) (j_side->value * cl.joystickAxis[j_side_axis->integer]) ); + } else { + cl.viewangles[YAW] += anglespeed * j_side->value * cl.joystickAxis[j_side_axis->integer]; + cmd->rightmove = ClampChar( cmd->rightmove + (int) (j_yaw->value * cl.joystickAxis[j_yaw_axis->integer]) ); + } + + if ( in_mlooking ) { + cl.viewangles[PITCH] += anglespeed * j_forward->value * cl.joystickAxis[j_forward_axis->integer]; + cmd->forwardmove = ClampChar( cmd->forwardmove + (int) (j_pitch->value * cl.joystickAxis[j_pitch_axis->integer]) ); + } else { + cl.viewangles[PITCH] += anglespeed * j_pitch->value * cl.joystickAxis[j_pitch_axis->integer]; + cmd->forwardmove = ClampChar( cmd->forwardmove + (int) (j_forward->value * cl.joystickAxis[j_forward_axis->integer]) ); + } + + cmd->upmove = ClampChar( cmd->upmove + cl.joystickAxis[AXIS_UP] ); +} + +/* +================= +CL_MouseMove +================= +*/ + +void CL_MouseMove(usercmd_t *cmd) +{ + float mx, my; + + // allow mouse smoothing + if (m_filter->integer) + { + mx = (cl.mouseDx[0] + cl.mouseDx[1]) * 0.5f; + my = (cl.mouseDy[0] + cl.mouseDy[1]) * 0.5f; + } + else + { + mx = cl.mouseDx[cl.mouseIndex]; + my = cl.mouseDy[cl.mouseIndex]; + } + + cl.mouseIndex ^= 1; + cl.mouseDx[cl.mouseIndex] = 0; + cl.mouseDy[cl.mouseIndex] = 0; + + if (mx == 0.0f && my == 0.0f) + return; + + if (cl_mouseAccel->value != 0.0f) + { + if(cl_mouseAccelStyle->integer == 0) + { + float accelSensitivity; + float rate; + + rate = sqrt(mx * mx + my * my) / (float) frame_msec; + + accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; + mx *= accelSensitivity; + my *= accelSensitivity; + + if(cl_showMouseRate->integer) + Com_Printf("rate: %f, accelSensitivity: %f\n", rate, accelSensitivity); + } + else + { + float rate[2]; + float power[2]; + + // sensitivity remains pretty much unchanged at low speeds + // cl_mouseAccel is a power value to how the acceleration is shaped + // cl_mouseAccelOffset is the rate for which the acceleration will have doubled the non accelerated amplification + // NOTE: decouple the config cvars for independent acceleration setup along X and Y? + + rate[0] = fabs(mx) / (float) frame_msec; + rate[1] = fabs(my) / (float) frame_msec; + power[0] = powf(rate[0] / cl_mouseAccelOffset->value, cl_mouseAccel->value); + power[1] = powf(rate[1] / cl_mouseAccelOffset->value, cl_mouseAccel->value); + + mx = cl_sensitivity->value * (mx + ((mx < 0) ? -power[0] : power[0]) * cl_mouseAccelOffset->value); + my = cl_sensitivity->value * (my + ((my < 0) ? -power[1] : power[1]) * cl_mouseAccelOffset->value); + + if(cl_showMouseRate->integer) + Com_Printf("ratex: %f, ratey: %f, powx: %f, powy: %f\n", rate[0], rate[1], power[0], power[1]); + } + } + else + { + mx *= cl_sensitivity->value; + my *= cl_sensitivity->value; + } + + // ingame FOV + mx *= cl.cgameSensitivity; + my *= cl.cgameSensitivity; + + // add mouse X/Y movement to cmd + if(in_strafe.active) + cmd->rightmove = ClampChar(cmd->rightmove + m_side->value * mx); + else + cl.viewangles[YAW] -= m_yaw->value * mx; + + if ((in_mlooking || cl_freelook->integer) && !in_strafe.active) + cl.viewangles[PITCH] += m_pitch->value * my; + else + cmd->forwardmove = ClampChar(cmd->forwardmove - m_forward->value * my); +} + + +/* +============== +CL_CmdButtons +============== +*/ +void CL_CmdButtons( usercmd_t *cmd ) { + int i; + + // + // figure button bits + // send a button bit even if the key was pressed and released in + // less than a frame + // + for (i = 0 ; i < 15 ; i++) { + if ( in_buttons[i].active || in_buttons[i].wasPressed ) { + cmd->buttons |= 1 << i; + } + in_buttons[i].wasPressed = qfalse; + } + + if ( Key_GetCatcher( ) ) { + cmd->buttons |= BUTTON_TALK; + } + + // allow the game to know if any key at all is + // currently pressed, even if it isn't bound to anything + if ( anykeydown && Key_GetCatcher( ) == 0 ) { + cmd->buttons |= BUTTON_ANY; + } +} + + +/* +============== +CL_FinishMove +============== +*/ +void CL_FinishMove( usercmd_t *cmd ) { + int i; + + // copy the state that the cgame is currently sending + cmd->weapon = cl.cgameUserCmdValue; + + // send the current server time so the amount of movement + // can be determined without allowing cheating + cmd->serverTime = cl.serverTime; + + for (i=0 ; i<3 ; i++) { + cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); + } +} + + +/* +================= +CL_CreateCmd +================= +*/ +usercmd_t CL_CreateCmd( void ) { + usercmd_t cmd; + vec3_t oldAngles; + + VectorCopy( cl.viewangles, oldAngles ); + + // keyboard angle adjustment + CL_AdjustAngles (); + + Com_Memset( &cmd, 0, sizeof( cmd ) ); + + CL_CmdButtons( &cmd ); + + // get basic movement from keyboard + CL_KeyMove( &cmd ); + + // get basic movement from mouse + CL_MouseMove( &cmd ); + + // get basic movement from joystick + CL_JoystickMove( &cmd ); + + // check to make sure the angles haven't wrapped + if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) { + cl.viewangles[PITCH] = oldAngles[PITCH] + 90; + } else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) { + cl.viewangles[PITCH] = oldAngles[PITCH] - 90; + } + + // store out the final values + CL_FinishMove( &cmd ); + + // draw debug graphs of turning for mouse testing + if ( cl_debugMove->integer ) { + if ( cl_debugMove->integer == 1 ) { + SCR_DebugGraph( abs(cl.viewangles[YAW] - oldAngles[YAW]), 0 ); + } + if ( cl_debugMove->integer == 2 ) { + SCR_DebugGraph( abs(cl.viewangles[PITCH] - oldAngles[PITCH]), 0 ); + } + } + + return cmd; +} + + +/* +================= +CL_CreateNewCommands + +Create a new usercmd_t structure for this frame +================= +*/ +void CL_CreateNewCommands( void ) { + usercmd_t *cmd; + int cmdNum; + + // no need to create usercmds until we have a gamestate + if ( cls.state < CA_PRIMED ) { + return; + } + + frame_msec = com_frameTime - old_com_frameTime; + + // if running less than 5fps, truncate the extra time to prevent + // unexpected moves after a hitch + if ( frame_msec > 200 ) { + frame_msec = 200; + } + old_com_frameTime = com_frameTime; + + + // generate a command for this frame + cl.cmdNumber++; + cmdNum = cl.cmdNumber & CMD_MASK; + cl.cmds[cmdNum] = CL_CreateCmd (); + cmd = &cl.cmds[cmdNum]; +} + +/* +================= +CL_ReadyToSendPacket + +Returns qfalse if we are over the maxpackets limit +and should choke back the bandwidth a bit by not sending +a packet this frame. All the commands will still get +delivered in the next packet, but saving a header and +getting more delta compression will reduce total bandwidth. +================= +*/ +qboolean CL_ReadyToSendPacket( void ) { + int oldPacketNum; + int delta; + + // don't send anything if playing back a demo + if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { + return qfalse; + } + + // If we are downloading, we send no less than 50ms between packets + if ( *clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 50 ) { + return qfalse; + } + + // if we don't have a valid gamestate yet, only send + // one packet a second + if ( cls.state != CA_ACTIVE && + cls.state != CA_PRIMED && + !*clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 1000 ) { + return qfalse; + } + + // send every frame for loopbacks + if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) { + return qtrue; + } + + // send every frame for LAN + if ( cl_lanForcePackets->integer && Sys_IsLANAddress( clc.netchan.remoteAddress ) ) { + return qtrue; + } + + // check for exceeding cl_maxpackets + if ( cl_maxpackets->integer < 15 ) { + Cvar_Set( "cl_maxpackets", "15" ); + } else if ( cl_maxpackets->integer > 125 ) { + Cvar_Set( "cl_maxpackets", "125" ); + } + oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK; + delta = cls.realtime - cl.outPackets[ oldPacketNum ].p_realtime; + if ( delta < 1000 / cl_maxpackets->integer ) { + // the accumulated commands will go out in the next packet + return qfalse; + } + + return qtrue; +} + +/* +=================== +CL_WritePacket + +Create and send the command packet to the server +Including both the reliable commands and the usercmds + +During normal gameplay, a client packet will contain something like: + +4 sequence number +2 qport +4 serverid +4 acknowledged sequence number +4 clc.serverCommandSequence + +1 clc_move or clc_moveNoDelta +1 command count + + +=================== +*/ +void CL_WritePacket( void ) { + msg_t buf; + byte data[MAX_MSGLEN]; + int i, j; + usercmd_t *cmd, *oldcmd; + usercmd_t nullcmd; + int packetNum; + int oldPacketNum; + int count, key; + + // don't send anything if playing back a demo + if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { + return; + } + + Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + + MSG_Init( &buf, data, sizeof(data) ); + + MSG_Bitstream( &buf ); + // write the current serverId so the server + // can tell if this is from the current gameState + MSG_WriteLong( &buf, cl.serverId ); + + // write the last message we received, which can + // be used for delta compression, and is also used + // to tell if we dropped a gamestate + MSG_WriteLong( &buf, clc.serverMessageSequence ); + + // write the last reliable message we received + MSG_WriteLong( &buf, clc.serverCommandSequence ); + + // write any unacknowledged clientCommands + for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { + MSG_WriteByte( &buf, clc_clientCommand ); + MSG_WriteLong( &buf, i ); + MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + + // we want to send all the usercmds that were generated in the last + // few packet, so even if a couple packets are dropped in a row, + // all the cmds will make it to the server + if ( cl_packetdup->integer < 0 ) { + Cvar_Set( "cl_packetdup", "0" ); + } else if ( cl_packetdup->integer > 5 ) { + Cvar_Set( "cl_packetdup", "5" ); + } + oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; + count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; + if ( count > MAX_PACKET_USERCMDS ) { + count = MAX_PACKET_USERCMDS; + Com_Printf("MAX_PACKET_USERCMDS\n"); + } + +#ifdef USE_VOIP + if (clc.voipOutgoingDataSize > 0) { // only send if data. + MSG_WriteByte (&buf, clc_EOF); // placate legacy servers. + MSG_WriteByte (&buf, clc_extension); + MSG_WriteByte (&buf, clc_voip); + MSG_WriteByte (&buf, clc.voipOutgoingGeneration); + MSG_WriteLong (&buf, clc.voipOutgoingSequence); + MSG_WriteByte (&buf, clc.voipOutgoingDataFrames); + MSG_WriteLong (&buf, clc.voipTarget1); + MSG_WriteLong (&buf, clc.voipTarget2); + MSG_WriteLong (&buf, clc.voipTarget3); + MSG_WriteShort (&buf, clc.voipOutgoingDataSize); + MSG_WriteData (&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize); + + // If we're recording a demo, we have to fake a server packet with + // this VoIP data so it gets to disk; the server doesn't send it + // back to us, and we might as well eliminate concerns about dropped + // and misordered packets here. + if ( clc.demorecording && !clc.demowaiting ) { + const int voipSize = clc.voipOutgoingDataSize; + msg_t fakemsg; + byte fakedata[MAX_MSGLEN]; + MSG_Init (&fakemsg, fakedata, sizeof (fakedata)); + MSG_Bitstream (&fakemsg); + MSG_WriteLong (&fakemsg, clc.reliableAcknowledge); + MSG_WriteByte (&fakemsg, svc_EOF); + MSG_WriteByte (&fakemsg, svc_extension); + MSG_WriteByte (&fakemsg, svc_voip); + MSG_WriteShort (&fakemsg, clc.clientNum); + MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration); + MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence); + MSG_WriteByte (&fakemsg, clc.voipOutgoingDataFrames); + MSG_WriteShort (&fakemsg, clc.voipOutgoingDataSize ); + MSG_WriteData (&fakemsg, clc.voipOutgoingData, voipSize); + MSG_WriteByte (&fakemsg, svc_EOF); + CL_WriteDemoMessage (&fakemsg, 0); + } + + clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; + clc.voipOutgoingDataSize = 0; + clc.voipOutgoingDataFrames = 0; + } else +#endif + + if ( count >= 1 ) { + if ( cl_showSend->integer ) { + Com_Printf( "(%i)", count ); + } + + // begin a client move command + if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting + || clc.serverMessageSequence != cl.snap.messageNum ) { + MSG_WriteByte (&buf, clc_moveNoDelta); + } else { + MSG_WriteByte (&buf, clc_move); + } + + // write the command count + MSG_WriteByte( &buf, count ); + + // use the checksum feed in the key + key = clc.checksumFeed; + // also use the message acknowledge + key ^= clc.serverMessageSequence; + // also use the last acknowledged server command in the key + key ^= MSG_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); + + // write all the commands, including the predicted command + for ( i = 0 ; i < count ; i++ ) { + j = (cl.cmdNumber - count + i + 1) & CMD_MASK; + cmd = &cl.cmds[j]; + MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); + oldcmd = cmd; + } + } + + // + // deliver the message + // + packetNum = clc.netchan.outgoingSequence & PACKET_MASK; + cl.outPackets[ packetNum ].p_realtime = cls.realtime; + cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; + cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; + clc.lastPacketSentTime = cls.realtime; + + if ( cl_showSend->integer ) { + Com_Printf( "%i ", buf.cursize ); + } + + CL_Netchan_Transmit (&clc.netchan, &buf); + + // clients never really should have messages large enough + // to fragment, but in case they do, fire them all off + // at once + // TTimo: this causes a packet burst, which is bad karma for winsock + // added a WARNING message, we'll see if there are legit situations where this happens + while ( clc.netchan.unsentFragments ) { + Com_DPrintf( "WARNING: #462 unsent fragments (not supposed to happen!)\n" ); + CL_Netchan_TransmitNextFragment( &clc.netchan ); + } +} + +/* +================= +CL_SendCmd + +Called every frame to builds and sends a command packet to the server. +================= +*/ +void CL_SendCmd( void ) { + // don't send any message if not connected + if ( cls.state < CA_CONNECTED ) { + return; + } + + // don't send commands if paused + if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer ) { + return; + } + + // we create commands even if a demo is playing, + CL_CreateNewCommands(); + + // don't send a packet if the last packet was sent too recently + if ( !CL_ReadyToSendPacket() ) { + if ( cl_showSend->integer ) { + Com_Printf( ". " ); + } + return; + } + + CL_WritePacket(); +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput( void ) { + Cmd_AddCommand ("centerview",IN_CenterView); + + Cmd_AddCommand ("+moveup",IN_UpDown); + Cmd_AddCommand ("-moveup",IN_UpUp); + Cmd_AddCommand ("+movedown",IN_DownDown); + Cmd_AddCommand ("-movedown",IN_DownUp); + Cmd_AddCommand ("+left",IN_LeftDown); + Cmd_AddCommand ("-left",IN_LeftUp); + Cmd_AddCommand ("+right",IN_RightDown); + Cmd_AddCommand ("-right",IN_RightUp); + Cmd_AddCommand ("+forward",IN_ForwardDown); + Cmd_AddCommand ("-forward",IN_ForwardUp); + Cmd_AddCommand ("+back",IN_BackDown); + Cmd_AddCommand ("-back",IN_BackUp); + Cmd_AddCommand ("+lookup", IN_LookupDown); + Cmd_AddCommand ("-lookup", IN_LookupUp); + Cmd_AddCommand ("+lookdown", IN_LookdownDown); + Cmd_AddCommand ("-lookdown", IN_LookdownUp); + Cmd_AddCommand ("+strafe", IN_StrafeDown); + Cmd_AddCommand ("-strafe", IN_StrafeUp); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp); + Cmd_AddCommand ("+moveright", IN_MoverightDown); + Cmd_AddCommand ("-moveright", IN_MoverightUp); + Cmd_AddCommand ("+speed", IN_SpeedDown); + Cmd_AddCommand ("-speed", IN_SpeedUp); + Cmd_AddCommand ("+attack", IN_Button0Down); + Cmd_AddCommand ("-attack", IN_Button0Up); + Cmd_AddCommand ("+button0", IN_Button0Down); + Cmd_AddCommand ("-button0", IN_Button0Up); + Cmd_AddCommand ("+button1", IN_Button1Down); + Cmd_AddCommand ("-button1", IN_Button1Up); + Cmd_AddCommand ("+button2", IN_Button2Down); + Cmd_AddCommand ("-button2", IN_Button2Up); + Cmd_AddCommand ("+button3", IN_Button3Down); + Cmd_AddCommand ("-button3", IN_Button3Up); + Cmd_AddCommand ("+button4", IN_Button4Down); + Cmd_AddCommand ("-button4", IN_Button4Up); + Cmd_AddCommand ("+button5", IN_Button5Down); + Cmd_AddCommand ("-button5", IN_Button5Up); + Cmd_AddCommand ("+button6", IN_Button6Down); + Cmd_AddCommand ("-button6", IN_Button6Up); + Cmd_AddCommand ("+button7", IN_Button7Down); + Cmd_AddCommand ("-button7", IN_Button7Up); + Cmd_AddCommand ("+button8", IN_Button8Down); + Cmd_AddCommand ("-button8", IN_Button8Up); + Cmd_AddCommand ("+button9", IN_Button9Down); + Cmd_AddCommand ("-button9", IN_Button9Up); + Cmd_AddCommand ("+button10", IN_Button10Down); + Cmd_AddCommand ("-button10", IN_Button10Up); + Cmd_AddCommand ("+button11", IN_Button11Down); + Cmd_AddCommand ("-button11", IN_Button11Up); + Cmd_AddCommand ("+button12", IN_Button12Down); + Cmd_AddCommand ("-button12", IN_Button12Up); + Cmd_AddCommand ("+button13", IN_Button13Down); + Cmd_AddCommand ("-button13", IN_Button13Up); + Cmd_AddCommand ("+button14", IN_Button14Down); + Cmd_AddCommand ("-button14", IN_Button14Up); + Cmd_AddCommand ("+mlook", IN_MLookDown); + Cmd_AddCommand ("-mlook", IN_MLookUp); + +#ifdef USE_VOIP + Cmd_AddCommand ("+voiprecord", IN_VoipRecordDown); + Cmd_AddCommand ("-voiprecord", IN_VoipRecordUp); +#endif + + cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0); + cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0); +} diff --git a/src/client/cl_keys.c b/src/client/cl_keys.c new file mode 100644 index 0000000..71569fe --- /dev/null +++ b/src/client/cl_keys.c @@ -0,0 +1,1490 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "client.h" + +/* + +key up events are sent even if in console mode + +*/ + +field_t historyEditLines[COMMAND_HISTORY]; + +int nextHistoryLine; // the last line in the history buffer, not masked +int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + +field_t g_consoleField; + + +qboolean key_overstrikeMode; + +int anykeydown; +qkey_t keys[MAX_KEYS]; + + +typedef struct { + char *name; + int keynum; +} keyname_t; + + +// names not in this list can either be lowercase ascii, or '0xnn' hex sequences +keyname_t keynames[] = +{ + {"TAB", K_TAB}, + {"ENTER", K_ENTER}, + {"ESCAPE", K_ESCAPE}, + {"SPACE", K_SPACE}, + {"BACKSPACE", K_BACKSPACE}, + {"UPARROW", K_UPARROW}, + {"DOWNARROW", K_DOWNARROW}, + {"LEFTARROW", K_LEFTARROW}, + {"RIGHTARROW", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"SHIFT", K_SHIFT}, + + {"COMMAND", K_COMMAND}, + + {"CAPSLOCK", K_CAPSLOCK}, + + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + {"F13", K_F13}, + {"F14", K_F14}, + {"F15", K_F15}, + + {"INS", K_INS}, + {"DEL", K_DEL}, + {"PGDN", K_PGDN}, + {"PGUP", K_PGUP}, + {"HOME", K_HOME}, + {"END", K_END}, + + {"MOUSE1", K_MOUSE1}, + {"MOUSE2", K_MOUSE2}, + {"MOUSE3", K_MOUSE3}, + {"MOUSE4", K_MOUSE4}, + {"MOUSE5", K_MOUSE5}, + + {"MWHEELUP", K_MWHEELUP }, + {"MWHEELDOWN", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + + {"KP_HOME", K_KP_HOME }, + {"KP_UPARROW", K_KP_UPARROW }, + {"KP_PGUP", K_KP_PGUP }, + {"KP_LEFTARROW", K_KP_LEFTARROW }, + {"KP_5", K_KP_5 }, + {"KP_RIGHTARROW", K_KP_RIGHTARROW }, + {"KP_END", K_KP_END }, + {"KP_DOWNARROW", K_KP_DOWNARROW }, + {"KP_PGDN", K_KP_PGDN }, + {"KP_ENTER", K_KP_ENTER }, + {"KP_INS", K_KP_INS }, + {"KP_DEL", K_KP_DEL }, + {"KP_SLASH", K_KP_SLASH }, + {"KP_MINUS", K_KP_MINUS }, + {"KP_PLUS", K_KP_PLUS }, + {"KP_NUMLOCK", K_KP_NUMLOCK }, + {"KP_STAR", K_KP_STAR }, + {"KP_EQUALS", K_KP_EQUALS }, + + {"PAUSE", K_PAUSE}, + + {"SEMICOLON", ';'}, // because a raw semicolon seperates commands + + {"WORLD_0", K_WORLD_0}, + {"WORLD_1", K_WORLD_1}, + {"WORLD_2", K_WORLD_2}, + {"WORLD_3", K_WORLD_3}, + {"WORLD_4", K_WORLD_4}, + {"WORLD_5", K_WORLD_5}, + {"WORLD_6", K_WORLD_6}, + {"WORLD_7", K_WORLD_7}, + {"WORLD_8", K_WORLD_8}, + {"WORLD_9", K_WORLD_9}, + {"WORLD_10", K_WORLD_10}, + {"WORLD_11", K_WORLD_11}, + {"WORLD_12", K_WORLD_12}, + {"WORLD_13", K_WORLD_13}, + {"WORLD_14", K_WORLD_14}, + {"WORLD_15", K_WORLD_15}, + {"WORLD_16", K_WORLD_16}, + {"WORLD_17", K_WORLD_17}, + {"WORLD_18", K_WORLD_18}, + {"WORLD_19", K_WORLD_19}, + {"WORLD_20", K_WORLD_20}, + {"WORLD_21", K_WORLD_21}, + {"WORLD_22", K_WORLD_22}, + {"WORLD_23", K_WORLD_23}, + {"WORLD_24", K_WORLD_24}, + {"WORLD_25", K_WORLD_25}, + {"WORLD_26", K_WORLD_26}, + {"WORLD_27", K_WORLD_27}, + {"WORLD_28", K_WORLD_28}, + {"WORLD_29", K_WORLD_29}, + {"WORLD_30", K_WORLD_30}, + {"WORLD_31", K_WORLD_31}, + {"WORLD_32", K_WORLD_32}, + {"WORLD_33", K_WORLD_33}, + {"WORLD_34", K_WORLD_34}, + {"WORLD_35", K_WORLD_35}, + {"WORLD_36", K_WORLD_36}, + {"WORLD_37", K_WORLD_37}, + {"WORLD_38", K_WORLD_38}, + {"WORLD_39", K_WORLD_39}, + {"WORLD_40", K_WORLD_40}, + {"WORLD_41", K_WORLD_41}, + {"WORLD_42", K_WORLD_42}, + {"WORLD_43", K_WORLD_43}, + {"WORLD_44", K_WORLD_44}, + {"WORLD_45", K_WORLD_45}, + {"WORLD_46", K_WORLD_46}, + {"WORLD_47", K_WORLD_47}, + {"WORLD_48", K_WORLD_48}, + {"WORLD_49", K_WORLD_49}, + {"WORLD_50", K_WORLD_50}, + {"WORLD_51", K_WORLD_51}, + {"WORLD_52", K_WORLD_52}, + {"WORLD_53", K_WORLD_53}, + {"WORLD_54", K_WORLD_54}, + {"WORLD_55", K_WORLD_55}, + {"WORLD_56", K_WORLD_56}, + {"WORLD_57", K_WORLD_57}, + {"WORLD_58", K_WORLD_58}, + {"WORLD_59", K_WORLD_59}, + {"WORLD_60", K_WORLD_60}, + {"WORLD_61", K_WORLD_61}, + {"WORLD_62", K_WORLD_62}, + {"WORLD_63", K_WORLD_63}, + {"WORLD_64", K_WORLD_64}, + {"WORLD_65", K_WORLD_65}, + {"WORLD_66", K_WORLD_66}, + {"WORLD_67", K_WORLD_67}, + {"WORLD_68", K_WORLD_68}, + {"WORLD_69", K_WORLD_69}, + {"WORLD_70", K_WORLD_70}, + {"WORLD_71", K_WORLD_71}, + {"WORLD_72", K_WORLD_72}, + {"WORLD_73", K_WORLD_73}, + {"WORLD_74", K_WORLD_74}, + {"WORLD_75", K_WORLD_75}, + {"WORLD_76", K_WORLD_76}, + {"WORLD_77", K_WORLD_77}, + {"WORLD_78", K_WORLD_78}, + {"WORLD_79", K_WORLD_79}, + {"WORLD_80", K_WORLD_80}, + {"WORLD_81", K_WORLD_81}, + {"WORLD_82", K_WORLD_82}, + {"WORLD_83", K_WORLD_83}, + {"WORLD_84", K_WORLD_84}, + {"WORLD_85", K_WORLD_85}, + {"WORLD_86", K_WORLD_86}, + {"WORLD_87", K_WORLD_87}, + {"WORLD_88", K_WORLD_88}, + {"WORLD_89", K_WORLD_89}, + {"WORLD_90", K_WORLD_90}, + {"WORLD_91", K_WORLD_91}, + {"WORLD_92", K_WORLD_92}, + {"WORLD_93", K_WORLD_93}, + {"WORLD_94", K_WORLD_94}, + {"WORLD_95", K_WORLD_95}, + + {"WINDOWS", K_SUPER}, + {"COMPOSE", K_COMPOSE}, + {"MODE", K_MODE}, + {"HELP", K_HELP}, + {"PRINT", K_PRINT}, + {"SYSREQ", K_SYSREQ}, + {"SCROLLOCK", K_SCROLLOCK }, + {"BREAK", K_BREAK}, + {"MENU", K_MENU}, + {"POWER", K_POWER}, + {"EURO", K_EURO}, + {"UNDO", K_UNDO}, + + {NULL,0} +}; + +/* +============================================================================= + +EDIT FIELDS + +============================================================================= +*/ + + +/* +=================== +Field_Draw + +Handles horizontal scrolling and cursor blinking +x, y, and width are in pixels +=================== +*/ +void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor, + qboolean noColorEscape ) { + int len; + int drawLen; + int prestep; + int cursorChar; + char str[MAX_STRING_CHARS]; + int i; + + drawLen = edit->widthInChars - 1; // - 1 so there is always a space for the cursor + len = strlen( edit->buffer ); + + // guarantee that cursor will be visible + if ( len <= drawLen ) { + prestep = 0; + } else { + if ( edit->scroll + drawLen > len ) { + edit->scroll = len - drawLen; + if ( edit->scroll < 0 ) { + edit->scroll = 0; + } + } + prestep = edit->scroll; + } + + if ( prestep + drawLen > len ) { + drawLen = len - prestep; + } + + // extract characters from the field at + if ( drawLen >= MAX_STRING_CHARS ) { + Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" ); + } + + Com_Memcpy( str, edit->buffer + prestep, drawLen ); + str[ drawLen ] = 0; + + // draw it + if ( size == SMALLCHAR_WIDTH ) { + float color[4]; + + color[0] = color[1] = color[2] = color[3] = 1.0; + SCR_DrawSmallStringExt( x, y, str, color, qfalse, noColorEscape ); + } else { + // draw big string with drop shadow + SCR_DrawBigString( x, y, str, 1.0, noColorEscape ); + } + + // draw the cursor + if ( showCursor ) { + if ( (int)( cls.realtime >> 8 ) & 1 ) { + return; // off blink + } + + if ( key_overstrikeMode ) { + cursorChar = 11; + } else { + cursorChar = 10; + } + + i = drawLen - strlen( str ); + + if ( size == SMALLCHAR_WIDTH ) { + SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar ); + } else { + str[0] = cursorChar; + str[1] = 0; + SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0, qfalse ); + + } + } +} + +void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ) +{ + Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor, noColorEscape ); +} + +void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ) +{ + Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor, noColorEscape ); +} + +/* +================ +Field_Paste +================ +*/ +void Field_Paste( field_t *edit ) { + char *cbd; + int pasteLen, i; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + return; + } + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( cbd ); + for ( i = 0 ; i < pasteLen ; i++ ) { + Field_CharEvent( edit, cbd[i] ); + } + + Z_Free( cbd ); +} + +/* +================= +Field_KeyDownEvent + +Performs the basic line editing functions for the console, +in-game talk, and menu fields + +Key events are used for non-printable characters, others are gotten from char events. +================= +*/ +void Field_KeyDownEvent( field_t *edit, int key ) { + int len; + + // shift-insert is paste + if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) { + Field_Paste( edit ); + return; + } + + key = tolower( key ); + len = strlen( edit->buffer ); + + switch ( key ) { + case K_DEL: + if ( edit->cursor < len ) { + memmove( edit->buffer + edit->cursor, + edit->buffer + edit->cursor + 1, len - edit->cursor ); + } + break; + + case K_RIGHTARROW: + if ( edit->cursor < len ) { + edit->cursor++; + } + break; + + case K_LEFTARROW: + if ( edit->cursor > 0 ) { + edit->cursor--; + } + break; + + case K_HOME: + edit->cursor = 0; + break; + + case K_END: + edit->cursor = len; + break; + + case K_INS: + key_overstrikeMode = !key_overstrikeMode; + break; + + default: + break; + } + + // Change scroll if cursor is no longer visible + if ( edit->cursor < edit->scroll ) { + edit->scroll = edit->cursor; + } else if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) { + edit->scroll = edit->cursor - edit->widthInChars + 1; + } +} + +/* +================== +Field_CharEvent +================== +*/ +void Field_CharEvent( field_t *edit, int ch ) { + int len; + + if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste + Field_Paste( edit ); + return; + } + + if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field + Field_Clear( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace + if ( edit->cursor > 0 ) { + memmove( edit->buffer + edit->cursor - 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->cursor--; + if ( edit->cursor < edit->scroll ) + { + edit->scroll--; + } + } + return; + } + + if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home + edit->cursor = 0; + edit->scroll = 0; + return; + } + + if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end + edit->cursor = len; + edit->scroll = edit->cursor - edit->widthInChars; + return; + } + + // + // ignore any other non printable chars + // + if ( ch < 32 ) { + return; + } + + if ( key_overstrikeMode ) { + // - 2 to leave room for the leading slash and trailing \0 + if ( edit->cursor == MAX_EDIT_LINE - 2 ) + return; + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } else { // insert mode + // - 2 to leave room for the leading slash and trailing \0 + if ( len == MAX_EDIT_LINE - 2 ) { + return; // all full + } + memmove( edit->buffer + edit->cursor + 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } + + + if ( edit->cursor >= edit->widthInChars ) { + edit->scroll++; + } + + if ( edit->cursor == len + 1) { + edit->buffer[edit->cursor] = 0; + } +} + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ + +/* +==================== +Console_Key + +Handles history and console scrollback +==================== +*/ +void Console_Key (int key) { + // ctrl-L clears screen + if ( key == 'l' && keys[K_CTRL].down ) { + Cbuf_AddText ("clear\n"); + return; + } + + // enter finishes the line + if ( key == K_ENTER || key == K_KP_ENTER ) { + // if not in the game explicitly prepend a slash if needed + if ( cls.state != CA_ACTIVE && + g_consoleField.buffer[0] && + g_consoleField.buffer[0] != '\\' && + g_consoleField.buffer[0] != '/' ) { + char temp[MAX_EDIT_LINE-1]; + + Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) ); + Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp ); + g_consoleField.cursor++; + } + + Com_Printf ( "]%s\n", g_consoleField.buffer ); + + // leading slash is an explicit command + if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) { + Cbuf_AddText( g_consoleField.buffer+1 ); // valid command + Cbuf_AddText ("\n"); + } else { + // other text will be chat messages + if ( !g_consoleField.buffer[0] ) { + return; // empty lines just scroll the console without adding to history + } else { + Cbuf_AddText ("cmd say "); + Cbuf_AddText( g_consoleField.buffer ); + Cbuf_AddText ("\n"); + } + } + + // copy line to history buffer + historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField; + nextHistoryLine++; + historyLine = nextHistoryLine; + + Field_Clear( &g_consoleField ); + + g_consoleField.widthInChars = g_console_field_width; + + CL_SaveConsoleHistory( ); + + if ( cls.state == CA_DISCONNECTED ) { + SCR_UpdateScreen (); // force an update, because the command + } // may take some time + return; + } + + // command completion + + if (key == K_TAB) { + Field_AutoComplete(&g_consoleField); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + + if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || + ( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) { + if ( nextHistoryLine - historyLine < COMMAND_HISTORY + && historyLine > 0 ) { + historyLine--; + } + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || + ( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) { + historyLine++; + if (historyLine >= nextHistoryLine) { + historyLine = nextHistoryLine; + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + return; + } + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + // console scrolling + if ( key == K_PGUP ) { + Con_PageUp(); + return; + } + + if ( key == K_PGDN) { + Con_PageDown(); + return; + } + + if ( key == K_MWHEELUP) { //----(SA) added some mousewheel functionality to the console + Con_PageUp(); + if(keys[K_CTRL].down) { // hold to accelerate scrolling + Con_PageUp(); + Con_PageUp(); + } + return; + } + + if ( key == K_MWHEELDOWN) { //----(SA) added some mousewheel functionality to the console + Con_PageDown(); + if(keys[K_CTRL].down) { // hold to accelerate scrolling + Con_PageDown(); + Con_PageDown(); + } + return; + } + + // ctrl-home = top of console + if ( key == K_HOME && keys[K_CTRL].down ) { + Con_Top(); + return; + } + + // ctrl-end = bottom of console + if ( key == K_END && keys[K_CTRL].down ) { + Con_Bottom(); + return; + } + + // pass to the normal editline routine + Field_KeyDownEvent( &g_consoleField, key ); +} + + +//============================================================================ + + +qboolean Key_GetOverstrikeMode( void ) { + return key_overstrikeMode; +} + + +void Key_SetOverstrikeMode( qboolean state ) { + key_overstrikeMode = state; +} + + +/* +=================== +Key_IsDown +=================== +*/ +qboolean Key_IsDown( int keynum ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return qfalse; + } + + return keys[keynum].down; +} + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keys[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. + +0x11 will be interpreted as raw hex, which will allow new controlers + +to be configured even if they don't have defined names. +=================== +*/ +int Key_StringToKeynum( char *str ) { + keyname_t *kn; + + if ( !str || !str[0] ) { + return -1; + } + if ( !str[1] ) { + return str[0]; + } + + // check for hex code + if ( strlen( str ) == 4 ) { + int n = Com_HexStrToInt( str ); + + if ( n >= 0 ) { + return n; + } + } + + // scan for a text match + for ( kn=keynames ; kn->name ; kn++ ) { + if ( !Q_stricmp( str,kn->name ) ) + return kn->keynum; + } + + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the +given keynum. +=================== +*/ +char *Key_KeynumToString( int keynum ) { + keyname_t *kn; + static char tinystr[5]; + int i, j; + + if ( keynum == -1 ) { + return ""; + } + + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return ""; + } + + // check for printable ascii (don't use quote) + if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) { + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + // check for a key string + for ( kn=keynames ; kn->name ; kn++ ) { + if (keynum == kn->keynum) { + return kn->name; + } + } + + // make a hex string + i = keynum >> 4; + j = keynum & 15; + + tinystr[0] = '0'; + tinystr[1] = 'x'; + tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; + tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; + tinystr[4] = 0; + + return tinystr; +} + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding( int keynum, const char *binding ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return; + } + + // free old bindings + if ( keys[ keynum ].binding ) { + Z_Free( keys[ keynum ].binding ); + } + + // allocate memory for new binding + keys[keynum].binding = CopyString( binding ); + + // consider this like modifying an archived cvar, so the + // file write will be triggered at the next oportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; +} + + +/* +=================== +Key_GetBinding +=================== +*/ +char *Key_GetBinding( int keynum ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return ""; + } + + return keys[ keynum ].binding; +} + +/* +=================== +Key_GetKey +=================== +*/ + +int Key_GetKey(const char *binding) { + int i; + + if (binding) { + for (i=0 ; i < MAX_KEYS ; i++) { + if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) { + return i; + } + } + } + return -1; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc() != 2) + { + Com_Printf ("unbind : remove commands from a key\n"); + return; + } + + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + Key_SetBinding (b, ""); +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f (void) +{ + int i; + + for (i=0 ; i < MAX_KEYS; i++) + if (keys[i].binding) + Key_SetBinding (i, ""); +} + + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f (void) +{ + int i, c, b; + char cmd[1024]; + + c = Cmd_Argc(); + + if (c < 2) + { + Com_Printf ("bind [command] : attach a command to a key\n"); + return; + } + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + if (c == 2) + { + if (keys[b].binding) + Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keys[b].binding ); + else + Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); + return; + } + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for (i=2 ; i< c ; i++) + { + strcat (cmd, Cmd_Argv(i)); + if (i != (c-1)) + strcat (cmd, " "); + } + + Key_SetBinding (b, cmd); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings( fileHandle_t f ) { + int i; + + FS_Printf (f, "unbindall\n" ); + + for (i=0 ; i args ) + Field_CompleteKeyname( ); + } +} + +/* +==================== +Key_CompleteBind +==================== +*/ +static void Key_CompleteBind( char *args, int argNum ) +{ + char *p; + + if( argNum == 2 ) + { + // Skip "bind " + p = Com_SkipTokens( args, 1, " " ); + + if( p > args ) + Field_CompleteKeyname( ); + } + else if( argNum >= 3 ) + { + // Skip "bind " + p = Com_SkipTokens( args, 2, " " ); + + if( p > args ) + Field_CompleteCommand( p, qtrue, qtrue ); + } +} + +/* +=================== +CL_InitKeyCommands +=================== +*/ +void CL_InitKeyCommands( void ) { + // register our functions + Cmd_AddCommand ("bind",Key_Bind_f); + Cmd_SetCommandCompletionFunc( "bind", Key_CompleteBind ); + Cmd_AddCommand ("unbind",Key_Unbind_f); + Cmd_SetCommandCompletionFunc( "unbind", Key_CompleteUnbind ); + Cmd_AddCommand ("unbindall",Key_Unbindall_f); + Cmd_AddCommand ("bindlist",Key_Bindlist_f); +} + +/* +=================== +CL_ParseBinding + +Execute the commands in the bind string +=================== +*/ +void CL_ParseBinding( int key, qboolean down, unsigned time ) +{ + char buf[ MAX_STRING_CHARS ], *p = buf, *end; + + if( !keys[key].binding || !keys[key].binding[0] ) + return; + Q_strncpyz( buf, keys[key].binding, sizeof( buf ) ); + + while( 1 ) + { + while( isspace( *p ) ) + p++; + end = strchr( p, ';' ); + if( end ) + *end = '\0'; + if( *p == '+' ) + { + // button commands add keynum and time as parameters + // so that multiple sources can be discriminated and + // subframe corrected + char cmd[1024]; + Com_sprintf( cmd, sizeof( cmd ), "%c%s %d %d\n", + ( down ) ? '+' : '-', p + 1, key, time ); + Cbuf_AddText( cmd ); + } + else if( down ) + { + // normal commands only execute on key press + Cbuf_AddText( p ); + Cbuf_AddText( "\n" ); + } + if( !end ) + break; + p = end + 1; + } +} + +/* +=================== +CL_KeyDownEvent + +Called by CL_KeyEvent to handle a keypress +=================== +*/ +void CL_KeyDownEvent( int key, unsigned time ) +{ + keys[key].down = qtrue; + keys[key].repeats++; + if( keys[key].repeats == 1 && key != K_SCROLLOCK && key != K_KP_NUMLOCK && key != K_CAPSLOCK ) + anykeydown++; + + if( keys[K_ALT].down && key == K_ENTER ) + { + Cvar_SetValue( "r_fullscreen", + !Cvar_VariableIntegerValue( "r_fullscreen" ) ); + return; + } + + // console key is hardcoded, so the user can never unbind it + if( key == K_CONSOLE || ( keys[K_SHIFT].down && key == K_ESCAPE ) ) + { + Con_ToggleConsole_f (); + Key_ClearStates (); + return; + } + + + // keys can still be used for bound actions + if ( ( key < 128 || key == K_MOUSE1 ) && + ( !clc.demoplaying && cls.state == CA_CINEMATIC ) && Key_GetCatcher( ) == 0 ) { + + if (Cvar_VariableValue ("com_cameraMode") == 0) { + Cvar_Set ("nextdemo",""); + key = K_ESCAPE; + } + } + + // escape is always handled special + if ( key == K_ESCAPE ) { + // escape always gets out of CGAME stuff + if (Key_GetCatcher( ) & KEYCATCH_CGAME) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME ); + VM_Call (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE); + return; + } + + if ( !( Key_GetCatcher( ) & KEYCATCH_UI ) ) { + if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME ); + } + else if ( cls.state != CA_DISCONNECTED ) { + CL_Disconnect_f(); + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + return; + } + + VM_Call( uivm, UI_KEY_EVENT, key, qtrue ); + return; + } + + // distribute the key down event to the apropriate handler + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) { + Console_Key( key ); + } else if ( Key_GetCatcher( ) & KEYCATCH_UI ) { + if ( uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, qtrue ); + } + } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME ) { + if ( cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, qtrue ); + } + } else if ( cls.state == CA_DISCONNECTED ) { + Console_Key( key ); + } else { + // send the bound action + CL_ParseBinding( key, qtrue, time ); + } + return; +} + +/* +=================== +CL_KeyUpEvent + +Called by CL_KeyEvent to handle a keyrelease +=================== +*/ +void CL_KeyUpEvent( int key, unsigned time ) +{ + keys[key].repeats = 0; + keys[key].down = qfalse; + if (key != K_SCROLLOCK && key != K_KP_NUMLOCK && key != K_CAPSLOCK) + anykeydown--; + + if (anykeydown < 0) { + anykeydown = 0; + } + + // don't process key-up events for the console key + if ( key == K_CONSOLE || ( key == K_ESCAPE && keys[K_SHIFT].down ) ) + return; + + // + // key up events only perform actions if the game key binding is + // a button command (leading + sign). These will be processed even in + // console mode and menu mode, to keep the character from continuing + // an action started before a mode switch. + // + if( cls.state != CA_DISCONNECTED ) + CL_ParseBinding( key, qfalse, time ); + + if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, qfalse ); + } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME && cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, qfalse ); + } +} + +/* +=================== +CL_KeyEvent + +Called by the system for both key up and key down events +=================== +*/ +void CL_KeyEvent (int key, qboolean down, unsigned time) { + if( down ) + CL_KeyDownEvent( key, time ); + else + CL_KeyUpEvent( key, time ); +} + +/* +=================== +CL_CharEvent + +Normal keyboard characters, already shifted / capslocked / etc +=================== +*/ +void CL_CharEvent( int key ) { + // delete is not a printable character and is + // otherwise handled by Field_KeyDownEvent + if ( key == 127 ) { + return; + } + + // distribute the key down event to the apropriate handler + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) + { + Field_CharEvent( &g_consoleField, key ); + } + else if ( Key_GetCatcher( ) & KEYCATCH_UI ) + { + VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue ); + } + else if ( cls.state == CA_DISCONNECTED ) + { + Field_CharEvent( &g_consoleField, key ); + } +} + + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates (void) +{ + int i; + + anykeydown = 0; + + for ( i=0 ; i < MAX_KEYS ; i++ ) { + if (i == K_SCROLLOCK || i == K_KP_NUMLOCK || i == K_CAPSLOCK) + continue; + + if ( keys[i].down ) { + CL_KeyEvent( i, qfalse, 0 ); + + } + keys[i].down = 0; + keys[i].repeats = 0; + } +} + +/* +==================== +Key_KeynumToStringBuf +==================== +*/ +void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { + Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen ); +} + +/* +==================== +Key_GetBindingBuf +==================== +*/ +void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + char *value; + + value = Key_GetBinding( keynum ); + if ( value ) { + Q_strncpyz( buf, value, buflen ); + } + else { + *buf = 0; + } +} + +static int keyCatchers = 0; + +/* +==================== +Key_GetCatcher +==================== +*/ +int Key_GetCatcher( void ) { + return keyCatchers; +} + +/* +==================== +Key_SetCatcher +==================== +*/ +void Key_SetCatcher( int catcher ) { + // If the catcher state is changing, clear all key states + if( catcher != keyCatchers ) + Key_ClearStates( ); + + keyCatchers = catcher; +} + +// This must not exceed MAX_CMD_LINE +#define MAX_CONSOLE_SAVE_BUFFER 1024 +#define CONSOLE_HISTORY_FILE "q3history" +static char consoleSaveBuffer[ MAX_CONSOLE_SAVE_BUFFER ]; +static int consoleSaveBufferSize = 0; + +/* +================ +CL_LoadConsoleHistory + +Load the console history from cl_consoleHistory +================ +*/ +void CL_LoadConsoleHistory( void ) +{ + char *token, *text_p; + int i, numChars, numLines = 0; + fileHandle_t f; + + consoleSaveBufferSize = FS_FOpenFileRead( CONSOLE_HISTORY_FILE, &f, qfalse ); + if( !f ) + { + Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE ); + return; + } + + if( consoleSaveBufferSize <= MAX_CONSOLE_SAVE_BUFFER && + FS_Read( consoleSaveBuffer, consoleSaveBufferSize, f ) == consoleSaveBufferSize ) + { + text_p = consoleSaveBuffer; + + for( i = COMMAND_HISTORY - 1; i >= 0; i-- ) + { + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + historyEditLines[ i ].cursor = atoi( token ); + + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + historyEditLines[ i ].scroll = atoi( token ); + + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + numChars = atoi( token ); + text_p++; + if( numChars > ( strlen( consoleSaveBuffer ) - ( text_p - consoleSaveBuffer ) ) ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: probable corrupt history\n" ); + break; + } + Com_Memcpy( historyEditLines[ i ].buffer, + text_p, numChars ); + historyEditLines[ i ].buffer[ numChars ] = '\0'; + text_p += numChars; + + numLines++; + } + + memmove( &historyEditLines[ 0 ], &historyEditLines[ i + 1 ], + numLines * sizeof( field_t ) ); + for( i = numLines; i < COMMAND_HISTORY; i++ ) + Field_Clear( &historyEditLines[ i ] ); + + historyLine = nextHistoryLine = numLines; + } + else + Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE ); + + FS_FCloseFile( f ); +} + +/* +================ +CL_SaveConsoleHistory + +Save the console history into the cvar cl_consoleHistory +so that it persists across invocations of q3 +================ +*/ +void CL_SaveConsoleHistory( void ) +{ + int i; + int lineLength, saveBufferLength, additionalLength; + fileHandle_t f; + + consoleSaveBuffer[ 0 ] = '\0'; + + i = ( nextHistoryLine - 1 ) % COMMAND_HISTORY; + do + { + if( historyEditLines[ i ].buffer[ 0 ] ) + { + lineLength = strlen( historyEditLines[ i ].buffer ); + saveBufferLength = strlen( consoleSaveBuffer ); + + //ICK + additionalLength = lineLength + strlen( "999 999 999 " ); + + if( saveBufferLength + additionalLength < MAX_CONSOLE_SAVE_BUFFER ) + { + Q_strcat( consoleSaveBuffer, MAX_CONSOLE_SAVE_BUFFER, + va( "%d %d %d %s ", + historyEditLines[ i ].cursor, + historyEditLines[ i ].scroll, + lineLength, + historyEditLines[ i ].buffer ) ); + } + else + break; + } + i = ( i - 1 + COMMAND_HISTORY ) % COMMAND_HISTORY; + } + while( i != ( nextHistoryLine - 1 ) % COMMAND_HISTORY ); + + consoleSaveBufferSize = strlen( consoleSaveBuffer ); + + f = FS_FOpenFileWrite( CONSOLE_HISTORY_FILE ); + if( !f ) + { + Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE ); + return; + } + + if( FS_Write( consoleSaveBuffer, consoleSaveBufferSize, f ) < consoleSaveBufferSize ) + Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE ); + + FS_FCloseFile( f ); +} diff --git a/src/client/cl_main.c b/src/client/cl_main.c new file mode 100644 index 0000000..d545fc9 --- /dev/null +++ b/src/client/cl_main.c @@ -0,0 +1,4418 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_main.c -- client main loop + +#include "client.h" +#include + +#ifdef USE_MUMBLE +#include "libmumblelink.h" +#endif + +#ifdef USE_MUMBLE +cvar_t *cl_useMumble; +cvar_t *cl_mumbleScale; +#endif + +#ifdef USE_VOIP +cvar_t *cl_voipUseVAD; +cvar_t *cl_voipVADThreshold; +cvar_t *cl_voipSend; +cvar_t *cl_voipSendTarget; +cvar_t *cl_voipGainDuringCapture; +cvar_t *cl_voipCaptureMult; +cvar_t *cl_voipShowMeter; +cvar_t *cl_voip; +#endif + +cvar_t *cl_nodelta; +cvar_t *cl_debugMove; + +cvar_t *cl_noprint; +cvar_t *cl_motd; + +cvar_t *rcon_client_password; +cvar_t *rconAddress; + +cvar_t *cl_timeout; +cvar_t *cl_maxpackets; +cvar_t *cl_packetdup; +cvar_t *cl_timeNudge; +cvar_t *cl_showTimeDelta; +cvar_t *cl_freezeDemo; + +cvar_t *cl_shownet; +cvar_t *cl_showSend; +cvar_t *cl_timedemo; +cvar_t *cl_timedemoLog; +cvar_t *cl_autoRecordDemo; +cvar_t *cl_aviFrameRate; +cvar_t *cl_aviMotionJpeg; +cvar_t *cl_forceavidemo; + +cvar_t *cl_freelook; +cvar_t *cl_sensitivity; + +cvar_t *cl_mouseAccel; +cvar_t *cl_mouseAccelOffset; +cvar_t *cl_mouseAccelStyle; +cvar_t *cl_showMouseRate; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; +cvar_t *m_filter; + +cvar_t *j_pitch; +cvar_t *j_yaw; +cvar_t *j_forward; +cvar_t *j_side; +cvar_t *j_pitch_axis; +cvar_t *j_yaw_axis; +cvar_t *j_forward_axis; +cvar_t *j_side_axis; + +cvar_t *cl_activeAction; + +cvar_t *cl_motdString; + +cvar_t *cl_allowDownload; +cvar_t *com_downloadPrompt; +cvar_t *cl_conXOffset; +cvar_t *cl_inGameVideo; + +cvar_t *cl_serverStatusResendTime; +cvar_t *cl_trn; + +cvar_t *cl_lanForcePackets; + +cvar_t *cl_guidServerUniq; + +cvar_t *cl_consoleKeys; + +cvar_t *cl_gamename; + +clientActive_t cl; +clientConnection_t clc; +clientStatic_t cls; +vm_t *cgvm; + +// Structure containing functions exported from refresh DLL +refexport_t re; + +ping_t cl_pinglist[MAX_PINGREQUESTS]; + +typedef struct serverStatus_s +{ + char string[BIG_INFO_STRING]; + netadr_t address; + int time, startTime; + qboolean pending; + qboolean print; + qboolean retrieved; +} serverStatus_t; + +serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; +int serverStatusCount; + +#if defined __USEA3D && defined __A3D_GEOM + void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); +#endif + +extern void GLimp_Minimize(void); +extern void SV_BotFrame( int time ); +void CL_CheckForResend( void ); +void CL_ShowIP_f(void); +void CL_ServerStatus_f(void); +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); + +/* +=============== +CL_CDDialog + +Called by Com_Error when a cd is needed +=============== +*/ +void CL_CDDialog( void ) { + cls.cddialog = qtrue; // start it next frame +} + +#ifdef USE_MUMBLE +static +void CL_UpdateMumble(void) +{ + vec3_t pos, forward, up; + float scale = cl_mumbleScale->value; + float tmp; + + if(!cl_useMumble->integer) + return; + + // !!! FIXME: not sure if this is even close to correct. + AngleVectors( cl.snap.ps.viewangles, forward, NULL, up); + + pos[0] = cl.snap.ps.origin[0] * scale; + pos[1] = cl.snap.ps.origin[2] * scale; + pos[2] = cl.snap.ps.origin[1] * scale; + + tmp = forward[1]; + forward[1] = forward[2]; + forward[2] = tmp; + + tmp = up[1]; + up[1] = up[2]; + up[2] = tmp; + + if(cl_useMumble->integer > 1) { + fprintf(stderr, "%f %f %f, %f %f %f, %f %f %f\n", + pos[0], pos[1], pos[2], + forward[0], forward[1], forward[2], + up[0], up[1], up[2]); + } + + mumble_update_coordinates(pos, forward, up); +} +#endif + + +#ifdef USE_VOIP +static +void CL_UpdateVoipIgnore(const char *idstr, qboolean ignore) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if ((id >= 0) && (id < MAX_CLIENTS)) { + clc.voipIgnore[id] = ignore; + CL_AddReliableCommand(va("voip %s %d", + ignore ? "ignore" : "unignore", id), qfalse); + Com_Printf("VoIP: %s ignoring player #%d\n", + ignore ? "Now" : "No longer", id); + return; + } + } + Com_Printf("VoIP: invalid player ID#\n"); +} + +static +void CL_UpdateVoipGain(const char *idstr, float gain) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if (gain < 0.0f) + gain = 0.0f; + if ((id >= 0) && (id < MAX_CLIENTS)) { + clc.voipGain[id] = gain; + Com_Printf("VoIP: player #%d gain now set to %f\n", id, gain); + } + } +} + +/* +================ +CL_VoipParseTargets + +Sets clc.voipTarget{1,2,3} by asking the cgame to produce a string and then +parsing it as a series of client numbers +Perhaps it would be better to allow the cgame to set the three integers +directly, but this way we can change the net protocol without changing the +vmcall +================ +*/ +static void CL_VoipParseTargets( void ) +{ + const char *target = cl_voipSendTarget->string; + intptr_t p = VM_Call( cgvm, CG_VOIP_STRING ); + + if( p ) + target = VM_ExplicitArgPtr( cgvm, p ); + + if( !target[ 0 ] || Q_stricmp( target, "all" ) == 0 ) + clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0x7FFFFFFF; + else if( Q_stricmp( target, "none" ) == 0 ) + clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0; + else + { + char *end; + int val; + clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0; + + while( 1 ) + { + while( *target && !isdigit( *target ) ) + target++; + if( !*target ) + break; + + val = strtol( target, &end, 10 ); + assert( target != end ); + if( val < 0 || val >= MAX_CLIENTS ) + Com_Printf( S_COLOR_YELLOW "WARNING: VoIP target %d is not a valid " + "client number\n", val ); + else if( val < 31 ) + clc.voipTarget1 |= 1 << val; + else if( ( val -= 31 ) < 31 ) + clc.voipTarget2 |= 1 << val; + else if( ( val -= 31 ) < 31 ) + clc.voipTarget3 |= 1 << val; + target = end; + } + } +} + +void CL_Voip_f( void ) +{ + const char *cmd = Cmd_Argv(1); + const char *reason = NULL; + + if (cls.state != CA_ACTIVE) + reason = "Not connected to a server"; + else if (!clc.speexInitialized) + reason = "Speex not initialized"; + else if (!cl_connectedToVoipServer) + reason = "Server doesn't support VoIP"; + + if (reason != NULL) { + Com_Printf("VoIP: command ignored: %s\n", reason); + return; + } + + if (strcmp(cmd, "ignore") == 0) { + CL_UpdateVoipIgnore(Cmd_Argv(2), qtrue); + } else if (strcmp(cmd, "unignore") == 0) { + CL_UpdateVoipIgnore(Cmd_Argv(2), qfalse); + } else if (strcmp(cmd, "gain") == 0) { + if (Cmd_Argc() > 3) { + CL_UpdateVoipGain(Cmd_Argv(2), atof(Cmd_Argv(3))); + } else if (Q_isanumber(Cmd_Argv(2))) { + int id = atoi(Cmd_Argv(2)); + if (id >= 0 && id < MAX_CLIENTS) { + Com_Printf("VoIP: current gain for player #%d " + "is %f\n", id, clc.voipGain[id]); + } else { + Com_Printf("VoIP: invalid player ID#\n"); + } + } else { + Com_Printf("usage: voip gain [value]\n"); + } + } else if (strcmp(cmd, "muteall") == 0) { + Com_Printf("VoIP: muting incoming voice\n"); + CL_AddReliableCommand("voip muteall", qfalse); + clc.voipMuteAll = qtrue; + } else if (strcmp(cmd, "unmuteall") == 0) { + Com_Printf("VoIP: unmuting incoming voice\n"); + CL_AddReliableCommand("voip unmuteall", qfalse); + clc.voipMuteAll = qfalse; + } else { + Com_Printf("usage: voip [un]ignore \n" + " voip [un]muteall\n" + " voip gain [value]\n"); + } +} + + +static +void CL_VoipNewGeneration(void) +{ + // don't have a zero generation so new clients won't match, and don't + // wrap to negative so MSG_ReadLong() doesn't "fail." + clc.voipOutgoingGeneration++; + if (clc.voipOutgoingGeneration <= 0) + clc.voipOutgoingGeneration = 1; + clc.voipPower = 0.0f; + clc.voipOutgoingSequence = 0; +} + +/* +=============== +CL_CaptureVoip + +Record more audio from the hardware if required and encode it into Speex + data for later transmission. +=============== +*/ +static +void CL_CaptureVoip(void) +{ + const float audioMult = cl_voipCaptureMult->value; + const qboolean useVad = (cl_voipUseVAD->integer != 0); + qboolean initialFrame = qfalse; + qboolean finalFrame = qfalse; + +#if USE_MUMBLE + // if we're using Mumble, don't try to handle VoIP transmission ourselves. + if (cl_useMumble->integer) + return; +#endif + + if (!clc.speexInitialized) + return; // just in case this gets called at a bad time. + + if (clc.voipOutgoingDataSize > 0) + return; // packet is pending transmission, don't record more yet. + + if (cl_voipUseVAD->modified) { + Cvar_Set("cl_voipSend", (useVad) ? "1" : "0"); + cl_voipUseVAD->modified = qfalse; + } + + if ((useVad) && (!cl_voipSend->integer)) + Cvar_Set("cl_voipSend", "1"); // lots of things reset this. + + if (cl_voipSend->modified) { + qboolean dontCapture = qfalse; + if (cls.state != CA_ACTIVE) + dontCapture = qtrue; // not connected to a server. + else if (!cl_connectedToVoipServer) + dontCapture = qtrue; // server doesn't support VoIP. + else if (clc.demoplaying) + dontCapture = qtrue; // playing back a demo. + else if ( cl_voip->integer == 0 ) + dontCapture = qtrue; // client has VoIP support disabled. + else if ( audioMult == 0.0f ) + dontCapture = qtrue; // basically silenced incoming audio. + + cl_voipSend->modified = qfalse; + + if (dontCapture) { + cl_voipSend->integer = 0; + return; + } + + if (cl_voipSend->integer) { + initialFrame = qtrue; + } else { + finalFrame = qtrue; + } + } + + // try to get more audio data from the sound card... + + if (initialFrame) { + float gain = cl_voipGainDuringCapture->value; + if (gain < 0.0f) gain = 0.0f; else if (gain >= 1.0f) gain = 1.0f; + S_MasterGain(cl_voipGainDuringCapture->value); + S_StartCapture(); + CL_VoipNewGeneration(); + CL_VoipParseTargets(); + } + + if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio? + int samples = S_AvailableCaptureSamples(); + const int mult = (finalFrame) ? 1 : 12; // 12 == 240ms of audio. + + // enough data buffered in audio hardware to process yet? + if (samples >= (clc.speexFrameSize * mult)) { + // audio capture is always MONO16 (and that's what speex wants!). + // 2048 will cover 12 uncompressed frames in narrowband mode. + static int16_t sampbuffer[2048]; + float voipPower = 0.0f; + int speexFrames = 0; + int wpos = 0; + int pos = 0; + + if (samples > (clc.speexFrameSize * 12)) + samples = (clc.speexFrameSize * 12); + + // !!! FIXME: maybe separate recording from encoding, so voipPower + // !!! FIXME: updates faster than 4Hz? + + samples -= samples % clc.speexFrameSize; + S_Capture(samples, (byte *) sampbuffer); // grab from audio card. + + // this will probably generate multiple speex packets each time. + while (samples > 0) { + int16_t *sampptr = &sampbuffer[pos]; + int i, bytes; + + // preprocess samples to remove noise... + speex_preprocess_run(clc.speexPreprocessor, sampptr); + + // check the "power" of this packet... + for (i = 0; i < clc.speexFrameSize; i++) { + const float flsamp = (float) sampptr[i]; + const float s = fabs(flsamp); + voipPower += s * s; + sampptr[i] = (int16_t) ((flsamp) * audioMult); + } + + // encode raw audio samples into Speex data... + speex_bits_reset(&clc.speexEncoderBits); + speex_encode_int(clc.speexEncoder, sampptr, + &clc.speexEncoderBits); + bytes = speex_bits_write(&clc.speexEncoderBits, + (char *) &clc.voipOutgoingData[wpos+1], + sizeof (clc.voipOutgoingData) - (wpos+1)); + assert((bytes > 0) && (bytes < 256)); + clc.voipOutgoingData[wpos] = (byte) bytes; + wpos += bytes + 1; + + // look at the data for the next packet... + pos += clc.speexFrameSize; + samples -= clc.speexFrameSize; + speexFrames++; + } + + clc.voipPower = (voipPower / (32768.0f * 32768.0f * + ((float) (clc.speexFrameSize * speexFrames)))) * + 100.0f; + + if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) { + CL_VoipNewGeneration(); // no "talk" for at least 1/4 second. + } else { + clc.voipOutgoingDataSize = wpos; + clc.voipOutgoingDataFrames = speexFrames; + + Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n", + speexFrames, wpos, clc.voipPower); + + #if 0 + static FILE *encio = NULL; + if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb"); + if (encio != NULL) { fwrite(clc.voipOutgoingData, wpos, 1, encio); fflush(encio); } + static FILE *decio = NULL; + if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb"); + if (decio != NULL) { fwrite(sampbuffer, speexFrames * clc.speexFrameSize * 2, 1, decio); fflush(decio); } + #endif + } + } + } + + // User requested we stop recording, and we've now processed the last of + // any previously-buffered data. Pause the capture device, etc. + if (finalFrame) { + S_StopCapture(); + S_MasterGain(1.0f); + clc.voipPower = 0.0f; // force this value so it doesn't linger. + } +} +#endif + +/* +======================================================================= + +CLIENT RELIABLE COMMAND COMMUNICATION + +======================================================================= +*/ + +/* +====================== +CL_AddReliableCommand + +The given command will be transmitted to the server, and is gauranteed to +not have future usercmd_t executed before it is executed +====================== +*/ +void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd) +{ + int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + // also leave one slot open for the disconnect command in this case. + + if ((isDisconnectCmd && unacknowledged > MAX_RELIABLE_COMMANDS) || + (!isDisconnectCmd && unacknowledged >= MAX_RELIABLE_COMMANDS)) + { + if(com_errorEntered) + return; + else + Com_Error(ERR_DROP, "Client command overflow"); + } + + Q_strncpyz(clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)], + cmd, sizeof(*clc.reliableCommands)); +} + +/* +====================== +CL_ChangeReliableCommand +====================== +*/ +void CL_ChangeReliableCommand( void ) { + int r, index, l; + + r = clc.reliableSequence - (random() * 5); + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + l = strlen(clc.reliableCommands[ index ]); + if ( l >= MAX_STRING_CHARS - 1 ) { + l = MAX_STRING_CHARS - 2; + } + clc.reliableCommands[ index ][ l ] = '\n'; + clc.reliableCommands[ index ][ l+1 ] = '\0'; +} + +/* +======================================================================= + +CLIENT SIDE DEMO RECORDING + +======================================================================= +*/ + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length +==================== +*/ +void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { + int len, swlen; + + // write the packet sequence + len = clc.serverMessageSequence; + swlen = LittleLong( len ); + FS_Write (&swlen, 4, clc.demofile); + + // skip the packet sequencing information + len = msg->cursize - headerBytes; + swlen = LittleLong(len); + FS_Write (&swlen, 4, clc.demofile); + FS_Write ( msg->data + headerBytes, len, clc.demofile ); +} + + +/* +==================== +CL_StopRecording_f + +stop recording a demo +==================== +*/ +void CL_StopRecord_f( void ) { + int len; + + if ( !clc.demorecording ) { + Com_Printf ("Not recording a demo.\n"); + return; + } + + // finish up + len = -1; + FS_Write (&len, 4, clc.demofile); + FS_Write (&len, 4, clc.demofile); + FS_FCloseFile (clc.demofile); + clc.demofile = 0; + clc.demorecording = qfalse; + clc.spDemoRecording = qfalse; + Com_Printf ("Stopped demo.\n"); +} + +/* +================== +CL_DemoFilename +================== +*/ +void CL_DemoFilename( int number, char *fileName ) { + int a,b,c,d; + + if(number < 0 || number > 9999) + number = 9999; + + a = number / 1000; + number -= a*1000; + b = number / 100; + number -= b*100; + c = number / 10; + number -= c*10; + d = number; + + Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" + , a, b, c, d ); +} + +/* +==================== +CL_Record_f + +record + +Begins recording a demo from the current position +==================== +*/ +static char demoName[MAX_QPATH]; // compiler bug workaround +void CL_Record_f( void ) { + char name[MAX_OSPATH]; + byte bufData[MAX_MSGLEN]; + msg_t buf; + int i; + int len; + entityState_t *ent; + entityState_t nullstate; + char *s; + + if ( Cmd_Argc() > 2 ) { + Com_Printf ("record \n"); + return; + } + + if ( clc.demorecording ) { + if (!clc.spDemoRecording) { + Com_Printf ("Already recording.\n"); + } + return; + } + + if ( cls.state != CA_ACTIVE ) { + Com_Printf ("You must be in a level to record.\n"); + return; + } + + // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. + if ( NET_IsLocalAddress( clc.serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) { + Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); + } + + if ( Cmd_Argc() == 2 ) { + s = Cmd_Argv(1); + Q_strncpyz( demoName, s, sizeof( demoName ) ); + Com_sprintf (name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, PROTOCOL_VERSION ); + } else { + int number; + + // scan for a free demo name + for ( number = 0 ; number <= 9999 ; number++ ) { + CL_DemoFilename( number, demoName ); + Com_sprintf (name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, PROTOCOL_VERSION ); + + if (!FS_FileExists(name)) + break; // file doesn't exist + } + } + + // open the demo file + + Com_Printf ("recording to %s.\n", name); + clc.demofile = FS_FOpenFileWrite( name ); + if ( !clc.demofile ) { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + clc.demorecording = qtrue; + if (Cvar_VariableValue("ui_recordSPDemo")) { + clc.spDemoRecording = qtrue; + } else { + clc.spDemoRecording = qfalse; + } + + + Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); + + // don't start saving messages until a non-delta compressed message is received + clc.demowaiting = qtrue; + + // write out the gamestate message + MSG_Init (&buf, bufData, sizeof(bufData)); + MSG_Bitstream(&buf); + + // NOTE, MRE: all server->client messages now acknowledge + MSG_WriteLong( &buf, clc.reliableSequence ); + + MSG_WriteByte (&buf, svc_gamestate); + MSG_WriteLong (&buf, clc.serverCommandSequence ); + + // configstrings + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( !cl.gameState.stringOffsets[i] ) { + continue; + } + s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; + MSG_WriteByte (&buf, svc_configstring); + MSG_WriteShort (&buf, i); + MSG_WriteBigString (&buf, s); + } + + // baselines + Com_Memset (&nullstate, 0, sizeof(nullstate)); + for ( i = 0; i < MAX_GENTITIES ; i++ ) { + ent = &cl.entityBaselines[i]; + if ( !ent->number ) { + continue; + } + MSG_WriteByte (&buf, svc_baseline); + MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); + } + + MSG_WriteByte( &buf, svc_EOF ); + + // finished writing the gamestate stuff + + // write the client num + MSG_WriteLong(&buf, clc.clientNum); + // write the checksum feed + MSG_WriteLong(&buf, clc.checksumFeed); + + // finished writing the client packet + MSG_WriteByte( &buf, svc_EOF ); + + // write it to the demo file + len = LittleLong( clc.serverMessageSequence - 1 ); + FS_Write (&len, 4, clc.demofile); + + len = LittleLong (buf.cursize); + FS_Write (&len, 4, clc.demofile); + FS_Write (buf.data, buf.cursize, clc.demofile); + + // the rest of the demo file will be copied from net messages +} + +/* +======================================================================= + +CLIENT SIDE DEMO PLAYBACK + +======================================================================= +*/ + +/* +================= +CL_DemoFrameDurationSDev +================= +*/ +static float CL_DemoFrameDurationSDev( void ) +{ + int i; + int numFrames; + float mean = 0.0f; + float variance = 0.0f; + + if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS ) + numFrames = MAX_TIMEDEMO_DURATIONS; + else + numFrames = clc.timeDemoFrames - 1; + + for( i = 0; i < numFrames; i++ ) + mean += clc.timeDemoDurations[ i ]; + mean /= numFrames; + + for( i = 0; i < numFrames; i++ ) + { + float x = clc.timeDemoDurations[ i ]; + + variance += ( ( x - mean ) * ( x - mean ) ); + } + variance /= numFrames; + + return sqrt( variance ); +} + +/* +================= +CL_DemoCompleted +================= +*/ +void CL_DemoCompleted( void ) +{ + char buffer[ MAX_STRING_CHARS ]; + + if( cl_timedemo && cl_timedemo->integer ) + { + int time; + + time = Sys_Milliseconds() - clc.timeDemoStart; + if( time > 0 ) + { + // Millisecond times are frame durations: + // minimum/average/maximum/std deviation + Com_sprintf( buffer, sizeof( buffer ), + "%i frames %3.1f seconds %3.1f fps %d.0/%.1f/%d.0/%.1f ms\n", + clc.timeDemoFrames, + time/1000.0, + clc.timeDemoFrames*1000.0 / time, + clc.timeDemoMinDuration, + time / (float)clc.timeDemoFrames, + clc.timeDemoMaxDuration, + CL_DemoFrameDurationSDev( ) ); + Com_Printf( "%s", buffer ); + + // Write a log of all the frame durations + if( cl_timedemoLog && strlen( cl_timedemoLog->string ) > 0 ) + { + int i; + int numFrames; + fileHandle_t f; + + if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS ) + numFrames = MAX_TIMEDEMO_DURATIONS; + else + numFrames = clc.timeDemoFrames - 1; + + f = FS_FOpenFileWrite( cl_timedemoLog->string ); + if( f ) + { + FS_Printf( f, "# %s", buffer ); + + for( i = 0; i < numFrames; i++ ) + FS_Printf( f, "%d\n", clc.timeDemoDurations[ i ] ); + + FS_FCloseFile( f ); + Com_Printf( "%s written\n", cl_timedemoLog->string ); + } + else + { + Com_Printf( "Couldn't open %s for writing\n", + cl_timedemoLog->string ); + } + } + } + } + + CL_Disconnect( qtrue ); + CL_NextDemo(); +} + +/* +================= +CL_ReadDemoMessage +================= +*/ +void CL_ReadDemoMessage( void ) { + int r; + msg_t buf; + byte bufData[ MAX_MSGLEN ]; + int s; + + if ( !clc.demofile ) { + CL_DemoCompleted (); + return; + } + + // get the sequence number + r = FS_Read( &s, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + clc.serverMessageSequence = LittleLong( s ); + + // init the message + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + // get the length + r = FS_Read (&buf.cursize, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + buf.cursize = LittleLong( buf.cursize ); + if ( buf.cursize == -1 ) { + CL_DemoCompleted (); + return; + } + if ( buf.cursize > buf.maxsize ) { + Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); + } + r = FS_Read( buf.data, buf.cursize, clc.demofile ); + if ( r != buf.cursize ) { + Com_Printf( "Demo file was truncated.\n"); + CL_DemoCompleted (); + return; + } + + clc.lastPacketTime = cls.realtime; + buf.readcount = 0; + CL_ParseServerMessage( &buf ); +} + +/* +==================== +CL_WalkDemoExt +==================== +*/ +static void CL_WalkDemoExt(char *arg, char *name, int *demofile) +{ + int i = 0; + *demofile = 0; + + Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, PROTOCOL_VERSION); + + FS_FOpenFileRead( name, demofile, qtrue ); + + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + return; + } + + Com_Printf("Not found: %s\n", name); + + while(demo_protocols[i]) + { + Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]); + FS_FOpenFileRead( name, demofile, qtrue ); + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + break; + } + else + Com_Printf("Not found: %s\n", name); + i++; + } +} + +/* +==================== +CL_CompleteDemoName +==================== +*/ +static void CL_CompleteDemoName( char *args, int argNum ) +{ + if( argNum == 2 ) + { + char demoExt[ 16 ]; + + Com_sprintf(demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, PROTOCOL_VERSION); + Field_CompleteFilename( "demos", demoExt, qtrue, qtrue ); + } +} + +/* +==================== +CL_PlayDemo_f + +demo + +==================== +*/ +void CL_PlayDemo_f( void ) { + char name[MAX_OSPATH]; + char *arg, *ext_test; + int protocol, i; + char retry[MAX_OSPATH]; + + if (Cmd_Argc() != 2) { + Com_Printf ("demo \n"); + return; + } + + // make sure a local server is killed + // 2 means don't force disconnect of local client + Cvar_Set( "sv_killserver", "2" ); + + // open the demo file + arg = Cmd_Argv(1); + + CL_Disconnect( qtrue ); + + // check for an extension .DEMOEXT_?? (?? is protocol) + ext_test = Q_strrchr(arg, '.'); + + if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1)) + { + protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT)); + + for(i = 0; demo_protocols[i]; i++) + { + if(demo_protocols[i] == protocol) + break; + } + + if(demo_protocols[i] || protocol == PROTOCOL_VERSION) + { + Com_sprintf(name, sizeof(name), "demos/%s", arg); + FS_FOpenFileRead(name, &clc.demofile, qtrue); + } + else + { + int len; + + Com_Printf("Protocol %d not supported for demos\n", protocol); + len = ext_test - arg; + + if(len >= ARRAY_LEN(retry)) + len = ARRAY_LEN(retry) - 1; + + Q_strncpyz(retry, arg, len + 1); + retry[len] = '\0'; + CL_WalkDemoExt(retry, name, &clc.demofile); + } + } + else + CL_WalkDemoExt(arg, name, &clc.demofile); + + if (!clc.demofile) { + Com_Error( ERR_DROP, "couldn't open %s", name); + return; + } + Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); + + Con_Close(); + + cls.state = CA_CONNECTED; + clc.demoplaying = qtrue; + Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); + + // read demo messages until connected + while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { + CL_ReadDemoMessage(); + } + // don't get the first snapshot this frame, to prevent the long + // time from the gamestate load from messing causing a time skip + clc.firstDemoFrameSkipped = qfalse; +} + + +/* +==================== +CL_StartDemoLoop + +Closing the main menu will restart the demo loop +==================== +*/ +void CL_StartDemoLoop( void ) { + // start the demo loop again + Cbuf_AddText ("d1\n"); + Key_SetCatcher( 0 ); +} + +/* +================== +CL_NextDemo + +Called when a demo or cinematic finishes +If the "nextdemo" cvar is set, that command will be issued +================== +*/ +void CL_NextDemo( void ) { + char v[MAX_STRING_CHARS]; + + Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); + v[MAX_STRING_CHARS-1] = 0; + Com_DPrintf("CL_NextDemo: %s\n", v ); + if (!v[0]) { + return; + } + + Cvar_Set ("nextdemo",""); + Cbuf_AddText (v); + Cbuf_AddText ("\n"); + Cbuf_Execute(); +} + +/* +================== +CL_DemoState + +Returns the current state of the demo system +================== +*/ +demoState_t CL_DemoState( void ) { + if( clc.demoplaying ) { + return DS_PLAYBACK; + } else if( clc.demorecording ) { + return DS_RECORDING; + } else { + return DS_NONE; + } +} + +/* +================== +CL_DemoPos + +Returns the current position of the demo +================== +*/ +int CL_DemoPos( void ) { + if( clc.demoplaying || clc.demorecording ) { + return FS_FTell( clc.demofile ); + } else { + return 0; + } +} + +/* +================== +CL_DemoName + +Returns the name of the demo +================== +*/ +void CL_DemoName( char *buffer, int size ) { + if( clc.demoplaying || clc.demorecording ) { + Q_strncpyz( buffer, clc.demoName, size ); + } else if( size >= 1 ) { + buffer[ 0 ] = '\0'; + } +} + +//====================================================================== + +/* +===================== +CL_ShutdownAll +===================== +*/ +void CL_ShutdownAll(void) { + +#ifdef USE_CURL + CL_cURL_Shutdown(); +#endif + // clear sounds + S_DisableSounds(); + // shutdown CGame + CL_ShutdownCGame(); + // shutdown UI + CL_ShutdownUI(); + + // shutdown the renderer + if ( re.Shutdown ) { + re.Shutdown( qfalse ); // don't destroy window or context + } + + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.rendererStarted = qfalse; + cls.soundRegistered = qfalse; +} + +/* +================= +CL_FlushMemory + +Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only +ways a client gets into a game +Also called by Com_Error +================= +*/ +void CL_FlushMemory( void ) { + + // shutdown all the client stuff + CL_ShutdownAll(); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + // clear collision map data + CM_ClearMap(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + CL_StartHunkUsers( qfalse ); +} + +/* +===================== +CL_MapLoading + +A local server is starting to load a map, so update the +screen to let the user know about it, then dump all client +memory on the hunk from cgame, ui, and renderer +===================== +*/ +void CL_MapLoading( void ) { + if ( com_dedicated->integer ) { + cls.state = CA_DISCONNECTED; + Key_SetCatcher( KEYCATCH_CONSOLE ); + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + Con_Close(); + Key_SetCatcher( 0 ); + + // if we are already connected to the local host, stay connected + if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { + cls.state = CA_CONNECTED; // so the connect screen is drawn + Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); + Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); + Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + clc.lastPacketSentTime = -9999; + SCR_UpdateScreen(); + } else { + CL_Disconnect( qtrue ); + Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); + cls.state = CA_CHALLENGING; // so the connect screen is drawn + Key_SetCatcher( 0 ); + SCR_UpdateScreen(); + clc.connectTime = -RETRANSMIT_TIMEOUT; + NET_StringToAdr( cls.servername, &clc.serverAddress, NA_UNSPEC); + // we don't need a challenge on the localhost + + CL_CheckForResend(); + } +} + +/* +===================== +CL_ClearState + +Called before parsing a gamestate +===================== +*/ +void CL_ClearState (void) { + +// S_StopAllSounds(); + + Com_Memset( &cl, 0, sizeof( cl ) ); +} + +/* +==================== +CL_UpdateGUID + +update cl_guid using QKEY_FILE and optional prefix +==================== +*/ +static void CL_UpdateGUID( const char *prefix, int prefix_len ) +{ + fileHandle_t f; + int len; + + len = FS_SV_FOpenFileRead( QKEY_FILE, &f ); + FS_FCloseFile( f ); + + if( len != QKEY_SIZE ) + Cvar_Set( "cl_guid", "" ); + else + Cvar_Set( "cl_guid", Com_MD5File( QKEY_FILE, QKEY_SIZE, + prefix, prefix_len ) ); +} + + +/* +===================== +CL_Disconnect + +Called when a connection, demo, or cinematic is being terminated. +Goes from a connected state to either a menu state or a console state +Sends a disconnect message to the server +This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( qboolean showMainMenu ) { + if ( !com_cl_running || !com_cl_running->integer ) { + return; + } + + Cvar_Set("fs_game", "gpp"); + + // shutting down the client so enter full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + if ( clc.demorecording ) { + CL_StopRecord_f (); + } + + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + +#ifdef USE_MUMBLE + if (cl_useMumble->integer && mumble_islinked()) { + Com_Printf("Mumble: Unlinking from Mumble application\n"); + mumble_unlink(); + } +#endif + +#ifdef USE_VOIP + if (cl_voipSend->integer) { + int tmp = cl_voipUseVAD->integer; + cl_voipUseVAD->integer = 0; // disable this for a moment. + clc.voipOutgoingDataSize = 0; // dump any pending VoIP transmission. + Cvar_Set("cl_voipSend", "0"); + CL_CaptureVoip(); // clean up any state... + cl_voipUseVAD->integer = tmp; + } + + if (clc.speexInitialized) { + int i; + speex_bits_destroy(&clc.speexEncoderBits); + speex_encoder_destroy(clc.speexEncoder); + speex_preprocess_state_destroy(clc.speexPreprocessor); + for (i = 0; i < MAX_CLIENTS; i++) { + speex_bits_destroy(&clc.speexDecoderBits[i]); + speex_decoder_destroy(clc.speexDecoder[i]); + } + } + Cmd_RemoveCommand ("voip"); +#endif + + if ( clc.demofile ) { + FS_FCloseFile( clc.demofile ); + clc.demofile = 0; + } + + if ( uivm && showMainMenu ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + + SCR_StopCinematic (); + S_ClearSoundBuffer(); + + // send a disconnect message to the server + // send it a few times in case one is dropped + if ( cls.state >= CA_CONNECTED ) { + CL_AddReliableCommand("disconnect", qtrue); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + } + + // Remove pure paks + FS_PureServerSetLoadedPaks("", ""); + + CL_ClearState (); + + // wipe the client connection + Com_Memset( &clc, 0, sizeof( clc ) ); + + cls.state = CA_DISCONNECTED; + + // allow cheats locally + Cvar_Set( "sv_cheats", "1" ); + + // not connected to a pure server anymore + cl_connectedToPureServer = qfalse; + +#ifdef USE_VOIP + // not connected to voip server anymore. + cl_connectedToVoipServer = qfalse; +#endif + + // Stop recording any video + if( CL_VideoRecording( ) ) { + // Finish rendering current frame + SCR_UpdateScreen( ); + CL_CloseAVI( ); + } + CL_UpdateGUID( NULL, 0 ); +} + + +/* +=================== +CL_ForwardCommandToServer + +adds the current command line as a clientCommand +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void CL_ForwardCommandToServer( const char *string ) { + char *cmd; + + cmd = Cmd_Argv(0); + + // ignore key up commands + if ( cmd[0] == '-' ) { + return; + } + + if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { + Com_Printf ("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd); + return; + } + + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand(string, qfalse); + } else { + CL_AddReliableCommand(cmd, qfalse); + } +} + +/* +=================== +CL_RequestMotd + +=================== +*/ +void CL_RequestMotd( void ) { + char info[MAX_INFO_STRING]; + + if ( !cl_motd->integer ) { + return; + } + Com_Printf( "Resolving %s\n", MASTER_SERVER_NAME ); + + switch( NET_StringToAdr( MASTER_SERVER_NAME, &cls.updateServer, + NA_UNSPEC ) ) + { + case 0: + Com_Printf( "Couldn't resolve master address\n" ); + return; + + case 2: + cls.updateServer.port = BigShort( PORT_MASTER ); + default: + break; + } + + Com_Printf( "%s resolved to %s\n", MASTER_SERVER_NAME, + NET_AdrToStringwPort( cls.updateServer ) ); + + info[0] = 0; + + Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), + "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); + + Info_SetValueForKey( info, "challenge", cls.updateChallenge ); + Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); + Info_SetValueForKey( info, "version", com_version->string ); + + NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd%s", info ); +} + +/* +====================================================================== + +CONSOLE COMMANDS + +====================================================================== +*/ + +/* +================== +CL_ForwardToServer_f +================== +*/ +void CL_ForwardToServer_f( void ) { + if ( cls.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf ("Not connected to a server.\n"); + return; + } + + // don't forward the first argument + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand(Cmd_Args(), qfalse); + } +} + +/* +================== +CL_Disconnect_f +================== +*/ +void CL_Disconnect_f( void ) { + SCR_StopCinematic(); + Cvar_Set("ui_singlePlayerActive", "0"); + if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { + Com_Error (ERR_DISCONNECT, "Disconnected from server"); + } +} + + +/* +================ +CL_Reconnect_f + +================ +*/ +void CL_Reconnect_f( void ) { + if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { + Com_Printf( "Can't reconnect to localhost.\n" ); + return; + } + Cvar_Set("ui_singlePlayerActive", "0"); + Cbuf_AddText( va("connect %s\n", cls.servername ) ); +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) { + char *server; + const char *serverString; + int argc = Cmd_Argc(); + netadrtype_t family = NA_UNSPEC; + + if ( argc != 2 && argc != 3 ) { + Com_Printf( "usage: connect [-4|-6] server\n"); + return; + } + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + Cvar_Set("ui_singlePlayerActive", "0"); + + // fire a message off to the motd server + CL_RequestMotd(); + + // clear any previous "server full" type messages + clc.serverMessage[0] = 0; + + if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { + // if running a local server, kill it + SV_Shutdown( "Server quit" ); + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + SV_Frame( 0 ); + + CL_Disconnect( qtrue ); + Con_Close(); + + Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); + + if (!NET_StringToAdr(cls.servername, &clc.serverAddress, family) ) { + Com_Printf ("Bad server address\n"); + cls.state = CA_DISCONNECTED; + return; + } + if (clc.serverAddress.port == 0) { + clc.serverAddress.port = BigShort( PORT_SERVER ); + } + + serverString = NET_AdrToStringwPort(clc.serverAddress); + + Com_Printf( "%s resolved to %s\n", cls.servername, serverString); + + if( cl_guidServerUniq->integer ) + CL_UpdateGUID( serverString, strlen( serverString ) ); + else + CL_UpdateGUID( NULL, 0 ); + + // if we aren't playing on a lan, we need to authenticate + // with the cd key + if(NET_IsLocalAddress(clc.serverAddress)) + cls.state = CA_CHALLENGING; + else + { + cls.state = CA_CONNECTING; + + // Set a client challenge number that ideally is mirrored back by the server. + clc.challenge = ((rand() << 16) ^ rand()) ^ Com_Milliseconds(); + } + + Key_SetCatcher( 0 ); + clc.connectTime = -99999; // CL_CheckForResend() will fire immediately + clc.connectPacketCount = 0; + + // server connection string + Cvar_Set( "cl_currentServerAddress", server ); +} + +#define MAX_RCON_MESSAGE 1024 + +/* +================== +CL_CompleteRcon +================== +*/ +static void CL_CompleteRcon( char *args, int argNum ) +{ + if( argNum == 2 ) + { + // Skip "rcon " + char *p = Com_SkipTokens( args, 1, " " ); + + if( p > args ) + Field_CompleteCommand( p, qtrue, qtrue ); + } +} + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) { + char message[MAX_RCON_MESSAGE]; + netadr_t to; + + if ( !rcon_client_password->string ) { + Com_Printf ("You must set 'rconpassword' before\n" + "issuing an rcon command.\n"); + return; + } + + message[0] = -1; + message[1] = -1; + message[2] = -1; + message[3] = -1; + message[4] = 0; + + Q_strcat (message, MAX_RCON_MESSAGE, "rcon "); + + Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string); + Q_strcat (message, MAX_RCON_MESSAGE, " "); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + Q_strcat (message, MAX_RCON_MESSAGE, Cmd_Cmd()+5); + + if ( cls.state >= CA_CONNECTED ) { + to = clc.netchan.remoteAddress; + } else { + if (!strlen(rconAddress->string)) { + Com_Printf ("You must either be connected,\n" + "or set the 'rconAddress' cvar\n" + "to issue rcon commands\n"); + + return; + } + NET_StringToAdr (rconAddress->string, &to, NA_UNSPEC); + if (to.port == 0) { + to.port = BigShort (PORT_SERVER); + } + } + + NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); +} + +/* +================= +CL_SendPureChecksums +================= +*/ +void CL_SendPureChecksums( void ) { + char cMsg[MAX_INFO_VALUE]; + + // if we are pure we need to send back a command with our referenced pk3 checksums + Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums()); + + CL_AddReliableCommand(cMsg, qfalse); +} + +/* +================= +CL_ResetPureClientAtServer +================= +*/ +void CL_ResetPureClientAtServer( void ) { + CL_AddReliableCommand("vdr", qfalse); +} + +/* +================= +CL_Vid_Restart_f + +Restart the video subsystem + +we also have to reload the UI and CGame because the renderer +doesn't know what graphics to reload +================= +*/ +void CL_Vid_Restart_f( void ) { + + // Settings may have changed so stop recording now + if( CL_VideoRecording( ) ) { + CL_CloseAVI( ); + } + + if(clc.demorecording) + CL_StopRecord_f(); + + // don't let them loop during the restart + S_StopAllSounds(); + // shutdown the UI + CL_ShutdownUI(); + // shutdown the CGame + CL_ShutdownCGame(); + // shutdown the renderer and clear the renderer interface + CL_ShutdownRef(); + // client is no longer pure untill new checksums are sent + CL_ResetPureClientAtServer(); + // clear pak references + FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); + // reinitialize the filesystem if the game directory or checksum has changed + FS_ConditionalRestart( clc.checksumFeed ); + + cls.rendererStarted = qfalse; + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.soundRegistered = qfalse; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + // initialize the renderer interface + CL_InitRef(); + + // startup all the client stuff + CL_StartHunkUsers( qfalse ); + + // start the cgame if connected + if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { + cls.cgameStarted = qtrue; + CL_InitCGame(); + // send pure checksums + CL_SendPureChecksums(); + } +} + +/* +================= +CL_Snd_Restart + +Restart the sound subsystem +================= +*/ +void CL_Snd_Restart(void) +{ + S_Shutdown(); + S_Init(); +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem +The cgame and game must also be forced to restart because +handles will be invalid +================= +*/ +void CL_Snd_Restart_f(void) +{ + CL_Snd_Restart(); + CL_Vid_Restart_f(); +} + + +/* +================== +CL_PK3List_f +================== +*/ +void CL_OpenedPK3List_f( void ) { + Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); +} + +/* +================== +CL_PureList_f +================== +*/ +void CL_ReferencedPK3List_f( void ) { + Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); +} + +/* +================== +CL_Configstrings_f +================== +*/ +void CL_Configstrings_f( void ) { + int i; + int ofs; + + if ( cls.state != CA_ACTIVE ) { + Com_Printf( "Not connected to a server.\n"); + return; + } + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + ofs = cl.gameState.stringOffsets[ i ]; + if ( !ofs ) { + continue; + } + Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); + } +} + +/* +============== +CL_Clientinfo_f +============== +*/ +void CL_Clientinfo_f( void ) { + Com_Printf( "--------- Client Information ---------\n" ); + Com_Printf( "state: %i\n", cls.state ); + Com_Printf( "Server: %s\n", cls.servername ); + Com_Printf ("User info settings:\n"); + Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); + Com_Printf( "--------------------------------------\n" ); +} + + +//==================================================================== + +/* +================= +CL_DownloadsComplete + +Called when all downloading has been completed +================= +*/ +void CL_DownloadsComplete( void ) { + Com_Printf("Downloads complete\n"); + +#ifdef USE_CURL + // if we downloaded with cURL + if(clc.cURLUsed) { + clc.cURLUsed = qfalse; + CL_cURL_Shutdown(); + if( clc.cURLDisconnected ) { + if(clc.downloadRestart) { + if( !clc.activeCURLNotGameRelated ) + FS_Restart(clc.checksumFeed); + clc.downloadRestart = qfalse; + } + clc.cURLDisconnected = qfalse; + if( !clc.activeCURLNotGameRelated ) + CL_Reconnect_f(); + return; + } + } +#endif + + // if we downloaded files we need to restart the file system + if (clc.downloadRestart) { + clc.downloadRestart = qfalse; + + FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it + + // inform the server so we get new gamestate info + CL_AddReliableCommand("donedl", qfalse); + + // by sending the donedl command we request a new gamestate + // so we don't want to load stuff yet + return; + } + + // let the client game init and load data + cls.state = CA_LOADING; + + // Pump the loop, this may change gamestate! + Com_EventLoop(); + + // if the gamestate was changed by calling Com_EventLoop + // then we loaded everything already and we don't want to do it again. + if ( cls.state != CA_LOADING ) { + return; + } + + // starting to load a map so we get out of full screen ui mode + Cvar_Set("r_uiFullScreen", "0"); + + // flush client memory and start loading stuff + // this will also (re)load the UI + // if this is a local client then only the client part of the hunk + // will be cleared, note that this is done after the hunk mark has been set + CL_FlushMemory(); + + // initialize the CGame + cls.cgameStarted = qtrue; + CL_InitCGame(); + + // set pure checksums + CL_SendPureChecksums(); + + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); +} + +/* +================= +CL_BeginDownload + +Requests a file to download from the server. Stores it in the current +game directory. +================= +*/ +void CL_BeginDownload( const char *localName, const char *remoteName ) { + + Com_DPrintf("***** CL_BeginDownload *****\n" + "Localname: %s\n" + "Remotename: %s\n" + "****************************\n", localName, remoteName); + + Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); + Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); + + // Set so UI gets access to it + Cvar_Set( "cl_downloadName", remoteName ); + Cvar_Set( "cl_downloadSize", "0" ); + Cvar_Set( "cl_downloadCount", "0" ); + Cvar_SetValue( "cl_downloadTime", cls.realtime ); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + // Stop any errant looping sounds that may be playing + S_ClearLoopingSounds( qtrue ); + + CL_AddReliableCommand( va("download %s", remoteName), qfalse ); +} + +/* +================= +CL_NextDownload + +A download completed or failed +================= +*/ +void CL_NextDownload(void) +{ + char *s; + char *remoteName, *localName; + qboolean useCURL = qfalse; + int prompt; + + // A download has finished, check whether this matches a referenced checksum + if(*clc.downloadName && !clc.activeCURLNotGameRelated) + { + char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, ""); + zippath[strlen(zippath)-1] = '\0'; + + if(!FS_CompareZipChecksum(zippath)) + Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName); + } + + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set("cl_downloadName", ""); + + // We are looking to start a download here + if (*clc.downloadList) { + + // Prompt if we do not allow automatic downloads + prompt = com_downloadPrompt->integer; + if( !( prompt & DLP_TYPE_MASK ) && + !( cl_allowDownload->integer & DLF_ENABLE ) ) { + char files[ MAX_INFO_STRING ] = ""; + char *name, *head, *pure_msg; + char *url_msg = ""; + int i = 0, others = 0, swap = 0, max_list = 12; + + // Set the download URL message + if( ( clc.sv_allowDownload & DLF_ENABLE ) && + !( clc.sv_allowDownload & DLF_NO_REDIRECT ) ) { + url_msg = va("The server redirects to the following URL:\n%s", + clc.sv_dlURL); + max_list -= 6; + } + + // Make a pretty version of the download list + name = clc.downloadList; + if( *name == '@' ) + name++; + + do { + // Copy remote name + head = name; + while( *head && *head != '@' ) + head++; + + swap = *head; + *head = 0; + + if( i++ < max_list ) { + if( i > 1 ) + Q_strcat( files, sizeof( files ), ", " ); + Q_strcat( files, sizeof( files ), name ); + } else { + others++; + } + + *head = swap; + if( !swap ) + break; + + // Skip local name + head++; + while( *head && *head != '@' ) + head++; + + name = head + 1; + } while( *head ); + + if( others ) { + Q_strcat( files, sizeof( files ), va( "(%d other file%s)\n", + others, others > 1 ? "s" : "" ) ); + } + + // Set the pure message + if( cl_connectedToPureServer ) { + if( !( clc.sv_allowDownload & DLF_ENABLE ) || + ( ( clc.sv_allowDownload & DLF_NO_UDP ) && + ( clc.sv_allowDownload & DLF_NO_REDIRECT ) ) ) { + pure_msg = "You are missing files required by the server. " + "The server does not allow downloading. " + "You must install these files manually:"; + } else { + pure_msg = "You are missing files required by the server. " + "You must download these files or disconnect:"; + } + } else { + pure_msg = "You are missing optional files provided by the " + "server. You may not need them to play but can " + "choose to download them anyway:"; + } + + Cvar_Set( "com_downloadPromptText", + va("%s\n\n%s\n%s", pure_msg, files, url_msg ) ); + Cvar_Set( "com_downloadPrompt", va("%d", DLP_SHOW ) ); + return; + } + + if( !( prompt & DLP_PROMPTED ) ) + Cvar_Set( "com_downloadPrompt", va("%d", prompt | DLP_PROMPTED ) ); + + prompt &= DLP_TYPE_MASK; + + s = clc.downloadList; + + // format is: + // @remotename@localname@remotename@localname, etc. + + if (*s == '@') + s++; + remoteName = s; + + if ( (s = strchr(s, '@')) == NULL ) { + CL_DownloadsComplete(); + return; + } + + *s++ = 0; + localName = s; + if ( (s = strchr(s, '@')) != NULL ) + *s++ = 0; + else + s = localName + strlen(localName); // point at the nul byte +#ifdef USE_CURL + if( ( ( cl_allowDownload->integer & DLF_ENABLE ) && + !( cl_allowDownload->integer & DLF_NO_REDIRECT ) ) || + prompt == DLP_CURL ) { + Com_Printf("Trying CURL download: %s; %s\n", localName, remoteName); + if(clc.sv_allowDownload & DLF_NO_REDIRECT) { + Com_Printf("WARNING: server does not " + "allow download redirection " + "(sv_allowDownload is %d)\n", + clc.sv_allowDownload); + } + else if(!*clc.sv_dlURL) { + Com_Printf("WARNING: server allows " + "download redirection, but does not " + "have sv_dlURL set\n"); + } + else if(!CL_cURL_Init()) { + Com_Printf("WARNING: could not load " + "cURL library\n"); + } + else { + CL_cURL_BeginDownload(localName, va("%s/%s", + clc.sv_dlURL, remoteName)); + useCURL = qtrue; + } + } + else if(!(clc.sv_allowDownload & DLF_NO_REDIRECT)) { + Com_Printf("WARNING: server allows download " + "redirection, but it disabled by client " + "configuration (cl_allowDownload is %d)\n", + cl_allowDownload->integer); + } +#endif /* USE_CURL */ + if(!useCURL) { + Com_Printf("Trying UDP download: %s; %s\n", localName, remoteName); + + if( ( !( cl_allowDownload->integer & DLF_ENABLE ) || + ( cl_allowDownload->integer & DLF_NO_UDP ) ) && + prompt != DLP_UDP ) { + if( cl_connectedToPureServer ) { + Com_Error(ERR_DROP, "Automatic downloads are " + "disabled on your client (cl_allowDownload is %d). " + "You can enable automatic downloads in the Options " + "menu.", + cl_allowDownload->integer); + return; + } + + Com_Printf("WARNING: UDP downloads are disabled.\n"); + CL_DownloadsComplete(); + return; + } + else { + CL_BeginDownload( localName, remoteName ); + } + } + clc.downloadRestart = qtrue; + + // move over the rest + memmove( clc.downloadList, s, strlen(s) + 1); + + return; + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_InitDownloads + +After receiving a valid game state, we valid the cgame and local zip files here +and determine if we need to download them +================= +*/ +void CL_InitDownloads(void) { + if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { + Com_Printf("Need paks: %s\n", clc.downloadList ); + Cvar_Set( "com_downloadPrompt", "0" ); + if ( *clc.downloadList ) { + cls.state = CA_CONNECTED; + + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + CL_NextDownload(); + return; + } + } + CL_DownloadsComplete(); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +void CL_CheckForResend( void ) { + int port, i; + char info[MAX_INFO_STRING]; + char data[MAX_INFO_STRING]; + + // don't send anything if playing back a demo + if ( clc.demoplaying ) { + return; + } + + // resend if we haven't gotten a reply yet + if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { + return; + } + + if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { + return; + } + + clc.connectTime = cls.realtime; // for retransmit requests + clc.connectPacketCount++; + + + switch ( cls.state ) { + case CA_CONNECTING: + // requesting a challenge + + // The challenge request shall be followed by a client challenge so no malicious server can hijack this connection. + Com_sprintf(data, sizeof(data), "getchallenge %d", clc.challenge); + + NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data); + break; + + case CA_CHALLENGING: + // sending back the challenge + port = Cvar_VariableValue ("net_qport"); + + Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); + Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); + Info_SetValueForKey( info, "qport", va("%i", port ) ); + Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); + + strcpy(data, "connect "); + // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server + // (Com_TokenizeString tokenizes around spaces) + data[8] = '"'; + + for(i=0;iadr = *address; + server->clients = 0; + server->hostName[0] = '\0'; + server->mapName[0] = '\0'; + server->label[0] = '\0'; + server->maxClients = 0; + server->maxPing = 0; + server->minPing = 0; + server->ping = -1; + server->game[0] = '\0'; + server->gameType = 0; + server->netType = 0; +} + +/* +=================== +CL_GSRSequenceInformation + +Parses this packet's index and the number of packets from a master server's +response. Updates the packet count and returns the index. Advances the data +pointer as appropriate (but only when parsing was successful) + +The sequencing information isn't terribly useful at present (we can skip +duplicate packets, but we don't bother to make sure we've got all of them). +=================== +*/ +int CL_GSRSequenceInformation( byte **data ) +{ + char *p = (char *)*data, *e; + int ind, num; + // '\0'-delimited fields: this packet's index, total number of packets + if( *p++ != '\0' ) + return -1; + + ind = strtol( p, (char **)&e, 10 ); + if( *e++ != '\0' ) + return -1; + + num = strtol( e, (char **)&p, 10 ); + if( *p++ != '\0' ) + return -1; + + if( num <= 0 || ind <= 0 || ind > num ) + return -1; // nonsensical response + + if( cls.numMasterPackets > 0 && num != cls.numMasterPackets ) + { + // Assume we sent two getservers and somehow they changed in + // between - only use the results that arrive later + Com_DPrintf( "Master changed its mind about packet count!\n" ); + cls.receivedMasterPackets = 0; + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + } + cls.numMasterPackets = num; + + // successfully parsed + *data = (byte *)p; + return ind; +} + +/* +=================== +CL_GSRFeaturedLabel + +Parses from the data an arbitrary text string labelling the servers in the +following getserversresponse packet. +The result is copied to *buf, and *data is advanced as appropriate +=================== +*/ +void CL_GSRFeaturedLabel( byte **data, char *buf, int size ) +{ + char *l = buf; + buf[0] = '\0'; + + // copy until '\0' which indicates field break + // or slash which indicates beginning of server list + while( **data && **data != '\\' && **data != '/' ) + { + if( l < &buf[ size - 1 ] ) + *l = **data; + else if( l == &buf[ size - 1 ] ) + Com_DPrintf( S_COLOR_YELLOW "Warning: " + "CL_GSRFeaturedLabel: overflow\n" ); + l++, (*data)++; + } +} + +#define MAX_SERVERSPERPACKET 256 + +/* +=================== +CL_ServersResponsePacket +=================== +*/ +void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extended ) { + int i, count, total; + netadr_t addresses[MAX_SERVERSPERPACKET]; + int numservers; + byte* buffptr; + byte* buffend; + char label[MAX_FEATLABEL_CHARS] = ""; + + Com_DPrintf("CL_ServersResponsePacket%s\n", + (extended) ? " (extended)" : ""); + + if (cls.numglobalservers == -1) { + // state to detect lack of servers or lack of response + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + cls.numMasterPackets = 0; + cls.receivedMasterPackets = 0; + } + + // parse through server response string + numservers = 0; + buffptr = msg->data; + buffend = buffptr + msg->cursize; + + // skip header + buffptr += 4; + + // advance to initial token + // I considered using strchr for this but I don't feel like relying + // on its behaviour with '\0' + while( *buffptr && *buffptr != '\\' && *buffptr != '/' ) + { + buffptr++; + + if( buffptr >= buffend ) + break; + } + + if( *buffptr == '\0' ) + { + int ind = CL_GSRSequenceInformation( &buffptr ); + if( ind >= 0 ) + { + // this denotes the start of new-syntax stuff + // have we already received this packet? + if( cls.receivedMasterPackets & ( 1 << ( ind - 1 ) ) ) + { + Com_DPrintf( "CL_ServersResponsePacket: " + "received packet %d again, ignoring\n", + ind ); + return; + } + // TODO: detect dropped packets and make another + // request + Com_DPrintf( "CL_ServersResponsePacket: packet " + "%d of %d\n", ind, cls.numMasterPackets ); + cls.receivedMasterPackets |= ( 1 << ( ind - 1 ) ); + + CL_GSRFeaturedLabel( &buffptr, label, sizeof( label ) ); + } + // now skip to the server list + for(; buffptr < buffend && *buffptr != '\\' && *buffptr != '/'; + buffptr++ ); + } + + while (buffptr + 1 < buffend) + { + // IPv4 address + if (*buffptr == '\\') + { + buffptr++; + + if (buffend - buffptr < sizeof(addresses[numservers].ip) + sizeof(addresses[numservers].port) + 1) + break; + + for(i = 0; i < sizeof(addresses[numservers].ip); i++) + addresses[numservers].ip[i] = *buffptr++; + + addresses[numservers].type = NA_IP; + } + // IPv6 address, if it's an extended response + else if (extended && *buffptr == '/') + { + buffptr++; + + if (buffend - buffptr < sizeof(addresses[numservers].ip6) + sizeof(addresses[numservers].port) + 1) + break; + + for(i = 0; i < sizeof(addresses[numservers].ip6); i++) + addresses[numservers].ip6[i] = *buffptr++; + + addresses[numservers].type = NA_IP6; + addresses[numservers].scope_id = from->scope_id; + } + else + // syntax error! + break; + + // parse out port + addresses[numservers].port = (*buffptr++) << 8; + addresses[numservers].port += *buffptr++; + addresses[numservers].port = BigShort( addresses[numservers].port ); + + // syntax check + if (*buffptr != '\\' && *buffptr != '/') + break; + + numservers++; + if (numservers >= MAX_SERVERSPERPACKET) + break; + } + + count = cls.numglobalservers; + + for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++) { + // build net address + serverInfo_t *server = &cls.globalServers[count]; + + CL_InitServerInfo( server, &addresses[i] ); + Q_strncpyz( server->label, label, sizeof( server->label ) ); + // advance to next slot + count++; + } + + // if getting the global list + if ( count >= MAX_GLOBAL_SERVERS && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) + { + // if we couldn't store the servers in the main list anymore + for (; i < numservers && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS; i++) + { + // just store the addresses in an additional list + cls.globalServerAddresses[cls.numGlobalServerAddresses++] = addresses[i]; + } + } + + cls.numglobalservers = count; + total = count + cls.numGlobalServerAddresses; + + Com_Printf("%d servers parsed (total %d)\n", numservers, total); +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + + Com_DPrintf ("CL packet %s: %s\n", NET_AdrToStringwPort(from), c); + + // challenge from the server we are connecting to + if (!Q_stricmp(c, "challengeResponse")) + { + if (cls.state != CA_CONNECTING) + { + Com_DPrintf("Unwanted challenge response received. Ignored.\n"); + return; + } + + if(!NET_CompareAdr(from, clc.serverAddress)) + { + // This challenge response is not coming from the expected address. + // Check whether we have a matching client challenge to prevent + // connection hi-jacking. + + c = Cmd_Argv(2); + + if(!*c || atoi(c) != clc.challenge) + { + Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); + return; + } + } + + // start sending challenge response instead of challenge request packets + clc.challenge = atoi(Cmd_Argv(1)); + cls.state = CA_CHALLENGING; + clc.connectPacketCount = 0; + clc.connectTime = -99999; + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + clc.serverAddress = from; + Com_DPrintf ("challengeResponse: %d\n", clc.challenge); + return; + } + + // server connection + if ( !Q_stricmp(c, "connectResponse") ) { + if ( cls.state >= CA_CONNECTED ) { + Com_Printf ("Dup connect received. Ignored.\n"); + return; + } + if ( cls.state != CA_CHALLENGING ) { + Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); + return; + } + if ( !NET_CompareAdr( from, clc.serverAddress ) ) { + Com_Printf( "connectResponse from wrong address. Ignored.\n" ); + return; + } + Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); + cls.state = CA_CONNECTED; + clc.lastPacketSentTime = -9999; // send first packet immediately + return; + } + + // server responding to an info broadcast + if ( !Q_stricmp(c, "infoResponse") ) { + CL_ServerInfoPacket( from, msg ); + return; + } + + // server responding to a get playerlist + if ( !Q_stricmp(c, "statusResponse") ) { + CL_ServerStatusResponse( from, msg ); + return; + } + + // a disconnect message from the server, which will happen if the server + // dropped the connection but it is still getting packets from us + if (!Q_stricmp(c, "disconnect")) { + CL_DisconnectPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "echo") ) { + NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); + return; + } + + // global MOTD from trem master + if ( !Q_stricmp(c, "motd") ) { + CL_MotdPacket( from, s ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "print") ) { + s = MSG_ReadString( msg ); + + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); + + while( clc.serverMessage[ strlen( clc.serverMessage ) - 1 ] == '\n' ) + clc.serverMessage[ strlen( clc.serverMessage ) - 1 ] = '\0'; + + Com_Printf( "%s", s ); + return; + } + + // list of servers sent back by a master server (classic) + if ( !Q_strncmp(c, "getserversResponse", 18) ) { + CL_ServersResponsePacket( &from, msg, qfalse ); + return; + } + + // list of servers sent back by a master server (extended) + if ( !Q_strncmp(c, "getserversExtResponse", 21) ) { + CL_ServersResponsePacket( &from, msg, qtrue ); + return; + } + + Com_DPrintf ("Unknown connectionless packet command.\n"); +} + + +/* +================= +CL_PacketEvent + +A packet has arrived from the main event loop +================= +*/ +void CL_PacketEvent( netadr_t from, msg_t *msg ) { + int headerBytes; + + clc.lastPacketTime = cls.realtime; + + if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { + CL_ConnectionlessPacket( from, msg ); + return; + } + + if ( cls.state < CA_CONNECTED ) { + return; // can't be a valid sequenced packet + } + + if ( msg->cursize < 4 ) { + Com_Printf ("%s: Runt packet\n", NET_AdrToStringwPort( from )); + return; + } + + // + // packet from server + // + if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { + Com_DPrintf ("%s:sequenced packet without connection\n" + , NET_AdrToStringwPort( from ) ); + // FIXME: send a client disconnect? + return; + } + + if (!CL_Netchan_Process( &clc.netchan, msg) ) { + return; // out of order, duplicated, etc + } + + // the header is different lengths for reliable and unreliable messages + headerBytes = msg->readcount; + + // track the last message received so it can be returned in + // client messages, allowing the server to detect a dropped + // gamestate + clc.serverMessageSequence = LittleLong( *(int *)msg->data ); + + clc.lastPacketTime = cls.realtime; + CL_ParseServerMessage( msg ); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // + if ( clc.demorecording && !clc.demowaiting ) { + CL_WriteDemoMessage( msg, headerBytes ); + } +} + +/* +================== +CL_CheckTimeout + +================== +*/ +void CL_CheckTimeout( void ) { + // + // check timeout + // + if ( ( !CL_CheckPaused() || !sv_paused->integer ) + && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC + && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { + if (++cl.timeoutcount > 5) { // timeoutcount saves debugger + Com_Printf ("\nServer connection timed out.\n"); + CL_Disconnect( qtrue ); + return; + } + } else { + cl.timeoutcount = 0; + } +} + +/* +================== +CL_CheckPaused +Check whether client has been paused. +================== +*/ +qboolean CL_CheckPaused(void) +{ + // if cl_paused->modified is set, the cvar has only been changed in + // this frame. Keep paused in this frame to ensure the server doesn't + // lag behind. + if(cl_paused->integer || cl_paused->modified) + return qtrue; + + return qfalse; +} + +//============================================================================ + +/* +================== +CL_CheckUserinfo + +================== +*/ +void CL_CheckUserinfo( void ) { + // don't add reliable commands when not yet connected + if(cls.state < CA_CHALLENGING) + return; + + // don't overflow the reliable command buffer when paused + if(CL_CheckPaused()) + return; + + // send a reliable userinfo update if needed + if(cvar_modifiedFlags & CVAR_USERINFO) + { + cvar_modifiedFlags &= ~CVAR_USERINFO; + CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ), qfalse); + } +} + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame ( int msec ) { + + if ( !com_cl_running->integer ) { + return; + } + + // We may have a download prompt ready + if( ( com_downloadPrompt->integer & DLP_TYPE_MASK ) && + !( com_downloadPrompt->integer & DLP_PROMPTED ) ) { + Com_Printf( "Download prompt returned %d\n", + com_downloadPrompt->integer ); + CL_NextDownload( ); + } + else if( com_downloadPrompt->integer & DLP_SHOW ) { + // If the UI VM does not support the download prompt, we need to catch + // the prompt here and replicate regular behavior. + // One frame will always run between requesting and showing the prompt. + + if( com_downloadPrompt->integer & DLP_STALE ) { + Com_Printf( "WARNING: UI VM does not support download prompt\n" ); + Cvar_Set( "com_downloadPrompt", va( "%d", DLP_IGNORE ) ); + CL_NextDownload( ); + } else { + Cvar_Set( "com_downloadPrompt", + va( "%d", com_downloadPrompt->integer | DLP_STALE ) ); + } + } + +#ifdef USE_CURL + if(clc.downloadCURLM) { + CL_cURL_PerformDownload(); + // we can't process frames normally when in disconnected + // download mode since the ui vm expects cls.state to be + // CA_CONNECTED + if(clc.cURLDisconnected) { + cls.realFrametime = msec; + cls.frametime = msec; + cls.realtime += cls.frametime; + SCR_UpdateScreen(); + S_Update(); + Con_RunConsole(); + cls.framecount++; + return; + } + } +#endif + + if ( cls.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI ) + && !com_sv_running->integer && uivm ) { + // if disconnected, bring up the menu + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + + // if recording an avi, lock to a fixed fps + if ( CL_VideoRecording( ) && cl_aviFrameRate->integer && msec) { + // save the current screen + if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { + CL_TakeVideoFrame( ); + + // fixed time for next frame' + msec = (int)ceil( (1000.0f / cl_aviFrameRate->value) * com_timescale->value ); + if (msec == 0) { + msec = 1; + } + } + } + + if( cl_autoRecordDemo->integer ) { + if( cls.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying ) { + // If not recording a demo, and we should be, start one + qtime_t now; + char *nowString; + char *p; + char mapName[ MAX_QPATH ]; + char serverName[ MAX_OSPATH ]; + + Com_RealTime( &now ); + nowString = va( "%04d%02d%02d%02d%02d%02d", + 1900 + now.tm_year, + 1 + now.tm_mon, + now.tm_mday, + now.tm_hour, + now.tm_min, + now.tm_sec ); + + Q_strncpyz( serverName, cls.servername, MAX_OSPATH ); + // Replace the ":" in the address as it is not a valid + // file name character + p = strstr( serverName, ":" ); + if( p ) { + *p = '.'; + } + + Q_strncpyz( mapName, COM_SkipPath( cl.mapname ), sizeof( cl.mapname ) ); + COM_StripExtension(mapName, mapName, sizeof(mapName)); + + Cbuf_ExecuteText( EXEC_NOW, + va( "record %s-%s-%s", nowString, serverName, mapName ) ); + } + else if( cls.state != CA_ACTIVE && clc.demorecording ) { + // Recording, but not CA_ACTIVE, so stop recording + CL_StopRecord_f( ); + } + } + + // save the msec before checking pause + cls.realFrametime = msec; + + // decide the simulation time + cls.frametime = msec; + + cls.realtime += cls.frametime; + + if ( cl_timegraph->integer ) { + SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); + } + + // see if we need to update any userinfo + CL_CheckUserinfo(); + + // if we haven't gotten a packet in a long time, + // drop the connection + CL_CheckTimeout(); + + // send intentions now + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // decide on the serverTime to render + CL_SetCGameTime(); + + // update the screen + SCR_UpdateScreen(); + + // update audio + S_Update(); + +#ifdef USE_VOIP + CL_CaptureVoip(); +#endif + +#ifdef USE_MUMBLE + CL_UpdateMumble(); +#endif + + // advance local effects for next frame + SCR_RunCinematic(); + + Con_RunConsole(); + + cls.framecount++; +} + + +//============================================================================ + +/* +================ +CL_RefPrintf + +DLL glue +================ +*/ +void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + if ( print_level == PRINT_ALL ) { + Com_Printf ("%s", msg); + } else if ( print_level == PRINT_WARNING ) { + Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow + } else if ( print_level == PRINT_DEVELOPER ) { + Com_DPrintf (S_COLOR_RED "%s", msg); // red + } +} + + + +/* +============ +CL_ShutdownRef +============ +*/ +void CL_ShutdownRef( void ) { + if ( !re.Shutdown ) { + return; + } + re.Shutdown( qtrue ); + Com_Memset( &re, 0, sizeof( re ) ); +} + +/* +============ +CL_InitRenderer +============ +*/ +void CL_InitRenderer( void ) { + // this sets up the renderer and calls R_Init + re.BeginRegistration( &cls.glconfig ); + + // load character sets + cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); + cls.whiteShader = re.RegisterShader( "white" ); + cls.consoleShader = re.RegisterShader( "console" ); + g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; + g_consoleField.widthInChars = g_console_field_width; +} + +/* +============================ +CL_StartHunkUsers + +After the server has cleared the hunk, these will need to be restarted +This is the only place that any of these functions are called from +============================ +*/ +void CL_StartHunkUsers( qboolean rendererOnly ) { + if (!com_cl_running) { + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + if ( !cls.rendererStarted ) { + cls.rendererStarted = qtrue; + CL_InitRenderer(); + } + + if ( rendererOnly ) { + return; + } + + if ( !cls.soundStarted ) { + cls.soundStarted = qtrue; + S_Init(); + } + + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; + S_BeginRegistration(); + } + + if( com_dedicated->integer ) { + return; + } + + if ( !cls.uiStarted ) { + cls.uiStarted = qtrue; + CL_InitUI(); + } +} + +/* +============ +CL_RefMalloc +============ +*/ +void *CL_RefMalloc( int size ) { + return Z_TagMalloc( size, TAG_RENDERER ); +} + +int CL_ScaledMilliseconds(void) { + return Sys_Milliseconds()*com_timescale->value; +} + +/* +============ +CL_InitRef +============ +*/ +void CL_InitRef( void ) { + refimport_t ri; + refexport_t *ret; + + Com_Printf( "----- Initializing Renderer ----\n" ); + + ri.Cmd_AddCommand = Cmd_AddCommand; + ri.Cmd_RemoveCommand = Cmd_RemoveCommand; + ri.Cmd_Argc = Cmd_Argc; + ri.Cmd_Argv = Cmd_Argv; + ri.Cmd_ExecuteText = Cbuf_ExecuteText; + ri.Printf = CL_RefPrintf; + ri.Error = Com_Error; + ri.Milliseconds = CL_ScaledMilliseconds; + ri.Malloc = CL_RefMalloc; + ri.Free = Z_Free; +#ifdef HUNK_DEBUG + ri.Hunk_AllocDebug = Hunk_AllocDebug; +#else + ri.Hunk_Alloc = Hunk_Alloc; +#endif + ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; + ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; + ri.CM_DrawDebugSurface = CM_DrawDebugSurface; + ri.FS_ReadFile = FS_ReadFile; + ri.FS_FreeFile = FS_FreeFile; + ri.FS_WriteFile = FS_WriteFile; + ri.FS_FreeFileList = FS_FreeFileList; + ri.FS_ListFiles = FS_ListFiles; + ri.FS_FileIsInPAK = FS_FileIsInPAK; + ri.FS_FileExists = FS_FileExists; + ri.Cvar_Get = Cvar_Get; + ri.Cvar_Set = Cvar_Set; + ri.Cvar_CheckRange = Cvar_CheckRange; + + // cinematic stuff + + ri.CIN_UploadCinematic = CIN_UploadCinematic; + ri.CIN_PlayCinematic = CIN_PlayCinematic; + ri.CIN_RunCinematic = CIN_RunCinematic; + + ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame; + + ret = GetRefAPI( REF_API_VERSION, &ri ); + +#if defined __USEA3D && defined __A3D_GEOM + hA3Dg_ExportRenderGeom (ret); +#endif + + Com_Printf( "-------------------------------\n"); + + if ( !ret ) { + Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); + } + + re = *ret; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); +} + + +//=========================================================================================== + + +void CL_SetModel_f( void ) { + char *arg; + char name[256]; + + arg = Cmd_Argv( 1 ); + if (arg[0]) { + Cvar_Set( "model", arg ); + Cvar_Set( "headmodel", arg ); + } else { + Cvar_VariableStringBuffer( "model", name, sizeof(name) ); + Com_Printf("model is set to %s\n", name); + } +} + + +//=========================================================================================== + + +/* +=============== +CL_Video_f + +video +video [filename] +=============== +*/ +void CL_Video_f( void ) +{ + char filename[ MAX_OSPATH ]; + int i, last; + + if( !clc.demoplaying ) + { + Com_Printf( "The video command can only be used when playing back demos\n" ); + return; + } + + if( Cmd_Argc( ) == 2 ) + { + // explicit filename + Com_sprintf( filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv( 1 ) ); + } + else + { + // scan for a free filename + for( i = 0; i <= 9999; i++ ) + { + int a, b, c, d; + + last = i; + + a = last / 1000; + last -= a * 1000; + b = last / 100; + last -= b * 100; + c = last / 10; + last -= c * 10; + d = last; + + Com_sprintf( filename, MAX_OSPATH, "videos/video%d%d%d%d.avi", + a, b, c, d ); + + if( !FS_FileExists( filename ) ) + break; // file doesn't exist + } + + if( i > 9999 ) + { + Com_Printf( S_COLOR_RED "ERROR: no free file names to create video\n" ); + return; + } + } + + CL_OpenAVIForWriting( filename ); +} + +/* +=============== +CL_StopVideo_f +=============== +*/ +void CL_StopVideo_f( void ) +{ + CL_CloseAVI( ); +} + +/* +=============== +CL_GenerateQKey + +test to see if a valid QKEY_FILE exists. If one does not, try to generate +it by filling it with 2048 bytes of random data. +=============== +*/ +static void CL_GenerateQKey(void) +{ + int len = 0; + unsigned char buff[ QKEY_SIZE ]; + fileHandle_t f; + + len = FS_SV_FOpenFileRead( QKEY_FILE, &f ); + FS_FCloseFile( f ); + if( len == QKEY_SIZE ) { + Com_Printf( "QKEY found.\n" ); + return; + } + else { + if( len > 0 ) { + Com_Printf( "QKEY file size != %d, regenerating\n", + QKEY_SIZE ); + } + + Com_Printf( "QKEY building random string\n" ); + Com_RandomBytes( buff, sizeof(buff) ); + + f = FS_SV_FOpenFileWrite( QKEY_FILE ); + if( !f ) { + Com_Printf( "QKEY could not open %s for write\n", + QKEY_FILE ); + return; + } + FS_Write( buff, sizeof(buff), f ); + FS_FCloseFile( f ); + Com_Printf( "QKEY generated\n" ); + } +} + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) { + Com_Printf( "----- Client Initialization -----\n" ); + + Con_Init (); + + CL_ClearState (); + + cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + + cls.realtime = 0; + + CL_InitInput (); + + // + // register our variables + // + cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); + cl_motd = Cvar_Get ("cl_motd", "1", 0); + + cl_timeout = Cvar_Get ("cl_timeout", "200", 0); + + cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); + cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); + cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); + cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); + rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); + + cl_timedemo = Cvar_Get ("timedemo", "0", 0); + cl_timedemoLog = Cvar_Get ("cl_timedemoLog", "", CVAR_ARCHIVE); + cl_autoRecordDemo = Cvar_Get ("cl_autoRecordDemo", "0", CVAR_ARCHIVE); + cl_aviFrameRate = Cvar_Get ("cl_aviFrameRate", "25", CVAR_ARCHIVE); + cl_aviMotionJpeg = Cvar_Get ("cl_aviMotionJpeg", "1", CVAR_ARCHIVE); + cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); + + rconAddress = Cvar_Get ("rconAddress", "", 0); + + cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); + cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); + cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); + + cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); + cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); + + cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); + cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); + cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); + cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); + + // 0: legacy mouse acceleration + // 1: new implementation + cl_mouseAccelStyle = Cvar_Get( "cl_mouseAccelStyle", "0", CVAR_ARCHIVE ); + // offset for the power function (for style 1, ignored otherwise) + // this should be set to the max rate value + cl_mouseAccelOffset = Cvar_Get( "cl_mouseAccelOffset", "5", CVAR_ARCHIVE ); + Cvar_CheckRange(cl_mouseAccelOffset, 0.001f, 50000.0f, qfalse); + + cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); + + cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); +#ifdef USE_CURL + cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE); +#endif + com_downloadPrompt = Cvar_Get ("com_downloadPrompt", "0", CVAR_ROM); + Cvar_Get( "com_downloadPromptText", "", CVAR_TEMP ); + + cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); +#ifdef MACOS_X + // In game video is REALLY slow in Mac OS X right now due to driver slowness + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); +#else + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); +#endif + + cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); + + m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); + m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); + m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); + m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); +#ifdef MACOS_X + // Input is jittery on OS X w/o this + m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); +#else + m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); +#endif + + j_pitch = Cvar_Get ("j_pitch", "0.022", CVAR_ARCHIVE); + j_yaw = Cvar_Get ("j_yaw", "-0.022", CVAR_ARCHIVE); + j_forward = Cvar_Get ("j_forward", "-0.25", CVAR_ARCHIVE); + j_side = Cvar_Get ("j_side", "0.25", CVAR_ARCHIVE); + j_pitch_axis = Cvar_Get ("j_pitch_axis", "3", CVAR_ARCHIVE); + j_yaw_axis = Cvar_Get ("j_yaw_axis", "4", CVAR_ARCHIVE); + j_forward_axis = Cvar_Get ("j_forward_axis", "1", CVAR_ARCHIVE); + j_side_axis = Cvar_Get ("j_side_axis", "0", CVAR_ARCHIVE); + + cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); + + Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); + + cl_lanForcePackets = Cvar_Get ("cl_lanForcePackets", "1", CVAR_ARCHIVE); + + cl_guidServerUniq = Cvar_Get ("cl_guidServerUniq", "1", CVAR_ARCHIVE); + + // ~ and `, as keys and characters + cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE); + + cl_gamename = Cvar_Get("cl_gamename", GAMENAME_FOR_MASTER, CVAR_TEMP); + + // userinfo + Cvar_Get ("name", Sys_GetCurrentUser( ), CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); + + Cvar_Get ("password", "", CVAR_USERINFO); + +#ifdef USE_MUMBLE + cl_useMumble = Cvar_Get ("cl_useMumble", "0", CVAR_ARCHIVE | CVAR_LATCH); + cl_mumbleScale = Cvar_Get ("cl_mumbleScale", "0.0254", CVAR_ARCHIVE); +#endif + +#ifdef USE_VOIP + cl_voipSend = Cvar_Get ("cl_voipSend", "0", 0); + cl_voipSendTarget = Cvar_Get ("cl_voipSendTarget", "all", 0); + cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE); + cl_voipCaptureMult = Cvar_Get ("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE); + cl_voipUseVAD = Cvar_Get ("cl_voipUseVAD", "0", CVAR_ARCHIVE); + cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE); + cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter", "1", CVAR_ARCHIVE); + + // This is a protocol version number. + cl_voip = Cvar_Get ("cl_voip", "1", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_LATCH); + Cvar_CheckRange( cl_voip, 0, 1, qtrue ); + + // If your data rate is too low, you'll get Connection Interrupted warnings + // when VoIP packets arrive, even if you have a broadband connection. + // This might work on rates lower than 25000, but for safety's sake, we'll + // just demand it. Who doesn't have at least a DSL line now, anyhow? If + // you don't, you don't need VoIP. :) + if ((cl_voip->integer) && (Cvar_VariableIntegerValue("rate") < 25000)) { + Com_Printf(S_COLOR_YELLOW "Your network rate is too slow for VoIP.\n"); + Com_Printf("Set 'Data Rate' to 'LAN/Cable/xDSL' in 'Setup/System/Network' and restart.\n"); + Com_Printf("Until then, VoIP is disabled.\n"); + Cvar_Set("cl_voip", "0"); + } +#endif + + + // cgame might not be initialized before menu is used + Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); + // Make sure cg_stereoSeparation is zero as that variable is deprecated and should not be used anymore. + Cvar_Get ("cg_stereoSeparation", "0", CVAR_ROM); + + // + // register our commands + // + Cmd_AddCommand ("cmd", CL_ForwardToServer_f); + Cmd_AddCommand ("configstrings", CL_Configstrings_f); + Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); + Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); + Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("demo", CL_PlayDemo_f); + Cmd_SetCommandCompletionFunc( "demo", CL_CompleteDemoName ); + Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); + Cmd_AddCommand ("stoprecord", CL_StopRecord_f); + Cmd_AddCommand ("connect", CL_Connect_f); + Cmd_AddCommand ("reconnect", CL_Reconnect_f); + Cmd_AddCommand ("localservers", CL_LocalServers_f); + Cmd_AddCommand ("globalservers", CL_GlobalServers_f); + Cmd_AddCommand ("rcon", CL_Rcon_f); + Cmd_SetCommandCompletionFunc( "rcon", CL_CompleteRcon ); + Cmd_AddCommand ("ping", CL_Ping_f ); + Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); + Cmd_AddCommand ("showip", CL_ShowIP_f ); + Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); + Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); + Cmd_AddCommand ("model", CL_SetModel_f ); + Cmd_AddCommand ("video", CL_Video_f ); + Cmd_AddCommand ("stopvideo", CL_StopVideo_f ); + Cmd_AddCommand("minimize", GLimp_Minimize); + CL_InitRef(); + + SCR_Init (); + +// Cbuf_Execute (); + + Cvar_Set( "cl_running", "1" ); + + CL_GenerateQKey(); + Cvar_Get( "cl_guid", "", CVAR_USERINFO | CVAR_ROM ); + CL_UpdateGUID( NULL, 0 ); + + Com_Printf( "----- Client Initialization Complete -----\n" ); +} + + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown( char *finalmsg ) { + static qboolean recursive = qfalse; + + // check whether the client is running at all. + if(!(com_cl_running && com_cl_running->integer)) + return; + + Com_Printf( "----- Client Shutdown (%s) -----\n", finalmsg ); + + if ( recursive ) { + Com_Printf( "WARNING: Recursive shutdown\n" ); + return; + } + recursive = qtrue; + + CL_Disconnect( qtrue ); + + S_Shutdown(); + CL_ShutdownRef(); + + CL_ShutdownUI(); + + Cmd_RemoveCommand ("cmd"); + Cmd_RemoveCommand ("configstrings"); + Cmd_RemoveCommand ("userinfo"); + Cmd_RemoveCommand ("snd_restart"); + Cmd_RemoveCommand ("vid_restart"); + Cmd_RemoveCommand ("disconnect"); + Cmd_RemoveCommand ("record"); + Cmd_RemoveCommand ("demo"); + Cmd_RemoveCommand ("cinematic"); + Cmd_RemoveCommand ("stoprecord"); + Cmd_RemoveCommand ("connect"); + Cmd_RemoveCommand ("localservers"); + Cmd_RemoveCommand ("globalservers"); + Cmd_RemoveCommand ("rcon"); + Cmd_RemoveCommand ("ping"); + Cmd_RemoveCommand ("serverstatus"); + Cmd_RemoveCommand ("showip"); + Cmd_RemoveCommand ("model"); + Cmd_RemoveCommand ("video"); + Cmd_RemoveCommand ("stopvideo"); + + Cvar_Set( "cl_running", "0" ); + + recursive = qfalse; + + Com_Memset( &cls, 0, sizeof( cls ) ); + Key_SetCatcher( 0 ); + + Com_Printf( "-----------------------\n" ); + +} + +static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { + if (server) { + if (info) { + const char *game; + + server->clients = atoi(Info_ValueForKey(info, "clients")); + Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_HOSTNAME_LENGTH ); + Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); + server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); + game = Info_ValueForKey(info, "game"); + Q_strncpyz(server->game, (game[0]) ? game : BASEGAME, MAX_NAME_LENGTH); + server->gameType = atoi(Info_ValueForKey(info, "gametype")); + server->netType = atoi(Info_ValueForKey(info, "nettype")); + server->minPing = atoi(Info_ValueForKey(info, "minping")); + server->maxPing = atoi(Info_ValueForKey(info, "maxping")); + } + server->ping = ping; + } +} + +static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { + int i; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.localServers[i].adr)) { + CL_SetServerInfo(&cls.localServers[i], info, ping); + } + } + + for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { + if (NET_CompareAdr(from, cls.globalServers[i].adr)) { + CL_SetServerInfo(&cls.globalServers[i], info, ping); + } + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { + CL_SetServerInfo(&cls.favoriteServers[i], info, ping); + } + } + +} + +/* +=================== +CL_ServerInfoPacket +=================== +*/ +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { + int i, type; + char info[MAX_INFO_STRING]; + char *infoString; + int prot; + + infoString = MSG_ReadString( msg ); + + // if this isn't the correct protocol version, ignore it + prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); + if ( prot != PROTOCOL_VERSION ) { + Com_DPrintf( "Different protocol info packet: %s\n", infoString ); + return; + } + + // iterate servers waiting for ping response + for (i=0; iretrieved = qtrue; + return qfalse; + } + + // if this server status request has the same address + if ( NET_CompareAdr( to, serverStatus->address) ) { + // if we recieved an response for this server status request + if (!serverStatus->pending) { + Q_strncpyz(serverStatusString, serverStatus->string, maxLen); + serverStatus->retrieved = qtrue; + serverStatus->startTime = 0; + return qtrue; + } + // resend the request regularly + else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->time = 0; + serverStatus->startTime = Com_Milliseconds(); + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + } + // if retrieved + else if ( serverStatus->retrieved ) { + serverStatus->address = to; + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->startTime = Com_Milliseconds(); + serverStatus->time = 0; + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + return qfalse; +} + +/* +=================== +CL_ServerStatusResponse +=================== +*/ +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { + char *s; + char info[MAX_INFO_STRING]; + int i, l, score, ping; + int len; + serverStatus_t *serverStatus; + + serverStatus = NULL; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { + serverStatus = &cl_serverStatusList[i]; + break; + } + } + // if we didn't request this server status + if (!serverStatus) { + return; + } + + s = MSG_ReadStringLine( msg ); + + len = 0; + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); + + if (serverStatus->print) { + Com_Printf("Server settings:\n"); + // print cvars + while (*s) { + for (i = 0; i < 2 && *s; i++) { + if (*s == '\\') + s++; + l = 0; + while (*s) { + info[l++] = *s; + if (l >= MAX_INFO_STRING-1) + break; + s++; + if (*s == '\\') { + break; + } + } + info[l] = '\0'; + if (i) { + Com_Printf("%s\n", info); + } + else { + Com_Printf("%-24s", info); + } + } + } + } + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + if (serverStatus->print) { + Com_Printf("\nPlayers:\n"); + Com_Printf("num: score: ping: name:\n"); + } + for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); + + if (serverStatus->print) { + score = ping = 0; + sscanf(s, "%d %d", &score, &ping); + s = strchr(s, ' '); + if (s) + s = strchr(s+1, ' '); + if (s) + s++; + else + s = "unknown"; + Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); + } + } + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + serverStatus->time = Com_Milliseconds(); + serverStatus->address = from; + serverStatus->pending = qfalse; + if (serverStatus->print) { + serverStatus->retrieved = qtrue; + } +} + +/* +================== +CL_LocalServers_f +================== +*/ +void CL_LocalServers_f( void ) { + char *message; + int i, j; + netadr_t to; + + Com_Printf( "Scanning for servers on the local network...\n"); + + // reset the list, waiting for response + cls.numlocalservers = 0; + cls.pingUpdateSource = AS_LOCAL; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + qboolean b = cls.localServers[i].visible; + Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); + cls.localServers[i].visible = b; + } + Com_Memset( &to, 0, sizeof( to ) ); + + // The 'xxx' in the message is a challenge that will be echoed back + // by the server. We don't care about that here, but master servers + // can use that to prevent spoofed server responses from invalid ip + message = "\377\377\377\377getinfo xxx"; + + // send each message twice in case one is dropped + for ( i = 0 ; i < 2 ; i++ ) { + // send a broadcast packet on each server port + // we support multiple server ports so a single machine + // can nicely run multiple servers + for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { + to.port = BigShort( (short)(PORT_SERVER + j) ); + + to.type = NA_BROADCAST; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + to.type = NA_MULTICAST6; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + } + } +} + +/* +================== +CL_GlobalServers_f +================== +*/ +void CL_GlobalServers_f( void ) { + netadr_t to; + int count, i, masterNum; + char command[1024], *masteraddress; + + if ((count = Cmd_Argc()) < 2 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS - 1) + { + Com_Printf("usage: globalservers [keywords]\n", MAX_MASTER_SERVERS - 1); + return; + } + + sprintf(command, "sv_master%d", masterNum + 1); + masteraddress = Cvar_VariableString(command); + + if(!*masteraddress) + { + Com_Printf( "CL_GlobalServers_f: Error: No master server address given.\n"); + return; + } + + // reset the list, waiting for response + // -1 is used to distinguish a "no response" + + i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC); + + if(!i) + { + Com_Printf( "CL_GlobalServers_f: Error: could not resolve address of master %s\n", masteraddress); + return; + } + else if(i == 2) + to.port = BigShort(PORT_MASTER); + + Com_Printf("Requesting servers from master %s...\n", masteraddress); + + cls.numglobalservers = -1; + cls.pingUpdateSource = AS_GLOBAL; + + Com_sprintf( command, sizeof( command ), "getserversExt %s %d", + cl_gamename->string, PROTOCOL_VERSION ); + // TODO: test if we only have IPv4/IPv6, if so request only the relevant + // servers with getserversExt %s %d ipvX + // not that big a deal since the extra servers won't respond to getinfo + // anyway. + + for (i=3; i < count; i++) + { + Q_strcat(command, sizeof(command), " "); + Q_strcat(command, sizeof(command), Cmd_Argv(i)); + } + + NET_OutOfBandPrint( NS_SERVER, to, "%s", command ); + CL_RequestMotd(); +} + + +/* +================== +CL_GetPing +================== +*/ +void CL_GetPing( int n, char *buf, int buflen, int *pingtime ) +{ + const char *str; + int time; + int maxPing; + + if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port) + { + // empty or invalid slot + buf[0] = '\0'; + *pingtime = 0; + return; + } + + str = NET_AdrToStringwPort( cl_pinglist[n].adr ); + Q_strncpyz( buf, str, buflen ); + + time = cl_pinglist[n].time; + if (!time) + { + // check for timeout + time = Sys_Milliseconds() - cl_pinglist[n].start; + maxPing = Cvar_VariableIntegerValue( "cl_maxPing" ); + if( maxPing < 100 ) { + maxPing = 100; + } + if (time < maxPing) + { + // not timed out yet + time = 0; + } + } + + CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time); + + *pingtime = time; +} + +/* +================== +CL_GetPingInfo +================== +*/ +void CL_GetPingInfo( int n, char *buf, int buflen ) +{ + if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port) + { + // empty or invalid slot + if (buflen) + buf[0] = '\0'; + return; + } + + Q_strncpyz( buf, cl_pinglist[n].info, buflen ); +} + +/* +================== +CL_ClearPing +================== +*/ +void CL_ClearPing( int n ) +{ + if (n < 0 || n >= MAX_PINGREQUESTS) + return; + + cl_pinglist[n].adr.port = 0; +} + +/* +================== +CL_GetPingQueueCount +================== +*/ +int CL_GetPingQueueCount( void ) +{ + int i; + int count; + ping_t* pingptr; + + count = 0; + pingptr = cl_pinglist; + + for (i=0; iadr.port) { + count++; + } + } + + return (count); +} + +/* +================== +CL_GetFreePing +================== +*/ +ping_t* CL_GetFreePing( void ) +{ + ping_t* pingptr; + ping_t* best; + int oldest; + int i; + int time; + + pingptr = cl_pinglist; + for (i=0; iadr.port) + { + if (!pingptr->time) + { + if (Sys_Milliseconds() - pingptr->start < 500) + { + // still waiting for response + continue; + } + } + else if (pingptr->time < 500) + { + // results have not been queried + continue; + } + } + + // clear it + pingptr->adr.port = 0; + return (pingptr); + } + + // use oldest entry + pingptr = cl_pinglist; + best = cl_pinglist; + oldest = INT_MIN; + for (i=0; istart; + if (time > oldest) + { + oldest = time; + best = pingptr; + } + } + + return (best); +} + +/* +================== +CL_Ping_f +================== +*/ +void CL_Ping_f( void ) { + netadr_t to; + ping_t* pingptr; + char* server; + int argc; + netadrtype_t family = NA_UNSPEC; + + argc = Cmd_Argc(); + + if ( argc != 2 && argc != 3 ) { + Com_Printf( "usage: ping [-4|-6] server\n"); + return; + } + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + Com_Memset( &to, 0, sizeof(netadr_t) ); + + if ( !NET_StringToAdr( server, &to, family ) ) { + return; + } + + pingptr = CL_GetFreePing(); + + memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); + pingptr->start = Sys_Milliseconds(); + pingptr->time = 0; + + CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); + + NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); +} + +/* +================== +CL_UpdateVisiblePings_f +================== +*/ +qboolean CL_UpdateVisiblePings_f(int source) { + int slots, i; + char buff[MAX_STRING_CHARS]; + int pingTime; + int max; + qboolean status = qfalse; + + if (source < 0 || source > AS_FAVORITES) { + return qfalse; + } + + cls.pingUpdateSource = source; + + slots = CL_GetPingQueueCount(); + if (slots < MAX_PINGREQUESTS) { + serverInfo_t *server = NULL; + + max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; + switch (source) { + case AS_LOCAL : + server = &cls.localServers[0]; + max = cls.numlocalservers; + break; + case AS_GLOBAL : + server = &cls.globalServers[0]; + max = cls.numglobalservers; + break; + case AS_FAVORITES : + server = &cls.favoriteServers[0]; + max = cls.numfavoriteservers; + break; + default: + return qfalse; + } + for (i = 0; i < max; i++) { + if (server[i].visible) { + if (server[i].ping == -1) { + int j; + + if (slots >= MAX_PINGREQUESTS) { + break; + } + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + continue; + } + if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { + // already on the list + break; + } + } + if (j >= MAX_PINGREQUESTS) { + status = qtrue; + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + break; + } + } + memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); + cl_pinglist[j].start = Sys_Milliseconds(); + cl_pinglist[j].time = 0; + NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); + slots++; + } + } + // if the server has a ping higher than cl_maxPing or + // the ping packet got lost + else if (server[i].ping == 0) { + // if we are updating global servers + if (source == AS_GLOBAL) { + // + if ( cls.numGlobalServerAddresses > 0 ) { + // overwrite this server with one from the additional global servers + cls.numGlobalServerAddresses--; + CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); + // NOTE: the server[i].visible flag stays untouched + } + } + } + } + } + } + + if (slots) { + status = qtrue; + } + for (i = 0; i < MAX_PINGREQUESTS; i++) { + if (!cl_pinglist[i].adr.port) { + continue; + } + CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); + if (pingTime != 0) { + CL_ClearPing(i); + status = qtrue; + } + } + + return status; +} + +/* +================== +CL_ServerStatus_f +================== +*/ +void CL_ServerStatus_f(void) { + netadr_t to, *toptr = NULL; + char *server; + serverStatus_t *serverStatus; + int argc; + netadrtype_t family = NA_UNSPEC; + + argc = Cmd_Argc(); + + if ( argc != 2 && argc != 3 ) + { + if (cls.state != CA_ACTIVE || clc.demoplaying) + { + Com_Printf ("Not connected to a server.\n"); + Com_Printf( "usage: serverstatus [-4|-6] server\n"); + return; + } + + toptr = &clc.serverAddress; + } + + if(!toptr) + { + Com_Memset( &to, 0, sizeof(netadr_t) ); + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + toptr = &to; + if ( !NET_StringToAdr( server, toptr, family ) ) + return; + } + + NET_OutOfBandPrint( NS_CLIENT, *toptr, "getstatus" ); + + serverStatus = CL_GetServerStatus( *toptr ); + serverStatus->address = *toptr; + serverStatus->print = qtrue; + serverStatus->pending = qtrue; +} + +/* +================== +CL_ShowIP_f +================== +*/ +void CL_ShowIP_f(void) { + Sys_ShowIP(); +} diff --git a/src/client/cl_net_chan.c b/src/client/cl_net_chan.c new file mode 100644 index 0000000..561f322 --- /dev/null +++ b/src/client/cl_net_chan.c @@ -0,0 +1,168 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "client.h" + +/* +============== +CL_Netchan_Encode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Encode( msg_t *msg ) { + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit, soob; + byte key, *string; + + if ( msg->cursize <= CL_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = 0; + + serverId = MSG_ReadLong(msg); + messageAcknowledge = MSG_ReadLong(msg); + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)clc.serverCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // + key = clc.challenge ^ serverId ^ messageAcknowledge; + for (i = CL_ENCODE_START; i < msg->cursize; i++) { + // modify the key with the last received now acknowledged server command + if (!string[index]) + index = 0; + if (string[index] > 127) { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // encode the data with this key + *(msg->data + i) = (*(msg->data + i)) ^ key; + } +} + +/* +============== +CL_Netchan_Decode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Decode( msg_t *msg ) { + long reliableAcknowledge, i, index; + byte key, *string; + int srdc, sbit, soob; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = 0; + + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *) clc.reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // xor the client challenge with the netchan sequence number (need something that changes every message) + key = clc.challenge ^ LittleLong( *(unsigned *)msg->data ); + for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++) { + // modify the key with the last sent and with this message acknowledged client command + if (!string[index]) + index = 0; + if (string[index] > 127) { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // decode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +================= +CL_Netchan_TransmitNextFragment +================= +*/ +void CL_Netchan_TransmitNextFragment( netchan_t *chan ) { + Netchan_TransmitNextFragment( chan ); +} + +/* +=============== +CL_Netchan_Transmit +================ +*/ +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { + MSG_WriteByte( msg, clc_EOF ); + + CL_Netchan_Encode( msg ); + Netchan_Transmit( chan, msg->cursize, msg->data ); +} + +extern int oldsize; +int newsize = 0; + +/* +================= +CL_Netchan_Process +================= +*/ +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { + int ret; + + ret = Netchan_Process( chan, msg ); + if (!ret) + return qfalse; + CL_Netchan_Decode( msg ); + newsize += msg->cursize; + return qtrue; +} diff --git a/src/client/cl_parse.c b/src/client/cl_parse.c new file mode 100644 index 0000000..42465f1 --- /dev/null +++ b/src/client/cl_parse.c @@ -0,0 +1,910 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_parse.c -- parse a message received from the server + +#include "client.h" + +char *svc_strings[256] = { + "svc_bad", + + "svc_nop", + "svc_gamestate", + "svc_configstring", + "svc_baseline", + "svc_serverCommand", + "svc_download", + "svc_snapshot", + "svc_EOF", + "svc_extension", + "svc_voip", +}; + +void SHOWNET( msg_t *msg, char *s) { + if ( cl_shownet->integer >= 2) { + Com_Printf ("%3i:%s\n", msg->readcount-1, s); + } +} + + +/* +========================================================================= + +MESSAGE PARSING + +========================================================================= +*/ + +/* +================== +CL_DeltaEntity + +Parses deltas from the given base and adds the resulting entity +to the current frame +================== +*/ +void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, + qboolean unchanged) { + entityState_t *state; + + // save the parsed entity state into the big circular buffer so + // it can be used as the source for a later delta + state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)]; + + if ( unchanged ) { + *state = *old; + } else { + MSG_ReadDeltaEntity( msg, old, state, newnum ); + } + + if ( state->number == (MAX_GENTITIES-1) ) { + return; // entity was delta removed + } + cl.parseEntitiesNum++; + frame->numEntities++; +} + +/* +================== +CL_ParsePacketEntities + +================== +*/ +void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) { + int newnum; + entityState_t *oldstate; + int oldindex, oldnum; + + newframe->parseEntitiesNum = cl.parseEntitiesNum; + newframe->numEntities = 0; + + // delta from the entities present in oldframe + oldindex = 0; + oldstate = NULL; + if (!oldframe) { + oldnum = 99999; + } else { + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + + while ( 1 ) { + // read the entity index number + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + + if ( newnum == (MAX_GENTITIES-1) ) { + break; + } + + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message"); + } + + while ( oldnum < newnum ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + if (oldnum == newnum) { + // delta from previous state + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + continue; + } + + if ( oldnum > newnum ) { + // delta from baseline + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse ); + continue; + } + + } + + // any remaining entities in the old frame are copied over + while ( oldnum != 99999 ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } +} + + +/* +================ +CL_ParseSnapshot + +If the snapshot is parsed properly, it will be copied to +cl.snap and saved in cl.snapshots[]. If the snapshot is invalid +for any reason, no changes to the state will be made at all. +================ +*/ +void CL_ParseSnapshot( msg_t *msg ) { + int len; + clSnapshot_t *old; + clSnapshot_t newSnap; + int deltaNum; + int oldMessageNum; + int i, packetNum; + + // get the reliable sequence acknowledge number + // NOTE: now sent with all server to client messages + //clc.reliableAcknowledge = MSG_ReadLong( msg ); + + // read in the new snapshot to a temporary buffer + // we will only copy to cl.snap if it is valid + Com_Memset (&newSnap, 0, sizeof(newSnap)); + + // we will have read any new server commands in this + // message before we got to svc_snapshot + newSnap.serverCommandNum = clc.serverCommandSequence; + + newSnap.serverTime = MSG_ReadLong( msg ); + + // if we were just unpaused, we can only *now* really let the + // change come into effect or the client hangs. + cl_paused->modified = 0; + + newSnap.messageNum = clc.serverMessageSequence; + + deltaNum = MSG_ReadByte( msg ); + if ( !deltaNum ) { + newSnap.deltaNum = -1; + } else { + newSnap.deltaNum = newSnap.messageNum - deltaNum; + } + newSnap.snapFlags = MSG_ReadByte( msg ); + + // If the frame is delta compressed from data that we + // no longer have available, we must suck up the rest of + // the frame, but not use it, then ask for a non-compressed + // message + if ( newSnap.deltaNum <= 0 ) { + newSnap.valid = qtrue; // uncompressed frame + old = NULL; + clc.demowaiting = qfalse; // we can start recording now + } else { + old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; + if ( !old->valid ) { + // should never happen + Com_Printf ("Delta from invalid frame (not supposed to happen!).\n"); + } else if ( old->messageNum != newSnap.deltaNum ) { + // The frame that the server did the delta from + // is too old, so we can't reconstruct it properly. + Com_Printf ("Delta frame too old.\n"); + } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) { + Com_Printf ("Delta parseEntitiesNum too old.\n"); + } else { + newSnap.valid = qtrue; // valid delta parse + } + } + + // read areamask + len = MSG_ReadByte( msg ); + + if(len > sizeof(newSnap.areamask)) + { + Com_Error (ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask.", len); + return; + } + + MSG_ReadData( msg, &newSnap.areamask, len); + + // read playerinfo + SHOWNET( msg, "playerstate" ); + if ( old ) { + MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps ); + } else { + MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps ); + } + + // read packet entities + SHOWNET( msg, "packet entities" ); + CL_ParsePacketEntities( msg, old, &newSnap ); + + // if not valid, dump the entire thing now that it has + // been properly read + if ( !newSnap.valid ) { + return; + } + + // clear the valid flags of any snapshots between the last + // received and this one, so if there was a dropped packet + // it won't look like something valid to delta from next + // time we wrap around in the buffer + oldMessageNum = cl.snap.messageNum + 1; + + if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { + oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); + } + for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { + cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; + } + + // copy to the current good spot + cl.snap = newSnap; + cl.snap.ping = 999; + // calculate ping time + for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { + packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK; + if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) { + cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime; + break; + } + } + // save the frame off in the backup array for later delta comparisons + cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; + + if (cl_shownet->integer == 3) { + Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, + cl.snap.deltaNum, cl.snap.ping ); + } + + cl.newSnapshots = qtrue; +} + + +//===================================================================== + +int cl_connectedToPureServer; +int cl_connectedToCheatServer; + +#ifdef USE_VOIP +int cl_connectedToVoipServer; +#endif + +/* +================== +CL_SystemInfoChanged + +The systeminfo configstring has been changed, so parse +new information out of it. This will happen at every +gamestate, and possibly during gameplay. +================== +*/ +void CL_SystemInfoChanged( void ) { + char *systemInfo; + const char *s, *t; + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + qboolean gameSet; + + systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ]; + // NOTE TTimo: + // when the serverId changes, any further messages we send to the server will use this new serverId + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // in some cases, outdated cp commands might get sent with this news serverId + cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) ); + + // don't set any vars when playing a demo + if ( clc.demoplaying ) { + return; + } + +#ifdef USE_VOIP + // in the future, (val) will be a protocol version string, so only + // accept explicitly 1, not generally non-zero. + s = Info_ValueForKey( systemInfo, "sv_voip" ); + cl_connectedToVoipServer = (atoi( s ) == 1); +#endif + + s = Info_ValueForKey( systemInfo, "sv_cheats" ); + cl_connectedToCheatServer = atoi( s ); + if ( !cl_connectedToCheatServer ) { + Cvar_SetCheatState(); + } + + // check pure server string + s = Info_ValueForKey( systemInfo, "sv_paks" ); + t = Info_ValueForKey( systemInfo, "sv_pakNames" ); + FS_PureServerSetLoadedPaks( s, t ); + + s = Info_ValueForKey( systemInfo, "sv_referencedPaks" ); + t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" ); + FS_PureServerSetReferencedPaks( s, t ); + + gameSet = qfalse; + // scan through all the variables in the systeminfo and locally set cvars to match + s = systemInfo; + while ( s ) { + int cvar_flags; + + Info_NextPair( &s, key, value ); + if ( !key[0] ) { + break; + } + + // ehw! + if (!Q_stricmp(key, "fs_game")) + { + if(FS_CheckDirTraversal(value)) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Server sent invalid fs_game value %s\n", value); + continue; + } + + gameSet = qtrue; + } + + if((cvar_flags = Cvar_Flags(key)) == CVAR_NONEXISTENT) + Cvar_Get(key, value, CVAR_SERVER_CREATED | CVAR_ROM); + else + { + // If this cvar may not be modified by a server discard the value. + if(!(cvar_flags & (CVAR_SYSTEMINFO | CVAR_SERVER_CREATED | CVAR_USER_CREATED))) + { + Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value); + continue; + } + + Cvar_SetSafe(key, value); + } + } + // if game folder should not be set and it is set at the client side + if ( !gameSet && *Cvar_VariableString("fs_game") ) { + Cvar_Set( "fs_game", "" ); + } + cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" ); +} + +/* +================== +CL_ParseServerInfo +================== +*/ +static void CL_ParseServerInfo(void) +{ + const char *serverInfo; + + serverInfo = cl.gameState.stringData + + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + + clc.sv_allowDownload = atoi(Info_ValueForKey(serverInfo, + "sv_allowDownload")); + Q_strncpyz(clc.sv_dlURL, + Info_ValueForKey(serverInfo, "sv_dlURL"), + sizeof(clc.sv_dlURL)); +} + +/* +================== +CL_ParseGamestate +================== +*/ +void CL_ParseGamestate( msg_t *msg ) { + int i; + entityState_t *es; + int newnum; + entityState_t nullstate; + int cmd; + char *s; + + Con_Close(); + + clc.connectPacketCount = 0; + + // wipe local client state + CL_ClearState(); + + // a gamestate always marks a server command sequence + clc.serverCommandSequence = MSG_ReadLong( msg ); + + // parse all the configstrings and baselines + cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings + while ( 1 ) { + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF ) { + break; + } + + if ( cmd == svc_configstring ) { + int len; + + i = MSG_ReadShort( msg ); + if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + s = MSG_ReadBigString( msg ); + len = strlen( s ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); + cl.gameState.dataCount += len + 1; + } else if ( cmd == svc_baseline ) { + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + if ( newnum < 0 || newnum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); + } + Com_Memset (&nullstate, 0, sizeof(nullstate)); + es = &cl.entityBaselines[ newnum ]; + MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); + } else { + Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); + } + } + + clc.clientNum = MSG_ReadLong(msg); + // read the checksum feed + clc.checksumFeed = MSG_ReadLong( msg ); + + // parse useful values out of CS_SERVERINFO + CL_ParseServerInfo(); + + // parse serverId and other cvars + CL_SystemInfoChanged(); + + // stop recording now so the demo won't have an unnecessary level load at the end. + if(cl_autoRecordDemo->integer && clc.demorecording) + CL_StopRecord_f(); + + // reinitialize the filesystem if the game directory has changed + FS_ConditionalRestart( clc.checksumFeed ); + + // This used to call CL_StartHunkUsers, but now we enter the download state before loading the + // cgame + CL_InitDownloads(); + + // make sure the game starts + Cvar_Set( "cl_paused", "0" ); +} + + +//===================================================================== + +/* +===================== +CL_ParseDownload + +A download message has been received from the server +===================== +*/ +void CL_ParseDownload ( msg_t *msg ) { + int size; + unsigned char data[MAX_MSGLEN]; + int block; + + if (!*clc.downloadTempName) { + Com_Printf("Server sending download, but no download was requested\n"); + CL_AddReliableCommand("stopdl", qfalse); + return; + } + + // read the data + block = MSG_ReadShort ( msg ); + + if ( !block ) + { + // block zero is special, contains file size + clc.downloadSize = MSG_ReadLong ( msg ); + + Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); + + if (clc.downloadSize < 0) + { + Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) ); + return; + } + } + + size = MSG_ReadShort ( msg ); + if (size < 0 || size > sizeof(data)) + { + Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk.", size); + return; + } + + MSG_ReadData(msg, data, size); + + if (clc.downloadBlock != block) { + Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", clc.downloadBlock, block); + return; + } + + // open the file if not opened yet + if (!clc.download) + { + clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName ); + + if (!clc.download) { + Com_Printf( "Could not create %s\n", clc.downloadTempName ); + CL_AddReliableCommand("stopdl", qfalse); + CL_NextDownload(); + return; + } + } + + if (size) + FS_Write( data, size, clc.download ); + + CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), qfalse); + clc.downloadBlock++; + + clc.downloadCount += size; + + // So UI gets access to it + Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); + + if (!size) { // A zero length block means EOF + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + + // rename the file + FS_SV_Rename ( clc.downloadTempName, clc.downloadName ); + } + + // send intentions now + // We need this because without it, we would hold the last nextdl and then start + // loading right away. If we take a while to load, the server is happily trying + // to send us that last block over and over. + // Write it twice to help make sure we acknowledge the download + CL_WritePacket(); + CL_WritePacket(); + + // get another file if needed + CL_NextDownload (); + } +} + +#ifdef USE_VOIP +static +qboolean CL_ShouldIgnoreVoipSender(int sender) +{ + if (!cl_voip->integer) + return qtrue; // VoIP is disabled. + else if ((sender == clc.clientNum) && (!clc.demoplaying)) + return qtrue; // ignore own voice (unless playing back a demo). + else if (clc.voipMuteAll) + return qtrue; // all channels are muted with extreme prejudice. + else if (clc.voipIgnore[sender]) + return qtrue; // just ignoring this guy. + else if (clc.voipGain[sender] == 0.0f) + return qtrue; // too quiet to play. + + return qfalse; +} + +/* +===================== +CL_ParseVoip + +A VoIP message has been received from the server +===================== +*/ +static +void CL_ParseVoip ( msg_t *msg ) { + static short decoded[4096]; // !!! FIXME: don't hardcode. + + const int sender = MSG_ReadShort(msg); + const int generation = MSG_ReadByte(msg); + const int sequence = MSG_ReadLong(msg); + const int frames = MSG_ReadByte(msg); + const int packetsize = MSG_ReadShort(msg); + char encoded[1024]; + int seqdiff = sequence - clc.voipIncomingSequence[sender]; + int written = 0; + int i; + + Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender); + + if (sender < 0) + return; // short/invalid packet, bail. + else if (generation < 0) + return; // short/invalid packet, bail. + else if (sequence < 0) + return; // short/invalid packet, bail. + else if (frames < 0) + return; // short/invalid packet, bail. + else if (packetsize < 0) + return; // short/invalid packet, bail. + + if (packetsize > sizeof (encoded)) { // overlarge packet? + int bytesleft = packetsize; + while (bytesleft) { + int br = bytesleft; + if (br > sizeof (encoded)) + br = sizeof (encoded); + MSG_ReadData(msg, encoded, br); + bytesleft -= br; + } + return; // overlarge packet, bail. + } + + if (!clc.speexInitialized) { + MSG_ReadData(msg, encoded, packetsize); // skip payload. + return; // can't handle VoIP without libspeex! + } else if (sender >= MAX_CLIENTS) { + MSG_ReadData(msg, encoded, packetsize); // skip payload. + return; // bogus sender. + } else if (CL_ShouldIgnoreVoipSender(sender)) { + MSG_ReadData(msg, encoded, packetsize); // skip payload. + return; // Channel is muted, bail. + } + + // !!! FIXME: make sure data is narrowband? Does decoder handle this? + + Com_DPrintf("VoIP: packet accepted!\n"); + + // This is a new "generation" ... a new recording started, reset the bits. + if (generation != clc.voipIncomingGeneration[sender]) { + Com_DPrintf("VoIP: new generation %d!\n", generation); + speex_bits_reset(&clc.speexDecoderBits[sender]); + clc.voipIncomingGeneration[sender] = generation; + seqdiff = 0; + } else if (seqdiff < 0) { // we're ahead of the sequence?! + // This shouldn't happen unless the packet is corrupted or something. + Com_DPrintf("VoIP: misordered sequence! %d < %d!\n", + sequence, clc.voipIncomingSequence[sender]); + // reset the bits just in case. + speex_bits_reset(&clc.speexDecoderBits[sender]); + seqdiff = 0; + } else if (seqdiff > 100) { // more than 2 seconds of audio dropped? + // just start over. + Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n", + seqdiff, sender); + speex_bits_reset(&clc.speexDecoderBits[sender]); + seqdiff = 0; + } + + if (seqdiff != 0) { + Com_DPrintf("VoIP: Dropped %d frames from client #%d\n", + seqdiff, sender); + // tell speex that we're missing frames... + for (i = 0; i < seqdiff; i++) { + assert((written + clc.speexFrameSize) * 2 < sizeof (decoded)); + speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written); + written += clc.speexFrameSize; + } + } + + for (i = 0; i < frames; i++) { + char encoded[256]; + const int len = MSG_ReadByte(msg); + if (len < 0) { + Com_DPrintf("VoIP: Short packet!\n"); + break; + } + MSG_ReadData(msg, encoded, len); + + // shouldn't happen, but just in case... + if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) { + Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", + written * 2, written, i); + S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1, + (const byte *) decoded, clc.voipGain[sender]); + written = 0; + } + + speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len); + speex_decode_int(clc.speexDecoder[sender], + &clc.speexDecoderBits[sender], decoded + written); + + #if 0 + static FILE *encio = NULL; + if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb"); + if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); } + static FILE *decio = NULL; + if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb"); + if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); } + #endif + + written += clc.speexFrameSize; + } + + Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", + written * 2, written, i); + + if (written > 0) { + S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1, + (const byte *) decoded, clc.voipGain[sender]); + } + + clc.voipIncomingSequence[sender] = sequence + frames; +} +#endif + + +/* +===================== +CL_ParseCommandString + +Command strings are just saved off until cgame asks for them +when it transitions a snapshot +===================== +*/ +void CL_ParseCommandString( msg_t *msg ) { + char *s; + int seq; + int index; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed stored it off + if ( clc.serverCommandSequence >= seq ) { + return; + } + clc.serverCommandSequence = seq; + + index = seq & (MAX_RELIABLE_COMMANDS-1); + Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) ); +} + + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage( msg_t *msg ) { + int cmd; + + if ( cl_shownet->integer == 1 ) { + Com_Printf ("%i ",msg->cursize); + } else if ( cl_shownet->integer >= 2 ) { + Com_Printf ("------------------\n"); + } + + MSG_Bitstream(msg); + + // get the reliable sequence acknowledge number + clc.reliableAcknowledge = MSG_ReadLong( msg ); + // + if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { + clc.reliableAcknowledge = clc.reliableSequence; + } + + // + // parse the message + // + while ( 1 ) { + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); + break; + } + + cmd = MSG_ReadByte( msg ); + + // See if this is an extension command after the EOF, which means we + // got data that a legacy client should ignore. + if ((cmd == svc_EOF) && (MSG_LookaheadByte( msg ) == svc_extension)) { + SHOWNET( msg, "EXTENSION" ); + MSG_ReadByte( msg ); // throw the svc_extension byte away. + cmd = MSG_ReadByte( msg ); // something legacy clients can't do! + // sometimes you get a svc_extension at end of stream...dangling + // bits in the huffman decoder giving a bogus value? + if (cmd == -1) { + cmd = svc_EOF; + } + } + + if (cmd == svc_EOF) { + SHOWNET( msg, "END OF MESSAGE" ); + break; + } + + if ( cl_shownet->integer >= 2 ) { + if ( (cmd < 0) || (!svc_strings[cmd]) ) { + Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); + } else { + SHOWNET( msg, svc_strings[cmd] ); + } + } + + // other commands + switch ( cmd ) { + default: + Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); + break; + case svc_nop: + break; + case svc_serverCommand: + CL_ParseCommandString( msg ); + break; + case svc_gamestate: + CL_ParseGamestate( msg ); + break; + case svc_snapshot: + CL_ParseSnapshot( msg ); + break; + case svc_download: + CL_ParseDownload( msg ); + break; + case svc_voip: +#ifdef USE_VOIP + CL_ParseVoip( msg ); +#endif + break; + } + } +} + + diff --git a/src/client/cl_scrn.c b/src/client/cl_scrn.c new file mode 100644 index 0000000..c856847 --- /dev/null +++ b/src/client/cl_scrn.c @@ -0,0 +1,571 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc + +#include "client.h" + +qboolean scr_initialized; // ready to draw + +cvar_t *cl_timegraph; +cvar_t *cl_debuggraph; +cvar_t *cl_graphheight; +cvar_t *cl_graphscale; +cvar_t *cl_graphshift; + +/* +================ +SCR_DrawNamedPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { + qhandle_t hShader; + + assert( width != 0 ); + + hShader = re.RegisterShader( picname ); + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + +/* +================ +SCR_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ) { + float xscale; + float yscale; + +#if 0 + // adjust for wide screens + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + + // scale for screen sizes + xscale = cls.glconfig.vidWidth / 640.0; + yscale = cls.glconfig.vidHeight / 480.0; + if ( x ) { + *x *= xscale; + } + if ( y ) { + *y *= yscale; + } + if ( w ) { + *w *= xscale; + } + if ( h ) { + *h *= yscale; + } +} + +/* +================ +SCR_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_FillRect( float x, float y, float width, float height, const float *color ) { + re.SetColor( color ); + + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader ); + + re.SetColor( NULL ); +} + + +/* +================ +SCR_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +/* +** SCR_DrawChar +** chars are drawn at 640*480 virtual screen size +*/ +static void SCR_DrawChar( int x, int y, float size, int ch ) { + int row, col; + float frow, fcol; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -size ) { + return; + } + + ax = x; + ay = y; + aw = size; + ah = size; + SCR_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + re.DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cls.charSetShader ); +} + +/* +** SCR_DrawSmallChar +** small chars are drawn at native screen resolution +*/ +void SCR_DrawSmallChar( int x, int y, int ch ) { + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -SMALLCHAR_HEIGHT ) { + return; + } + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + re.DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, + fcol, frow, + fcol + size, frow + size, + cls.charSetShader ); +} + + +/* +================== +SCR_DrawBigString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void SCR_DrawStringExt( int x, int y, float size, const char *string, float *setColor, qboolean forceColor, + qboolean noColorEscape ) { + vec4_t color; + const char *s; + int xx; + + // draw the drop shadow + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + re.SetColor( color ); + s = string; + xx = x; + while ( *s ) { + if ( !noColorEscape && Q_IsColorString( s ) ) { + s += 2; + continue; + } + SCR_DrawChar( xx+2, y+2, size, *s ); + xx += size; + s++; + } + + + // draw the colored text + s = string; + xx = x; + re.SetColor( setColor ); + while ( *s ) { + if ( !noColorEscape && Q_IsColorString( s ) ) { + if ( !forceColor ) { + Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + re.SetColor( color ); + } + s += 2; + continue; + } + SCR_DrawChar( xx, y, size, *s ); + xx += size; + s++; + } + re.SetColor( NULL ); +} + + +void SCR_DrawBigString( int x, int y, const char *s, float alpha, qboolean noColorEscape ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qfalse, noColorEscape ); +} + +void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color, qboolean noColorEscape ) { + SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qtrue, noColorEscape ); +} + + +/* +================== +SCR_DrawSmallString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. +================== +*/ +void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor, + qboolean noColorEscape ) { + vec4_t color; + const char *s; + int xx; + + // draw the colored text + s = string; + xx = x; + re.SetColor( setColor ); + while ( *s ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + re.SetColor( color ); + } + if ( !noColorEscape ) { + s += 2; + continue; + } + } + SCR_DrawSmallChar( xx, y, *s ); + xx += SMALLCHAR_WIDTH; + s++; + } + re.SetColor( NULL ); +} + + + +/* +** SCR_Strlen -- skips color escape codes +*/ +static int SCR_Strlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +** SCR_GetBigStringWidth +*/ +int SCR_GetBigStringWidth( const char *str ) { + return SCR_Strlen( str ) * 16; +} + + +//=============================================================================== + + +#ifdef USE_VOIP +/* +================= +SCR_DrawVoipMeter + +FIXME: inherited from ioq3, move to cgame/ui +================= +*/ +void SCR_DrawVoipMeter( void ) { + char buffer[16]; + char string[256]; + int limit, i; + + if (!cl_voipShowMeter->integer) + return; // player doesn't want to show meter at all. + else if (!cl_voipSend->integer) + return; // not recording at the moment. + else if (cls.state != CA_ACTIVE) + return; // not connected to a server. + else if (!cl_connectedToVoipServer) + return; // server doesn't support VoIP. + else if (clc.demoplaying) + return; // playing back a demo. + else if (!cl_voip->integer) + return; // client has VoIP support disabled. + + limit = (int) (clc.voipPower * 10.0f); + if (limit > 10) + limit = 10; + + for (i = 0; i < limit; i++) + buffer[i] = '*'; + while (i < 10) + buffer[i++] = ' '; + buffer[i] = '\0'; + + sprintf( string, "VoIP: [%s]", buffer ); + SCR_DrawStringExt( 320 - strlen( string ) * 4, 10, 8, string, g_color_table[7], qtrue, qfalse ); +} +#endif + + + + +/* +=============================================================================== + +DEBUG GRAPH + +=============================================================================== +*/ + +typedef struct +{ + float value; + int color; +} graphsamp_t; + +static int current; +static graphsamp_t values[1024]; + +/* +============== +SCR_DebugGraph +============== +*/ +void SCR_DebugGraph (float value, int color) +{ + values[current&1023].value = value; + values[current&1023].color = color; + current++; +} + +/* +============== +SCR_DrawDebugGraph +============== +*/ +void SCR_DrawDebugGraph (void) +{ + int a, x, y, w, i, h; + float v; + int color; + + // + // draw the graph + // + w = cls.glconfig.vidWidth; + x = 0; + y = cls.glconfig.vidHeight; + re.SetColor( g_color_table[0] ); + re.DrawStretchPic(x, y - cl_graphheight->integer, + w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + + for (a=0 ; ainteger + cl_graphshift->integer; + + if (v < 0) + v += cl_graphheight->integer * (1+(int)(-v / cl_graphheight->integer)); + h = (int)v % cl_graphheight->integer; + re.DrawStretchPic( x+w-1-a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader ); + } +} + +//============================================================================= + +/* +================== +SCR_Init +================== +*/ +void SCR_Init( void ) { + cl_timegraph = Cvar_Get ("timegraph", "0", CVAR_CHEAT); + cl_debuggraph = Cvar_Get ("debuggraph", "0", CVAR_CHEAT); + cl_graphheight = Cvar_Get ("graphheight", "32", CVAR_CHEAT); + cl_graphscale = Cvar_Get ("graphscale", "1", CVAR_CHEAT); + cl_graphshift = Cvar_Get ("graphshift", "0", CVAR_CHEAT); + + scr_initialized = qtrue; +} + + +//======================================================= + +/* +================== +SCR_DrawScreenField + +This will be called twice if rendering in stereo mode +================== +*/ +void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { + re.BeginFrame( stereoFrame ); + + // wide aspect ratio screens need to have the sides cleared + // unless they are displaying game renderings + if ( cls.state != CA_ACTIVE && cls.state != CA_CINEMATIC ) { + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + re.SetColor( g_color_table[0] ); + re.DrawStretchPic( 0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + } + } + + // if the menu is going to cover the entire screen, we + // don't need to render anything under it + if ( uivm && !VM_Call( uivm, UI_IS_FULLSCREEN )) { + switch( cls.state ) { + default: + Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad cls.state" ); + break; + case CA_CINEMATIC: + SCR_DrawCinematic(); + break; + case CA_DISCONNECTED: + // force menu up + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + break; + case CA_CONNECTING: + case CA_CHALLENGING: + case CA_CONNECTED: + // connecting clients will only show the connection dialog + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); + break; + case CA_LOADING: + case CA_PRIMED: + // draw the game information screen and loading progress + CL_CGameRendering(stereoFrame); + break; + case CA_ACTIVE: + // always supply STEREO_CENTER as vieworg offset is now done by the engine. + CL_CGameRendering(stereoFrame); +#ifdef USE_VOIP + SCR_DrawVoipMeter(); +#endif + break; + } + } + + // the menu draws next + if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_REFRESH, cls.realtime ); + } + + // console draws next + Con_DrawConsole (); + + // debug graph can be drawn on top of anything + if ( cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer ) { + SCR_DrawDebugGraph (); + } +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. +================== +*/ +void SCR_UpdateScreen( void ) { + static int recursive; + + if ( !scr_initialized ) { + return; // not initialized yet + } + + if ( ++recursive > 2 ) { + Com_Error( ERR_FATAL, "SCR_UpdateScreen: recursively called" ); + } + recursive = 1; + + // If there is no VM, there are also no rendering commands issued. Stop the renderer in + // that case. + if( uivm || com_dedicated->integer ) + { + // XXX + extern cvar_t* r_anaglyphMode; + // if running in stereo, we need to draw the frame twice + if ( cls.glconfig.stereoEnabled || r_anaglyphMode->integer) { + SCR_DrawScreenField( STEREO_LEFT ); + SCR_DrawScreenField( STEREO_RIGHT ); + } else { + SCR_DrawScreenField( STEREO_CENTER ); + } + + if ( com_speeds->integer ) { + re.EndFrame( &time_frontend, &time_backend ); + } else { + re.EndFrame( NULL, NULL ); + } + } + + recursive = 0; +} + diff --git a/src/client/cl_ui.c b/src/client/cl_ui.c new file mode 100644 index 0000000..edf5ef3 --- /dev/null +++ b/src/client/cl_ui.c @@ -0,0 +1,1140 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" + +vm_t *uivm; + +/* +==================== +GetClientState +==================== +*/ +static void GetClientState( uiClientState_t *state ) { + state->connectPacketCount = clc.connectPacketCount; + state->connState = cls.state; + Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) ); + Q_strncpyz( state->updateInfoString, cls.updateInfoString, sizeof( state->updateInfoString ) ); + Q_strncpyz( state->messageString, clc.serverMessage, sizeof( state->messageString ) ); + state->clientNum = cl.snap.ps.clientNum; +} + +/* +==================== +LAN_LoadCachedServers +==================== +*/ +void LAN_LoadCachedServers( void ) { + int size; + fileHandle_t fileIn; + cls.numglobalservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + if (FS_SV_FOpenFileRead("servercache.dat", &fileIn)) { + FS_Read(&cls.numglobalservers, sizeof(int), fileIn); + FS_Read(&cls.numfavoriteservers, sizeof(int), fileIn); + FS_Read(&size, sizeof(int), fileIn); + if (size == sizeof(cls.globalServers) + sizeof(cls.favoriteServers)) { + FS_Read(&cls.globalServers, sizeof(cls.globalServers), fileIn); + FS_Read(&cls.favoriteServers, sizeof(cls.favoriteServers), fileIn); + } else { + cls.numglobalservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + } + FS_FCloseFile(fileIn); + } +} + +/* +==================== +LAN_SaveServersToCache +==================== +*/ +void LAN_SaveServersToCache( void ) { + int size; + fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat"); + FS_Write(&cls.numglobalservers, sizeof(int), fileOut); + FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut); + size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers); + FS_Write(&size, sizeof(int), fileOut); + FS_Write(&cls.globalServers, sizeof(cls.globalServers), fileOut); + FS_Write(&cls.favoriteServers, sizeof(cls.favoriteServers), fileOut); + FS_FCloseFile(fileOut); +} + +/* +==================== +GetNews +==================== +*/ +qboolean GetNews( qboolean begin ) +{ +#ifdef USE_CURL + qboolean finished = qfalse; + fileHandle_t fileIn; + int readSize; + + if( begin ) { // if not already using curl, start the download + if( !clc.downloadCURLM ) { + if(!CL_cURL_Init()) { + Cvar_Set( "cl_newsString", "^1Error: Could not load cURL library" ); + return qtrue; + } + clc.activeCURLNotGameRelated = qtrue; + CL_cURL_BeginDownload("news.dat", + "http://tremulous.net/clientnews.txt"); + return qfalse; + } + } + + if ( !clc.downloadCURLM && FS_SV_FOpenFileRead("news.dat", &fileIn)) { + readSize = FS_Read(clc.newsString, sizeof( clc.newsString ), fileIn); + FS_FCloseFile(fileIn); + clc.newsString[ readSize ] = '\0'; + if( readSize > 0 ) { + finished = qtrue; + clc.cURLUsed = qfalse; + CL_cURL_Shutdown(); + clc.activeCURLNotGameRelated = qfalse; + } + } + if( !finished ) + strcpy( clc.newsString, "Retrieving..." ); + Cvar_Set( "cl_newsString", clc.newsString ); + return finished; +#else + Cvar_Set( "cl_newsString", + "^1You must compile your client with CURL support to use this feature" ); + return qtrue; +#endif +} + +/* +==================== +LAN_ResetPings +==================== +*/ +static void LAN_ResetPings(int source) { + int count,i; + serverInfo_t *servers = NULL; + count = 0; + + switch (source) { + case AS_LOCAL : + servers = &cls.localServers[0]; + count = MAX_OTHER_SERVERS; + break; + case AS_MPLAYER: + case AS_GLOBAL : + servers = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES : + servers = &cls.favoriteServers[0]; + count = MAX_OTHER_SERVERS; + break; + } + if (servers) { + for (i = 0; i < count; i++) { + servers[i].ping = -1; + } + } +} + +/* +==================== +LAN_AddServer +==================== +*/ +static int LAN_AddServer(int source, const char *name, const char *address) { + int max, *count, i; + netadr_t adr; + serverInfo_t *servers = NULL; + max = MAX_OTHER_SERVERS; + count = NULL; + + switch (source) { + case AS_LOCAL : + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL : + max = MAX_GLOBAL_SERVERS; + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES : + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if (servers && *count < max) { + NET_StringToAdr( address, &adr, NA_IP ); + for ( i = 0; i < *count; i++ ) { + if (NET_CompareAdr(servers[i].adr, adr)) { + break; + } + } + if (i >= *count) { + servers[*count].adr = adr; + Q_strncpyz(servers[*count].hostName, name, sizeof(servers[*count].hostName)); + servers[*count].visible = qtrue; + (*count)++; + return 1; + } + return 0; + } + return -1; +} + +/* +==================== +LAN_RemoveServer +==================== +*/ +static void LAN_RemoveServer(int source, const char *addr) { + int *count, i; + serverInfo_t *servers = NULL; + count = NULL; + switch (source) { + case AS_LOCAL : + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL : + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES : + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if (servers) { + netadr_t comp; + NET_StringToAdr( addr, &comp, NA_IP ); + for (i = 0; i < *count; i++) { + if (NET_CompareAdr( comp, servers[i].adr)) { + int j = i; + while (j < *count - 1) { + Com_Memcpy(&servers[j], &servers[j+1], sizeof(servers[j])); + j++; + } + (*count)--; + break; + } + } + } +} + + +/* +==================== +LAN_GetServerCount +==================== +*/ +static int LAN_GetServerCount( int source ) { + switch (source) { + case AS_LOCAL : + return cls.numlocalservers; + break; + case AS_MPLAYER: + case AS_GLOBAL : + return cls.numglobalservers; + break; + case AS_FAVORITES : + return cls.numfavoriteservers; + break; + } + return 0; +} + +/* +==================== +LAN_GetLocalServerAddressString +==================== +*/ +static void LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + Q_strncpyz(buf, NET_AdrToStringwPort( cls.localServers[n].adr) , buflen ); + return; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + Q_strncpyz(buf, NET_AdrToStringwPort( cls.globalServers[n].adr) , buflen ); + return; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + Q_strncpyz(buf, NET_AdrToStringwPort( cls.favoriteServers[n].adr) , buflen ); + return; + } + break; + } + buf[0] = '\0'; +} + +/* +==================== +LAN_GetServerInfo +==================== +*/ +static void LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { + char info[MAX_STRING_CHARS]; + serverInfo_t *server = NULL; + info[0] = '\0'; + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.favoriteServers[n]; + } + break; + } + if (server && buf) { + buf[0] = '\0'; + Info_SetValueForKey( info, "hostname", server->hostName); + Info_SetValueForKey( info, "mapname", server->mapName); + Info_SetValueForKey( info, "label", server->label); + Info_SetValueForKey( info, "clients", va("%i",server->clients)); + Info_SetValueForKey( info, "sv_maxclients", va("%i",server->maxClients)); + Info_SetValueForKey( info, "ping", va("%i",server->ping)); + Info_SetValueForKey( info, "minping", va("%i",server->minPing)); + Info_SetValueForKey( info, "maxping", va("%i",server->maxPing)); + Info_SetValueForKey( info, "game", server->game); + Info_SetValueForKey( info, "gametype", va("%i",server->gameType)); + Info_SetValueForKey( info, "nettype", va("%i",server->netType)); + Info_SetValueForKey( info, "addr", NET_AdrToStringwPort(server->adr)); + Q_strncpyz(buf, info, buflen); + } else { + if (buf) { + buf[0] = '\0'; + } + } +} + +/* +==================== +LAN_GetServerPing +==================== +*/ +static int LAN_GetServerPing( int source, int n ) { + serverInfo_t *server = NULL; + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.favoriteServers[n]; + } + break; + } + if (server) { + return server->ping; + } + return -1; +} + +/* +==================== +LAN_GetServerPtr +==================== +*/ +static serverInfo_t *LAN_GetServerPtr( int source, int n ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + return &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return &cls.favoriteServers[n]; + } + break; + } + return NULL; +} + +#define FEATURED_MAXPING 200 +/* +==================== +LAN_CompareServers +==================== +*/ +static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { + int res; + serverInfo_t *server1, *server2; + + server1 = LAN_GetServerPtr(source, s1); + server2 = LAN_GetServerPtr(source, s2); + if (!server1 || !server2) { + return 0; + } + + // featured servers on top + if( ( server1->label[ 0 ] && server1->ping <= FEATURED_MAXPING ) || + ( server2->label[ 0 ] && server2->ping <= FEATURED_MAXPING ) ) { + res = Q_stricmpn( server1->label, server2->label, MAX_FEATLABEL_CHARS ); + if( res ) + return -res; + } + + res = 0; + switch( sortKey ) { + case SORT_HOST: + { + char hostName1[ MAX_HOSTNAME_LENGTH ]; + char hostName2[ MAX_HOSTNAME_LENGTH ]; + char *p; + int i; + + for( p = server1->hostName, i = 0; *p != '\0'; p++ ) + { + if( Q_isalpha( *p ) ) + hostName1[ i++ ] = *p; + } + hostName1[ i ] = '\0'; + + for( p = server2->hostName, i = 0; *p != '\0'; p++ ) + { + if( Q_isalpha( *p ) ) + hostName2[ i++ ] = *p; + } + hostName2[ i ] = '\0'; + + res = Q_stricmp( hostName1, hostName2 ); + } + break; + + case SORT_GAME: + res = Q_stricmp( server1->game, server2->game ); + break; + case SORT_MAP: + res = Q_stricmp( server1->mapName, server2->mapName ); + break; + case SORT_CLIENTS: + if (server1->clients < server2->clients) { + res = -1; + } + else if (server1->clients > server2->clients) { + res = 1; + } + else { + res = 0; + } + break; + case SORT_PING: + if (server1->ping < server2->ping) { + res = -1; + } + else if (server1->ping > server2->ping) { + res = 1; + } + else { + res = 0; + } + break; + } + + if (sortDir) { + if (res < 0) + return 1; + if (res > 0) + return -1; + return 0; + } + return res; +} + +/* +==================== +LAN_GetPingQueueCount +==================== +*/ +static int LAN_GetPingQueueCount( void ) { + return (CL_GetPingQueueCount()); +} + +/* +==================== +LAN_ClearPing +==================== +*/ +static void LAN_ClearPing( int n ) { + CL_ClearPing( n ); +} + +/* +==================== +LAN_GetPing +==================== +*/ +static void LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { + CL_GetPing( n, buf, buflen, pingtime ); +} + +/* +==================== +LAN_GetPingInfo +==================== +*/ +static void LAN_GetPingInfo( int n, char *buf, int buflen ) { + CL_GetPingInfo( n, buf, buflen ); +} + +/* +==================== +LAN_MarkServerVisible +==================== +*/ +static void LAN_MarkServerVisible(int source, int n, qboolean visible ) { + if (n == -1) { + int count = MAX_OTHER_SERVERS; + serverInfo_t *server = NULL; + switch (source) { + case AS_LOCAL : + server = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL : + server = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES : + server = &cls.favoriteServers[0]; + break; + } + if (server) { + for (n = 0; n < count; n++) { + server[n].visible = visible; + } + } + + } else { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + cls.localServers[n].visible = visible; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + cls.globalServers[n].visible = visible; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + cls.favoriteServers[n].visible = visible; + } + break; + } + } +} + + +/* +======================= +LAN_ServerIsVisible +======================= +*/ +static int LAN_ServerIsVisible(int source, int n ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return cls.localServers[n].visible; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + return cls.globalServers[n].visible; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return cls.favoriteServers[n].visible; + } + break; + } + return qfalse; +} + +/* +======================= +LAN_UpdateVisiblePings +======================= +*/ +qboolean LAN_UpdateVisiblePings(int source ) { + return CL_UpdateVisiblePings_f(source); +} + +/* +==================== +LAN_GetServerStatus +==================== +*/ +int LAN_GetServerStatus( char *serverAddress, char *serverStatus, int maxLen ) { + return CL_ServerStatus( serverAddress, serverStatus, maxLen ); +} + +/* +==================== +CL_GetGlConfig +==================== +*/ +static void CL_GetGlconfig( glconfig_t *config ) { + *config = cls.glconfig; +} + +/* +==================== +CL_GetClipboardData +==================== +*/ +static void CL_GetClipboardData( char *buf, int buflen ) { + char *cbd; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + *buf = 0; + return; + } + + Q_strncpyz( buf, cbd, buflen ); + + Z_Free( cbd ); +} + +/* +==================== +GetConfigString +==================== +*/ +static int GetConfigString(int index, char *buf, int size) +{ + int offset; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + return qfalse; + + offset = cl.gameState.stringOffsets[index]; + if (!offset) { + if( size ) { + buf[0] = 0; + } + return qfalse; + } + + Q_strncpyz( buf, cl.gameState.stringData+offset, size); + + return qtrue; +} + +/* +==================== +FloatAsInt +==================== +*/ +static int FloatAsInt( float f ) { + floatint_t fi; + fi.f = f; + return fi.i; +} + +/* +==================== +CL_UISystemCalls + +The ui module is making a system call +==================== +*/ +intptr_t CL_UISystemCalls( intptr_t *args ) { + switch( args[0] ) { + case UI_ERROR: + Com_Error( ERR_DROP, "%s", (const char*)VMA(1) ); + return 0; + + case UI_PRINT: + Com_Printf( "%s", (const char*)VMA(1) ); + return 0; + + case UI_MILLISECONDS: + return Sys_Milliseconds(); + + case UI_CVAR_REGISTER: + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + return 0; + + case UI_CVAR_UPDATE: + Cvar_Update( VMA(1) ); + return 0; + + case UI_CVAR_SET: + Cvar_SetSafe( VMA(1), VMA(2) ); + return 0; + + case UI_CVAR_VARIABLEVALUE: + return FloatAsInt( Cvar_VariableValue( VMA(1) ) ); + + case UI_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_CVAR_SETVALUE: + Cvar_SetValueSafe( VMA(1), VMF(2) ); + return 0; + + case UI_CVAR_RESET: + Cvar_Reset( VMA(1) ); + return 0; + + case UI_CVAR_CREATE: + Cvar_Get( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_CVAR_INFOSTRINGBUFFER: + Cvar_InfoStringBuffer( args[1], VMA(2), args[3] ); + return 0; + + case UI_ARGC: + return Cmd_Argc(); + + case UI_ARGV: + Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); + return 0; + + case UI_CMD_EXECUTETEXT: + if(args[1] == 0 + && (!strncmp(VMA(2), "snd_restart", 11) + || !strncmp(VMA(2), "vid_restart", 11) + || !strncmp(VMA(2), "quit", 5))) + { + Com_Printf (S_COLOR_YELLOW "turning EXEC_NOW '%.11s' into EXEC_INSERT\n", (const char*)VMA(2)); + args[1] = EXEC_INSERT; + } + Cbuf_ExecuteText( args[1], VMA(2) ); + return 0; + + case UI_FS_FOPENFILE: + return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); + + case UI_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + + case UI_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + + case UI_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + + case UI_FS_GETFILELIST: + return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); + + case UI_FS_SEEK: + return FS_Seek( args[1], args[2], args[3] ); + + case UI_R_REGISTERMODEL: + return re.RegisterModel( VMA(1) ); + + case UI_R_REGISTERSKIN: + return re.RegisterSkin( VMA(1) ); + + case UI_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( VMA(1) ); + + case UI_R_CLEARSCENE: + re.ClearScene(); + return 0; + + case UI_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( VMA(1) ); + return 0; + + case UI_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); + return 0; + + case UI_R_ADDLIGHTTOSCENE: + re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + + case UI_R_RENDERSCENE: + re.RenderScene( VMA(1) ); + return 0; + + case UI_R_SETCOLOR: + re.SetColor( VMA(1) ); + return 0; + + case UI_R_SETCLIPREGION: + re.SetClipRegion( VMA(1) ); + return 0; + + case UI_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); + return 0; + + case UI_R_MODELBOUNDS: + re.ModelBounds( args[1], VMA(2), VMA(3) ); + return 0; + + case UI_UPDATESCREEN: + SCR_UpdateScreen(); + return 0; + + case UI_CM_LERPTAG: + re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); + return 0; + + case UI_S_REGISTERSOUND: + return S_RegisterSound( VMA(1), args[2] ); + + case UI_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + + case UI_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf( args[1], VMA(2), args[3] ); + return 0; + + case UI_KEY_GETBINDINGBUF: + Key_GetBindingBuf( args[1], VMA(2), args[3] ); + return 0; + + case UI_KEY_SETBINDING: + Key_SetBinding( args[1], VMA(2) ); + return 0; + + case UI_KEY_ISDOWN: + return Key_IsDown( args[1] ); + + case UI_KEY_GETOVERSTRIKEMODE: + return Key_GetOverstrikeMode(); + + case UI_KEY_SETOVERSTRIKEMODE: + Key_SetOverstrikeMode( args[1] ); + return 0; + + case UI_KEY_CLEARSTATES: + Key_ClearStates(); + return 0; + + case UI_KEY_GETCATCHER: + return Key_GetCatcher(); + + case UI_KEY_SETCATCHER: + // Don't allow the ui module to close the console + Key_SetCatcher( args[1] | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ); + return 0; + + case UI_GETCLIPBOARDDATA: + CL_GetClipboardData( VMA(1), args[2] ); + return 0; + + case UI_GETCLIENTSTATE: + GetClientState( VMA(1) ); + return 0; + + case UI_GETGLCONFIG: + CL_GetGlconfig( VMA(1) ); + return 0; + + case UI_GETCONFIGSTRING: + return GetConfigString( args[1], VMA(2), args[3] ); + + case UI_LAN_LOADCACHEDSERVERS: + LAN_LoadCachedServers(); + return 0; + + case UI_LAN_SAVECACHEDSERVERS: + LAN_SaveServersToCache(); + return 0; + + case UI_LAN_ADDSERVER: + return LAN_AddServer(args[1], VMA(2), VMA(3)); + + case UI_LAN_REMOVESERVER: + LAN_RemoveServer(args[1], VMA(2)); + return 0; + + case UI_LAN_GETPINGQUEUECOUNT: + return LAN_GetPingQueueCount(); + + case UI_LAN_CLEARPING: + LAN_ClearPing( args[1] ); + return 0; + + case UI_LAN_GETPING: + LAN_GetPing( args[1], VMA(2), args[3], VMA(4) ); + return 0; + + case UI_LAN_GETPINGINFO: + LAN_GetPingInfo( args[1], VMA(2), args[3] ); + return 0; + + case UI_LAN_GETSERVERCOUNT: + return LAN_GetServerCount(args[1]); + + case UI_LAN_GETSERVERADDRESSSTRING: + LAN_GetServerAddressString( args[1], args[2], VMA(3), args[4] ); + return 0; + + case UI_LAN_GETSERVERINFO: + LAN_GetServerInfo( args[1], args[2], VMA(3), args[4] ); + return 0; + + case UI_LAN_GETSERVERPING: + return LAN_GetServerPing( args[1], args[2] ); + + case UI_LAN_MARKSERVERVISIBLE: + LAN_MarkServerVisible( args[1], args[2], args[3] ); + return 0; + + case UI_LAN_SERVERISVISIBLE: + return LAN_ServerIsVisible( args[1], args[2] ); + + case UI_LAN_UPDATEVISIBLEPINGS: + return LAN_UpdateVisiblePings( args[1] ); + + case UI_LAN_RESETPINGS: + LAN_ResetPings( args[1] ); + return 0; + + case UI_LAN_SERVERSTATUS: + return LAN_GetServerStatus( VMA(1), VMA(2), args[3] ); + + case UI_GETNEWS: + return GetNews( args[1] ); + + case UI_LAN_COMPARESERVERS: + return LAN_CompareServers( args[1], args[2], args[3], args[4], args[5] ); + + case UI_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + + case UI_SET_PBCLSTATUS: + return 0; + + case UI_R_REGISTERFONT: + re.RegisterFont( VMA(1), args[2], VMA(3)); + return 0; + + case UI_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + + case UI_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_STRNCPY: + strncpy( VMA(1), VMA(2), args[3] ); + return args[1]; + + case UI_SIN: + return FloatAsInt( sin( VMF(1) ) ); + + case UI_COS: + return FloatAsInt( cos( VMF(1) ) ); + + case UI_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + + case UI_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + + case UI_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + + case UI_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + + case UI_PARSE_ADD_GLOBAL_DEFINE: + return Parse_AddGlobalDefine( VMA(1) ); + case UI_PARSE_LOAD_SOURCE: + return Parse_LoadSourceHandle( VMA(1) ); + case UI_PARSE_FREE_SOURCE: + return Parse_FreeSourceHandle( args[1] ); + case UI_PARSE_READ_TOKEN: + return Parse_ReadTokenHandle( args[1], VMA(2) ); + case UI_PARSE_SOURCE_FILE_AND_LINE: + return Parse_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + + case UI_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + case UI_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( VMA(1), VMA(2)); + return 0; + + case UI_REAL_TIME: + return Com_RealTime( VMA(1) ); + + case UI_CIN_PLAYCINEMATIC: + Com_DPrintf("UI_CIN_PlayCinematic\n"); + return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case UI_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case UI_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case UI_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case UI_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case UI_R_REMAP_SHADER: + re.RemapShader( VMA(1), VMA(2), VMA(3) ); + return 0; + + default: + Com_Error( ERR_DROP, "Bad UI system trap: %ld", (long int) args[0] ); + + } + + return 0; +} + +/* +==================== +CL_ShutdownUI +==================== +*/ +void CL_ShutdownUI( void ) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_UI ); + cls.uiStarted = qfalse; + if ( !uivm ) { + return; + } + VM_Call( uivm, UI_SHUTDOWN ); + VM_Free( uivm ); + uivm = NULL; +} + +/* +==================== +CL_InitUI +==================== +*/ +#define UI_OLD_API_VERSION 4 + +void CL_InitUI( void ) { + int v; + vmInterpret_t interpret; + + // load the dll or bytecode + if ( cl_connectedToPureServer != 0 ) { + // if sv_pure is set we only allow qvms to be loaded + interpret = VMI_COMPILED; + } + else { + interpret = Cvar_VariableValue( "vm_ui" ); + } + uivm = VM_Create( "ui", CL_UISystemCalls, interpret ); + if ( !uivm ) { + Com_Printf( "Failed to find a valid UI vm. The following paths were searched:\n" ); + Cmd_ExecuteString( "path /\n" ); + Com_Error( ERR_FATAL, "VM_Create on UI failed" ); + } + + // sanity check + v = VM_Call( uivm, UI_GETAPIVERSION ); + if (v == UI_OLD_API_VERSION) { + // init for this gamestate + VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE)); + } + else if (v != UI_API_VERSION) { + Com_Error( ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION ); + cls.uiStarted = qfalse; + } + else { + // init for this gamestate + VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE) ); + + // show where the ui folder was loaded from + Cmd_ExecuteString( "which ui/\n" ); + } + + // reset any CVAR_CHEAT cvars registered by ui + if ( !clc.demoplaying && !cl_connectedToCheatServer ) + Cvar_SetCheatState(); + + clc.newsString[ 0 ] = '\0'; +} + +/* +==================== +UI_GameCommand + +See if the current console command is claimed by the ui +==================== +*/ +qboolean UI_GameCommand( void ) { + if ( !uivm ) { + return qfalse; + } + + return VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime ); +} diff --git a/src/client/client.h b/src/client/client.h new file mode 100644 index 0000000..26af507 --- /dev/null +++ b/src/client/client.h @@ -0,0 +1,645 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// client.h -- primary header for client + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +#include "../ui/ui_public.h" +#include "keys.h" +#include "snd_public.h" +#include "../cgame/cg_public.h" +#include "../game/bg_public.h" + +#ifdef USE_CURL +#include "cl_curl.h" +#endif /* USE_CURL */ + +#ifdef USE_VOIP +#include "speex/speex.h" +#include "speex/speex_preprocess.h" +#endif + +// file full of random crap that gets used to create cl_guid +#define QKEY_FILE "qkey" +#define QKEY_SIZE 2048 + +#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits + +// snapshots are a view of the server at a given time +typedef struct { + qboolean valid; // cleared if delta parsing was invalid + int snapFlags; // rate delayed and dropped commands + + int serverTime; // server time the message is valid for (in msec) + + int messageNum; // copied from netchan->incoming_sequence + int deltaNum; // messageNum the delta is from + int ping; // time from when cmdNum-1 was sent to time packet was reeceived + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + int cmdNum; // the next cmdNum the server is expecting + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + int parseEntitiesNum; // at the time of this snapshot + + int serverCommandNum; // execute all commands up to this before + // making the snapshot current +} clSnapshot_t; + + + +/* +============================================================================= + +the clientActive_t structure is wiped completely at every +new gamestate_t, potentially several times during an established connection + +============================================================================= +*/ + +typedef struct { + int p_cmdNumber; // cl.cmdNumber when packet was sent + int p_serverTime; // usercmd->serverTime when packet was sent + int p_realtime; // cls.realtime when packet was sent +} outPacket_t; + +// the parseEntities array must be large enough to hold PACKET_BACKUP frames of +// entities, so that when a delta compressed message arives from the server +// it can be un-deltad from the original +#define MAX_PARSE_ENTITIES 2048 + +extern int g_console_field_width; + +typedef struct { + int timeoutcount; // it requres several frames in a timeout condition + // to disconnect, preventing debugging breaks from + // causing immediate disconnects on continue + clSnapshot_t snap; // latest received from server + + int serverTime; // may be paused during play + int oldServerTime; // to prevent time from flowing bakcwards + int oldFrameServerTime; // to check tournament restarts + int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta + // this value changes as net lag varies + qboolean extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate + // cleared when CL_AdjustTimeDelta looks at it + qboolean newSnapshots; // set on parse of any valid packet + + gameState_t gameState; // configstrings + char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO + + int parseEntitiesNum; // index (not anded off) into cl_parse_entities[] + + int mouseDx[2], mouseDy[2]; // added to by mouse events + int mouseIndex; + int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + + // cgame communicates a few values to the client system + int cgameUserCmdValue; // current weapon to add to usercmd_t + float cgameSensitivity; + + // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last + // properly generated command + usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds + int cmdNumber; // incremented each frame, because multiple + // frames may need to be packed into a single packet + + outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out + + // the client maintains its own idea of view angles, which are + // sent to the server each frame. It is cleared to 0 upon entering each level. + // the server sends a delta each frame which is added to the locally + // tracked view angles to account for standing on rotating objects, + // and teleport direction changes + vec3_t viewangles; + + int serverId; // included in each client message so the server + // can tell if it is for a prior map_restart + // big stuff at end of structure so most offsets are 15 bits or less + clSnapshot_t snapshots[PACKET_BACKUP]; + + entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame + + entityState_t parseEntities[MAX_PARSE_ENTITIES]; +} clientActive_t; + +extern clientActive_t cl; + +/* +============================================================================= + +the clientConnection_t structure is wiped when disconnecting from a server, +either to go to a full screen console, play a demo, or connect to a different server + +A connection can be to either a server through the network layer or a +demo through a file. + +============================================================================= +*/ + +#define MAX_TIMEDEMO_DURATIONS 4096 + +typedef struct { + + int clientNum; + int lastPacketSentTime; // for retransmits during connection + int lastPacketTime; // for timeouts + + netadr_t serverAddress; + int connectTime; // for connection retransmits + int connectPacketCount; // for display on connection dialog + char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog + + int challenge; // from the server to use for connecting + int checksumFeed; // from the server for checksum calculations + + // these are our reliable messages that go to the server + int reliableSequence; + int reliableAcknowledge; // the last one the server has executed + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // server message (unreliable) and command (reliable) sequence + // numbers are NOT cleared at level changes, but continue to + // increase as long as the connection is valid + + // message sequence is used by both the network layer and the + // delta compression layer + int serverMessageSequence; + + // reliable messages received from server + int serverCommandSequence; + int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand + char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // file transfer from server + fileHandle_t download; + char downloadTempName[MAX_OSPATH]; + char downloadName[MAX_OSPATH]; +#ifdef USE_CURL + qboolean cURLEnabled; + qboolean cURLUsed; + qboolean cURLDisconnected; + char downloadURL[MAX_OSPATH]; + CURL *downloadCURL; + CURLM *downloadCURLM; + qboolean activeCURLNotGameRelated; +#endif /* USE_CURL */ + int sv_allowDownload; + char sv_dlURL[MAX_CVAR_VALUE_STRING]; + int downloadNumber; + int downloadBlock; // block we are waiting for + int downloadCount; // how many bytes we got + int downloadSize; // how many bytes we got + char downloadList[MAX_INFO_STRING]; // list of paks we need to download + qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak + char newsString[ MAX_NEWS_STRING ]; + + // demo information + char demoName[MAX_QPATH]; + qboolean spDemoRecording; + qboolean demorecording; + qboolean demoplaying; + qboolean demowaiting; // don't record until a non-delta message is received + qboolean firstDemoFrameSkipped; + fileHandle_t demofile; + + int timeDemoFrames; // counter of rendered frames + int timeDemoStart; // cls.realtime before first frame + int timeDemoBaseTime; // each frame will be at this time + frameNum * 50 + int timeDemoLastFrame;// time the last frame was rendered + int timeDemoMinDuration; // minimum frame duration + int timeDemoMaxDuration; // maximum frame duration + unsigned char timeDemoDurations[ MAX_TIMEDEMO_DURATIONS ]; // log of frame durations + +#ifdef USE_VOIP + qboolean speexInitialized; + int speexFrameSize; + int speexSampleRate; + + // incoming data... + // !!! FIXME: convert from parallel arrays to array of a struct. + SpeexBits speexDecoderBits[MAX_CLIENTS]; + void *speexDecoder[MAX_CLIENTS]; + byte voipIncomingGeneration[MAX_CLIENTS]; + int voipIncomingSequence[MAX_CLIENTS]; + float voipGain[MAX_CLIENTS]; + qboolean voipIgnore[MAX_CLIENTS]; + qboolean voipMuteAll; + + // outgoing data... + int voipTarget1; // these three ints make up a bit mask of 92 bits. + int voipTarget2; // the bits say who a VoIP pack is addressed to: + int voipTarget3; // (1 << clientnum). See cl_voipSendTarget cvar. + SpeexPreprocessState *speexPreprocessor; + SpeexBits speexEncoderBits; + void *speexEncoder; + int voipOutgoingDataSize; + int voipOutgoingDataFrames; + int voipOutgoingSequence; + byte voipOutgoingGeneration; + byte voipOutgoingData[1024]; + float voipPower; +#endif + + // big stuff at end of structure so most offsets are 15 bits or less + netchan_t netchan; +} clientConnection_t; + +extern clientConnection_t clc; + +/* +================================================================== + +the clientStatic_t structure is never wiped, and is used even when +no client connection is active at all + +================================================================== +*/ + +typedef struct { + netadr_t adr; + int start; + int time; + char info[MAX_INFO_STRING]; +} ping_t; + +#define MAX_FEATLABEL_CHARS 32 +typedef struct { + netadr_t adr; + char hostName[MAX_HOSTNAME_LENGTH]; + char mapName[MAX_NAME_LENGTH]; + char game[MAX_NAME_LENGTH]; + char label[MAX_FEATLABEL_CHARS]; // for featured servers, NULL otherwise + int netType; + int gameType; + int clients; + int maxClients; + int minPing; + int maxPing; + int ping; + qboolean visible; +} serverInfo_t; + +typedef struct { + connstate_t state; // connection status + + qboolean cddialog; // bring up the cd needed dialog next frame + + char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect) + + // when the server clears the hunk, all of these must be restarted + qboolean rendererStarted; + qboolean soundStarted; + qboolean soundRegistered; + qboolean uiStarted; + qboolean cgameStarted; + + int framecount; + int frametime; // msec since last frame + + int realtime; // ignores pause + int realFrametime; // ignoring pause, so console always works + + // master server sequence information + int numMasterPackets; + unsigned int receivedMasterPackets; // bitfield + + int numlocalservers; + serverInfo_t localServers[MAX_OTHER_SERVERS]; + + int numglobalservers; + serverInfo_t globalServers[MAX_GLOBAL_SERVERS]; + // additional global servers + int numGlobalServerAddresses; + netadr_t globalServerAddresses[MAX_GLOBAL_SERVERS]; + + int numfavoriteservers; + serverInfo_t favoriteServers[MAX_OTHER_SERVERS]; + + int pingUpdateSource; // source currently pinging or updating + + // update server info + netadr_t updateServer; + char updateChallenge[MAX_TOKEN_CHARS]; + char updateInfoString[MAX_INFO_STRING]; + + netadr_t authorizeServer; + + // rendering info + glconfig_t glconfig; + qhandle_t charSetShader; + qhandle_t whiteShader; + qhandle_t consoleShader; +} clientStatic_t; + +extern clientStatic_t cls; + +//============================================================================= + +extern vm_t *cgvm; // interface to cgame dll or vm +extern vm_t *uivm; // interface to ui dll or vm +extern refexport_t re; // interface to refresh .dll + + +// +// cvars +// +extern cvar_t *cl_nodelta; +extern cvar_t *cl_debugMove; +extern cvar_t *cl_noprint; +extern cvar_t *cl_timegraph; +extern cvar_t *cl_maxpackets; +extern cvar_t *cl_packetdup; +extern cvar_t *cl_shownet; +extern cvar_t *cl_showSend; +extern cvar_t *cl_timeNudge; +extern cvar_t *cl_showTimeDelta; +extern cvar_t *cl_freezeDemo; + +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_run; +extern cvar_t *cl_anglespeedkey; + +extern cvar_t *cl_sensitivity; +extern cvar_t *cl_freelook; + +extern cvar_t *cl_mouseAccel; +extern cvar_t *cl_mouseAccelOffset; +extern cvar_t *cl_mouseAccelStyle; +extern cvar_t *cl_showMouseRate; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; +extern cvar_t *m_filter; + +extern cvar_t *j_pitch; +extern cvar_t *j_yaw; +extern cvar_t *j_forward; +extern cvar_t *j_side; +extern cvar_t *j_pitch_axis; +extern cvar_t *j_yaw_axis; +extern cvar_t *j_forward_axis; +extern cvar_t *j_side_axis; + +extern cvar_t *cl_timedemo; +extern cvar_t *cl_aviFrameRate; +extern cvar_t *cl_aviMotionJpeg; + +extern cvar_t *cl_activeAction; + +extern cvar_t *cl_allowDownload; +extern cvar_t *cl_downloadMethod; +extern cvar_t *cl_conXOffset; +extern cvar_t *cl_inGameVideo; + +extern cvar_t *cl_lanForcePackets; +extern cvar_t *cl_autoRecordDemo; + +extern cvar_t *cl_consoleKeys; + +#ifdef USE_MUMBLE +extern cvar_t *cl_useMumble; +extern cvar_t *cl_mumbleScale; +#endif + +#ifdef USE_VOIP +// cl_voipSendTarget is a string: "all" to broadcast to everyone, "none" to +// send to no one, or a comma-separated list of client numbers: +// "0,7,2,23" ... an empty string is treated like "all". +extern cvar_t *cl_voipUseVAD; +extern cvar_t *cl_voipVADThreshold; +extern cvar_t *cl_voipSend; +extern cvar_t *cl_voipSendTarget; +extern cvar_t *cl_voipGainDuringCapture; +extern cvar_t *cl_voipCaptureMult; +extern cvar_t *cl_voipShowMeter; +extern cvar_t *cl_voip; +#endif + +//================================================= + +// +// cl_main +// + +void CL_Init (void); +void CL_FlushMemory(void); +void CL_ShutdownAll(void); +void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd); + +void CL_StartHunkUsers( qboolean rendererOnly ); + +void CL_Disconnect_f (void); +void CL_GetChallengePacket (void); +void CL_Vid_Restart_f( void ); +void CL_Snd_Restart_f (void); +void CL_StartDemoLoop( void ); +void CL_NextDemo( void ); +void CL_ReadDemoMessage( void ); +demoState_t CL_DemoState( void ); +int CL_DemoPos( void ); +void CL_DemoName( char *buffer, int size ); +void CL_StopRecord_f( void ); + +void CL_InitDownloads(void); +void CL_NextDownload(void); + +void CL_GetPing( int n, char *buf, int buflen, int *pingtime ); +void CL_GetPingInfo( int n, char *buf, int buflen ); +void CL_ClearPing( int n ); +int CL_GetPingQueueCount( void ); + +void CL_ShutdownRef( void ); +void CL_InitRef( void ); +int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ); + +qboolean CL_CheckPaused(void); + +// +// cl_input +// +typedef struct { + int down[2]; // key nums holding it down + unsigned downtime; // msec timestamp + unsigned msec; // msec down this frame if both a down and up happened + qboolean active; // current state + qboolean wasPressed; // set when down, not cleared when up +} kbutton_t; + +extern kbutton_t in_mlook, in_klook; +extern kbutton_t in_strafe; +extern kbutton_t in_speed; + +#ifdef USE_VOIP +extern kbutton_t in_voiprecord; +#endif + +void CL_InitInput (void); +void CL_SendCmd (void); +void CL_ClearState (void); +void CL_ReadPackets (void); + +void CL_WritePacket( void ); +void IN_CenterView (void); + +void CL_VerifyCode( void ); + +float CL_KeyState (kbutton_t *key); +int Key_StringToKeynum( char *str ); +char *Key_KeynumToString (int keynum); + +// +// cl_parse.c +// +extern int cl_connectedToPureServer; +extern int cl_connectedToCheatServer; + +#ifdef USE_VOIP +extern int cl_connectedToVoipServer; +void CL_Voip_f( void ); +#endif + +void CL_SystemInfoChanged( void ); +void CL_ParseServerMessage( msg_t *msg ); + +//==================================================================== + +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ); +void CL_LocalServers_f( void ); +void CL_GlobalServers_f( void ); +void CL_FavoriteServers_f( void ); +void CL_Ping_f( void ); +qboolean CL_UpdateVisiblePings_f( int source ); + + +// +// console +// +void Con_DrawCharacter (int cx, int line, int num); + +void Con_CheckResize (void); +void Con_Init (void); +void Con_Clear_f (void); +void Con_ToggleConsole_f (void); +void Con_DrawNotify (void); +void Con_ClearNotify (void); +void Con_RunConsole (void); +void Con_DrawConsole (void); +void Con_PageUp( void ); +void Con_PageDown( void ); +void Con_Top( void ); +void Con_Bottom( void ); +void Con_Close( void ); + +void CL_LoadConsoleHistory( void ); +void CL_SaveConsoleHistory( void ); + +// +// cl_scrn.c +// +void SCR_Init (void); +void SCR_UpdateScreen (void); + +void SCR_DebugGraph (float value, int color); + +int SCR_GetBigStringWidth( const char *str ); // returns in virtual 640x480 coordinates + +void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ); +void SCR_FillRect( float x, float y, float width, float height, + const float *color ); +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ); + +void SCR_DrawBigString( int x, int y, const char *s, float alpha, qboolean noColorEscape ); // draws a string with embedded color control characters with fade +void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color, qboolean noColorEscape ); // ignores embedded color control characters +void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor, qboolean noColorEscape ); +void SCR_DrawSmallChar( int x, int y, int ch ); + + +// +// cl_cin.c +// + +void CL_PlayCinematic_f( void ); +void SCR_DrawCinematic (void); +void SCR_RunCinematic (void); +void SCR_StopCinematic (void); +int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status CIN_StopCinematic(int handle); +e_status CIN_RunCinematic (int handle); +void CIN_DrawCinematic (int handle); +void CIN_SetExtents (int handle, int x, int y, int w, int h); +void CIN_SetLooping (int handle, qboolean loop); +void CIN_UploadCinematic(int handle); +void CIN_CloseAllVideos(void); + +// +// cl_cgame.c +// +void CL_InitCGame( void ); +void CL_ShutdownCGame( void ); +qboolean CL_GameCommand( void ); +void CL_GameConsoleText( void ); +void CL_CGameRendering( stereoFrame_t stereo ); +void CL_SetCGameTime( void ); +void CL_FirstSnapshot( void ); +void CL_ShaderStateChanged(void); + +// +// cl_ui.c +// +void CL_InitUI( void ); +void CL_ShutdownUI( void ); +int Key_GetCatcher( void ); +void Key_SetCatcher( int catcher ); +void LAN_LoadCachedServers( void ); +void LAN_SaveServersToCache( void ); + + +// +// cl_net_chan.c +// +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg); //int length, const byte *data ); +void CL_Netchan_TransmitNextFragment( netchan_t *chan ); +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); + +// +// cl_avi.c +// +qboolean CL_OpenAVIForWriting( const char *filename ); +void CL_TakeVideoFrame( void ); +void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size ); +void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size ); +qboolean CL_CloseAVI( void ); +qboolean CL_VideoRecording( void ); + +// +// cl_main.c +// +void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ); + diff --git a/src/client/keycodes.h b/src/client/keycodes.h new file mode 100644 index 0000000..c13194c --- /dev/null +++ b/src/client/keycodes.h @@ -0,0 +1,281 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#ifndef __KEYCODES_H__ +#define __KEYCODES_H__ + +// +// these are the key numbers that should be passed to KeyEvent +// + +// normal keys should be passed as lowercased ascii + +typedef enum { + K_NONE = -1, + K_TAB = 9, + K_ENTER = 13, + K_ESCAPE = 27, + K_SPACE = 32, + + K_BACKSPACE = 127, + + K_COMMAND = 128, + K_CAPSLOCK, + K_POWER, + K_PAUSE, + + K_UPARROW, + K_DOWNARROW, + K_LEFTARROW, + K_RIGHTARROW, + + K_ALT, + K_CTRL, + K_SHIFT, + K_INS, + K_DEL, + K_PGDN, + K_PGUP, + K_HOME, + K_END, + + K_F1, + K_F2, + K_F3, + K_F4, + K_F5, + K_F6, + K_F7, + K_F8, + K_F9, + K_F10, + K_F11, + K_F12, + K_F13, + K_F14, + K_F15, + + K_KP_HOME, + K_KP_UPARROW, + K_KP_PGUP, + K_KP_LEFTARROW, + K_KP_5, + K_KP_RIGHTARROW, + K_KP_END, + K_KP_DOWNARROW, + K_KP_PGDN, + K_KP_ENTER, + K_KP_INS, + K_KP_DEL, + K_KP_SLASH, + K_KP_MINUS, + K_KP_PLUS, + K_KP_NUMLOCK, + K_KP_STAR, + K_KP_EQUALS, + + K_MOUSE1, + K_MOUSE2, + K_MOUSE3, + K_MOUSE4, + K_MOUSE5, + + K_MWHEELDOWN, + K_MWHEELUP, + + K_JOY1, + K_JOY2, + K_JOY3, + K_JOY4, + K_JOY5, + K_JOY6, + K_JOY7, + K_JOY8, + K_JOY9, + K_JOY10, + K_JOY11, + K_JOY12, + K_JOY13, + K_JOY14, + K_JOY15, + K_JOY16, + K_JOY17, + K_JOY18, + K_JOY19, + K_JOY20, + K_JOY21, + K_JOY22, + K_JOY23, + K_JOY24, + K_JOY25, + K_JOY26, + K_JOY27, + K_JOY28, + K_JOY29, + K_JOY30, + K_JOY31, + K_JOY32, + + K_AUX1, + K_AUX2, + K_AUX3, + K_AUX4, + K_AUX5, + K_AUX6, + K_AUX7, + K_AUX8, + K_AUX9, + K_AUX10, + K_AUX11, + K_AUX12, + K_AUX13, + K_AUX14, + K_AUX15, + K_AUX16, + + K_WORLD_0, + K_WORLD_1, + K_WORLD_2, + K_WORLD_3, + K_WORLD_4, + K_WORLD_5, + K_WORLD_6, + K_WORLD_7, + K_WORLD_8, + K_WORLD_9, + K_WORLD_10, + K_WORLD_11, + K_WORLD_12, + K_WORLD_13, + K_WORLD_14, + K_WORLD_15, + K_WORLD_16, + K_WORLD_17, + K_WORLD_18, + K_WORLD_19, + K_WORLD_20, + K_WORLD_21, + K_WORLD_22, + K_WORLD_23, + K_WORLD_24, + K_WORLD_25, + K_WORLD_26, + K_WORLD_27, + K_WORLD_28, + K_WORLD_29, + K_WORLD_30, + K_WORLD_31, + K_WORLD_32, + K_WORLD_33, + K_WORLD_34, + K_WORLD_35, + K_WORLD_36, + K_WORLD_37, + K_WORLD_38, + K_WORLD_39, + K_WORLD_40, + K_WORLD_41, + K_WORLD_42, + K_WORLD_43, + K_WORLD_44, + K_WORLD_45, + K_WORLD_46, + K_WORLD_47, + K_WORLD_48, + K_WORLD_49, + K_WORLD_50, + K_WORLD_51, + K_WORLD_52, + K_WORLD_53, + K_WORLD_54, + K_WORLD_55, + K_WORLD_56, + K_WORLD_57, + K_WORLD_58, + K_WORLD_59, + K_WORLD_60, + K_WORLD_61, + K_WORLD_62, + K_WORLD_63, + K_WORLD_64, + K_WORLD_65, + K_WORLD_66, + K_WORLD_67, + K_WORLD_68, + K_WORLD_69, + K_WORLD_70, + K_WORLD_71, + K_WORLD_72, + K_WORLD_73, + K_WORLD_74, + K_WORLD_75, + K_WORLD_76, + K_WORLD_77, + K_WORLD_78, + K_WORLD_79, + K_WORLD_80, + K_WORLD_81, + K_WORLD_82, + K_WORLD_83, + K_WORLD_84, + K_WORLD_85, + K_WORLD_86, + K_WORLD_87, + K_WORLD_88, + K_WORLD_89, + K_WORLD_90, + K_WORLD_91, + K_WORLD_92, + K_WORLD_93, + K_WORLD_94, + K_WORLD_95, + + K_SUPER, + K_COMPOSE, + K_MODE, + K_HELP, + K_PRINT, + K_SYSREQ, + K_SCROLLOCK, + K_BREAK, + K_MENU, + K_EURO, + K_UNDO, + + // Pseudo-key that brings the console down + K_CONSOLE, + + MAX_KEYS +} keyNum_t; + +// MAX_KEYS replaces K_LAST_KEY, however some mods may have used K_LAST_KEY +// in detecting binds, so we leave it defined to the old hardcoded value +// of maxiumum keys to prevent mods from crashing older versions of the engine +#define K_LAST_KEY 256 + +// The menu code needs to get both key and char events, but +// to avoid duplicating the paths, the char events are just +// distinguished by or'ing in K_CHAR_FLAG (ugly) +#define K_CHAR_FLAG 1024 + +#endif diff --git a/src/client/keys.h b/src/client/keys.h new file mode 100644 index 0000000..adb9537 --- /dev/null +++ b/src/client/keys.h @@ -0,0 +1,58 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "keycodes.h" + +typedef struct { + qboolean down; + int repeats; // if > 1, it is autorepeating + char *binding; +} qkey_t; + +extern qboolean key_overstrikeMode; +extern qkey_t keys[MAX_KEYS]; + +// NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h +void Field_KeyDownEvent( field_t *edit, int key ); +void Field_CharEvent( field_t *edit, int ch ); +void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ); +void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ); + +#define COMMAND_HISTORY 32 +extern field_t historyEditLines[COMMAND_HISTORY]; + +extern field_t g_consoleField; +extern field_t chatField; +extern int anykeydown; +extern qboolean chat_team; +extern int chat_playerNum; + +void Key_WriteBindings( fileHandle_t f ); +void Key_SetBinding( int keynum, const char *binding ); +char *Key_GetBinding( int keynum ); +qboolean Key_IsDown( int keynum ); +qboolean Key_GetOverstrikeMode( void ); +void Key_SetOverstrikeMode( qboolean state ); +void Key_ClearStates( void ); +int Key_GetKey(const char *binding); +void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); +void Key_GetBindingBuf( int keynum, char *buf, int buflen ); diff --git a/src/client/libmumblelink.c b/src/client/libmumblelink.c new file mode 100644 index 0000000..25ca678 --- /dev/null +++ b/src/client/libmumblelink.c @@ -0,0 +1,189 @@ +/* libmumblelink.c -- mumble link interface + + Copyright (C) 2008 Ludwig Nussel + Copyright (C) 2000-2009 Darklegion Development + + 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. + +*/ + +#ifdef WIN32 +#include +#define uint32_t UINT32 +#else +#include +#ifdef __sun +#define _POSIX_C_SOURCE 199309L +#endif +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "libmumblelink.h" + +#ifndef MIN +#define MIN(a, b) ((a)<(b)?(a):(b)) +#endif + +typedef struct +{ + uint32_t uiVersion; + uint32_t uiTick; + float fAvatarPosition[3]; + float fAvatarFront[3]; + float fAvatarTop[3]; + wchar_t name[256]; + /* new in mumble 1.2 */ + float fCameraPosition[3]; + float fCameraFront[3]; + float fCameraTop[3]; + wchar_t identity[256]; + uint32_t context_len; + unsigned char context[256]; + wchar_t description[2048]; +} LinkedMem; + +static LinkedMem *lm = NULL; + +#ifdef WIN32 +static HANDLE hMapObject = NULL; +#else +static int32_t GetTickCount(void) +{ + struct timeval tv; + gettimeofday(&tv,NULL); + + return tv.tv_usec / 1000 + tv.tv_sec * 1000; +} +#endif + +int mumble_link(const char* name) +{ +#ifdef WIN32 + if(lm) + return 0; + + hMapObject = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"MumbleLink"); + if (hMapObject == NULL) + return -1; + + lm = (LinkedMem *) MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinkedMem)); + if (lm == NULL) { + CloseHandle(hMapObject); + hMapObject = NULL; + return -1; + } +#else + char file[256]; + int shmfd; + if(lm) + return 0; + + snprintf(file, sizeof (file), "/MumbleLink.%d", getuid()); + shmfd = shm_open(file, O_RDWR, S_IRUSR | S_IWUSR); + if(shmfd < 0) { + return -1; + } + + lm = (LinkedMem *) (mmap(NULL, sizeof(LinkedMem), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd,0)); + if (lm == (void *) (-1)) { + lm = NULL; + close(shmfd); + return -1; + } + close(shmfd); +#endif + memset(lm, 0, sizeof(LinkedMem)); + mbstowcs(lm->name, name, sizeof(lm->name)); + + return 0; +} + +void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3]) +{ + mumble_update_coordinates2(fPosition, fFront, fTop, fPosition, fFront, fTop); +} + +void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3], + float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3]) +{ + if (!lm) + return; + + memcpy(lm->fAvatarPosition, fAvatarPosition, sizeof(lm->fAvatarPosition)); + memcpy(lm->fAvatarFront, fAvatarFront, sizeof(lm->fAvatarFront)); + memcpy(lm->fAvatarTop, fAvatarTop, sizeof(lm->fAvatarTop)); + memcpy(lm->fCameraPosition, fCameraPosition, sizeof(lm->fCameraPosition)); + memcpy(lm->fCameraFront, fCameraFront, sizeof(lm->fCameraFront)); + memcpy(lm->fCameraTop, fCameraTop, sizeof(lm->fCameraTop)); + lm->uiVersion = 2; + lm->uiTick = GetTickCount(); +} + +void mumble_set_identity(const char* identity) +{ + size_t len; + if (!lm) + return; + len = MIN(sizeof(lm->identity), strlen(identity)+1); + mbstowcs(lm->identity, identity, len); +} + +void mumble_set_context(const unsigned char* context, size_t len) +{ + if (!lm) + return; + len = MIN(sizeof(lm->context), len); + lm->context_len = len; + memcpy(lm->context, context, len); +} + +void mumble_set_description(const char* description) +{ + size_t len; + if (!lm) + return; + len = MIN(sizeof(lm->description), strlen(description)+1); + mbstowcs(lm->description, description, len); +} + +void mumble_unlink() +{ + if(!lm) + return; +#ifdef WIN32 + UnmapViewOfFile(lm); + CloseHandle(hMapObject); + hMapObject = NULL; +#else + munmap(lm, sizeof(LinkedMem)); +#endif + lm = NULL; +} + +int mumble_islinked(void) +{ + return lm != NULL; +} diff --git a/src/client/libmumblelink.h b/src/client/libmumblelink.h new file mode 100644 index 0000000..fc4929f --- /dev/null +++ b/src/client/libmumblelink.h @@ -0,0 +1,36 @@ +/* libmumblelink.h -- mumble link interface + + Copyright (C) 2008 Ludwig Nussel + Copyright (C) 2000-2009 Darklegion Development + + 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. + +*/ + +int mumble_link(const char* name); +int mumble_islinked(void); +void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3]); + +/* new for mumble 1.2: also set camera position */ +void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3], + float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3]); + +void mumble_set_description(const char* description); +void mumble_set_context(const unsigned char* context, size_t len); +void mumble_set_identity(const char* identity); + +void mumble_unlink(void); diff --git a/src/client/qal.c b/src/client/qal.c new file mode 100644 index 0000000..57db8ed --- /dev/null +++ b/src/client/qal.c @@ -0,0 +1,352 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// Dynamically loads OpenAL + +#ifdef USE_OPENAL + +#include "qal.h" + +#ifdef USE_OPENAL_DLOPEN + +#include "../sys/sys_loadlib.h" + +LPALENABLE qalEnable; +LPALDISABLE qalDisable; +LPALISENABLED qalIsEnabled; +LPALGETSTRING qalGetString; +LPALGETBOOLEANV qalGetBooleanv; +LPALGETINTEGERV qalGetIntegerv; +LPALGETFLOATV qalGetFloatv; +LPALGETDOUBLEV qalGetDoublev; +LPALGETBOOLEAN qalGetBoolean; +LPALGETINTEGER qalGetInteger; +LPALGETFLOAT qalGetFloat; +LPALGETDOUBLE qalGetDouble; +LPALGETERROR qalGetError; +LPALISEXTENSIONPRESENT qalIsExtensionPresent; +LPALGETPROCADDRESS qalGetProcAddress; +LPALGETENUMVALUE qalGetEnumValue; +LPALLISTENERF qalListenerf; +LPALLISTENER3F qalListener3f; +LPALLISTENERFV qalListenerfv; +LPALLISTENERI qalListeneri; +LPALGETLISTENERF qalGetListenerf; +LPALGETLISTENER3F qalGetListener3f; +LPALGETLISTENERFV qalGetListenerfv; +LPALGETLISTENERI qalGetListeneri; +LPALGENSOURCES qalGenSources; +LPALDELETESOURCES qalDeleteSources; +LPALISSOURCE qalIsSource; +LPALSOURCEF qalSourcef; +LPALSOURCE3F qalSource3f; +LPALSOURCEFV qalSourcefv; +LPALSOURCEI qalSourcei; +LPALGETSOURCEF qalGetSourcef; +LPALGETSOURCE3F qalGetSource3f; +LPALGETSOURCEFV qalGetSourcefv; +LPALGETSOURCEI qalGetSourcei; +LPALSOURCEPLAYV qalSourcePlayv; +LPALSOURCESTOPV qalSourceStopv; +LPALSOURCEREWINDV qalSourceRewindv; +LPALSOURCEPAUSEV qalSourcePausev; +LPALSOURCEPLAY qalSourcePlay; +LPALSOURCESTOP qalSourceStop; +LPALSOURCEREWIND qalSourceRewind; +LPALSOURCEPAUSE qalSourcePause; +LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers; +LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers; +LPALGENBUFFERS qalGenBuffers; +LPALDELETEBUFFERS qalDeleteBuffers; +LPALISBUFFER qalIsBuffer; +LPALBUFFERDATA qalBufferData; +LPALGETBUFFERF qalGetBufferf; +LPALGETBUFFERI qalGetBufferi; +LPALDOPPLERFACTOR qalDopplerFactor; +LPALDOPPLERVELOCITY qalDopplerVelocity; +LPALDISTANCEMODEL qalDistanceModel; + +LPALCCREATECONTEXT qalcCreateContext; +LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent; +LPALCPROCESSCONTEXT qalcProcessContext; +LPALCSUSPENDCONTEXT qalcSuspendContext; +LPALCDESTROYCONTEXT qalcDestroyContext; +LPALCGETCURRENTCONTEXT qalcGetCurrentContext; +LPALCGETCONTEXTSDEVICE qalcGetContextsDevice; +LPALCOPENDEVICE qalcOpenDevice; +LPALCCLOSEDEVICE qalcCloseDevice; +LPALCGETERROR qalcGetError; +LPALCISEXTENSIONPRESENT qalcIsExtensionPresent; +LPALCGETPROCADDRESS qalcGetProcAddress; +LPALCGETENUMVALUE qalcGetEnumValue; +LPALCGETSTRING qalcGetString; +LPALCGETINTEGERV qalcGetIntegerv; +LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice; +LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice; +LPALCCAPTURESTART qalcCaptureStart; +LPALCCAPTURESTOP qalcCaptureStop; +LPALCCAPTURESAMPLES qalcCaptureSamples; + +static void *OpenALLib = NULL; + +static qboolean alinit_fail = qfalse; + +/* +================= +GPA +================= +*/ +static void *GPA(char *str) +{ + void *rv; + + rv = Sys_LoadFunction(OpenALLib, str); + if(!rv) + { + Com_Printf( " Can't load symbol %s\n", str); + alinit_fail = qtrue; + return NULL; + } + else + { + Com_DPrintf( " Loaded symbol %s (%p)\n", str, rv); + return rv; + } +} + +/* +================= +QAL_Init +================= +*/ +qboolean QAL_Init(const char *libname) +{ + if(OpenALLib) + return qtrue; + + Com_Printf( "Loading \"%s\"...\n", libname); + if( (OpenALLib = Sys_LoadLibrary(libname)) == 0 ) + { +#ifdef _WIN32 + return qfalse; +#else + char fn[1024]; + Q_strncpyz( fn, Sys_Cwd( ), sizeof( fn ) ); + strncat(fn, "/", sizeof(fn) - strlen(fn) - 1); + strncat(fn, libname, sizeof(fn) - strlen(fn) - 1); + + if( (OpenALLib = Sys_LoadLibrary(fn)) == 0 ) + { + return qfalse; + } +#endif + } + + alinit_fail = qfalse; + + qalEnable = GPA("alEnable"); + qalDisable = GPA("alDisable"); + qalIsEnabled = GPA("alIsEnabled"); + qalGetString = GPA("alGetString"); + qalGetBooleanv = GPA("alGetBooleanv"); + qalGetIntegerv = GPA("alGetIntegerv"); + qalGetFloatv = GPA("alGetFloatv"); + qalGetDoublev = GPA("alGetDoublev"); + qalGetBoolean = GPA("alGetBoolean"); + qalGetInteger = GPA("alGetInteger"); + qalGetFloat = GPA("alGetFloat"); + qalGetDouble = GPA("alGetDouble"); + qalGetError = GPA("alGetError"); + qalIsExtensionPresent = GPA("alIsExtensionPresent"); + qalGetProcAddress = GPA("alGetProcAddress"); + qalGetEnumValue = GPA("alGetEnumValue"); + qalListenerf = GPA("alListenerf"); + qalListener3f = GPA("alListener3f"); + qalListenerfv = GPA("alListenerfv"); + qalListeneri = GPA("alListeneri"); + qalGetListenerf = GPA("alGetListenerf"); + qalGetListener3f = GPA("alGetListener3f"); + qalGetListenerfv = GPA("alGetListenerfv"); + qalGetListeneri = GPA("alGetListeneri"); + qalGenSources = GPA("alGenSources"); + qalDeleteSources = GPA("alDeleteSources"); + qalIsSource = GPA("alIsSource"); + qalSourcef = GPA("alSourcef"); + qalSource3f = GPA("alSource3f"); + qalSourcefv = GPA("alSourcefv"); + qalSourcei = GPA("alSourcei"); + qalGetSourcef = GPA("alGetSourcef"); + qalGetSource3f = GPA("alGetSource3f"); + qalGetSourcefv = GPA("alGetSourcefv"); + qalGetSourcei = GPA("alGetSourcei"); + qalSourcePlayv = GPA("alSourcePlayv"); + qalSourceStopv = GPA("alSourceStopv"); + qalSourceRewindv = GPA("alSourceRewindv"); + qalSourcePausev = GPA("alSourcePausev"); + qalSourcePlay = GPA("alSourcePlay"); + qalSourceStop = GPA("alSourceStop"); + qalSourceRewind = GPA("alSourceRewind"); + qalSourcePause = GPA("alSourcePause"); + qalSourceQueueBuffers = GPA("alSourceQueueBuffers"); + qalSourceUnqueueBuffers = GPA("alSourceUnqueueBuffers"); + qalGenBuffers = GPA("alGenBuffers"); + qalDeleteBuffers = GPA("alDeleteBuffers"); + qalIsBuffer = GPA("alIsBuffer"); + qalBufferData = GPA("alBufferData"); + qalGetBufferf = GPA("alGetBufferf"); + qalGetBufferi = GPA("alGetBufferi"); + qalDopplerFactor = GPA("alDopplerFactor"); + qalDopplerVelocity = GPA("alDopplerVelocity"); + qalDistanceModel = GPA("alDistanceModel"); + + qalcCreateContext = GPA("alcCreateContext"); + qalcMakeContextCurrent = GPA("alcMakeContextCurrent"); + qalcProcessContext = GPA("alcProcessContext"); + qalcSuspendContext = GPA("alcSuspendContext"); + qalcDestroyContext = GPA("alcDestroyContext"); + qalcGetCurrentContext = GPA("alcGetCurrentContext"); + qalcGetContextsDevice = GPA("alcGetContextsDevice"); + qalcOpenDevice = GPA("alcOpenDevice"); + qalcCloseDevice = GPA("alcCloseDevice"); + qalcGetError = GPA("alcGetError"); + qalcIsExtensionPresent = GPA("alcIsExtensionPresent"); + qalcGetProcAddress = GPA("alcGetProcAddress"); + qalcGetEnumValue = GPA("alcGetEnumValue"); + qalcGetString = GPA("alcGetString"); + qalcGetIntegerv = GPA("alcGetIntegerv"); + qalcCaptureOpenDevice = GPA("alcCaptureOpenDevice"); + qalcCaptureCloseDevice = GPA("alcCaptureCloseDevice"); + qalcCaptureStart = GPA("alcCaptureStart"); + qalcCaptureStop = GPA("alcCaptureStop"); + qalcCaptureSamples = GPA("alcCaptureSamples"); + + if(alinit_fail) + { + QAL_Shutdown(); + Com_Printf( " One or more symbols not found\n"); + return qfalse; + } + + return qtrue; +} + +/* +================= +QAL_Shutdown +================= +*/ +void QAL_Shutdown( void ) +{ + if(OpenALLib) + { + Sys_UnloadLibrary(OpenALLib); + OpenALLib = NULL; + } + + qalEnable = NULL; + qalDisable = NULL; + qalIsEnabled = NULL; + qalGetString = NULL; + qalGetBooleanv = NULL; + qalGetIntegerv = NULL; + qalGetFloatv = NULL; + qalGetDoublev = NULL; + qalGetBoolean = NULL; + qalGetInteger = NULL; + qalGetFloat = NULL; + qalGetDouble = NULL; + qalGetError = NULL; + qalIsExtensionPresent = NULL; + qalGetProcAddress = NULL; + qalGetEnumValue = NULL; + qalListenerf = NULL; + qalListener3f = NULL; + qalListenerfv = NULL; + qalListeneri = NULL; + qalGetListenerf = NULL; + qalGetListener3f = NULL; + qalGetListenerfv = NULL; + qalGetListeneri = NULL; + qalGenSources = NULL; + qalDeleteSources = NULL; + qalIsSource = NULL; + qalSourcef = NULL; + qalSource3f = NULL; + qalSourcefv = NULL; + qalSourcei = NULL; + qalGetSourcef = NULL; + qalGetSource3f = NULL; + qalGetSourcefv = NULL; + qalGetSourcei = NULL; + qalSourcePlayv = NULL; + qalSourceStopv = NULL; + qalSourceRewindv = NULL; + qalSourcePausev = NULL; + qalSourcePlay = NULL; + qalSourceStop = NULL; + qalSourceRewind = NULL; + qalSourcePause = NULL; + qalSourceQueueBuffers = NULL; + qalSourceUnqueueBuffers = NULL; + qalGenBuffers = NULL; + qalDeleteBuffers = NULL; + qalIsBuffer = NULL; + qalBufferData = NULL; + qalGetBufferf = NULL; + qalGetBufferi = NULL; + qalDopplerFactor = NULL; + qalDopplerVelocity = NULL; + qalDistanceModel = NULL; + + qalcCreateContext = NULL; + qalcMakeContextCurrent = NULL; + qalcProcessContext = NULL; + qalcSuspendContext = NULL; + qalcDestroyContext = NULL; + qalcGetCurrentContext = NULL; + qalcGetContextsDevice = NULL; + qalcOpenDevice = NULL; + qalcCloseDevice = NULL; + qalcGetError = NULL; + qalcIsExtensionPresent = NULL; + qalcGetProcAddress = NULL; + qalcGetEnumValue = NULL; + qalcGetString = NULL; + qalcGetIntegerv = NULL; + qalcCaptureOpenDevice = NULL; + qalcCaptureCloseDevice = NULL; + qalcCaptureStart = NULL; + qalcCaptureStop = NULL; + qalcCaptureSamples = NULL; +} +#else +qboolean QAL_Init(const char *libname) +{ + return qtrue; +} +void QAL_Shutdown( void ) +{ +} +#endif +#endif diff --git a/src/client/qal.h b/src/client/qal.h new file mode 100644 index 0000000..7665e6e --- /dev/null +++ b/src/client/qal.h @@ -0,0 +1,253 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#ifndef __QAL_H__ +#define __QAL_H__ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +#ifdef USE_OPENAL_DLOPEN +#define AL_NO_PROTOTYPES +#define ALC_NO_PROTOTYPES +#endif + +#ifdef USE_LOCAL_HEADERS +#include "../AL/al.h" +#include "../AL/alc.h" +#else +#ifdef _MSC_VER + // MSVC users must install the OpenAL SDK which doesn't use the AL/*.h scheme. + #include + #include +#else + #include + #include +#endif +#endif + +/* Hack to enable compiling both on OpenAL SDK and OpenAL-soft. */ +#ifndef ALC_ENUMERATE_ALL_EXT +# define ALC_ENUMERATE_ALL_EXT 1 +# define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 +# define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#endif + +#ifdef USE_OPENAL_DLOPEN +extern LPALENABLE qalEnable; +extern LPALDISABLE qalDisable; +extern LPALISENABLED qalIsEnabled; +extern LPALGETSTRING qalGetString; +extern LPALGETBOOLEANV qalGetBooleanv; +extern LPALGETINTEGERV qalGetIntegerv; +extern LPALGETFLOATV qalGetFloatv; +extern LPALGETDOUBLEV qalGetDoublev; +extern LPALGETBOOLEAN qalGetBoolean; +extern LPALGETINTEGER qalGetInteger; +extern LPALGETFLOAT qalGetFloat; +extern LPALGETDOUBLE qalGetDouble; +extern LPALGETERROR qalGetError; +extern LPALISEXTENSIONPRESENT qalIsExtensionPresent; +extern LPALGETPROCADDRESS qalGetProcAddress; +extern LPALGETENUMVALUE qalGetEnumValue; +extern LPALLISTENERF qalListenerf; +extern LPALLISTENER3F qalListener3f; +extern LPALLISTENERFV qalListenerfv; +extern LPALLISTENERI qalListeneri; +extern LPALLISTENER3I qalListener3i; +extern LPALLISTENERIV qalListeneriv; +extern LPALGETLISTENERF qalGetListenerf; +extern LPALGETLISTENER3F qalGetListener3f; +extern LPALGETLISTENERFV qalGetListenerfv; +extern LPALGETLISTENERI qalGetListeneri; +extern LPALGETLISTENER3I qalGetListener3i; +extern LPALGETLISTENERIV qalGetListeneriv; +extern LPALGENSOURCES qalGenSources; +extern LPALDELETESOURCES qalDeleteSources; +extern LPALISSOURCE qalIsSource; +extern LPALSOURCEF qalSourcef; +extern LPALSOURCE3F qalSource3f; +extern LPALSOURCEFV qalSourcefv; +extern LPALSOURCEI qalSourcei; +extern LPALSOURCE3I qalSource3i; +extern LPALSOURCEIV qalSourceiv; +extern LPALGETSOURCEF qalGetSourcef; +extern LPALGETSOURCE3F qalGetSource3f; +extern LPALGETSOURCEFV qalGetSourcefv; +extern LPALGETSOURCEI qalGetSourcei; +extern LPALGETSOURCE3I qalGetSource3i; +extern LPALGETSOURCEIV qalGetSourceiv; +extern LPALSOURCEPLAYV qalSourcePlayv; +extern LPALSOURCESTOPV qalSourceStopv; +extern LPALSOURCEREWINDV qalSourceRewindv; +extern LPALSOURCEPAUSEV qalSourcePausev; +extern LPALSOURCEPLAY qalSourcePlay; +extern LPALSOURCESTOP qalSourceStop; +extern LPALSOURCEREWIND qalSourceRewind; +extern LPALSOURCEPAUSE qalSourcePause; +extern LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers; +extern LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers; +extern LPALGENBUFFERS qalGenBuffers; +extern LPALDELETEBUFFERS qalDeleteBuffers; +extern LPALISBUFFER qalIsBuffer; +extern LPALBUFFERDATA qalBufferData; +extern LPALBUFFERF qalBufferf; +extern LPALBUFFER3F qalBuffer3f; +extern LPALBUFFERFV qalBufferfv; +extern LPALBUFFERF qalBufferi; +extern LPALBUFFER3F qalBuffer3i; +extern LPALBUFFERFV qalBufferiv; +extern LPALGETBUFFERF qalGetBufferf; +extern LPALGETBUFFER3F qalGetBuffer3f; +extern LPALGETBUFFERFV qalGetBufferfv; +extern LPALGETBUFFERI qalGetBufferi; +extern LPALGETBUFFER3I qalGetBuffer3i; +extern LPALGETBUFFERIV qalGetBufferiv; +extern LPALDOPPLERFACTOR qalDopplerFactor; +extern LPALDOPPLERVELOCITY qalDopplerVelocity; +extern LPALSPEEDOFSOUND qalSpeedOfSound; +extern LPALDISTANCEMODEL qalDistanceModel; + +extern LPALCCREATECONTEXT qalcCreateContext; +extern LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent; +extern LPALCPROCESSCONTEXT qalcProcessContext; +extern LPALCSUSPENDCONTEXT qalcSuspendContext; +extern LPALCDESTROYCONTEXT qalcDestroyContext; +extern LPALCGETCURRENTCONTEXT qalcGetCurrentContext; +extern LPALCGETCONTEXTSDEVICE qalcGetContextsDevice; +extern LPALCOPENDEVICE qalcOpenDevice; +extern LPALCCLOSEDEVICE qalcCloseDevice; +extern LPALCGETERROR qalcGetError; +extern LPALCISEXTENSIONPRESENT qalcIsExtensionPresent; +extern LPALCGETPROCADDRESS qalcGetProcAddress; +extern LPALCGETENUMVALUE qalcGetEnumValue; +extern LPALCGETSTRING qalcGetString; +extern LPALCGETINTEGERV qalcGetIntegerv; +extern LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice; +extern LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice; +extern LPALCCAPTURESTART qalcCaptureStart; +extern LPALCCAPTURESTOP qalcCaptureStop; +extern LPALCCAPTURESAMPLES qalcCaptureSamples; +#else +#define qalEnable alEnable +#define qalDisable alDisable +#define qalIsEnabled alIsEnabled +#define qalGetString alGetString +#define qalGetBooleanv alGetBooleanv +#define qalGetIntegerv alGetIntegerv +#define qalGetFloatv alGetFloatv +#define qalGetDoublev alGetDoublev +#define qalGetBoolean alGetBoolean +#define qalGetInteger alGetInteger +#define qalGetFloat alGetFloat +#define qalGetDouble alGetDouble +#define qalGetError alGetError +#define qalIsExtensionPresent alIsExtensionPresent +#define qalGetProcAddress alGetProcAddress +#define qalGetEnumValue alGetEnumValue +#define qalListenerf alListenerf +#define qalListener3f alListener3f +#define qalListenerfv alListenerfv +#define qalListeneri alListeneri +#define qalListener3i alListener3i +#define qalListeneriv alListeneriv +#define qalGetListenerf alGetListenerf +#define qalGetListener3f alGetListener3f +#define qalGetListenerfv alGetListenerfv +#define qalGetListeneri alGetListeneri +#define qalGetListener3i alGetListener3i +#define qalGetListeneriv alGetListeneriv +#define qalGenSources alGenSources +#define qalDeleteSources alDeleteSources +#define qalIsSource alIsSource +#define qalSourcef alSourcef +#define qalSource3f alSource3f +#define qalSourcefv alSourcefv +#define qalSourcei alSourcei +#define qalSource3i alSource3i +#define qalSourceiv alSourceiv +#define qalGetSourcef alGetSourcef +#define qalGetSource3f alGetSource3f +#define qalGetSourcefv alGetSourcefv +#define qalGetSourcei alGetSourcei +#define qalGetSource3i alGetSource3i +#define qalGetSourceiv alGetSourceiv +#define qalSourcePlayv alSourcePlayv +#define qalSourceStopv alSourceStopv +#define qalSourceRewindv alSourceRewindv +#define qalSourcePausev alSourcePausev +#define qalSourcePlay alSourcePlay +#define qalSourceStop alSourceStop +#define qalSourceRewind alSourceRewind +#define qalSourcePause alSourcePause +#define qalSourceQueueBuffers alSourceQueueBuffers +#define qalSourceUnqueueBuffers alSourceUnqueueBuffers +#define qalGenBuffers alGenBuffers +#define qalDeleteBuffers alDeleteBuffers +#define qalIsBuffer alIsBuffer +#define qalBufferData alBufferData +#define qalBufferf alBufferf +#define qalBuffer3f alBuffer3f +#define qalBufferfv alBufferfv +#define qalBufferi alBufferi +#define qalBuffer3i alBuffer3i +#define qalBufferiv alBufferiv +#define qalGetBufferf alGetBufferf +#define qalGetBuffer3f alGetBuffer3f +#define qalGetBufferfv alGetBufferfv +#define qalGetBufferi alGetBufferi +#define qalGetBuffer3i alGetBuffer3i +#define qalGetBufferiv alGetBufferiv +#define qalDopplerFactor alDopplerFactor +#define qalDopplerVelocity alDopplerVelocity +#define qalSpeedOfSound alSpeedOfSound +#define qalDistanceModel alDistanceModel + +#define qalcCreateContext alcCreateContext +#define qalcMakeContextCurrent alcMakeContextCurrent +#define qalcProcessContext alcProcessContext +#define qalcSuspendContext alcSuspendContext +#define qalcDestroyContext alcDestroyContext +#define qalcGetCurrentContext alcGetCurrentContext +#define qalcGetContextsDevice alcGetContextsDevice +#define qalcOpenDevice alcOpenDevice +#define qalcCloseDevice alcCloseDevice +#define qalcGetError alcGetError +#define qalcIsExtensionPresent alcIsExtensionPresent +#define qalcGetProcAddress alcGetProcAddress +#define qalcGetEnumValue alcGetEnumValue +#define qalcGetString alcGetString +#define qalcGetIntegerv alcGetIntegerv +#define qalcCaptureOpenDevice alcCaptureOpenDevice +#define qalcCaptureCloseDevice alcCaptureCloseDevice +#define qalcCaptureStart alcCaptureStart +#define qalcCaptureStop alcCaptureStop +#define qalcCaptureSamples alcCaptureSamples +#endif + +qboolean QAL_Init(const char *libname); +void QAL_Shutdown( void ); + +#endif // __QAL_H__ diff --git a/src/client/snd_adpcm.c b/src/client/snd_adpcm.c new file mode 100644 index 0000000..89e68f4 --- /dev/null +++ b/src/client/snd_adpcm.c @@ -0,0 +1,330 @@ +/*********************************************************** +Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The +Netherlands. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the names of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +******************************************************************/ + +/* +** Intel/DVI ADPCM coder/decoder. +** +** The algorithm for this coder was taken from the IMA Compatability Project +** proceedings, Vol 2, Number 2; May 1992. +** +** Version 1.2, 18-Dec-92. +*/ + +#include "snd_local.h" + + +/* Intel ADPCM step variation table */ +static int indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +}; + +static int stepsizeTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + + +void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) { + short *inp; /* Input buffer pointer */ + signed char *outp; /* output buffer pointer */ + int val; /* Current input sample value */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int diff; /* Difference between val and sample */ + int step; /* Stepsize */ + int valpred; /* Predicted output value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int outputbuffer; /* place to keep previous 4-bit value */ + int bufferstep; /* toggle between outputbuffer/output */ + + outp = (signed char *)outdata; + inp = indata; + + valpred = state->sample; + index = state->index; + step = stepsizeTable[index]; + + outputbuffer = 0; // quiet a compiler warning + bufferstep = 1; + + for ( ; len > 0 ; len-- ) { + val = *inp++; + + /* Step 1 - compute difference with previous value */ + diff = val - valpred; + sign = (diff < 0) ? 8 : 0; + if ( sign ) diff = (-diff); + + /* Step 2 - Divide and clamp */ + /* Note: + ** This code *approximately* computes: + ** delta = diff*4/step; + ** vpdiff = (delta+0.5)*step/4; + ** but in shift step bits are dropped. The net result of this is + ** that even if you have fast mul/div hardware you cannot put it to + ** good use since the fixup would be too expensive. + */ + delta = 0; + vpdiff = (step >> 3); + + if ( diff >= step ) { + delta = 4; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 2; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 1; + vpdiff += step; + } + + /* Step 3 - Update previous value */ + if ( sign ) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 4 - Clamp previous value to 16 bits */ + if ( valpred > 32767 ) + valpred = 32767; + else if ( valpred < -32768 ) + valpred = -32768; + + /* Step 5 - Assemble value, update index and step values */ + delta |= sign; + + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + step = stepsizeTable[index]; + + /* Step 6 - Output value */ + if ( bufferstep ) { + outputbuffer = (delta << 4) & 0xf0; + } else { + *outp++ = (delta & 0x0f) | outputbuffer; + } + bufferstep = !bufferstep; + } + + /* Output last step, if needed */ + if ( !bufferstep ) + *outp++ = outputbuffer; + + state->sample = valpred; + state->index = index; +} + + +/* static */ void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) { + signed char *inp; /* Input buffer pointer */ + int outp; /* output buffer pointer */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int step; /* Stepsize */ + int valpred; /* Predicted value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int inputbuffer; /* place to keep next 4-bit value */ + int bufferstep; /* toggle between inputbuffer/input */ + + outp = 0; + inp = (signed char *)indata; + + valpred = state->sample; + index = state->index; + step = stepsizeTable[index]; + + bufferstep = 0; + inputbuffer = 0; // quiet a compiler warning + for ( ; len > 0 ; len-- ) { + + /* Step 1 - get the delta value */ + if ( bufferstep ) { + delta = inputbuffer & 0xf; + } else { + inputbuffer = *inp++; + delta = (inputbuffer >> 4) & 0xf; + } + bufferstep = !bufferstep; + + /* Step 2 - Find new index value (for later) */ + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + + /* Step 3 - Separate sign and magnitude */ + sign = delta & 8; + delta = delta & 7; + + /* Step 4 - Compute difference and new predicted value */ + /* + ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment + ** in adpcm_coder. + */ + vpdiff = step >> 3; + if ( delta & 4 ) vpdiff += step; + if ( delta & 2 ) vpdiff += step>>1; + if ( delta & 1 ) vpdiff += step>>2; + + if ( sign ) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 5 - clamp output value */ + if ( valpred > 32767 ) + valpred = 32767; + else if ( valpred < -32768 ) + valpred = -32768; + + /* Step 6 - Update step value */ + step = stepsizeTable[index]; + + /* Step 7 - Output value */ + outdata[outp] = valpred; + outp++; + } + + state->sample = valpred; + state->index = index; +} + + +/* +==================== +S_AdpcmMemoryNeeded + +Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format +==================== +*/ +int S_AdpcmMemoryNeeded( const wavinfo_t *info ) { + float scale; + int scaledSampleCount; + int sampleMemory; + int blockCount; + int headerMemory; + + // determine scale to convert from input sampling rate to desired sampling rate + scale = (float)info->rate / dma.speed; + + // calc number of samples at playback sampling rate + scaledSampleCount = info->samples / scale; + + // calc memory need to store those samples using ADPCM at 4 bits per sample + sampleMemory = scaledSampleCount / 2; + + // calc number of sample blocks needed of PAINTBUFFER_SIZE + blockCount = scaledSampleCount / PAINTBUFFER_SIZE; + if( scaledSampleCount % PAINTBUFFER_SIZE ) { + blockCount++; + } + + // calc memory needed to store the block headers + headerMemory = blockCount * sizeof(adpcm_state_t); + + return sampleMemory + headerMemory; +} + + +/* +==================== +S_AdpcmGetSamples +==================== +*/ +void S_AdpcmGetSamples(sndBuffer *chunk, short *to) { + adpcm_state_t state; + byte *out; + + // get the starting state from the block header + state.index = chunk->adpcm.index; + state.sample = chunk->adpcm.sample; + + out = (byte *)chunk->sndChunk; + // get samples + S_AdpcmDecode((char *) out, to, SND_CHUNK_SIZE_BYTE*2, &state ); +} + + +/* +==================== +S_AdpcmEncodeSound +==================== +*/ +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { + adpcm_state_t state; + int inOffset; + int count; + int n; + sndBuffer *newchunk, *chunk; + byte *out; + + inOffset = 0; + count = sfx->soundLength; + state.index = 0; + state.sample = samples[0]; + + chunk = NULL; + while( count ) { + n = count; + if( n > SND_CHUNK_SIZE_BYTE*2 ) { + n = SND_CHUNK_SIZE_BYTE*2; + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + + // output the header + chunk->adpcm.index = state.index; + chunk->adpcm.sample = state.sample; + + out = (byte *)chunk->sndChunk; + + // encode the samples + S_AdpcmEncode( samples + inOffset, (char *) out, n, &state ); + + inOffset += n; + count -= n; + } +} diff --git a/src/client/snd_codec.c b/src/client/snd_codec.c new file mode 100644 index 0000000..b11e087 --- /dev/null +++ b/src/client/snd_codec.c @@ -0,0 +1,238 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" + +static snd_codec_t *codecs; + +/* +================= +S_FileExtension +================= +*/ +static char *S_FileExtension(const char *fni) +{ + // we should search from the ending to the last '/' + + char *fn = (char *) fni + strlen(fni) - 1; + char *eptr = NULL; + + while(*fn != '/' && fn != fni) + { + if(*fn == '.') + { + eptr = fn; + break; + } + fn--; + } + + return eptr; +} + +/* +================= +S_FindCodecForFile + +Select an appropriate codec for a file based on its extension +================= +*/ +static snd_codec_t *S_FindCodecForFile(const char *filename) +{ + char *ext = S_FileExtension(filename); + snd_codec_t *codec = codecs; + + if(!ext) + { + // No extension - auto-detect + while(codec) + { + char fn[MAX_QPATH]; + + // there is no extension so we do not need to subtract 4 chars + Q_strncpyz(fn, filename, MAX_QPATH); + COM_DefaultExtension(fn, MAX_QPATH, codec->ext); + + // Check it exists + if(FS_ReadFile(fn, NULL) != -1) + return codec; + + // Nope. Next! + codec = codec->next; + } + + // Nothin' + return NULL; + } + + while(codec) + { + if(!Q_stricmp(ext, codec->ext)) + return codec; + codec = codec->next; + } + + return NULL; +} + +/* +================= +S_CodecInit +================= +*/ +void S_CodecInit() +{ + codecs = NULL; + S_CodecRegister(&wav_codec); +#ifdef USE_CODEC_VORBIS + S_CodecRegister(&ogg_codec); +#endif +} + +/* +================= +S_CodecShutdown +================= +*/ +void S_CodecShutdown() +{ + codecs = NULL; +} + +/* +================= +S_CodecRegister +================= +*/ +void S_CodecRegister(snd_codec_t *codec) +{ + codec->next = codecs; + codecs = codec; +} + +/* +================= +S_CodecLoad +================= +*/ +void *S_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_codec_t *codec; + char fn[MAX_QPATH]; + + codec = S_FindCodecForFile(filename); + if(!codec) + { + Com_Printf("Unknown extension for %s\n", filename); + return NULL; + } + + strncpy(fn, filename, sizeof(fn)); + COM_DefaultExtension(fn, sizeof(fn), codec->ext); + + return codec->load(fn, info); +} + +/* +================= +S_CodecOpenStream +================= +*/ +snd_stream_t *S_CodecOpenStream(const char *filename) +{ + snd_codec_t *codec; + char fn[MAX_QPATH]; + + codec = S_FindCodecForFile(filename); + if(!codec) + { + Com_Printf("Unknown extension for %s\n", filename); + return NULL; + } + + strncpy(fn, filename, sizeof(fn)); + COM_DefaultExtension(fn, sizeof(fn), codec->ext); + + return codec->open(fn); +} + +void S_CodecCloseStream(snd_stream_t *stream) +{ + stream->codec->close(stream); +} + +int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + return stream->codec->read(stream, bytes, buffer); +} + +//======================================================================= +// Util functions (used by codecs) + +/* +================= +S_CodecUtilOpen +================= +*/ +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec) +{ + snd_stream_t *stream; + fileHandle_t hnd; + int length; + + // Try to open the file + length = FS_FOpenFileRead(filename, &hnd, qtrue); + if(!hnd) + { + Com_Printf("Can't read sound file %s\n", filename); + return NULL; + } + + // Allocate a stream + stream = Z_Malloc(sizeof(snd_stream_t)); + if(!stream) + { + FS_FCloseFile(hnd); + return NULL; + } + + // Copy over, return + stream->codec = codec; + stream->file = hnd; + stream->length = length; + return stream; +} + +/* +================= +S_CodecUtilClose +================= +*/ +void S_CodecUtilClose(snd_stream_t **stream) +{ + FS_FCloseFile((*stream)->file); + Z_Free(*stream); + *stream = NULL; +} diff --git a/src/client/snd_codec.h b/src/client/snd_codec.h new file mode 100644 index 0000000..5497a7c --- /dev/null +++ b/src/client/snd_codec.h @@ -0,0 +1,99 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef _SND_CODEC_H_ +#define _SND_CODEC_H_ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +typedef struct snd_info_s +{ + int rate; + int width; + int channels; + int samples; + int size; + int dataofs; +} snd_info_t; + +typedef struct snd_codec_s snd_codec_t; + +typedef struct snd_stream_s +{ + snd_codec_t *codec; + fileHandle_t file; + snd_info_t info; + int length; + int pos; + void *ptr; +} snd_stream_t; + +// Codec functions +typedef void *(*CODEC_LOAD)(const char *filename, snd_info_t *info); +typedef snd_stream_t *(*CODEC_OPEN)(const char *filename); +typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer); +typedef void (*CODEC_CLOSE)(snd_stream_t *stream); + +// Codec data structure +struct snd_codec_s +{ + char *ext; + CODEC_LOAD load; + CODEC_OPEN open; + CODEC_READ read; + CODEC_CLOSE close; + snd_codec_t *next; +}; + +// Codec management +void S_CodecInit( void ); +void S_CodecShutdown( void ); +void S_CodecRegister(snd_codec_t *codec); +void *S_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_CodecOpenStream(const char *filename); +void S_CodecCloseStream(snd_stream_t *stream); +int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); + +// Util functions (used by codecs) +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec); +void S_CodecUtilClose(snd_stream_t **stream); + +// WAV Codec +extern snd_codec_t wav_codec; +void *S_WAV_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_WAV_CodecOpenStream(const char *filename); +void S_WAV_CodecCloseStream(snd_stream_t *stream); +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); + +// Ogg Vorbis codec +#ifdef USE_CODEC_VORBIS +extern snd_codec_t ogg_codec; +void *S_OGG_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_OGG_CodecOpenStream(const char *filename); +void S_OGG_CodecCloseStream(snd_stream_t *stream); +int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); +#endif // USE_CODEC_VORBIS + +#endif // !_SND_CODEC_H_ diff --git a/src/client/snd_codec_ogg.c b/src/client/snd_codec_ogg.c new file mode 100644 index 0000000..0ca2f68 --- /dev/null +++ b/src/client/snd_codec_ogg.c @@ -0,0 +1,479 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// OGG support is enabled by this define +#ifdef USE_CODEC_VORBIS + +// includes for the Q3 sound system +#include "client.h" +#include "snd_codec.h" + +// includes for the OGG codec +#include +#define OV_EXCLUDE_STATIC_CALLBACKS +#include + +// The OGG codec can return the samples in a number of different formats, +// we use the standard signed short format. +#define OGG_SAMPLEWIDTH 2 + +// Q3 OGG codec +snd_codec_t ogg_codec = +{ + ".ogg", + S_OGG_CodecLoad, + S_OGG_CodecOpenStream, + S_OGG_CodecReadStream, + S_OGG_CodecCloseStream, + NULL +}; + +// callbacks for vobisfile + +// fread() replacement +size_t S_OGG_Callback_read(void *ptr, size_t size, size_t nmemb, void *datasource) +{ + snd_stream_t *stream; + int byteSize = 0; + int bytesRead = 0; + size_t nMembRead = 0; + + // check if input is valid + if(!ptr) + { + errno = EFAULT; + return 0; + } + + if(!(size && nmemb)) + { + // It's not an error, caller just wants zero bytes! + errno = 0; + return 0; + } + + if(!datasource) + { + errno = EBADF; + return 0; + } + + // we use a snd_stream_t in the generic pointer to pass around + stream = (snd_stream_t *) datasource; + + // FS_Read does not support multi-byte elements + byteSize = nmemb * size; + + // read it with the Q3 function FS_Read() + bytesRead = FS_Read(ptr, byteSize, stream->file); + + // update the file position + stream->pos += bytesRead; + + // this function returns the number of elements read not the number of bytes + nMembRead = bytesRead / size; + + // even if the last member is only read partially + // it is counted as a whole in the return value + if(bytesRead % size) + { + nMembRead++; + } + + return nMembRead; +} + +// fseek() replacement +int S_OGG_Callback_seek(void *datasource, ogg_int64_t offset, int whence) +{ + snd_stream_t *stream; + int retVal = 0; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + // we must map the whence to its Q3 counterpart + switch(whence) + { + case SEEK_SET : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = (int) offset; + break; + } + + case SEEK_CUR : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos += (int) offset; + break; + } + + case SEEK_END : + { + // Quake 3 seems to have trouble with FS_SEEK_END + // so we use the file length and FS_SEEK_SET + + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) stream->length + (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = stream->length + (int) offset; + break; + } + + default : + { + // unknown whence, so we return an error + errno = EINVAL; + return -1; + } + } + + // stream->pos shouldn't be smaller than zero or bigger than the filesize + stream->pos = (stream->pos < 0) ? 0 : stream->pos; + stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos; + + return 0; +} + +// fclose() replacement +int S_OGG_Callback_close(void *datasource) +{ + // we do nothing here and close all things manually in S_OGG_CodecCloseStream() + return 0; +} + +// ftell() replacement +long S_OGG_Callback_tell(void *datasource) +{ + snd_stream_t *stream; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + return (long) FS_FTell(stream->file); +} + +// the callback structure +const ov_callbacks S_OGG_Callbacks = +{ + &S_OGG_Callback_read, + &S_OGG_Callback_seek, + &S_OGG_Callback_close, + &S_OGG_Callback_tell +}; + +/* +================= +S_OGG_CodecOpenStream +================= +*/ +snd_stream_t *S_OGG_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + + // OGG codec control structure + OggVorbis_File *vf; + + // some variables used to get informations about the OGG + vorbis_info *OGGInfo; + ogg_int64_t numSamples; + + // check if input is valid + if(!filename) + { + return NULL; + } + + // Open the stream + stream = S_CodecUtilOpen(filename, &ogg_codec); + if(!stream) + { + return NULL; + } + + // alloctate the OggVorbis_File + vf = Z_Malloc(sizeof(OggVorbis_File)); + if(!vf) + { + S_CodecUtilClose(&stream); + + return NULL; + } + + // open the codec with our callbacks and stream as the generic pointer + if(ov_open_callbacks(stream, vf, NULL, 0, S_OGG_Callbacks) != 0) + { + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // the stream must be seekable + if(!ov_seekable(vf)) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // we only support OGGs with one substream + if(ov_streams(vf) != 1) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the info about channels and rate + OGGInfo = ov_info(vf, 0); + if(!OGGInfo) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the number of sample-frames in the OGG + numSamples = ov_pcm_total(vf, 0); + + // fill in the info-structure in the stream + stream->info.rate = OGGInfo->rate; + stream->info.width = OGG_SAMPLEWIDTH; + stream->info.channels = OGGInfo->channels; + stream->info.samples = numSamples; + stream->info.size = stream->info.samples * stream->info.channels * stream->info.width; + stream->info.dataofs = 0; + + // We use stream->pos for the file pointer in the compressed ogg file + stream->pos = 0; + + // We use the generic pointer in stream for the OGG codec control structure + stream->ptr = vf; + + return stream; +} + +/* +================= +S_OGG_CodecCloseStream +================= +*/ +void S_OGG_CodecCloseStream(snd_stream_t *stream) +{ + // check if input is valid + if(!stream) + { + return; + } + + // let the OGG codec cleanup its stuff + ov_clear((OggVorbis_File *) stream->ptr); + + // free the OGG codec control struct + Z_Free(stream->ptr); + + // close the stream + S_CodecUtilClose(&stream); +} + +/* +================= +S_OGG_CodecReadStream +================= +*/ +int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + // buffer handling + int bytesRead, bytesLeft, c; + char *bufPtr; + + // Bitstream for the decoder + int BS = 0; + + // big endian machines want their samples in big endian order + int IsBigEndian = 0; + +# ifdef Q3_BIG_ENDIAN + IsBigEndian = 1; +# endif // Q3_BIG_ENDIAN + + // check if input is valid + if(!(stream && buffer)) + { + return 0; + } + + if(bytes <= 0) + { + return 0; + } + + bytesRead = 0; + bytesLeft = bytes; + bufPtr = buffer; + + // cycle until we have the requested or all available bytes read + while(-1) + { + // read some bytes from the OGG codec + c = ov_read((OggVorbis_File *) stream->ptr, bufPtr, bytesLeft, IsBigEndian, OGG_SAMPLEWIDTH, 1, &BS); + + // no more bytes are left + if(c <= 0) + { + break; + } + + bytesRead += c; + bytesLeft -= c; + bufPtr += c; + + // we have enough bytes + if(bytesLeft <= 0) + { + break; + } + } + + return bytesRead; +} + +/* +===================================================================== +S_OGG_CodecLoad + +We handle S_OGG_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. +====================================================================== +*/ +void *S_OGG_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *buffer; + int bytesRead; + + // check if input is valid + if(!(filename && info)) + { + return NULL; + } + + // open the file as a stream + stream = S_OGG_CodecOpenStream(filename); + if(!stream) + { + return NULL; + } + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->size = stream->info.size; + info->dataofs = stream->info.dataofs; + + // allocate a buffer + // this buffer must be free-ed by the caller of this function + buffer = Z_Malloc(info->size); + if(!buffer) + { + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + // fill the buffer + bytesRead = S_OGG_CodecReadStream(stream, info->size, buffer); + + // we don't even have read a single byte + if(bytesRead <= 0) + { + Z_Free(buffer); + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + S_OGG_CodecCloseStream(stream); + + return buffer; +} + +#endif // USE_CODEC_VORBIS diff --git a/src/client/snd_codec_wav.c b/src/client/snd_codec_wav.c new file mode 100644 index 0000000..dce561a --- /dev/null +++ b/src/client/snd_codec_wav.c @@ -0,0 +1,295 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" + +/* +================= +FGetLittleLong +================= +*/ +static int FGetLittleLong( fileHandle_t f ) { + int v; + + FS_Read( &v, sizeof(v), f ); + + return LittleLong( v); +} + +/* +================= +FGetLittleShort +================= +*/ +static short FGetLittleShort( fileHandle_t f ) { + short v; + + FS_Read( &v, sizeof(v), f ); + + return LittleShort( v); +} + +/* +================= +S_ReadChunkInfo +================= +*/ +static int S_ReadChunkInfo(fileHandle_t f, char *name) +{ + int len, r; + + name[4] = 0; + + r = FS_Read(name, 4, f); + if(r != 4) + return -1; + + len = FGetLittleLong(f); + if( len < 0 ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Negative chunk length\n" ); + return -1; + } + + return len; +} + +/* +================= +S_FindRIFFChunk + +Returns the length of the data in the chunk, or -1 if not found +================= +*/ +static int S_FindRIFFChunk( fileHandle_t f, char *chunk ) { + char name[5]; + int len; + + while( ( len = S_ReadChunkInfo(f, name) ) >= 0 ) + { + // If this is the right chunk, return + if( !Q_strncmp( name, chunk, 4 ) ) + return len; + + len = PAD( len, 2 ); + + // Not the right chunk - skip it + FS_Seek( f, len, FS_SEEK_CUR ); + } + + return -1; +} + +/* +================= +S_ByteSwapRawSamples +================= +*/ +static void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + +/* +================= +S_ReadRIFFHeader +================= +*/ +static qboolean S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info) +{ + char dump[16]; + int wav_format; + int bits; + int fmtlen = 0; + + // skip the riff wav header + FS_Read(dump, 12, file); + + // Scan for the format chunk + if((fmtlen = S_FindRIFFChunk(file, "fmt ")) < 0) + { + Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"fmt\" chunk\n"); + return qfalse; + } + + // Save the parameters + wav_format = FGetLittleShort(file); + info->channels = FGetLittleShort(file); + info->rate = FGetLittleLong(file); + FGetLittleLong(file); + FGetLittleShort(file); + bits = FGetLittleShort(file); + + if( bits < 8 ) + { + Com_Printf( S_COLOR_RED "ERROR: Less than 8 bit sound is not supported\n"); + return qfalse; + } + + info->width = bits / 8; + info->dataofs = 0; + + // Skip the rest of the format chunk if required + if(fmtlen > 16) + { + fmtlen -= 16; + FS_Seek( file, fmtlen, FS_SEEK_CUR ); + } + + // Scan for the data chunk + if( (info->size = S_FindRIFFChunk(file, "data")) < 0) + { + Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"data\" chunk\n"); + return qfalse; + } + info->samples = (info->size / info->width) / info->channels; + + return qtrue; +} + +// WAV codec +snd_codec_t wav_codec = +{ + ".wav", + S_WAV_CodecLoad, + S_WAV_CodecOpenStream, + S_WAV_CodecReadStream, + S_WAV_CodecCloseStream, + NULL +}; + +/* +================= +S_WAV_CodecLoad +================= +*/ +void *S_WAV_CodecLoad(const char *filename, snd_info_t *info) +{ + fileHandle_t file; + void *buffer; + + // Try to open the file + FS_FOpenFileRead(filename, &file, qtrue); + if(!file) + { + Com_Printf( S_COLOR_RED "ERROR: Could not open \"%s\"\n", + filename); + return NULL; + } + + // Read the RIFF header + if(!S_ReadRIFFHeader(file, info)) + { + FS_FCloseFile(file); + Com_Printf( S_COLOR_RED "ERROR: Incorrect/unsupported format in \"%s\"\n", + filename); + return NULL; + } + + // Allocate some memory + buffer = Z_Malloc(info->size); + if(!buffer) + { + FS_FCloseFile(file); + Com_Printf( S_COLOR_RED "ERROR: Out of memory reading \"%s\"\n", + filename); + return NULL; + } + + // Read, byteswap + FS_Read(buffer, info->size, file); + S_ByteSwapRawSamples(info->samples, info->width, info->channels, (byte *)buffer); + + // Close and return + FS_FCloseFile(file); + return buffer; +} + +/* +================= +S_WAV_CodecOpenStream +================= +*/ +snd_stream_t *S_WAV_CodecOpenStream(const char *filename) +{ + snd_stream_t *rv; + + // Open + rv = S_CodecUtilOpen(filename, &wav_codec); + if(!rv) + return NULL; + + // Read the RIFF header + if(!S_ReadRIFFHeader(rv->file, &rv->info)) + { + S_CodecUtilClose(&rv); + return NULL; + } + + return rv; +} + +/* +================= +S_WAV_CodecCloseStream +================= +*/ +void S_WAV_CodecCloseStream(snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +/* +================= +S_WAV_CodecReadStream +================= +*/ +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + int remaining = stream->info.size - stream->pos; + int samples; + + if(remaining <= 0) + return 0; + if(bytes > remaining) + bytes = remaining; + stream->pos += bytes; + samples = (bytes / stream->info.width) / stream->info.channels; + FS_Read(buffer, bytes, stream->file); + S_ByteSwapRawSamples(samples, stream->info.width, stream->info.channels, buffer); + return bytes; +} diff --git a/src/client/snd_dma.c b/src/client/snd_dma.c new file mode 100644 index 0000000..5d53533 --- /dev/null +++ b/src/client/snd_dma.c @@ -0,0 +1,1558 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: snd_dma.c + * + * desc: main control for any streaming sound output device + * + * $Archive: /MissionPack/code/client/snd_dma.c $ + * + *****************************************************************************/ + +#include "snd_local.h" +#include "snd_codec.h" +#include "client.h" + +void S_Update_( void ); +void S_Base_StopAllSounds(void); +void S_Base_StopBackgroundTrack( void ); + +snd_stream_t *s_backgroundStream = NULL; +static char s_backgroundLoop[MAX_QPATH]; +//static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused + + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +// only begin attenuating sound volumes when outside the FULLVOLUME range +#define SOUND_FULLVOLUME 80 + +#define SOUND_ATTENUATE 0.0008f + +channel_t s_channels[MAX_CHANNELS]; +channel_t loop_channels[MAX_CHANNELS]; +int numLoopChannels; + +static int s_soundStarted; +static qboolean s_soundMuted; + +dma_t dma; + +static int listener_number; +static vec3_t listener_origin; +static vec3_t listener_axis[3]; + +int s_soundtime; // sample PAIRS +int s_paintedtime; // sample PAIRS + +// MAX_SFX may be larger than MAX_SOUNDS because +// of custom player sounds +#define MAX_SFX 4096 +sfx_t s_knownSfx[MAX_SFX]; +int s_numSfx = 0; + +#define LOOP_HASH 128 +static sfx_t *sfxHash[LOOP_HASH]; + +cvar_t *s_testsound; +cvar_t *s_khz; +cvar_t *s_show; +cvar_t *s_mixahead; +cvar_t *s_mixPreStep; + +static loopSound_t loopSounds[MAX_GENTITIES]; +static channel_t *freelist = NULL; + +int s_rawend[MAX_RAW_STREAMS]; +portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; + + +// ==================================================================== +// User-setable variables +// ==================================================================== + + +void S_Base_SoundInfo(void) { + Com_Printf("----- Sound Info -----\n" ); + if (!s_soundStarted) { + Com_Printf ("sound system not started\n"); + } else { + Com_Printf("%5d stereo\n", dma.channels - 1); + Com_Printf("%5d samples\n", dma.samples); + Com_Printf("%5d samplebits\n", dma.samplebits); + Com_Printf("%5d submission_chunk\n", dma.submission_chunk); + Com_Printf("%5d speed\n", dma.speed); + Com_Printf("%p dma buffer\n", dma.buffer); + if ( s_backgroundStream ) { + Com_Printf("Background file: %s\n", s_backgroundLoop ); + } else { + Com_Printf("No background file.\n" ); + } + + } + Com_Printf("----------------------\n" ); +} + + +#ifdef USE_VOIP +static +void S_Base_StartCapture( void ) +{ + // !!! FIXME: write me. +} + +static +int S_Base_AvailableCaptureSamples( void ) +{ + // !!! FIXME: write me. + return 0; +} + +static +void S_Base_Capture( int samples, byte *data ) +{ + // !!! FIXME: write me. +} + +static +void S_Base_StopCapture( void ) +{ + // !!! FIXME: write me. +} + +static +void S_Base_MasterGain( float val ) +{ + // !!! FIXME: write me. +} +#endif + + + +/* +================= +S_Base_SoundList +================= +*/ +void S_Base_SoundList( void ) { + int i; + sfx_t *sfx; + int size, total; + char type[4][16]; + char mem[2][16]; + + strcpy(type[0], "16bit"); + strcpy(type[1], "adpcm"); + strcpy(type[2], "daub4"); + strcpy(type[3], "mulaw"); + strcpy(mem[0], "paged out"); + strcpy(mem[1], "resident "); + total = 0; + for (sfx=s_knownSfx, i=0 ; isoundLength; + total += size; + Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], + sfx->soundName, mem[sfx->inMemory] ); + } + Com_Printf ("Total resident: %i\n", total); + S_DisplayFreeMemory(); +} + + + +void S_ChannelFree(channel_t *v) { + v->thesfx = NULL; + *(channel_t **)v = freelist; + freelist = (channel_t*)v; +} + +channel_t* S_ChannelMalloc( void ) { + channel_t *v; + if (freelist == NULL) { + return NULL; + } + v = freelist; + freelist = *(channel_t **)freelist; + v->allocTime = Com_Milliseconds(); + return v; +} + +void S_ChannelSetup( void ) { + channel_t *p, *q; + + // clear all the sounds so they don't + Com_Memset( s_channels, 0, sizeof( s_channels ) ); + + p = s_channels;; + q = p + MAX_CHANNELS; + while (--q > p) { + *(channel_t **)q = q-1; + } + + *(channel_t **)q = NULL; + freelist = p + MAX_CHANNELS - 1; + Com_DPrintf("Channel memory manager started\n"); +} + + + +// ======================================================================= +// Load a sound +// ======================================================================= + +/* +================ +return a hash value for the sfx name +================ +*/ +static long S_HashSFXName(const char *name) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (name[i] != '\0') { + letter = tolower(name[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (LOOP_HASH-1); + return hash; +} + +/* +================== +S_FindName + +Will allocate a new sfx if it isn't found +================== +*/ +static sfx_t *S_FindName( const char *name ) { + int i; + int hash; + + sfx_t *sfx; + + if (!name) { + Com_Error (ERR_FATAL, "S_FindName: NULL\n"); + } + if (!name[0]) { + Com_Error (ERR_FATAL, "S_FindName: empty name\n"); + } + + if (strlen(name) >= MAX_QPATH) { + Com_Error (ERR_FATAL, "Sound name too long: %s", name); + } + + hash = S_HashSFXName(name); + + sfx = sfxHash[hash]; + // see if already loaded + while (sfx) { + if (!Q_stricmp(sfx->soundName, name) ) { + return sfx; + } + sfx = sfx->next; + } + + // find a free sfx + for (i=0 ; i < s_numSfx ; i++) { + if (!s_knownSfx[i].soundName[0]) { + break; + } + } + + if (i == s_numSfx) { + if (s_numSfx == MAX_SFX) { + Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); + } + s_numSfx++; + } + + sfx = &s_knownSfx[i]; + Com_Memset (sfx, 0, sizeof(*sfx)); + strcpy (sfx->soundName, name); + + sfx->next = sfxHash[hash]; + sfxHash[hash] = sfx; + + return sfx; +} + +/* +================= +S_DefaultSound +================= +*/ +void S_DefaultSound( sfx_t *sfx ) { + + int i; + + sfx->soundLength = 512; + sfx->soundData = SND_malloc(); + sfx->soundData->next = NULL; + + + for ( i = 0 ; i < sfx->soundLength ; i++ ) { + sfx->soundData->sndChunk[i] = i; + } +} + +/* +=================== +S_DisableSounds + +Disables sounds until the next S_BeginRegistration. +This is called when the hunk is cleared and the sounds +are no longer valid. +=================== +*/ +void S_Base_DisableSounds( void ) { + S_Base_StopAllSounds(); + s_soundMuted = qtrue; +} + +/* +================== +S_RegisterSound + +Creates a default buzz sound if the file can't be loaded +================== +*/ +sfxHandle_t S_Base_RegisterSound( const char *name, qboolean compressed ) { + sfx_t *sfx; + + compressed = qfalse; + if (!s_soundStarted) { + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Sound name exceeds MAX_QPATH\n" ); + return 0; + } + + sfx = S_FindName( name ); + if ( sfx->soundData ) { + if ( sfx->defaultSound ) { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + return 0; + } + return sfx - s_knownSfx; + } + + sfx->inMemory = qfalse; + sfx->soundCompressed = compressed; + + S_memoryLoad(sfx); + + if ( sfx->defaultSound ) { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + return 0; + } + + return sfx - s_knownSfx; +} + +/* +================== +S_Base_SoundDuration +================== +*/ +static int S_Base_SoundDuration( sfxHandle_t handle ) { + if ( handle < 0 || handle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_Base_SoundDuration: handle %i out of range\n", handle ); + return 0; + } + return s_knownSfx[ handle ].duration; +} + + + +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_Base_BeginRegistration( void ) { + s_soundMuted = qfalse; // we can play again + + if (s_numSfx == 0) { + SND_setup(); + + s_numSfx = 0; + Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) ); + Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); + + S_Base_RegisterSound("sound/feedback/hit.wav", qfalse); // changed to a sound in baseq3 + } +} + +void S_memoryLoad(sfx_t *sfx) { + // load the sound file + if ( !S_LoadSound ( sfx ) ) { +// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName ); + sfx->defaultSound = qtrue; + } + sfx->inMemory = qtrue; +} + +//============================================================================= + +/* +================= +S_SpatializeOrigin + +Used for spatializing s_channels +================= +*/ +void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol) +{ + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + vec3_t vec; + + const float dist_mult = SOUND_ATTENUATE; + + // calculate stereo seperation and distance attenuation + VectorSubtract(origin, listener_origin, source_vec); + + dist = VectorNormalize(source_vec); + dist -= SOUND_FULLVOLUME; + if (dist < 0) + dist = 0; // close enough to be at full volume + dist *= dist_mult; // different attenuation levels + + VectorRotate( source_vec, listener_axis, vec ); + + dot = -vec[1]; + + if (dma.channels == 1) + { // no attenuation = no spatialization + rscale = 1.0; + lscale = 1.0; + } + else + { + rscale = 0.5 * (1.0 + dot); + lscale = 0.5 * (1.0 - dot); + if ( rscale < 0 ) { + rscale = 0; + } + if ( lscale < 0 ) { + lscale = 0; + } + } + + // add in distance effect + scale = (1.0 - dist) * rscale; + *right_vol = (master_vol * scale); + if (*right_vol < 0) + *right_vol = 0; + + scale = (1.0 - dist) * lscale; + *left_vol = (master_vol * scale); + if (*left_vol < 0) + *left_vol = 0; +} + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +/* +==================== +S_StartSound + +Validates the parms and ques the sound up +if pos is NULL, the sound will be dynamically sourced from the entity +Entchannel 0 will never override a playing sound +==================== +*/ +void S_Base_StartSound(vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { + channel_t *ch; + sfx_t *sfx; + int i, oldest, chosen, time; + int inplay, allowed; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_StartSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( s_show->integer == 1 ) { + Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName ); + } + + time = Com_Milliseconds(); + +// Com_Printf("playing %s\n", sfx->soundName); + // pick a channel to play on + + allowed = 4; + if (entityNum == listener_number) { + allowed = 8; + } + + ch = s_channels; + inplay = 0; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum == entityNum && ch->thesfx == sfx) { + if (time - ch->allocTime < 50) { +// if (Cvar_VariableValue( "cg_showmiss" )) { +// Com_Printf("double sound start\n"); +// } + return; + } + inplay++; + } + } + + if (inplay>allowed) { + return; + } + + sfx->lastTimeUsed = time; + + ch = S_ChannelMalloc(); // entityNum, entchannel); + if (!ch) { + ch = s_channels; + + oldest = sfx->lastTimeUsed; + chosen = -1; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + oldest = ch->allocTime; + chosen = i; + } + } + if (chosen == -1) { + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum != listener_number && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + oldest = ch->allocTime; + chosen = i; + } + } + if (chosen == -1) { + ch = s_channels; + if (ch->entnum == listener_number) { + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->allocTimeallocTime; + chosen = i; + } + } + } + if (chosen == -1) { + Com_DPrintf("dropping sound\n"); + return; + } + } + } + ch = &s_channels[chosen]; + ch->allocTime = sfx->lastTimeUsed; + } + + if (origin) { + VectorCopy (origin, ch->origin); + ch->fixed_origin = qtrue; + } else { + ch->fixed_origin = qfalse; + } + + ch->master_vol = 127; + ch->entnum = entityNum; + ch->thesfx = sfx; + ch->startSample = START_SAMPLE_IMMEDIATE; + ch->entchannel = entchannel; + ch->leftvol = ch->master_vol; // these will get calced at next spatialize + ch->rightvol = ch->master_vol; // unless the game isn't running + ch->doppler = qfalse; +} + + +/* +================== +S_StartLocalSound +================== +*/ +void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_StartLocalSound: handle %i out of range\n", sfxHandle ); + return; + } + + S_Base_StartSound (NULL, listener_number, channelNum, sfxHandle ); +} + + +/* +================== +S_ClearSoundBuffer + +If we are about to perform file access, clear the buffer +so sound doesn't stutter. +================== +*/ +void S_Base_ClearSoundBuffer( void ) { + int clear; + + if (!s_soundStarted) + return; + + // stop looping sounds + Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t)); + Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t)); + numLoopChannels = 0; + + S_ChannelSetup(); + + Com_Memset(s_rawend, '\0', sizeof (s_rawend)); + + if (dma.samplebits == 8) + clear = 0x80; + else + clear = 0; + + SNDDMA_BeginPainting (); + if (dma.buffer) + Com_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8); + SNDDMA_Submit (); +} + +/* +================== +S_StopAllSounds +================== +*/ +void S_Base_StopAllSounds(void) { + if ( !s_soundStarted ) { + return; + } + + // stop the background music + S_Base_StopBackgroundTrack(); + + S_Base_ClearSoundBuffer (); +} + +/* +============================================================== + +continuous looping sounds are added each frame + +============================================================== +*/ + +void S_Base_StopLoopingSound(int entityNum) { + loopSounds[entityNum].active = qfalse; +// loopSounds[entityNum].sfx = 0; + loopSounds[entityNum].kill = qfalse; +} + +/* +================== +S_ClearLoopingSounds + +================== +*/ +void S_Base_ClearLoopingSounds( qboolean killall ) { + int i; + for ( i = 0 ; i < MAX_GENTITIES ; i++) { + if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) { + loopSounds[i].kill = qfalse; + S_Base_StopLoopingSound(i); + } + } + numLoopChannels = 0; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_Base_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_AddLoopingSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + + VectorCopy( origin, loopSounds[entityNum].origin ); + VectorCopy( velocity, loopSounds[entityNum].velocity ); + loopSounds[entityNum].active = qtrue; + loopSounds[entityNum].kill = qtrue; + loopSounds[entityNum].doppler = qfalse; + loopSounds[entityNum].oldDopplerScale = 1.0; + loopSounds[entityNum].dopplerScale = 1.0; + loopSounds[entityNum].sfx = sfx; + + if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) { + vec3_t out; + float lena, lenb; + + loopSounds[entityNum].doppler = qtrue; + lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin); + VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out); + lenb = DistanceSquared(loopSounds[listener_number].origin, out); + if ((loopSounds[entityNum].framenum+1) != cls.framecount) { + loopSounds[entityNum].oldDopplerScale = 1.0; + } else { + loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale; + } + loopSounds[entityNum].dopplerScale = lenb/(lena*100); + if (loopSounds[entityNum].dopplerScale<=1.0) { + loopSounds[entityNum].doppler = qfalse; // don't bother doing the math + } else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) { + loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE; + } + } + + loopSounds[entityNum].framenum = cls.framecount; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_Base_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + VectorCopy( origin, loopSounds[entityNum].origin ); + VectorCopy( velocity, loopSounds[entityNum].velocity ); + loopSounds[entityNum].sfx = sfx; + loopSounds[entityNum].active = qtrue; + loopSounds[entityNum].kill = qfalse; + loopSounds[entityNum].doppler = qfalse; +} + + + +/* +================== +S_AddLoopSounds + +Spatialize all of the looping sounds. +All sounds are on the same cycle, so any duplicates can just +sum up the channel multipliers. +================== +*/ +void S_AddLoopSounds (void) { + int i, j, time; + int left_total, right_total, left, right; + channel_t *ch; + loopSound_t *loop, *loop2; + static int loopFrame; + + + numLoopChannels = 0; + + time = Com_Milliseconds(); + + loopFrame++; + for ( i = 0 ; i < MAX_GENTITIES ; i++) { + loop = &loopSounds[i]; + if ( !loop->active || loop->mergeFrame == loopFrame ) { + continue; // already merged into an earlier sound + } + + if (loop->kill) { + S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total); // 3d + } else { + S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total); // sphere + } + + loop->sfx->lastTimeUsed = time; + + for (j=(i+1); j< MAX_GENTITIES ; j++) { + loop2 = &loopSounds[j]; + if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) { + continue; + } + loop2->mergeFrame = loopFrame; + + if (loop2->kill) { + S_SpatializeOrigin( loop2->origin, 127, &left, &right); // 3d + } else { + S_SpatializeOrigin( loop2->origin, 90, &left, &right); // sphere + } + + loop2->sfx->lastTimeUsed = time; + left_total += left; + right_total += right; + } + if (left_total == 0 && right_total == 0) { + continue; // not audible + } + + // allocate a channel + ch = &loop_channels[numLoopChannels]; + + if (left_total > 255) { + left_total = 255; + } + if (right_total > 255) { + right_total = 255; + } + + ch->master_vol = 127; + ch->leftvol = left_total; + ch->rightvol = right_total; + ch->thesfx = loop->sfx; + ch->doppler = loop->doppler; + ch->dopplerScale = loop->dopplerScale; + ch->oldDopplerScale = loop->oldDopplerScale; + numLoopChannels++; + if (numLoopChannels == MAX_CHANNELS) { + return; + } + } +} + +//============================================================================= + +/* +================= +S_ByteSwapRawSamples + +If raw data has been loaded in little endien binary form, this must be done. +If raw data was calculated, as with ADPCM, this should not be called. +================= +*/ +void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + +/* +============ +S_Base_RawSamples + +Music streaming +============ +*/ +void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume ) { + int i; + int src, dst; + float scale; + int intVolume; + portable_samplepair_t *rawsamples; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) { + return; + } + rawsamples = s_rawsamples[stream]; + + if(s_muted->integer) + intVolume = 0; + else + intVolume = 256 * volume * s_volume->value; + + if ( s_rawend[stream] < s_soundtime ) { + Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_soundtime ); + s_rawend[stream] = s_soundtime; + } + + scale = (float)rate / dma.speed; + +//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]); + if (s_channels == 2 && width == 2) + { + if (scale == 1.0) + { // optimized case + for (i=0 ; i= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src*2] * intVolume; + rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; + } + } + } + else if (s_channels == 1 && width == 2) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src] * intVolume; + rawsamples[dst].right = ((short *)data)[src] * intVolume; + } + } + else if (s_channels == 2 && width == 1) + { + intVolume *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((char *)data)[src*2] * intVolume; + rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; + } + } + else if (s_channels == 1 && width == 1) + { + intVolume *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; + rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; + } + } + + if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) { + Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime ); + } +} + +//============================================================================= + +/* +===================== +S_UpdateEntityPosition + +let the sound system know where an entity currently is +====================== +*/ +void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + } + VectorCopy( origin, loopSounds[entityNum].origin ); +} + + +/* +============ +S_Respatialize + +Change the volumes of all the playing sounds for changes in their positions +============ +*/ +void S_Base_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { + int i; + channel_t *ch; + vec3_t origin; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + listener_number = entityNum; + VectorCopy(head, listener_origin); + VectorCopy(axis[0], listener_axis[0]); + VectorCopy(axis[1], listener_axis[1]); + VectorCopy(axis[2], listener_axis[2]); + + // update spatialization for dynamic sounds + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx ) { + continue; + } + // anything coming from the view entity will always be full volume + if (ch->entnum == listener_number) { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + } else { + if (ch->fixed_origin) { + VectorCopy( ch->origin, origin ); + } else { + VectorCopy( loopSounds[ ch->entnum ].origin, origin ); + } + + S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol); + } + } + + // add loopsounds + S_AddLoopSounds (); +} + + +/* +======================== +S_ScanChannelStarts + +Returns qtrue if any new sounds were started since the last mix +======================== +*/ +qboolean S_ScanChannelStarts( void ) { + channel_t *ch; + int i; + qboolean newSamples; + + newSamples = qfalse; + ch = s_channels; + + for (i=0; ithesfx ) { + continue; + } + // if this channel was just started this frame, + // set the sample count to it begins mixing + // into the very first sample + if ( ch->startSample == START_SAMPLE_IMMEDIATE ) { + ch->startSample = s_paintedtime; + newSamples = qtrue; + continue; + } + + // if it is completely finished by now, clear it + if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) { + S_ChannelFree(ch); + } + } + + return newSamples; +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Base_Update( void ) { + int i; + int total; + channel_t *ch; + + if ( !s_soundStarted || s_soundMuted ) { +// Com_DPrintf ("not started or muted\n"); + return; + } + + // + // debugging output + // + if ( s_show->integer == 2 ) { + total = 0; + ch = s_channels; + for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { + Com_Printf ("%d %d %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName); + total++; + } + } + + Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime); + } + + // add raw data from streamed samples + S_UpdateBackgroundTrack(); + + // mix some sound + S_Update_(); +} + +void S_GetSoundtime(void) +{ + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = dma.samples / dma.channels; + + if( CL_VideoRecording( ) ) + { + s_soundtime += (int)ceil( dma.speed / cl_aviFrameRate->value ); + return; + } + + // it is possible to miscount buffers if it has wrapped twice between + // calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + if (samplepos < oldsamplepos) + { + buffers++; // buffer wrapped + + if (s_paintedtime > 0x40000000) + { // time to chop things off to avoid 32 bit limits + buffers = 0; + s_paintedtime = fullsamples; + S_Base_StopAllSounds (); + } + } + oldsamplepos = samplepos; + + s_soundtime = buffers*fullsamples + samplepos/dma.channels; + +#if 0 +// check to make sure that we haven't overshot + if (s_paintedtime < s_soundtime) + { + Com_DPrintf ("S_Update_ : overflow\n"); + s_paintedtime = s_soundtime; + } +#endif + + if ( dma.submission_chunk < 256 ) { + s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed; + } else { + s_paintedtime = s_soundtime + dma.submission_chunk; + } +} + + +void S_Update_(void) { + unsigned endtime; + int samps; + static float lastTime = 0.0f; + float ma, op; + float thisTime, sane; + static int ot = -1; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + thisTime = Com_Milliseconds(); + + // Updates s_soundtime + S_GetSoundtime(); + + if (s_soundtime == ot) { + return; + } + ot = s_soundtime; + + // clear any sound effects that end before the current time, + // and start any new sounds + S_ScanChannelStarts(); + + sane = thisTime - lastTime; + if (sane<11) { + sane = 11; // 85hz + } + + ma = s_mixahead->value * dma.speed; + op = s_mixPreStep->value + sane*dma.speed*0.01; + + if (op < ma) { + ma = op; + } + + // mix ahead of current position + endtime = s_soundtime + ma; + + // mix to an even submission block size + endtime = (endtime + dma.submission_chunk-1) + & ~(dma.submission_chunk-1); + + // never mix more than the complete buffer + samps = dma.samples >> (dma.channels-1); + if (endtime - s_soundtime > samps) + endtime = s_soundtime + samps; + + + + SNDDMA_BeginPainting (); + + S_PaintChannels (endtime); + + SNDDMA_Submit (); + + lastTime = thisTime; +} + + + +/* +=============================================================================== + +background music functions + +=============================================================================== +*/ + +/* +====================== +S_StopBackgroundTrack +====================== +*/ +void S_Base_StopBackgroundTrack( void ) { + if(!s_backgroundStream) + return; + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + s_rawend[0] = 0; +} + +/* +====================== +S_StartBackgroundTrack +====================== +*/ +void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){ + if ( !intro ) { + intro = ""; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop ); + + if(!*intro) + { + S_Base_StopBackgroundTrack(); + return; + } + + if( !loop ) { + s_backgroundLoop[0] = 0; + } else { + Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + } + + // close the background track, but DON'T reset s_rawend + // if restarting the same back ground track + if(s_backgroundStream) + { + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + } + + // Open stream + s_backgroundStream = S_CodecOpenStream(intro); + if(!s_backgroundStream) { + Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", intro ); + return; + } + + if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) { + Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", intro ); + } +} + +/* +====================== +S_UpdateBackgroundTrack +====================== +*/ +void S_UpdateBackgroundTrack( void ) { + int bufferSamples; + int fileSamples; + byte raw[30000]; // just enough to fit in a mac stack frame + int fileBytes; + int r; + + if(!s_backgroundStream) { + return; + } + + // don't bother playing anything if musicvolume is 0 + if ( s_musicVolume->value <= 0 ) { + return; + } + + // see how many samples should be copied into the raw buffer + if ( s_rawend[0] < s_soundtime ) { + s_rawend[0] = s_soundtime; + } + + while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed; + + if (!fileSamples) + return; + + // our max buffer size + fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels); + if ( fileBytes > sizeof(raw) ) { + fileBytes = sizeof(raw); + fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels); + } + + // Read + r = S_CodecReadStream(s_backgroundStream, fileBytes, raw); + if(r < fileBytes) + { + fileBytes = r; + fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels); + } + + if(r > 0) + { + // add to raw buffer + S_Base_RawSamples( 0, fileSamples, s_backgroundStream->info.rate, + s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value ); + } + else + { + // loop + if(s_backgroundLoop[0]) + { + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + S_Base_StartBackgroundTrack( s_backgroundLoop, s_backgroundLoop ); + if(!s_backgroundStream) + return; + } + else + { + S_Base_StopBackgroundTrack(); + return; + } + } + + } +} + + +/* +====================== +S_FreeOldestSound +====================== +*/ + +void S_FreeOldestSound( void ) { + int i, oldest, used; + sfx_t *sfx; + sndBuffer *buffer, *nbuffer; + + oldest = Com_Milliseconds(); + used = 0; + + for (i=1 ; i < s_numSfx ; i++) { + sfx = &s_knownSfx[i]; + if (sfx->inMemory && sfx->lastTimeUsedlastTimeUsed; + } + } + + sfx = &s_knownSfx[used]; + + Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName); + + buffer = sfx->soundData; + while(buffer != NULL) { + nbuffer = buffer->next; + SND_free(buffer); + buffer = nbuffer; + } + sfx->inMemory = qfalse; + sfx->soundData = NULL; +} + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= + +void S_Base_Shutdown( void ) { + if ( !s_soundStarted ) { + return; + } + + SNDDMA_Shutdown(); + + s_soundStarted = 0; + + Cmd_RemoveCommand("s_info"); +} + +/* +================ +S_Init +================ +*/ +qboolean S_Base_Init( soundInterface_t *si ) { + qboolean r; + + if( !si ) { + return qfalse; + } + + s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE); + s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); + s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); + s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); + s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); + + r = SNDDMA_Init(); + + if ( r ) { + s_soundStarted = 1; + s_soundMuted = 1; +// s_numSfx = 0; + + Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); + + s_soundtime = 0; + s_paintedtime = 0; + + S_Base_StopAllSounds( ); + } else { + return qfalse; + } + + si->Shutdown = S_Base_Shutdown; + si->StartSound = S_Base_StartSound; + si->StartLocalSound = S_Base_StartLocalSound; + si->StartBackgroundTrack = S_Base_StartBackgroundTrack; + si->StopBackgroundTrack = S_Base_StopBackgroundTrack; + si->RawSamples = S_Base_RawSamples; + si->StopAllSounds = S_Base_StopAllSounds; + si->ClearLoopingSounds = S_Base_ClearLoopingSounds; + si->AddLoopingSound = S_Base_AddLoopingSound; + si->AddRealLoopingSound = S_Base_AddRealLoopingSound; + si->StopLoopingSound = S_Base_StopLoopingSound; + si->Respatialize = S_Base_Respatialize; + si->UpdateEntityPosition = S_Base_UpdateEntityPosition; + si->Update = S_Base_Update; + si->DisableSounds = S_Base_DisableSounds; + si->BeginRegistration = S_Base_BeginRegistration; + si->RegisterSound = S_Base_RegisterSound; + si->SoundDuration = S_Base_SoundDuration; + si->ClearSoundBuffer = S_Base_ClearSoundBuffer; + si->SoundInfo = S_Base_SoundInfo; + si->SoundList = S_Base_SoundList; + +#ifdef USE_VOIP + si->StartCapture = S_Base_StartCapture; + si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples; + si->Capture = S_Base_Capture; + si->StopCapture = S_Base_StopCapture; + si->MasterGain = S_Base_MasterGain; +#endif + + return qtrue; +} diff --git a/src/client/snd_local.h b/src/client/snd_local.h new file mode 100644 index 0000000..53ddbca --- /dev/null +++ b/src/client/snd_local.h @@ -0,0 +1,254 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// snd_local.h -- private sound definations + + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "snd_public.h" + +#define PAINTBUFFER_SIZE 4096 // this is in samples + +#define SND_CHUNK_SIZE 1024 // samples +#define SND_CHUNK_SIZE_FLOAT (SND_CHUNK_SIZE/2) // floats +#define SND_CHUNK_SIZE_BYTE (SND_CHUNK_SIZE*2) // floats + +typedef struct { + int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down + int right; +} portable_samplepair_t; + +typedef struct adpcm_state { + short sample; /* Previous output value */ + char index; /* Index into stepsize table */ +} adpcm_state_t; + +typedef struct sndBuffer_s { + short sndChunk[SND_CHUNK_SIZE]; + struct sndBuffer_s *next; + int size; + adpcm_state_t adpcm; +} sndBuffer; + +typedef struct sfx_s { + sndBuffer *soundData; + qboolean defaultSound; // couldn't be loaded, so use buzz + qboolean inMemory; // not in Memory + qboolean soundCompressed; // not in Memory + int soundCompressionMethod; + int soundLength; + char soundName[MAX_QPATH]; + int lastTimeUsed; + int duration; + struct sfx_s *next; +} sfx_t; + +typedef struct { + int channels; + int samples; // mono samples in buffer + int submission_chunk; // don't mix less than this # + int samplebits; + int speed; + byte *buffer; +} dma_t; + +#define START_SAMPLE_IMMEDIATE 0x7fffffff + +#define MAX_DOPPLER_SCALE 50.0f //arbitrary + +typedef struct loopSound_s { + vec3_t origin; + vec3_t velocity; + sfx_t *sfx; + int mergeFrame; + qboolean active; + qboolean kill; + qboolean doppler; + float dopplerScale; + float oldDopplerScale; + int framenum; +} loopSound_t; + +typedef struct +{ + int allocTime; + int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix + int entnum; // to allow overriding a specific sound + int entchannel; // to allow overriding a specific sound + int leftvol; // 0-255 volume after spatialization + int rightvol; // 0-255 volume after spatialization + int master_vol; // 0-255 volume before spatialization + float dopplerScale; + float oldDopplerScale; + vec3_t origin; // only use if fixed_origin is set + qboolean fixed_origin; // use origin instead of fetching entnum's origin + sfx_t *thesfx; // sfx structure + qboolean doppler; +} channel_t; + + +#define WAV_FORMAT_PCM 1 + + +typedef struct { + int format; + int rate; + int width; + int channels; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + +// Interface between Q3 sound "api" and the sound backend +typedef struct +{ + void (*Shutdown)(void); + void (*StartSound)( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); + void (*StartLocalSound)( sfxHandle_t sfx, int channelNum ); + void (*StartBackgroundTrack)( const char *intro, const char *loop ); + void (*StopBackgroundTrack)( void ); + void (*RawSamples)(int stream, int samples, int rate, int width, int channels, const byte *data, float volume); + void (*StopAllSounds)( void ); + void (*ClearLoopingSounds)( qboolean killall ); + void (*AddLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); + void (*AddRealLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); + void (*StopLoopingSound)(int entityNum ); + void (*Respatialize)( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + void (*UpdateEntityPosition)( int entityNum, const vec3_t origin ); + void (*Update)( void ); + void (*DisableSounds)( void ); + void (*BeginRegistration)( void ); + sfxHandle_t (*RegisterSound)( const char *sample, qboolean compressed ); + int (*SoundDuration)( sfxHandle_t handle ); + void (*ClearSoundBuffer)( void ); + void (*SoundInfo)( void ); + void (*SoundList)( void ); +#ifdef USE_VOIP + void (*StartCapture)( void ); + int (*AvailableCaptureSamples)( void ); + void (*Capture)( int samples, byte *data ); + void (*StopCapture)( void ); + void (*MasterGain)( float gain ); +#endif +} soundInterface_t; + + +/* +==================================================================== + + SYSTEM SPECIFIC FUNCTIONS + +==================================================================== +*/ + +// initializes cycling through a DMA buffer and returns information on it +qboolean SNDDMA_Init(void); + +// gets the current DMA position +int SNDDMA_GetDMAPos(void); + +// shutdown the DMA xfer. +void SNDDMA_Shutdown(void); + +void SNDDMA_BeginPainting (void); + +void SNDDMA_Submit(void); + +//==================================================================== + +#define MAX_CHANNELS 96 + +extern channel_t s_channels[MAX_CHANNELS]; +extern channel_t loop_channels[MAX_CHANNELS]; +extern int numLoopChannels; + +extern int s_paintedtime; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; +extern dma_t dma; + +#define MAX_RAW_SAMPLES 16384 +#define MAX_RAW_STREAMS 128 +extern portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; +extern int s_rawend[MAX_RAW_STREAMS]; + +extern cvar_t *s_volume; +extern cvar_t *s_musicVolume; +extern cvar_t *s_muted; +extern cvar_t *s_doppler; + +extern cvar_t *s_testsound; + +qboolean S_LoadSound( sfx_t *sfx ); + +void SND_free(sndBuffer *v); +sndBuffer* SND_malloc( void ); +void SND_setup( void ); + +void S_PaintChannels(int endtime); + +void S_memoryLoad(sfx_t *sfx); + +// spatializes a channel +void S_Spatialize(channel_t *ch); + +// adpcm functions +int S_AdpcmMemoryNeeded( const wavinfo_t *info ); +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ); +void S_AdpcmGetSamples(sndBuffer *chunk, short *to); + +// wavelet function + +#define SENTINEL_MULAW_ZERO_RUN 127 +#define SENTINEL_MULAW_FOUR_BIT_RUN 126 + +void S_FreeOldestSound( void ); + +#define NXStream byte + +void encodeWavelet(sfx_t *sfx, short *packets); +void decodeWavelet( sndBuffer *stream, short *packets); + +void encodeMuLaw( sfx_t *sfx, short *packets); +extern short mulawToShort[256]; + +extern short *sfxScratchBuffer; +extern sfx_t *sfxScratchPointer; +extern int sfxScratchIndex; + +qboolean S_Base_Init( soundInterface_t *si ); + +// OpenAL stuff +typedef enum +{ + SRCPRI_AMBIENT = 0, // Ambient sound effects + SRCPRI_ENTITY, // Entity sound effects + SRCPRI_ONESHOT, // One-shot sounds + SRCPRI_LOCAL, // Local sounds + SRCPRI_STREAM // Streams (music, cutscenes) +} alSrcPriority_t; + +typedef int srcHandle_t; + +qboolean S_AL_Init( soundInterface_t *si ); diff --git a/src/client/snd_main.c b/src/client/snd_main.c new file mode 100644 index 0000000..22213cc --- /dev/null +++ b/src/client/snd_main.c @@ -0,0 +1,562 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" +#include "snd_local.h" +#include "snd_public.h" + +cvar_t *s_volume; +cvar_t *s_muted; +cvar_t *s_musicVolume; +cvar_t *s_doppler; +cvar_t *s_backend; +cvar_t *s_muteWhenMinimized; +cvar_t *s_muteWhenUnfocused; + +static soundInterface_t si; + +/* +================= +S_ValidateInterface +================= +*/ +static qboolean S_ValidSoundInterface( soundInterface_t *si ) +{ + if( !si->Shutdown ) return qfalse; + if( !si->StartSound ) return qfalse; + if( !si->StartLocalSound ) return qfalse; + if( !si->StartBackgroundTrack ) return qfalse; + if( !si->StopBackgroundTrack ) return qfalse; + if( !si->RawSamples ) return qfalse; + if( !si->StopAllSounds ) return qfalse; + if( !si->ClearLoopingSounds ) return qfalse; + if( !si->AddLoopingSound ) return qfalse; + if( !si->AddRealLoopingSound ) return qfalse; + if( !si->StopLoopingSound ) return qfalse; + if( !si->Respatialize ) return qfalse; + if( !si->UpdateEntityPosition ) return qfalse; + if( !si->Update ) return qfalse; + if( !si->DisableSounds ) return qfalse; + if( !si->BeginRegistration ) return qfalse; + if( !si->RegisterSound ) return qfalse; + if( !si->SoundDuration ) return qfalse; + if( !si->ClearSoundBuffer ) return qfalse; + if( !si->SoundInfo ) return qfalse; + if( !si->SoundList ) return qfalse; + +#ifdef USE_VOIP + if( !si->StartCapture ) return qfalse; + if( !si->AvailableCaptureSamples ) return qfalse; + if( !si->Capture ) return qfalse; + if( !si->StopCapture ) return qfalse; + if( !si->MasterGain ) return qfalse; +#endif + + return qtrue; +} + +/* +================= +S_StartSound +================= +*/ +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) +{ + if( si.StartSound ) { + si.StartSound( origin, entnum, entchannel, sfx ); + } +} + +/* +================= +S_StartLocalSound +================= +*/ +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ) +{ + if( si.StartLocalSound ) { + si.StartLocalSound( sfx, channelNum ); + } +} + +/* +================= +S_StartBackgroundTrack +================= +*/ +void S_StartBackgroundTrack( const char *intro, const char *loop ) +{ + if( si.StartBackgroundTrack ) { + si.StartBackgroundTrack( intro, loop ); + } +} + +/* +================= +S_StopBackgroundTrack +================= +*/ +void S_StopBackgroundTrack( void ) +{ + if( si.StopBackgroundTrack ) { + si.StopBackgroundTrack( ); + } +} + +/* +================= +S_RawSamples +================= +*/ +void S_RawSamples (int stream, int samples, int rate, int width, int channels, + const byte *data, float volume) +{ + if( si.RawSamples ) { + si.RawSamples( stream, samples, rate, width, channels, data, volume ); + } +} + +/* +================= +S_StopAllSounds +================= +*/ +void S_StopAllSounds( void ) +{ + if( si.StopAllSounds ) { + si.StopAllSounds( ); + } +} + +/* +================= +S_ClearLoopingSounds +================= +*/ +void S_ClearLoopingSounds( qboolean killall ) +{ + if( si.ClearLoopingSounds ) { + si.ClearLoopingSounds( killall ); + } +} + +/* +================= +S_AddLoopingSound +================= +*/ +void S_AddLoopingSound( int entityNum, const vec3_t origin, + const vec3_t velocity, sfxHandle_t sfx ) +{ + if( si.AddLoopingSound ) { + si.AddLoopingSound( entityNum, origin, velocity, sfx ); + } +} + +/* +================= +S_AddRealLoopingSound +================= +*/ +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, + const vec3_t velocity, sfxHandle_t sfx ) +{ + if( si.AddRealLoopingSound ) { + si.AddRealLoopingSound( entityNum, origin, velocity, sfx ); + } +} + +/* +================= +S_StopLoopingSound +================= +*/ +void S_StopLoopingSound( int entityNum ) +{ + if( si.StopLoopingSound ) { + si.StopLoopingSound( entityNum ); + } +} + +/* +================= +S_Respatialize +================= +*/ +void S_Respatialize( int entityNum, const vec3_t origin, + vec3_t axis[3], int inwater ) +{ + if( si.Respatialize ) { + si.Respatialize( entityNum, origin, axis, inwater ); + } +} + +/* +================= +S_UpdateEntityPosition +================= +*/ +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + if( si.UpdateEntityPosition ) { + si.UpdateEntityPosition( entityNum, origin ); + } +} + +/* +================= +S_Update +================= +*/ +void S_Update( void ) +{ + if(s_muted->integer) + { + if(!(s_muteWhenMinimized->integer && com_minimized->integer) && + !(s_muteWhenUnfocused->integer && com_unfocused->integer)) + { + s_muted->integer = qfalse; + s_muted->modified = qtrue; + } + } + else + { + if((s_muteWhenMinimized->integer && com_minimized->integer) || + (s_muteWhenUnfocused->integer && com_unfocused->integer)) + { + s_muted->integer = qtrue; + s_muted->modified = qtrue; + } + } + + if( si.Update ) { + si.Update( ); + } +} + +/* +================= +S_DisableSounds +================= +*/ +void S_DisableSounds( void ) +{ + if( si.DisableSounds ) { + si.DisableSounds( ); + } +} + +/* +================= +S_BeginRegistration +================= +*/ +void S_BeginRegistration( void ) +{ + if( si.BeginRegistration ) { + si.BeginRegistration( ); + } +} + +/* +================= +S_RegisterSound +================= +*/ +sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ) +{ + if( si.RegisterSound ) { + return si.RegisterSound( sample, compressed ); + } else { + return 0; + } +} + +/* +================= +S_SoundDuration +================= +*/ +int S_SoundDuration( sfxHandle_t handle ) +{ + if( si.SoundDuration ) + return si.SoundDuration( handle ); + else + return 0; +} + +/* +================= +S_ClearSoundBuffer +================= +*/ +void S_ClearSoundBuffer( void ) +{ + if( si.ClearSoundBuffer ) { + si.ClearSoundBuffer( ); + } +} + +/* +================= +S_SoundInfo +================= +*/ +void S_SoundInfo( void ) +{ + if( si.SoundInfo ) { + si.SoundInfo( ); + } +} + +/* +================= +S_SoundList +================= +*/ +void S_SoundList( void ) +{ + if( si.SoundList ) { + si.SoundList( ); + } +} + + +#ifdef USE_VOIP +/* +================= +S_StartCapture +================= +*/ +void S_StartCapture( void ) +{ + if( si.StartCapture ) { + si.StartCapture( ); + } +} + +/* +================= +S_AvailableCaptureSamples +================= +*/ +int S_AvailableCaptureSamples( void ) +{ + if( si.AvailableCaptureSamples ) { + return si.AvailableCaptureSamples( ); + } + return 0; +} + +/* +================= +S_Capture +================= +*/ +void S_Capture( int samples, byte *data ) +{ + if( si.Capture ) { + si.Capture( samples, data ); + } +} + +/* +================= +S_StopCapture +================= +*/ +void S_StopCapture( void ) +{ + if( si.StopCapture ) { + si.StopCapture( ); + } +} + +/* +================= +S_MasterGain +================= +*/ +void S_MasterGain( float gain ) +{ + if( si.MasterGain ) { + si.MasterGain( gain ); + } +} +#endif + +//============================================================================= + +/* +================= +S_Play_f +================= +*/ +void S_Play_f( void ) { + int i; + sfxHandle_t h; + char name[256]; + + if( !si.RegisterSound || !si.StartLocalSound ) { + return; + } + + i = 1; + while ( i [loopfile]\n"); + return; + } + +} + +/* +================= +S_Music_f +================= +*/ +void S_StopMusic_f( void ) +{ + if(!si.StopBackgroundTrack) + return; + + si.StopBackgroundTrack(); +} + + +//============================================================================= + +/* +================= +S_Init +================= +*/ +void S_Init( void ) +{ + cvar_t *cv; + qboolean started = qfalse; + + Com_Printf( "------ Initializing Sound ------\n" ); + + s_volume = Cvar_Get( "s_volume", "0.8", CVAR_ARCHIVE ); + s_musicVolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE ); + s_muted = Cvar_Get("s_muted", "0", CVAR_ROM); + s_doppler = Cvar_Get( "s_doppler", "1", CVAR_ARCHIVE ); + s_backend = Cvar_Get( "s_backend", "", CVAR_ROM ); + s_muteWhenMinimized = Cvar_Get( "s_muteWhenMinimized", "0", CVAR_ARCHIVE ); + s_muteWhenUnfocused = Cvar_Get( "s_muteWhenUnfocused", "0", CVAR_ARCHIVE ); + + cv = Cvar_Get( "s_initsound", "1", 0 ); + if( !cv->integer ) { + Com_Printf( "Sound disabled.\n" ); + } else { + + S_CodecInit( ); + + Cmd_AddCommand( "play", S_Play_f ); + Cmd_AddCommand( "music", S_Music_f ); + Cmd_AddCommand( "stopmusic", S_StopMusic_f ); + Cmd_AddCommand( "s_list", S_SoundList ); + Cmd_AddCommand( "s_stop", S_StopAllSounds ); + Cmd_AddCommand( "s_info", S_SoundInfo ); + + cv = Cvar_Get( "s_useOpenAL", "0", CVAR_ARCHIVE ); + if( cv->integer ) { + //OpenAL + started = S_AL_Init( &si ); + Cvar_Set( "s_backend", "OpenAL" ); + } + + if( !started ) { + started = S_Base_Init( &si ); + Cvar_Set( "s_backend", "base" ); + } + + if( started ) { + if( !S_ValidSoundInterface( &si ) ) { + Com_Error( ERR_FATAL, "Sound interface invalid." ); + } + + S_SoundInfo( ); + Com_Printf( "Sound initialization successful.\n" ); + } else { + Com_Printf( "Sound initialization failed.\n" ); + } + } + + Com_Printf( "--------------------------------\n"); +} + +/* +================= +S_Shutdown +================= +*/ +void S_Shutdown( void ) +{ + if( si.Shutdown ) { + si.Shutdown( ); + } + + Com_Memset( &si, 0, sizeof( soundInterface_t ) ); + + Cmd_RemoveCommand( "play" ); + Cmd_RemoveCommand( "music"); + Cmd_RemoveCommand( "stopmusic"); + Cmd_RemoveCommand( "s_list" ); + Cmd_RemoveCommand( "s_stop" ); + Cmd_RemoveCommand( "s_info" ); + + S_CodecShutdown( ); +} + diff --git a/src/client/snd_mem.c b/src/client/snd_mem.c new file mode 100644 index 0000000..f3d90dd --- /dev/null +++ b/src/client/snd_mem.c @@ -0,0 +1,270 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: snd_mem.c + * + * desc: sound caching + * + * $Archive: /MissionPack/code/client/snd_mem.c $ + * + *****************************************************************************/ + +#include "snd_local.h" +#include "snd_codec.h" + +#define DEF_COMSOUNDMEGS "8" + +/* +=============================================================================== + +memory management + +=============================================================================== +*/ + +static sndBuffer *buffer = NULL; +static sndBuffer *freelist = NULL; +static int inUse = 0; +static int totalInUse = 0; + +short *sfxScratchBuffer = NULL; +sfx_t *sfxScratchPointer = NULL; +int sfxScratchIndex = 0; + +void SND_free(sndBuffer *v) { + *(sndBuffer **)v = freelist; + freelist = (sndBuffer*)v; + inUse += sizeof(sndBuffer); +} + +sndBuffer* SND_malloc(void) { + sndBuffer *v; +redo: + if (freelist == NULL) { + S_FreeOldestSound(); + goto redo; + } + + inUse -= sizeof(sndBuffer); + totalInUse += sizeof(sndBuffer); + + v = freelist; + freelist = *(sndBuffer **)freelist; + v->next = NULL; + return v; +} + +void SND_setup(void) { + sndBuffer *p, *q; + cvar_t *cv; + int scs; + + cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE ); + + scs = (cv->integer*1536); + + buffer = malloc(scs*sizeof(sndBuffer) ); + // allocate the stack based hunk allocator + sfxScratchBuffer = malloc(SND_CHUNK_SIZE * sizeof(short) * 4); //Hunk_Alloc(SND_CHUNK_SIZE * sizeof(short) * 4); + sfxScratchPointer = NULL; + + inUse = scs*sizeof(sndBuffer); + p = buffer;; + q = p + scs; + while (--q > p) + *(sndBuffer **)q = q-1; + + *(sndBuffer **)q = NULL; + freelist = p + scs - 1; + + Com_Printf("Sound memory manager started\n"); +} + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) { + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + int part; + sndBuffer *chunk; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = sfx->soundLength / stepscale; + sfx->soundLength = outcount; + + samplefrac = 0; + fracstep = stepscale * 256; + chunk = sfx->soundData; + + for (i=0 ; i> 8; + samplefrac += fracstep; + if( inwidth == 2 ) { + sample = ( ((short *)data)[srcsample] ); + } else { + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + } + part = (i&(SND_CHUNK_SIZE-1)); + if (part == 0) { + sndBuffer *newchunk; + newchunk = SND_malloc(); + if (chunk == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + } + + chunk->sndChunk[part] = sample; + } +} + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) { + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = samples / stepscale; + + samplefrac = 0; + fracstep = stepscale * 256; + + for (i=0 ; i> 8; + samplefrac += fracstep; + if( inwidth == 2 ) { + sample = LittleShort ( ((short *)data)[srcsample] ); + } else { + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + } + sfx[i] = sample; + } + return outcount; +} + +//============================================================================= + +/* +============== +S_LoadSound + +The filename may be different than sfx->name in the case +of a forced fallback of a player specific sound +============== +*/ +qboolean S_LoadSound( sfx_t *sfx ) +{ + byte *data; + short *samples; + snd_info_t info; + int size_per_sec; + + // player specific sounds are never directly loaded + if ( sfx->soundName[0] == '*') { + return qfalse; + } + + // load it in + data = S_CodecLoad(sfx->soundName, &info); + if(!data) + return qfalse; + + size_per_sec = info.rate * info.channels * info.width; + if( size_per_sec > 0 ) + sfx->duration = (int)(1000.0f * ((double)info.size / size_per_sec)); + + if ( info.width == 1 ) { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName); + } + + if ( info.rate != 22050 ) { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName); + } + + samples = Hunk_AllocateTempMemory(info.samples * sizeof(short) * 2); + + sfx->lastTimeUsed = Com_Milliseconds()+1; + + // each of these compression schemes works just fine + // but the 16bit quality is much nicer and with a local + // install assured we can rely upon the sound memory + // manager to do the right thing for us and page + // sound in as needed + + if( sfx->soundCompressed == qtrue) { + sfx->soundCompressionMethod = 1; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, data + info.dataofs ); + S_AdpcmEncodeSound(sfx, samples); +#if 0 + } else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1) { + sfx->soundCompressionMethod = 3; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + encodeMuLaw( sfx, samples); + } else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) { + sfx->soundCompressionMethod = 2; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + encodeWavelet( sfx, samples); +#endif + } else { + sfx->soundCompressionMethod = 0; + sfx->soundLength = info.samples; + sfx->soundData = NULL; + ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse ); + } + + Hunk_FreeTempMemory(samples); + Z_Free(data); + + return qtrue; +} + +void S_DisplayFreeMemory(void) { + Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse); +} diff --git a/src/client/snd_mix.c b/src/client/snd_mix.c new file mode 100644 index 0000000..2dd4428 --- /dev/null +++ b/src/client/snd_mix.c @@ -0,0 +1,742 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// snd_mix.c -- portable code to mix sounds for snd_dma.c + +#include "client.h" +#include "snd_local.h" +#if idppc_altivec && !defined(MACOS_X) +#include +#endif + +static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +static int snd_vol; + +int* snd_p; +int snd_linear_count; +short* snd_out; + +#if !id386 // if configured not to use asm + +void S_WriteLinearBlastStereo16 (void) +{ + int i; + int val; + + for (i=0 ; i>8; + if (val > 0x7fff) + snd_out[i] = 0x7fff; + else if (val < -32768) + snd_out[i] = -32768; + else + snd_out[i] = val; + + val = snd_p[i+1]>>8; + if (val > 0x7fff) + snd_out[i+1] = 0x7fff; + else if (val < -32768) + snd_out[i+1] = -32768; + else + snd_out[i+1] = val; + } +} +#elif defined(__GNUC__) +// uses snd_mixa.s +void S_WriteLinearBlastStereo16 (void); +#else + +__declspec( naked ) void S_WriteLinearBlastStereo16 (void) +{ + __asm { + + push edi + push ebx + mov ecx,ds:dword ptr[snd_linear_count] + mov ebx,ds:dword ptr[snd_p] + mov edi,ds:dword ptr[snd_out] +LWLBLoopTop: + mov eax,ds:dword ptr[-8+ebx+ecx*4] + sar eax,8 + cmp eax,07FFFh + jg LClampHigh + cmp eax,0FFFF8000h + jnl LClampDone + mov eax,0FFFF8000h + jmp LClampDone +LClampHigh: + mov eax,07FFFh +LClampDone: + mov edx,ds:dword ptr[-4+ebx+ecx*4] + sar edx,8 + cmp edx,07FFFh + jg LClampHigh2 + cmp edx,0FFFF8000h + jnl LClampDone2 + mov edx,0FFFF8000h + jmp LClampDone2 +LClampHigh2: + mov edx,07FFFh +LClampDone2: + shl edx,16 + and eax,0FFFFh + or edx,eax + mov ds:dword ptr[-4+edi+ecx*2],edx + sub ecx,2 + jnz LWLBLoopTop + pop ebx + pop edi + ret + } +} + +#endif + +void S_TransferStereo16 (unsigned long *pbuf, int endtime) +{ + int lpos; + int ls_paintedtime; + + snd_p = (int *) paintbuffer; + ls_paintedtime = s_paintedtime; + + while (ls_paintedtime < endtime) + { + // handle recirculating buffer issues + lpos = ls_paintedtime & ((dma.samples>>1)-1); + + snd_out = (short *) pbuf + (lpos<<1); + + snd_linear_count = (dma.samples>>1) - lpos; + if (ls_paintedtime + snd_linear_count > endtime) + snd_linear_count = endtime - ls_paintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + S_WriteLinearBlastStereo16 (); + + snd_p += snd_linear_count; + ls_paintedtime += (snd_linear_count>>1); + + if( CL_VideoRecording( ) ) + CL_WriteAVIAudioFrame( (byte *)snd_out, snd_linear_count << 1 ); + } +} + +/* +=================== +S_TransferPaintBuffer + +=================== +*/ +void S_TransferPaintBuffer(int endtime) +{ + int out_idx; + int count; + int out_mask; + int *p; + int step; + int val; + unsigned long *pbuf; + + pbuf = (unsigned long *)dma.buffer; + + + if ( s_testsound->integer ) { + int i; + int count; + + // write a fixed sine wave + count = (endtime - s_paintedtime); + for (i=0 ; i> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < -32768) + val = -32768; + out[out_idx] = val; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (dma.samplebits == 8) + { + unsigned char *out = (unsigned char *) pbuf; + while (count--) + { + val = *p >> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < -32768) + val = -32768; + out[out_idx] = (val>>8) + 128; + out_idx = (out_idx + 1) & out_mask; + } + } + } +} + + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +#if idppc_altivec +static void S_PaintChannelFrom16_altivec( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata, fdiv, fleftvol, frightvol; + + samp = &paintbuffer[ bufferOffset ]; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + chunk = sc->soundData; + while (sampleOffset>=SND_CHUNK_SIZE) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler || ch->dopplerScale==1.0f) { + vector signed short volume_vec; + vector unsigned int volume_shift; + int vectorCount, samplesLeft, chunkSamplesLeft; + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + samples = chunk->sndChunk; + ((short *)&volume_vec)[0] = leftvol; + ((short *)&volume_vec)[1] = leftvol; + ((short *)&volume_vec)[4] = leftvol; + ((short *)&volume_vec)[5] = leftvol; + ((short *)&volume_vec)[2] = rightvol; + ((short *)&volume_vec)[3] = rightvol; + ((short *)&volume_vec)[6] = rightvol; + ((short *)&volume_vec)[7] = rightvol; + volume_shift = vec_splat_u32(8); + i = 0; + + while(i < count) { + /* Try to align destination to 16-byte boundary */ + while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) { + data = samples[sampleOffset++]; + samp[i].left += (data * leftvol)>>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + i++; + } + /* Destination is now aligned. Process as many 8-sample + chunks as we can before we run out of room from the current + sound chunk. We do 8 per loop to avoid extra source data reads. */ + samplesLeft = count - i; + chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset; + if(samplesLeft > chunkSamplesLeft) + samplesLeft = chunkSamplesLeft; + + vectorCount = samplesLeft / 8; + + if(vectorCount) + { + vector unsigned char tmp; + vector short s0, s1, sampleData0, sampleData1; + vector signed int merge0, merge1; + vector signed int d0, d1, d2, d3; + vector unsigned char samplePermute0 = + VECCONST_UINT8(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7); + vector unsigned char samplePermute1 = + VECCONST_UINT8(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15); + vector unsigned char loadPermute0, loadPermute1; + + // Rather than permute the vectors after we load them to do the sample + // replication and rearrangement, we permute the alignment vector so + // we do everything in one step below and avoid data shuffling. + tmp = vec_lvsl(0,&samples[sampleOffset]); + loadPermute0 = vec_perm(tmp,tmp,samplePermute0); + loadPermute1 = vec_perm(tmp,tmp,samplePermute1); + + s0 = *(vector short *)&samples[sampleOffset]; + while(vectorCount) + { + /* Load up source (16-bit) sample data */ + s1 = *(vector short *)&samples[sampleOffset+7]; + + /* Load up destination sample data */ + d0 = *(vector signed int *)&samp[i]; + d1 = *(vector signed int *)&samp[i+2]; + d2 = *(vector signed int *)&samp[i+4]; + d3 = *(vector signed int *)&samp[i+6]; + + sampleData0 = vec_perm(s0,s1,loadPermute0); + sampleData1 = vec_perm(s0,s1,loadPermute1); + + merge0 = vec_mule(sampleData0,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData0,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d0 = vec_add(merge0,d0); + d1 = vec_add(merge1,d1); + + merge0 = vec_mule(sampleData1,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData1,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d2 = vec_add(merge0,d2); + d3 = vec_add(merge1,d3); + + /* Store destination sample data */ + *(vector signed int *)&samp[i] = d0; + *(vector signed int *)&samp[i+2] = d1; + *(vector signed int *)&samp[i+4] = d2; + *(vector signed int *)&samp[i+6] = d3; + + i += 8; + vectorCount--; + s0 = s1; + sampleOffset += 8; + } + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } + } + } else { + fleftvol = ch->leftvol*snd_vol; + frightvol = ch->rightvol*snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + for ( i=0 ; idopplerScale; + boff = ooff; + fdata = 0; + for (j=aoff; jnext; + if (!chunk) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + fdata += samples[j&(SND_CHUNK_SIZE-1)]; + } + fdiv = 256 * (boff-aoff); + samp[i].left += (fdata * fleftvol)/fdiv; + samp[i].right += (fdata * frightvol)/fdiv; + } + } +} +#endif + +static void S_PaintChannelFrom16_scalar( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata, fdiv, fleftvol, frightvol; + + samp = &paintbuffer[ bufferOffset ]; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + chunk = sc->soundData; + while (sampleOffset>=SND_CHUNK_SIZE) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler || ch->dopplerScale==1.0f) { + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + samples = chunk->sndChunk; + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } + } else { + fleftvol = ch->leftvol*snd_vol; + frightvol = ch->rightvol*snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + + + + for ( i=0 ; idopplerScale; + boff = ooff; + fdata = 0; + for (j=aoff; jnext; + if (!chunk) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + fdata += samples[j&(SND_CHUNK_SIZE-1)]; + } + fdiv = 256 * (boff-aoff); + samp[i].left += (fdata * fleftvol)/fdiv; + samp[i].right += (fdata * frightvol)/fdiv; + } + } +} + +static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + S_PaintChannelFrom16_altivec( ch, sc, count, sampleOffset, bufferOffset ); + return; + } +#endif + S_PaintChannelFrom16_scalar( ch, sc, count, sampleOffset, bufferOffset ); +} + +void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while (sampleOffset>=(SND_CHUNK_SIZE_FLOAT*4)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE_FLOAT*4); + i++; + } + + if (i!=sfxScratchIndex || sfxScratchPointer != sc) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE*2) { + chunk = chunk->next; + decodeWavelet(chunk, sfxScratchBuffer); + sfxScratchIndex++; + sampleOffset = 0; + } + } +} + +void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + while (sampleOffset>=(SND_CHUNK_SIZE*4)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE*4); + i++; + } + + if (i!=sfxScratchIndex || sfxScratchPointer != sc) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE*4) { + chunk = chunk->next; + S_AdpcmGetSamples( chunk, sfxScratchBuffer); + sampleOffset = 0; + sfxScratchIndex++; + } + } +} + +void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + byte *samples; + float ooff; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while (sampleOffset>=(SND_CHUNK_SIZE*2)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE*2); + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler) { + samples = (byte *)chunk->sndChunk + sampleOffset; + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + samples++; + if (samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) { + chunk = chunk->next; + samples = (byte *)chunk->sndChunk; + } + } + } else { + ooff = sampleOffset; + samples = (byte *)chunk->sndChunk; + for ( i=0 ; idopplerScale; + samp[i].left += (data * leftvol)>>8; + samp[i].right += (data * rightvol)>>8; + if (ooff >= SND_CHUNK_SIZE*2) { + chunk = chunk->next; + if (!chunk) { + chunk = sc->soundData; + } + samples = (byte *)chunk->sndChunk; + ooff = 0.0; + } + } + } +} + +/* +=================== +S_PaintChannels +=================== +*/ +void S_PaintChannels( int endtime ) { + int i; + int end; + int stream; + channel_t *ch; + sfx_t *sc; + int ltime, count; + int sampleOffset; + + if(s_muted->integer) + snd_vol = 0; + else + snd_vol = s_volume->value*255; + +//Com_Printf ("%i to %i\n", s_paintedtime, endtime); + while ( s_paintedtime < endtime ) { + // if paintbuffer is smaller than DMA buffer + // we may need to fill it multiple times + end = endtime; + if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) { + end = s_paintedtime + PAINTBUFFER_SIZE; + } + + // clear the paint buffer and mix any raw samples... + Com_Memset(paintbuffer, 0, sizeof (paintbuffer)); + for (stream = 0; stream < MAX_RAW_STREAMS; stream++) { + if ( s_rawend[stream] >= s_paintedtime ) { + // copy from the streaming sound source + const portable_samplepair_t *rawsamples = s_rawsamples[stream]; + const int stop = (end < s_rawend[stream]) ? end : s_rawend[stream]; + for ( i = s_paintedtime ; i < stop ; i++ ) { + const int s = i&(MAX_RAW_SAMPLES-1); + paintbuffer[i-s_paintedtime].left += rawsamples[s].left; + paintbuffer[i-s_paintedtime].right += rawsamples[s].right; + } + } + } + + // paint in the channels. + ch = s_channels; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + + sampleOffset = ltime - ch->startSample; + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { + if( sc->soundCompressionMethod == 1) { + S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 2) { + S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 3) { + S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else { + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } + } + } + + // paint in the looped channels. + ch = loop_channels; + for ( i = 0; i < numLoopChannels ; i++, ch++ ) { + if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + + if (sc->soundData==NULL || sc->soundLength==0) { + continue; + } + // we might have to make two passes if it + // is a looping sound effect and the end of + // the sample is hit + do { + sampleOffset = (ltime % sc->soundLength); + + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { + if( sc->soundCompressionMethod == 1) { + S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 2) { + S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 3) { + S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else { + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } + ltime += count; + } + } while ( ltime < end); + } + + // transfer out according to DMA format + S_TransferPaintBuffer( end ); + s_paintedtime = end; + } +} diff --git a/src/client/snd_openal.c b/src/client/snd_openal.c new file mode 100644 index 0000000..9b60aa7 --- /dev/null +++ b/src/client/snd_openal.c @@ -0,0 +1,2601 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "snd_local.h" +#include "snd_codec.h" +#include "client.h" + +#ifdef USE_OPENAL + +#include "qal.h" + +// Console variables specific to OpenAL +cvar_t *s_alPrecache; +cvar_t *s_alGain; +cvar_t *s_alSources; +cvar_t *s_alDopplerFactor; +cvar_t *s_alDopplerSpeed; +cvar_t *s_alMinDistance; +cvar_t *s_alMaxDistance; +cvar_t *s_alRolloff; +cvar_t *s_alGraceDistance; +cvar_t *s_alDriver; +cvar_t *s_alDevice; +cvar_t *s_alInputDevice; +cvar_t *s_alAvailableDevices; +cvar_t *s_alAvailableInputDevices; + +static qboolean enumeration_ext = qfalse; +static qboolean enumeration_all_ext = qfalse; +static qboolean capture_ext = qfalse; + +/* +================= +S_AL_Format +================= +*/ +static +ALuint S_AL_Format(int width, int channels) +{ + ALuint format = AL_FORMAT_MONO16; + + // Work out format + if(width == 1) + { + if(channels == 1) + format = AL_FORMAT_MONO8; + else if(channels == 2) + format = AL_FORMAT_STEREO8; + } + else if(width == 2) + { + if(channels == 1) + format = AL_FORMAT_MONO16; + else if(channels == 2) + format = AL_FORMAT_STEREO16; + } + + return format; +} + +/* +================= +S_AL_ErrorMsg +================= +*/ +static const char *S_AL_ErrorMsg(ALenum error) +{ + switch(error) + { + case AL_NO_ERROR: + return "No error"; + case AL_INVALID_NAME: + return "Invalid name"; + case AL_INVALID_ENUM: + return "Invalid enumerator"; + case AL_INVALID_VALUE: + return "Invalid value"; + case AL_INVALID_OPERATION: + return "Invalid operation"; + case AL_OUT_OF_MEMORY: + return "Out of memory"; + default: + return "Unknown error"; + } +} + +/* +================= +S_AL_ClearError +================= +*/ +static void S_AL_ClearError( qboolean quiet ) +{ + int error = qalGetError(); + + if( quiet ) + return; + if(error != AL_NO_ERROR) + { + Com_Printf(S_COLOR_YELLOW "WARNING: unhandled AL error: %s\n", + S_AL_ErrorMsg(error)); + } +} + + +//=========================================================================== + + +typedef struct alSfx_s +{ + char filename[MAX_QPATH]; + ALuint buffer; // OpenAL buffer + snd_info_t info; // information for this sound like rate, sample count.. + + qboolean isDefault; // Couldn't be loaded - use default FX + qboolean isDefaultChecked; // Sound has been check if it isDefault + qboolean inMemory; // Sound is stored in memory + qboolean isLocked; // Sound is locked (can not be unloaded) + int lastUsedTime; // Time last used + + int duration; // Milliseconds + + int loopCnt; // number of loops using this sfx + int loopActiveCnt; // number of playing loops using this sfx + int masterLoopSrc; // All other sources looping this buffer are synced to this master src +} alSfx_t; + +static qboolean alBuffersInitialised = qfalse; + +// Sound effect storage, data structures +#define MAX_SFX 4096 +static alSfx_t knownSfx[MAX_SFX]; +static int numSfx = 0; + +static sfxHandle_t default_sfx; + +/* +================= +S_AL_BufferFindFree + +Find a free handle +================= +*/ +static sfxHandle_t S_AL_BufferFindFree( void ) +{ + int i; + + for(i = 0; i < MAX_SFX; i++) + { + // Got one + if(knownSfx[i].filename[0] == '\0') + { + if(i >= numSfx) + numSfx = i + 1; + return i; + } + } + + // Shit... + Com_Error(ERR_FATAL, "S_AL_BufferFindFree: No free sound handles"); + return -1; +} + +/* +================= +S_AL_BufferFind + +Find a sound effect if loaded, set up a handle otherwise +================= +*/ +static sfxHandle_t S_AL_BufferFind(const char *filename) +{ + // Look it up in the table + sfxHandle_t sfx = -1; + int i; + + for(i = 0; i < numSfx; i++) + { + if(!Q_stricmp(knownSfx[i].filename, filename)) + { + sfx = i; + break; + } + } + + // Not found in table? + if(sfx == -1) + { + alSfx_t *ptr; + + sfx = S_AL_BufferFindFree(); + + // Clear and copy the filename over + ptr = &knownSfx[sfx]; + memset(ptr, 0, sizeof(*ptr)); + ptr->masterLoopSrc = -1; + strcpy(ptr->filename, filename); + } + + // Return the handle + return sfx; +} + +/* +================= +S_AL_BufferUseDefault +================= +*/ +static void S_AL_BufferUseDefault(sfxHandle_t sfx) +{ + if(sfx == default_sfx) + Com_Error(ERR_FATAL, "Can't load default sound effect %s\n", knownSfx[sfx].filename); + + Com_Printf( S_COLOR_YELLOW "WARNING: Using default sound for %s\n", knownSfx[sfx].filename); + knownSfx[sfx].isDefault = qtrue; + knownSfx[sfx].buffer = knownSfx[default_sfx].buffer; +} + +/* +================= +S_AL_BufferUnload +================= +*/ +static void S_AL_BufferUnload(sfxHandle_t sfx) +{ + ALenum error; + + if(knownSfx[sfx].filename[0] == '\0') + return; + + if(!knownSfx[sfx].inMemory) + return; + + // Delete it + S_AL_ClearError( qfalse ); + qalDeleteBuffers(1, &knownSfx[sfx].buffer); + if((error = qalGetError()) != AL_NO_ERROR) + Com_Printf( S_COLOR_RED "ERROR: Can't delete sound buffer for %s\n", + knownSfx[sfx].filename); + + knownSfx[sfx].inMemory = qfalse; +} + +/* +================= +S_AL_BufferEvict +================= +*/ +static qboolean S_AL_BufferEvict( void ) +{ + int i, oldestBuffer = -1; + int oldestTime = Sys_Milliseconds( ); + + for( i = 0; i < numSfx; i++ ) + { + if( !knownSfx[ i ].filename[ 0 ] ) + continue; + + if( !knownSfx[ i ].inMemory ) + continue; + + if( knownSfx[ i ].lastUsedTime < oldestTime ) + { + oldestTime = knownSfx[ i ].lastUsedTime; + oldestBuffer = i; + } + } + + if( oldestBuffer >= 0 ) + { + S_AL_BufferUnload( oldestBuffer ); + return qtrue; + } + else + return qfalse; +} + +/* +================= +S_AL_BufferLoad +================= +*/ +static void S_AL_BufferLoad(sfxHandle_t sfx, qboolean cache) +{ + ALenum error; + ALuint format; + + void *data; + snd_info_t info; + alSfx_t *curSfx = &knownSfx[sfx]; + + // Nothing? + if(curSfx->filename[0] == '\0') + return; + + // Player SFX + if(curSfx->filename[0] == '*') + return; + + // Already done? + if((curSfx->inMemory) || (curSfx->isDefault) || (!cache && curSfx->isDefaultChecked)) + return; + + // Try to load + data = S_CodecLoad(curSfx->filename, &info); + if(!data) + { + S_AL_BufferUseDefault(sfx); + return; + } + + curSfx->isDefaultChecked = qtrue; + + if (!cache) + { + // Don't create AL cache + Z_Free(data); + return; + } + + format = S_AL_Format(info.width, info.channels); + + // Create a buffer + S_AL_ClearError( qfalse ); + qalGenBuffers(1, &curSfx->buffer); + if((error = qalGetError()) != AL_NO_ERROR) + { + S_AL_BufferUseDefault(sfx); + Z_Free(data); + Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n", + curSfx->filename, S_AL_ErrorMsg(error)); + return; + } + + // Fill the buffer + if( info.size == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData(curSfx->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050); + } + else + qalBufferData(curSfx->buffer, format, data, info.size, info.rate); + + error = qalGetError(); + + // If we ran out of memory, start evicting the least recently used sounds + while(error == AL_OUT_OF_MEMORY) + { + if( !S_AL_BufferEvict( ) ) + { + S_AL_BufferUseDefault(sfx); + Z_Free(data); + Com_Printf( S_COLOR_RED "ERROR: Out of memory loading %s\n", curSfx->filename); + return; + } + + // Try load it again + qalBufferData(curSfx->buffer, format, data, info.size, info.rate); + error = qalGetError(); + } + + // Some other error condition + if(error != AL_NO_ERROR) + { + S_AL_BufferUseDefault(sfx); + Z_Free(data); + Com_Printf( S_COLOR_RED "ERROR: Can't fill sound buffer for %s - %s\n", + curSfx->filename, S_AL_ErrorMsg(error)); + return; + } + + curSfx->info = info; + + // Free the memory + Z_Free(data); + + // Woo! + curSfx->inMemory = qtrue; +} + +/* +================= +S_AL_BufferUse +================= +*/ +static +void S_AL_BufferUse(sfxHandle_t sfx) +{ + if(knownSfx[sfx].filename[0] == '\0') + return; + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx, qtrue); + knownSfx[sfx].lastUsedTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_BufferInit +================= +*/ +static +qboolean S_AL_BufferInit( void ) +{ + if(alBuffersInitialised) + return qtrue; + + // Clear the hash table, and SFX table + memset(knownSfx, 0, sizeof(knownSfx)); + numSfx = 0; + + // Load the default sound, and lock it + default_sfx = S_AL_BufferFind("sound/feedback/hit.wav"); + S_AL_BufferUse(default_sfx); + knownSfx[default_sfx].isLocked = qtrue; + + // All done + alBuffersInitialised = qtrue; + return qtrue; +} + +/* +================= +S_AL_BufferShutdown +================= +*/ +static +void S_AL_BufferShutdown( void ) +{ + int i; + + if(!alBuffersInitialised) + return; + + // Unlock the default sound effect + knownSfx[default_sfx].isLocked = qfalse; + + // Free all used effects + for(i = 0; i < numSfx; i++) + S_AL_BufferUnload(i); + + // Clear the tables + memset(knownSfx, 0, sizeof(knownSfx)); + + // All undone + alBuffersInitialised = qfalse; +} + +/* +================= +S_AL_RegisterSound +================= +*/ +static +sfxHandle_t S_AL_RegisterSound( const char *sample, qboolean compressed ) +{ + sfxHandle_t sfx = S_AL_BufferFind(sample); + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx, s_alPrecache->integer); + knownSfx[sfx].lastUsedTime = Com_Milliseconds(); + + if (knownSfx[sfx].isDefault) { + return 0; + } + + return sfx; +} + +/* +================= +S_AL_SoundDuration +================= +*/ +static +int S_AL_SoundDuration( sfxHandle_t sfx ) +{ + if (sfx < 0 || sfx >= numSfx) + { + Com_Printf(S_COLOR_RED "ERROR: S_AL_SoundDuration: handle %i out of range\n", sfx); + return 0; + } + return knownSfx[sfx].duration; +} + +/* +================= +S_AL_BufferGet + +Return's an sfx's buffer +================= +*/ +static +ALuint S_AL_BufferGet(sfxHandle_t sfx) +{ + return knownSfx[sfx].buffer; +} + + +//=========================================================================== + + +typedef struct src_s +{ + ALuint alSource; // OpenAL source object + sfxHandle_t sfx; // Sound effect in use + + int lastUsedTime; // Last time used + alSrcPriority_t priority; // Priority + int entity; // Owning entity (-1 if none) + int channel; // Associated channel (-1 if none) + + qboolean isActive; // Is this source currently in use? + qboolean isPlaying; // Is this source currently playing, or stopped? + qboolean isLocked; // This is locked (un-allocatable) + qboolean isLooping; // Is this a looping effect (attached to an entity) + qboolean isTracking; // Is this object tracking its owner + + float curGain; // gain employed if source is within maxdistance. + float scaleGain; // Last gain value for this source. 0 if muted. + + float lastTimePos; // On stopped loops, the last position in the buffer + int lastSampleTime; // Time when this was stopped + vec3_t loopSpeakerPos; // Origin of the loop speaker + + qboolean local; // Is this local (relative to the cam) +} src_t; + +#ifdef MACOS_X + #define MAX_SRC 64 +#else + #define MAX_SRC 128 +#endif +static src_t srcList[MAX_SRC]; +static int srcCount = 0; +static int srcActiveCnt = 0; +static qboolean alSourcesInitialised = qfalse; +static vec3_t lastListenerOrigin = { 0.0f, 0.0f, 0.0f }; + +typedef struct sentity_s +{ + vec3_t origin; + + qboolean srcAllocated; // If a src_t has been allocated to this entity + int srcIndex; + + qboolean loopAddedThisFrame; + alSrcPriority_t loopPriority; + sfxHandle_t loopSfx; + qboolean startLoopingSound; +} sentity_t; + +static sentity_t entityList[MAX_GENTITIES]; + +/* +================= +S_AL_SanitiseVector +================= +*/ +#define S_AL_SanitiseVector(v) _S_AL_SanitiseVector(v,__LINE__) +static void _S_AL_SanitiseVector( vec3_t v, int line ) +{ + if( Q_isnan( v[ 0 ] ) || Q_isnan( v[ 1 ] ) || Q_isnan( v[ 2 ] ) ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: vector with one or more NaN components " + "being passed to OpenAL at %s:%d -- zeroing\n", __FILE__, line ); + VectorClear( v ); + } +} + + +#define AL_THIRD_PERSON_THRESHOLD_SQ (48.0f*48.0f) + +/* +================= +S_AL_Gain +Set gain to 0 if muted, otherwise set it to given value. +================= +*/ + +static void S_AL_Gain(ALuint source, float gainval) +{ + if(s_muted->integer) + qalSourcef(source, AL_GAIN, 0.0f); + else + qalSourcef(source, AL_GAIN, gainval); +} + +/* +================= +S_AL_ScaleGain +Adapt the gain if necessary to get a quicker fadeout when the source is too far away. +================= +*/ + +static void S_AL_ScaleGain(src_t *chksrc, vec3_t origin) +{ + float distance; + + if(!chksrc->local) + distance = Distance(origin, lastListenerOrigin); + + // If we exceed a certain distance, scale the gain linearly until the sound + // vanishes into nothingness. + if(!chksrc->local && (distance -= s_alMaxDistance->value) > 0) + { + float scaleFactor; + + if(distance >= s_alGraceDistance->value) + scaleFactor = 0.0f; + else + scaleFactor = 1.0f - distance / s_alGraceDistance->value; + + scaleFactor *= chksrc->curGain; + + if(chksrc->scaleGain != scaleFactor) + { + chksrc->scaleGain = scaleFactor; + S_AL_Gain(chksrc->alSource, chksrc->scaleGain); + } + } + else if(chksrc->scaleGain != chksrc->curGain) + { + chksrc->scaleGain = chksrc->curGain; + S_AL_Gain(chksrc->alSource, chksrc->scaleGain); + } +} + +/* +================= +S_AL_HearingThroughEntity +================= +*/ +static qboolean S_AL_HearingThroughEntity( int entityNum ) +{ + float distanceSq; + + if( clc.clientNum == entityNum ) + { + // FIXME: 28/02/06 This is an outrageous hack to detect + // whether or not the player is rendering in third person or not. We can't + // ask the renderer because the renderer has no notion of entities and we + // can't ask cgame since that would involve changing the API and hence mod + // compatibility. I don't think there is any way around this, but I'll leave + // the FIXME just in case anyone has a bright idea. + distanceSq = DistanceSquared( + entityList[ entityNum ].origin, + lastListenerOrigin ); + + if( distanceSq > AL_THIRD_PERSON_THRESHOLD_SQ ) + return qfalse; //we're the player, but third person + else + return qtrue; //we're the player + } + else + return qfalse; //not the player +} + +/* +================= +S_AL_SrcInit +================= +*/ +static +qboolean S_AL_SrcInit( void ) +{ + int i; + int limit; + ALenum error; + + // Clear the sources data structure + memset(srcList, 0, sizeof(srcList)); + srcCount = 0; + srcActiveCnt = 0; + + // Cap s_alSources to MAX_SRC + limit = s_alSources->integer; + if(limit > MAX_SRC) + limit = MAX_SRC; + else if(limit < 16) + limit = 16; + + S_AL_ClearError( qfalse ); + // Allocate as many sources as possible + for(i = 0; i < limit; i++) + { + qalGenSources(1, &srcList[i].alSource); + if((error = qalGetError()) != AL_NO_ERROR) + break; + srcCount++; + } + + // All done. Print this for informational purposes + Com_Printf( "Allocated %d sources.\n", srcCount); + alSourcesInitialised = qtrue; + return qtrue; +} + +/* +================= +S_AL_SrcShutdown +================= +*/ +static +void S_AL_SrcShutdown( void ) +{ + int i; + src_t *curSource; + + if(!alSourcesInitialised) + return; + + // Destroy all the sources + for(i = 0; i < srcCount; i++) + { + curSource = &srcList[i]; + + if(curSource->isLocked) + Com_DPrintf( S_COLOR_YELLOW "WARNING: Source %d is locked\n", i); + + if(curSource->entity > 0) + entityList[curSource->entity].srcAllocated = qfalse; + + qalSourceStop(srcList[i].alSource); + qalDeleteSources(1, &srcList[i].alSource); + } + + memset(srcList, 0, sizeof(srcList)); + + alSourcesInitialised = qfalse; +} + +/* +================= +S_AL_SrcSetup +================= +*/ +static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority, + int entity, int channel, qboolean local) +{ + ALuint buffer; + src_t *curSource; + + // Mark the SFX as used, and grab the raw AL buffer + S_AL_BufferUse(sfx); + buffer = S_AL_BufferGet(sfx); + + // Set up src struct + curSource = &srcList[src]; + + curSource->lastUsedTime = Sys_Milliseconds(); + curSource->sfx = sfx; + curSource->priority = priority; + curSource->entity = entity; + curSource->channel = channel; + curSource->isPlaying = qfalse; + curSource->isLocked = qfalse; + curSource->isLooping = qfalse; + curSource->isTracking = qfalse; + curSource->curGain = s_alGain->value * s_volume->value; + curSource->scaleGain = curSource->curGain; + curSource->local = local; + + // Set up OpenAL source + qalSourcei(curSource->alSource, AL_BUFFER, buffer); + qalSourcef(curSource->alSource, AL_PITCH, 1.0f); + S_AL_Gain(curSource->alSource, curSource->curGain); + qalSourcefv(curSource->alSource, AL_POSITION, vec3_origin); + qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin); + qalSourcei(curSource->alSource, AL_LOOPING, AL_FALSE); + qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(local) + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f); + } + else + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } +} + +/* +================= +S_AL_NewLoopMaster +Remove given source as loop master if it is the master and hand off master status to another source in this case. +================= +*/ + +static void S_AL_SaveLoopPos(src_t *dest, ALuint alSource) +{ + int error; + + S_AL_ClearError(qfalse); + + qalGetSourcef(alSource, AL_SEC_OFFSET, &dest->lastTimePos); + if((error = qalGetError()) != AL_NO_ERROR) + { + // Old OpenAL implementations don't support AL_SEC_OFFSET + + if(error != AL_INVALID_ENUM) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Could not get time offset for alSource %d: %s\n", + alSource, S_AL_ErrorMsg(error)); + } + + dest->lastTimePos = -1; + } + else + dest->lastSampleTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_NewLoopMaster +Remove given source as loop master if it is the master and hand off master status to another source in this case. +================= +*/ + +static void S_AL_NewLoopMaster(src_t *rmSource, qboolean iskilled) +{ + int index; + src_t *curSource = NULL; + alSfx_t *curSfx; + + curSfx = &knownSfx[rmSource->sfx]; + + if(rmSource->isPlaying) + curSfx->loopActiveCnt--; + if(iskilled) + curSfx->loopCnt--; + + if(curSfx->loopCnt) + { + if(rmSource->priority == SRCPRI_ENTITY) + { + if(!iskilled && rmSource->isPlaying) + { + // only sync ambient loops... + // It makes more sense to have sounds for weapons/projectiles unsynced + S_AL_SaveLoopPos(rmSource, rmSource->alSource); + } + } + else if(rmSource == &srcList[curSfx->masterLoopSrc]) + { + int firstInactive = -1; + + // Only if rmSource was the master and if there are still playing loops for + // this sound will we need to find a new master. + + if(iskilled || curSfx->loopActiveCnt) + { + for(index = 0; index < srcCount; index++) + { + curSource = &srcList[index]; + + if(curSource->sfx == rmSource->sfx && curSource != rmSource && + curSource->isActive && curSource->isLooping && curSource->priority == SRCPRI_AMBIENT) + { + if(curSource->isPlaying) + { + curSfx->masterLoopSrc = index; + break; + } + else if(firstInactive < 0) + firstInactive = index; + } + } + } + + if(!curSfx->loopActiveCnt) + { + if(firstInactive < 0) + { + if(iskilled) + { + curSfx->masterLoopSrc = -1; + return; + } + else + curSource = rmSource; + } + else + curSource = &srcList[firstInactive]; + + if(rmSource->isPlaying) + { + // this was the last not stopped source, save last sample position + time + S_AL_SaveLoopPos(curSource, rmSource->alSource); + } + else + { + // second case: all loops using this sound have stopped due to listener being of of range, + // and now the inactive master gets deleted. Just move over the soundpos settings to the + // new master. + curSource->lastTimePos = rmSource->lastTimePos; + curSource->lastSampleTime = rmSource->lastSampleTime; + } + } + } + } + else + curSfx->masterLoopSrc = -1; +} + +/* +================= +S_AL_SrcKill +================= +*/ +static void S_AL_SrcKill(srcHandle_t src) +{ + src_t *curSource = &srcList[src]; + + // I'm not touching it. Unlock it first. + if(curSource->isLocked) + return; + + // Remove the entity association and loop master status + if(curSource->isLooping) + { + curSource->isLooping = qfalse; + + if(curSource->entity != -1) + { + sentity_t *curEnt = &entityList[curSource->entity]; + + curEnt->srcAllocated = qfalse; + curEnt->srcIndex = -1; + curEnt->loopAddedThisFrame = qfalse; + curEnt->startLoopingSound = qfalse; + } + + S_AL_NewLoopMaster(curSource, qtrue); + } + + // Stop it if it's playing + if(curSource->isPlaying) + { + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + + // Remove the buffer + qalSourcei(curSource->alSource, AL_BUFFER, 0); + + curSource->sfx = 0; + curSource->lastUsedTime = 0; + curSource->priority = 0; + curSource->entity = -1; + curSource->channel = -1; + if(curSource->isActive) + { + curSource->isActive = qfalse; + srcActiveCnt--; + } + curSource->isLocked = qfalse; + curSource->isTracking = qfalse; + curSource->local = qfalse; +} + +/* +================= +S_AL_SrcAlloc +================= +*/ +static +srcHandle_t S_AL_SrcAlloc( alSrcPriority_t priority, int entnum, int channel ) +{ + int i; + int empty = -1; + int weakest = -1; + int weakest_time = Sys_Milliseconds(); + int weakest_pri = 999; + float weakest_gain = 1000.0; + qboolean weakest_isplaying = qtrue; + int weakest_numloops = 0; + src_t *curSource; + + for(i = 0; i < srcCount; i++) + { + curSource = &srcList[i]; + + // If it's locked, we aren't even going to look at it + if(curSource->isLocked) + continue; + + // Is it empty or not? + if(!curSource->isActive) + { + empty = i; + break; + } + + if(curSource->isPlaying) + { + if(weakest_isplaying && curSource->priority < priority && + (curSource->priority < weakest_pri || + (!curSource->isLooping && (curSource->scaleGain < weakest_gain || curSource->lastUsedTime < weakest_time)))) + { + // If it has lower priority, is fainter or older, flag it as weak + // the last two values are only compared if it's not a looping sound, because we want to prevent two + // loops (loops are added EVERY frame) fighting for a slot + weakest_pri = curSource->priority; + weakest_time = curSource->lastUsedTime; + weakest_gain = curSource->scaleGain; + weakest = i; + } + } + else + { + weakest_isplaying = qfalse; + + if(weakest < 0 || + knownSfx[curSource->sfx].loopCnt > weakest_numloops || + curSource->priority < weakest_pri || + curSource->lastUsedTime < weakest_time) + { + // Sources currently not playing of course have lowest priority + // also try to always keep at least one loop master for every loop sound + weakest_pri = curSource->priority; + weakest_time = curSource->lastUsedTime; + weakest_numloops = knownSfx[curSource->sfx].loopCnt; + weakest = i; + } + } + + // The channel system is not actually adhered to by baseq3, and not + // implemented in snd_dma.c, so while the following is strictly correct, it + // causes incorrect behaviour versus defacto baseq3 +#if 0 + // Is it an exact match, and not on channel 0? + if((curSource->entity == entnum) && (curSource->channel == channel) && (channel != 0)) + { + S_AL_SrcKill(i); + return i; + } +#endif + } + + if(empty == -1) + empty = weakest; + + if(empty >= 0) + { + S_AL_SrcKill(empty); + srcList[empty].isActive = qtrue; + srcActiveCnt++; + } + + return empty; +} + +/* +================= +S_AL_SrcFind + +Finds an active source with matching entity and channel numbers +Returns -1 if there isn't one +================= +*/ +#if 0 +static +srcHandle_t S_AL_SrcFind(int entnum, int channel) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if(!srcList[i].isActive) + continue; + if((srcList[i].entity == entnum) && (srcList[i].channel == channel)) + return i; + } + return -1; +} +#endif + +/* +================= +S_AL_SrcLock + +Locked sources will not be automatically reallocated or managed +================= +*/ +static +void S_AL_SrcLock(srcHandle_t src) +{ + srcList[src].isLocked = qtrue; +} + +/* +================= +S_AL_SrcUnlock + +Once unlocked, the source may be reallocated again +================= +*/ +static +void S_AL_SrcUnlock(srcHandle_t src) +{ + srcList[src].isLocked = qfalse; +} + +/* +================= +S_AL_UpdateEntityPosition +================= +*/ +static +void S_AL_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + vec3_t sanOrigin; + + VectorCopy( origin, sanOrigin ); + S_AL_SanitiseVector( sanOrigin ); + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + VectorCopy( sanOrigin, entityList[entityNum].origin ); +} + +/* +================= +S_AL_CheckInput +Check whether input values from mods are out of range. +Necessary for i.g. Western Quake3 mod which is buggy. +================= +*/ +static qboolean S_AL_CheckInput(int entityNum, sfxHandle_t sfx) +{ + if (entityNum < 0 || entityNum > MAX_GENTITIES) + Com_Error(ERR_DROP, "ERROR: S_AL_CheckInput: bad entitynum %i", entityNum); + + if (sfx < 0 || sfx >= numSfx) + { + Com_Printf(S_COLOR_RED "ERROR: S_AL_CheckInput: handle %i out of range\n", sfx); + return qtrue; + } + + return qfalse; +} + +/* +================= +S_AL_StartLocalSound + +Play a local (non-spatialized) sound effect +================= +*/ +static +void S_AL_StartLocalSound(sfxHandle_t sfx, int channel) +{ + srcHandle_t src; + + if(S_AL_CheckInput(0, sfx)) + return; + + // Try to grab a source + src = S_AL_SrcAlloc(SRCPRI_LOCAL, -1, channel); + + if(src == -1) + return; + + // Set up the effect + S_AL_SrcSetup(src, sfx, SRCPRI_LOCAL, -1, channel, qtrue); + + // Start it playing + srcList[src].isPlaying = qtrue; + qalSourcePlay(srcList[src].alSource); +} + +/* +================= +S_AL_StartSound + +Play a one-shot sound effect +================= +*/ +static void S_AL_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) +{ + vec3_t sorigin; + srcHandle_t src; + src_t *curSource; + + if(origin) + { + if(S_AL_CheckInput(0, sfx)) + return; + + VectorCopy(origin, sorigin); + } + else + { + if(S_AL_CheckInput(entnum, sfx)) + return; + + if(S_AL_HearingThroughEntity(entnum)) + { + S_AL_StartLocalSound(sfx, entchannel); + return; + } + + VectorCopy(entityList[entnum].origin, sorigin); + } + + S_AL_SanitiseVector(sorigin); + + if((srcActiveCnt > 5 * srcCount / 3) && + (DistanceSquared(sorigin, lastListenerOrigin) >= + (s_alMaxDistance->value + s_alGraceDistance->value) * (s_alMaxDistance->value + s_alGraceDistance->value))) + { + // We're getting tight on sources and source is not within hearing distance so don't add it + return; + } + + // Try to grab a source + src = S_AL_SrcAlloc(SRCPRI_ONESHOT, entnum, entchannel); + if(src == -1) + return; + + S_AL_SrcSetup(src, sfx, SRCPRI_ONESHOT, entnum, entchannel, qfalse); + + curSource = &srcList[src]; + + if(!origin) + curSource->isTracking = qtrue; + + qalSourcefv(curSource->alSource, AL_POSITION, sorigin ); + S_AL_ScaleGain(curSource, sorigin); + + // Start it playing + curSource->isPlaying = qtrue; + qalSourcePlay(curSource->alSource); +} + +/* +================= +S_AL_ClearLoopingSounds +================= +*/ +static +void S_AL_ClearLoopingSounds( qboolean killall ) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if((srcList[i].isLooping) && (srcList[i].entity != -1)) + entityList[srcList[i].entity].loopAddedThisFrame = qfalse; + } +} + +/* +================= +S_AL_SrcLoop +================= +*/ +static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx, + const vec3_t origin, const vec3_t velocity, int entityNum ) +{ + int src; + sentity_t *sent = &entityList[ entityNum ]; + src_t *curSource; + vec3_t sorigin, svelocity; + + if(S_AL_CheckInput(entityNum, sfx)) + return; + + // Do we need to allocate a new source for this entity + if( !sent->srcAllocated ) + { + // Try to get a channel + src = S_AL_SrcAlloc( priority, entityNum, -1 ); + if( src == -1 ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: Failed to allocate source " + "for loop sfx %d on entity %d\n", sfx, entityNum ); + return; + } + + curSource = &srcList[src]; + + sent->startLoopingSound = qtrue; + + curSource->lastTimePos = -1.0; + curSource->lastSampleTime = Sys_Milliseconds(); + } + else + { + src = sent->srcIndex; + curSource = &srcList[src]; + } + + sent->srcAllocated = qtrue; + sent->srcIndex = src; + + sent->loopPriority = priority; + sent->loopSfx = sfx; + + // If this is not set then the looping sound is stopped. + sent->loopAddedThisFrame = qtrue; + + // UGH + // These lines should be called via S_AL_SrcSetup, but we + // can't call that yet as it buffers sfxes that may change + // with subsequent calls to S_AL_SrcLoop + curSource->entity = entityNum; + curSource->isLooping = qtrue; + + if( S_AL_HearingThroughEntity( entityNum ) ) + { + curSource->local = qtrue; + + VectorClear(sorigin); + + qalSourcefv(curSource->alSource, AL_POSITION, sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, sorigin); + } + else + { + curSource->local = qfalse; + + if(origin) + VectorCopy(origin, sorigin); + else + VectorCopy(sent->origin, sorigin); + + S_AL_SanitiseVector(sorigin); + + VectorCopy(sorigin, curSource->loopSpeakerPos); + + if(velocity) + { + VectorCopy(velocity, svelocity); + S_AL_SanitiseVector(svelocity); + } + else + VectorClear(svelocity); + + qalSourcefv( curSource->alSource, AL_POSITION, (ALfloat *)sorigin ); + qalSourcefv( curSource->alSource, AL_VELOCITY, (ALfloat *)velocity ); + } +} + +/* +================= +S_AL_AddLoopingSound +================= +*/ +static void S_AL_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + S_AL_SrcLoop(SRCPRI_ENTITY, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_AddRealLoopingSound +================= +*/ +static void S_AL_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + S_AL_SrcLoop(SRCPRI_AMBIENT, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_StopLoopingSound +================= +*/ +static +void S_AL_StopLoopingSound(int entityNum ) +{ + if(entityList[entityNum].srcAllocated) + S_AL_SrcKill(entityList[entityNum].srcIndex); +} + +/* +================= +S_AL_SrcUpdate + +Update state (move things around, manage sources, and so on) +================= +*/ +static +void S_AL_SrcUpdate( void ) +{ + int i; + int entityNum; + ALint state; + src_t *curSource; + + for(i = 0; i < srcCount; i++) + { + entityNum = srcList[i].entity; + curSource = &srcList[i]; + + if(curSource->isLocked) + continue; + + if(!curSource->isActive) + continue; + + // Update source parameters + if((s_alGain->modified) || (s_volume->modified)) + curSource->curGain = s_alGain->value * s_volume->value; + if((s_alRolloff->modified) && (!curSource->local)) + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + if(s_alMinDistance->modified) + qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(curSource->isLooping) + { + sentity_t *sent = &entityList[ entityNum ]; + + // If a looping effect hasn't been touched this frame, pause or kill it + if(sent->loopAddedThisFrame) + { + alSfx_t *curSfx; + + // The sound has changed without an intervening removal + if(curSource->isActive && !sent->startLoopingSound && + curSource->sfx != sent->loopSfx) + { + S_AL_NewLoopMaster(curSource, qtrue); + + curSource->isPlaying = qfalse; + qalSourceStop(curSource->alSource); + qalSourcei(curSource->alSource, AL_BUFFER, 0); + sent->startLoopingSound = qtrue; + } + + // The sound hasn't been started yet + if(sent->startLoopingSound) + { + S_AL_SrcSetup(i, sent->loopSfx, sent->loopPriority, + entityNum, -1, curSource->local); + curSource->isLooping = qtrue; + + knownSfx[curSource->sfx].loopCnt++; + sent->startLoopingSound = qfalse; + } + + curSfx = &knownSfx[curSource->sfx]; + + S_AL_ScaleGain(curSource, curSource->loopSpeakerPos); + if(!curSource->scaleGain) + { + if(curSource->isPlaying) + { + // Sound is mute, stop playback until we are in range again + S_AL_NewLoopMaster(curSource, qfalse); + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + else if(!curSfx->loopActiveCnt && curSfx->masterLoopSrc < 0) + curSfx->masterLoopSrc = i; + + continue; + } + + if(!curSource->isPlaying) + { + if(curSource->priority == SRCPRI_AMBIENT) + { + // If there are other ambient looping sources with the same sound, + // make sure the sound of these sources are in sync. + + if(curSfx->loopActiveCnt) + { + int offset, error; + + // we already have a master loop playing, get buffer position. + S_AL_ClearError(qfalse); + qalGetSourcei(srcList[curSfx->masterLoopSrc].alSource, AL_SAMPLE_OFFSET, &offset); + if((error = qalGetError()) != AL_NO_ERROR) + { + if(error != AL_INVALID_ENUM) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Cannot get sample offset from source %d: " + "%s\n", i, S_AL_ErrorMsg(error)); + } + } + else + qalSourcei(curSource->alSource, AL_SAMPLE_OFFSET, offset); + } + else if(curSfx->loopCnt && curSfx->masterLoopSrc >= 0) + { + float secofs; + + src_t *master = &srcList[curSfx->masterLoopSrc]; + // This loop sound used to be played, but all sources are stopped. Use last sample position/time + // to calculate offset so the player thinks the sources continued playing while they were inaudible. + + if(master->lastTimePos >= 0) + { + secofs = master->lastTimePos + (Sys_Milliseconds() - master->lastSampleTime) / 1000.0f; + secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate); + + qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs); + } + + // I be the master now + curSfx->masterLoopSrc = i; + } + else + curSfx->masterLoopSrc = i; + } + else if(curSource->lastTimePos >= 0) + { + float secofs; + + // For unsynced loops (SRCPRI_ENTITY) just carry on playing as if the sound was never stopped + + secofs = curSource->lastTimePos + (Sys_Milliseconds() - curSource->lastSampleTime) / 1000.0f; + secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate); + qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs); + } + + curSfx->loopActiveCnt++; + + qalSourcei(curSource->alSource, AL_LOOPING, AL_TRUE); + curSource->isPlaying = qtrue; + qalSourcePlay(curSource->alSource); + } + + // Update locality + if(curSource->local) + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f); + } + else + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } + + } + else if(curSource->priority == SRCPRI_AMBIENT) + { + if(curSource->isPlaying) + { + S_AL_NewLoopMaster(curSource, qfalse); + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + } + else + S_AL_SrcKill(i); + + continue; + } + + // Check if it's done, and flag it + qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + curSource->isPlaying = qfalse; + S_AL_SrcKill(i); + continue; + } + + // Query relativity of source, don't move if it's true + qalGetSourcei(curSource->alSource, AL_SOURCE_RELATIVE, &state); + + // See if it needs to be moved + if(curSource->isTracking && !state) + { + qalSourcefv(curSource->alSource, AL_POSITION, entityList[entityNum].origin); + S_AL_ScaleGain(curSource, entityList[entityNum].origin); + } + } +} + +/* +================= +S_AL_SrcShutup +================= +*/ +static +void S_AL_SrcShutup( void ) +{ + int i; + for(i = 0; i < srcCount; i++) + S_AL_SrcKill(i); +} + +/* +================= +S_AL_SrcGet +================= +*/ +static +ALuint S_AL_SrcGet(srcHandle_t src) +{ + return srcList[src].alSource; +} + + +//=========================================================================== + +static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS]; +static qboolean streamPlaying[MAX_RAW_STREAMS]; +static ALuint streamSources[MAX_RAW_STREAMS]; + +/* +================= +S_AL_AllocateStreamChannel +================= +*/ +static void S_AL_AllocateStreamChannel( int stream ) +{ + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + // Allocate a streamSource at high priority + streamSourceHandles[stream] = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(streamSourceHandles[stream] == -1) + return; + + // Lock the streamSource so nobody else can use it, and get the raw streamSource + S_AL_SrcLock(streamSourceHandles[stream]); + streamSources[stream] = S_AL_SrcGet(streamSourceHandles[stream]); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[streamSourceHandles[stream]].scaleGain = 0.0f; + + // Set some streamSource parameters + qalSourcei (streamSources[stream], AL_BUFFER, 0 ); + qalSourcei (streamSources[stream], AL_LOOPING, AL_FALSE ); + qalSource3f(streamSources[stream], AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(streamSources[stream], AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(streamSources[stream], AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (streamSources[stream], AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (streamSources[stream], AL_SOURCE_RELATIVE, AL_TRUE ); +} + +/* +================= +S_AL_FreeStreamChannel +================= +*/ +static void S_AL_FreeStreamChannel( int stream ) +{ + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + // Release the output streamSource + S_AL_SrcUnlock(streamSourceHandles[stream]); + streamSources[stream] = 0; + streamSourceHandles[stream] = -1; +} + +/* +================= +S_AL_RawSamples +================= +*/ +static +void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, const byte *data, float volume) +{ + ALuint buffer; + ALuint format; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + format = S_AL_Format( width, channels ); + + // Create the streamSource if necessary + if(streamSourceHandles[stream] == -1) + { + S_AL_AllocateStreamChannel(stream); + + // Failed? + if(streamSourceHandles[stream] == -1) + { + Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n"); + return; + } + } + + // Create a buffer, and stuff the data into it + qalGenBuffers(1, &buffer); + qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate); + + // Shove the data onto the streamSource + qalSourceQueueBuffers(streamSources[stream], 1, &buffer); + + // Volume + S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value); +} + +/* +================= +S_AL_StreamUpdate +================= +*/ +static +void S_AL_StreamUpdate( int stream ) +{ + int numBuffers; + ALint state; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(streamSourceHandles[stream] == -1) + return; + + // Un-queue any buffers, and delete them + qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint buffer; + qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer); + qalDeleteBuffers(1, &buffer); + } + + // Start the streamSource playing if necessary + qalGetSourcei( streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers ); + + qalGetSourcei(streamSources[stream], AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + streamPlaying[stream] = qfalse; + + // If there are no buffers queued up, release the streamSource + if( !numBuffers ) + S_AL_FreeStreamChannel( stream ); + } + + if( !streamPlaying[stream] && numBuffers ) + { + qalSourcePlay( streamSources[stream] ); + streamPlaying[stream] = qtrue; + } +} + +/* +================= +S_AL_StreamDie +================= +*/ +static +void S_AL_StreamDie( int stream ) +{ + int numBuffers; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(streamSourceHandles[stream] == -1) + return; + + streamPlaying[stream] = qfalse; + qalSourceStop(streamSources[stream]); + + // Un-queue any buffers, and delete them + qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint buffer; + qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer); + qalDeleteBuffers(1, &buffer); + } + + S_AL_FreeStreamChannel(stream); +} + + +//=========================================================================== + + +#define NUM_MUSIC_BUFFERS 4 +#define MUSIC_BUFFER_SIZE 4096 + +static qboolean musicPlaying = qfalse; +static srcHandle_t musicSourceHandle = -1; +static ALuint musicSource; +static ALuint musicBuffers[NUM_MUSIC_BUFFERS]; + +static snd_stream_t *mus_stream; +static snd_stream_t *intro_stream; +static char s_backgroundLoop[MAX_QPATH]; + +static byte decode_buffer[MUSIC_BUFFER_SIZE]; + +/* +================= +S_AL_MusicSourceGet +================= +*/ +static void S_AL_MusicSourceGet( void ) +{ + // Allocate a musicSource at high priority + musicSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(musicSourceHandle == -1) + return; + + // Lock the musicSource so nobody else can use it, and get the raw musicSource + S_AL_SrcLock(musicSourceHandle); + musicSource = S_AL_SrcGet(musicSourceHandle); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[musicSourceHandle].scaleGain = 0.0f; + + // Set some musicSource parameters + qalSource3f(musicSource, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (musicSource, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (musicSource, AL_SOURCE_RELATIVE, AL_TRUE ); +} + +/* +================= +S_AL_MusicSourceFree +================= +*/ +static void S_AL_MusicSourceFree( void ) +{ + // Release the output musicSource + S_AL_SrcUnlock(musicSourceHandle); + musicSource = 0; + musicSourceHandle = -1; +} + +/* +================= +S_AL_CloseMusicFiles +================= +*/ +static void S_AL_CloseMusicFiles(void) +{ + if(intro_stream) + { + S_CodecCloseStream(intro_stream); + intro_stream = NULL; + } + + if(mus_stream) + { + S_CodecCloseStream(mus_stream); + mus_stream = NULL; + } +} + +/* +================= +S_AL_StopBackgroundTrack +================= +*/ +static +void S_AL_StopBackgroundTrack( void ) +{ + if(!musicPlaying) + return; + + // Stop playing + qalSourceStop(musicSource); + + // De-queue the musicBuffers + qalSourceUnqueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers); + + // Destroy the musicBuffers + qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers); + + // Free the musicSource + S_AL_MusicSourceFree(); + + // Unload the stream + S_AL_CloseMusicFiles(); + + musicPlaying = qfalse; +} + +/* +================= +S_AL_MusicProcess +================= +*/ +static +void S_AL_MusicProcess(ALuint b) +{ + ALenum error; + int l; + ALuint format; + snd_stream_t *curstream; + + S_AL_ClearError( qfalse ); + + if(intro_stream) + curstream = intro_stream; + else + curstream = mus_stream; + + if(!curstream) + return; + + l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer); + + // Run out data to read, start at the beginning again + if(l == 0) + { + S_CodecCloseStream(curstream); + + // the intro stream just finished playing so we don't need to reopen + // the music stream. + if(intro_stream) + intro_stream = NULL; + else + mus_stream = S_CodecOpenStream(s_backgroundLoop); + + curstream = mus_stream; + + if(!curstream) + { + S_AL_StopBackgroundTrack(); + return; + } + + l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer); + } + + format = S_AL_Format(curstream->info.width, curstream->info.channels); + + if( l == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData( b, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050 ); + } + else + qalBufferData(b, format, decode_buffer, l, curstream->info.rate); + + if( ( error = qalGetError( ) ) != AL_NO_ERROR ) + { + S_AL_StopBackgroundTrack( ); + Com_Printf( S_COLOR_RED "ERROR: while buffering data for music stream - %s\n", + S_AL_ErrorMsg( error ) ); + return; + } +} + +/* +================= +S_AL_StartBackgroundTrack +================= +*/ +static +void S_AL_StartBackgroundTrack( const char *intro, const char *loop ) +{ + int i; + qboolean issame; + + // Stop any existing music that might be playing + S_AL_StopBackgroundTrack(); + + if((!intro || !*intro) && (!loop || !*loop)) + return; + + // Allocate a musicSource + S_AL_MusicSourceGet(); + if(musicSourceHandle == -1) + return; + + if (!loop || !*loop) + { + loop = intro; + issame = qtrue; + } + else if(intro && *intro && !strcmp(intro, loop)) + issame = qtrue; + else + issame = qfalse; + + // Copy the loop over + strncpy( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + + if(!issame) + { + // Open the intro and don't mind whether it succeeds. + // The important part is the loop. + intro_stream = S_CodecOpenStream(intro); + } + else + intro_stream = NULL; + + mus_stream = S_CodecOpenStream(s_backgroundLoop); + if(!mus_stream) + { + S_AL_CloseMusicFiles(); + S_AL_MusicSourceFree(); + return; + } + + // Generate the musicBuffers + qalGenBuffers(NUM_MUSIC_BUFFERS, musicBuffers); + + // Queue the musicBuffers up + for(i = 0; i < NUM_MUSIC_BUFFERS; i++) + { + S_AL_MusicProcess(musicBuffers[i]); + } + + qalSourceQueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers); + + // Set the initial gain property + S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value); + + // Start playing + qalSourcePlay(musicSource); + + musicPlaying = qtrue; +} + +/* +================= +S_AL_MusicUpdate +================= +*/ +static +void S_AL_MusicUpdate( void ) +{ + int numBuffers; + ALint state; + + if(!musicPlaying) + return; + + qalGetSourcei( musicSource, AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint b; + qalSourceUnqueueBuffers(musicSource, 1, &b); + S_AL_MusicProcess(b); + qalSourceQueueBuffers(musicSource, 1, &b); + } + + // Hitches can cause OpenAL to be starved of buffers when streaming. + // If this happens, it will stop playback. This restarts the source if + // it is no longer playing, and if there are buffers available + qalGetSourcei( musicSource, AL_SOURCE_STATE, &state ); + qalGetSourcei( musicSource, AL_BUFFERS_QUEUED, &numBuffers ); + if( state == AL_STOPPED && numBuffers ) + { + Com_DPrintf( S_COLOR_YELLOW "Restarted OpenAL music\n" ); + qalSourcePlay(musicSource); + } + + // Set the gain property + S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value); +} + + +//=========================================================================== + + +// Local state variables +static ALCdevice *alDevice; +static ALCcontext *alContext; + +#ifdef USE_VOIP +static ALCdevice *alCaptureDevice; +static cvar_t *s_alCapture; +#endif + +#ifdef _WIN32 +#define ALDRIVER_DEFAULT "OpenAL32.dll" +#elif defined(MACOS_X) +#define ALDRIVER_DEFAULT "/System/Library/Frameworks/OpenAL.framework/OpenAL" +#else +#define ALDRIVER_DEFAULT "libopenal.so.1" +#endif + +/* +================= +S_AL_StopAllSounds +================= +*/ +static +void S_AL_StopAllSounds( void ) +{ + int i; + S_AL_SrcShutup(); + S_AL_StopBackgroundTrack(); + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamDie(i); +} + +/* +================= +S_AL_Respatialize +================= +*/ +static +void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) +{ + float velocity[3] = {0.0f, 0.0f, 0.0f}; + float orientation[6]; + vec3_t sorigin; + + VectorCopy( origin, sorigin ); + S_AL_SanitiseVector( sorigin ); + + S_AL_SanitiseVector( axis[ 0 ] ); + S_AL_SanitiseVector( axis[ 1 ] ); + S_AL_SanitiseVector( axis[ 2 ] ); + + orientation[0] = axis[0][0]; orientation[1] = axis[0][1]; orientation[2] = axis[0][2]; + orientation[3] = axis[2][0]; orientation[4] = axis[2][1]; orientation[5] = axis[2][2]; + + VectorCopy( sorigin, lastListenerOrigin ); + + // Set OpenAL listener paramaters + qalListenerfv(AL_POSITION, (ALfloat *)sorigin); + qalListenerfv(AL_VELOCITY, velocity); + qalListenerfv(AL_ORIENTATION, orientation); +} + +/* +================= +S_AL_Update +================= +*/ +static +void S_AL_Update( void ) +{ + int i; + + if(s_muted->modified) + { + // muted state changed. Let S_AL_Gain turn up all sources again. + for(i = 0; i < srcCount; i++) + { + if(srcList[i].isActive) + S_AL_Gain(srcList[i].alSource, srcList[i].scaleGain); + } + + s_muted->modified = qfalse; + } + + // Update SFX channels + S_AL_SrcUpdate(); + + // Update streams + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamUpdate(i); + S_AL_MusicUpdate(); + + // Doppler + if(s_doppler->modified) + { + s_alDopplerFactor->modified = qtrue; + s_doppler->modified = qfalse; + } + + // Doppler parameters + if(s_alDopplerFactor->modified) + { + if(s_doppler->integer) + qalDopplerFactor(s_alDopplerFactor->value); + else + qalDopplerFactor(0.0f); + s_alDopplerFactor->modified = qfalse; + } + if(s_alDopplerSpeed->modified) + { + qalDopplerVelocity(s_alDopplerSpeed->value); + s_alDopplerSpeed->modified = qfalse; + } + + // Clear the modified flags on the other cvars + s_alGain->modified = qfalse; + s_volume->modified = qfalse; + s_musicVolume->modified = qfalse; + s_alMinDistance->modified = qfalse; + s_alRolloff->modified = qfalse; +} + +/* +================= +S_AL_DisableSounds +================= +*/ +static +void S_AL_DisableSounds( void ) +{ + S_AL_StopAllSounds(); +} + +/* +================= +S_AL_BeginRegistration +================= +*/ +static +void S_AL_BeginRegistration( void ) +{ +} + +/* +================= +S_AL_ClearSoundBuffer +================= +*/ +static +void S_AL_ClearSoundBuffer( void ) +{ +} + +/* +================= +S_AL_SoundList +================= +*/ +static +void S_AL_SoundList( void ) +{ +} + +#ifdef USE_VOIP +static +void S_AL_StartCapture( void ) +{ + if (alCaptureDevice != NULL) + qalcCaptureStart(alCaptureDevice); +} + +static +int S_AL_AvailableCaptureSamples( void ) +{ + int retval = 0; + if (alCaptureDevice != NULL) + { + ALint samples = 0; + qalcGetIntegerv(alCaptureDevice, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples); + retval = (int) samples; + } + return retval; +} + +static +void S_AL_Capture( int samples, byte *data ) +{ + if (alCaptureDevice != NULL) + qalcCaptureSamples(alCaptureDevice, data, samples); +} + +void S_AL_StopCapture( void ) +{ + if (alCaptureDevice != NULL) + qalcCaptureStop(alCaptureDevice); +} + +void S_AL_MasterGain( float gain ) +{ + qalListenerf(AL_GAIN, gain); +} +#endif + + +/* +================= +S_AL_SoundInfo +================= +*/ +static void S_AL_SoundInfo(void) +{ + Com_Printf( "OpenAL info:\n" ); + Com_Printf( " Vendor: %s\n", qalGetString( AL_VENDOR ) ); + Com_Printf( " Version: %s\n", qalGetString( AL_VERSION ) ); + Com_Printf( " Renderer: %s\n", qalGetString( AL_RENDERER ) ); + Com_Printf( " AL Extensions: %s\n", qalGetString( AL_EXTENSIONS ) ); + Com_Printf( " ALC Extensions: %s\n", qalcGetString( alDevice, ALC_EXTENSIONS ) ); + + if(enumeration_all_ext) + Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_ALL_DEVICES_SPECIFIER)); + else if(enumeration_ext) + Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER)); + + if(enumeration_all_ext || enumeration_ext) + Com_Printf(" Available Devices:\n%s", s_alAvailableDevices->string); + +#ifdef USE_VOIP + if(capture_ext) + { + Com_Printf(" Input Device: %s\n", qalcGetString(alCaptureDevice, ALC_CAPTURE_DEVICE_SPECIFIER)); + Com_Printf(" Available Input Devices:\n%s", s_alAvailableInputDevices->string); + } +#endif +} + + + +/* +================= +S_AL_Shutdown +================= +*/ +static +void S_AL_Shutdown( void ) +{ + // Shut down everything + int i; + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamDie(i); + S_AL_StopBackgroundTrack( ); + S_AL_SrcShutdown( ); + S_AL_BufferShutdown( ); + + qalcDestroyContext(alContext); + qalcCloseDevice(alDevice); + +#ifdef USE_VOIP + if (alCaptureDevice != NULL) { + qalcCaptureStop(alCaptureDevice); + qalcCaptureCloseDevice(alCaptureDevice); + alCaptureDevice = NULL; + Com_Printf( "OpenAL capture device closed.\n" ); + } +#endif + + for (i = 0; i < MAX_RAW_STREAMS; i++) { + streamSourceHandles[i] = -1; + streamPlaying[i] = qfalse; + streamSources[i] = 0; + } + + QAL_Shutdown(); +} + +#endif + +/* +================= +S_AL_Init +================= +*/ +qboolean S_AL_Init( soundInterface_t *si ) +{ +#ifdef USE_OPENAL + const char* device = NULL; + const char* inputdevice = NULL; + int i; + + if( !si ) { + return qfalse; + } + + for (i = 0; i < MAX_RAW_STREAMS; i++) { + streamSourceHandles[i] = -1; + streamPlaying[i] = qfalse; + streamSources[i] = 0; + } + + // New console variables + s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE ); + s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE ); + s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE ); + s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE ); + s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "2200", CVAR_ARCHIVE ); + s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT ); + s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT); + s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT); + s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT); + + s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH ); + + s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH ); + s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH); + + // Load QAL + if( !QAL_Init( s_alDriver->string ) ) + { + Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string ); + return qfalse; + } + + device = s_alDevice->string; + if(device && !*device) + device = NULL; + + inputdevice = s_alInputDevice->string; + if(inputdevice && !*inputdevice) + inputdevice = NULL; + + + // Device enumeration support + enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"); + enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"); + + if(enumeration_ext || enumeration_all_ext) + { + char devicenames[16384] = ""; + const char *devicelist; + const char *defaultdevice; + int curlen; + + // get all available devices + the default device name. + if(enumeration_ext) + { + devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); + } + else + { + // We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration. + devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER); + defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + enumeration_ext = qtrue; + } + +#ifdef _WIN32 + // check whether the default device is generic hardware. If it is, change to + // Generic Software as that one works more reliably with various sound systems. + // If it's not, use OpenAL's default selection as we don't want to ignore + // native hardware acceleration. + if(!device && !strcmp(defaultdevice, "Generic Hardware")) + device = "Generic Software"; +#endif + + // dump a list of available devices to a cvar for the user to see. + while((curlen = strlen(devicelist))) + { + Q_strcat(devicenames, sizeof(devicenames), devicelist); + Q_strcat(devicenames, sizeof(devicenames), "\n"); + + devicelist += curlen + 1; + } + + s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART); + } + + alDevice = qalcOpenDevice(device); + if( !alDevice && device ) + { + Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", device ); + alDevice = qalcOpenDevice(NULL); + } + + if( !alDevice ) + { + QAL_Shutdown( ); + Com_Printf( "Failed to open OpenAL device.\n" ); + return qfalse; + } + + // Create OpenAL context + alContext = qalcCreateContext( alDevice, NULL ); + if( !alContext ) + { + QAL_Shutdown( ); + qalcCloseDevice( alDevice ); + Com_Printf( "Failed to create OpenAL context.\n" ); + return qfalse; + } + qalcMakeContextCurrent( alContext ); + + // Initialize sources, buffers, music + S_AL_BufferInit( ); + S_AL_SrcInit( ); + + // Set up OpenAL parameters (doppler, etc) + qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + qalDopplerFactor( s_alDopplerFactor->value ); + qalDopplerVelocity( s_alDopplerSpeed->value ); + +#ifdef USE_VOIP + // !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars. + // !!! FIXME: add support for capture device enumeration. + // !!! FIXME: add some better error reporting. + s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + if (!s_alCapture->integer) + { + Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n"); + } +#if USE_MUMBLE + else if (cl_useMumble->integer) + { + Com_Printf("OpenAL capture support disabled for Mumble support\n"); + } +#endif + else + { +#ifdef MACOS_X + // !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes + // !!! FIXME: capture support, but they don't list it in the + // !!! FIXME: extension string. We need to check the version string, + // !!! FIXME: then the extension string, but that's too much trouble, + // !!! FIXME: so we'll just check the function pointer for now. + if (qalcCaptureOpenDevice == NULL) +#else + if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture")) +#endif + { + Com_Printf("No ALC_EXT_capture support, can't record audio.\n"); + } + else + { + char inputdevicenames[16384] = ""; + const char *inputdevicelist; + const char *defaultinputdevice; + int curlen; + + capture_ext = qtrue; + + // get all available input devices + the default input device name. + inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); + defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + + // dump a list of available devices to a cvar for the user to see. + while((curlen = strlen(inputdevicelist))) + { + Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist); + Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n"); + inputdevicelist += curlen + 1; + } + + s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART); + + // !!! FIXME: 8000Hz is what Speex narrowband mode needs, but we + // !!! FIXME: should probably open the capture device after + // !!! FIXME: initializing Speex so we can change to wideband + // !!! FIXME: if we like. + Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice); + alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 8000, AL_FORMAT_MONO16, 4096); + if( !alCaptureDevice && inputdevice ) + { + Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice ); + alCaptureDevice = qalcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO16, 4096); + } + Com_Printf( "OpenAL capture device %s.\n", + (alCaptureDevice == NULL) ? "failed to open" : "opened"); + } + } +#endif + + si->Shutdown = S_AL_Shutdown; + si->StartSound = S_AL_StartSound; + si->StartLocalSound = S_AL_StartLocalSound; + si->StartBackgroundTrack = S_AL_StartBackgroundTrack; + si->StopBackgroundTrack = S_AL_StopBackgroundTrack; + si->RawSamples = S_AL_RawSamples; + si->StopAllSounds = S_AL_StopAllSounds; + si->ClearLoopingSounds = S_AL_ClearLoopingSounds; + si->AddLoopingSound = S_AL_AddLoopingSound; + si->AddRealLoopingSound = S_AL_AddRealLoopingSound; + si->StopLoopingSound = S_AL_StopLoopingSound; + si->Respatialize = S_AL_Respatialize; + si->UpdateEntityPosition = S_AL_UpdateEntityPosition; + si->Update = S_AL_Update; + si->DisableSounds = S_AL_DisableSounds; + si->BeginRegistration = S_AL_BeginRegistration; + si->RegisterSound = S_AL_RegisterSound; + si->SoundDuration = S_AL_SoundDuration; + si->ClearSoundBuffer = S_AL_ClearSoundBuffer; + si->SoundInfo = S_AL_SoundInfo; + si->SoundList = S_AL_SoundList; + +#ifdef USE_VOIP + si->StartCapture = S_AL_StartCapture; + si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples; + si->Capture = S_AL_Capture; + si->StopCapture = S_AL_StopCapture; + si->MasterGain = S_AL_MasterGain; +#endif + + return qtrue; +#else + return qfalse; +#endif +} + diff --git a/src/client/snd_public.h b/src/client/snd_public.h new file mode 100644 index 0000000..20c1c7f --- /dev/null +++ b/src/client/snd_public.h @@ -0,0 +1,85 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +void S_Init( void ); +void S_Shutdown( void ); + +// if origin is NULL, the sound will be dynamically sourced from the entity +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); + +void S_StartBackgroundTrack( const char *intro, const char *loop ); +void S_StopBackgroundTrack( void ); + +// cinematics and voice-over-network will send raw samples +// 1.0 volume will be direct output of source samples +void S_RawSamples (int stream, int samples, int rate, int width, int channels, + const byte *data, float volume); + +// stop all sounds and the background track +void S_StopAllSounds( void ); + +// all continuous looping sounds must be added before calling S_Update +void S_ClearLoopingSounds( qboolean killall ); +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void S_StopLoopingSound(int entityNum ); + +// recompute the reletive volumes for all running sounds +// reletive to the given entityNum / orientation +void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + +// let the sound system know where an entity currently is +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +void S_Update( void ); + +void S_DisableSounds( void ); + +void S_BeginRegistration( void ); + +// RegisterSound will allways return a valid sample, even if it +// has to create a placeholder. This prevents continuous filesystem +// checks for missing files +sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ); + +int S_SoundDuration( sfxHandle_t handle ); + +void S_DisplayFreeMemory(void); + +void S_ClearSoundBuffer( void ); + +void SNDDMA_Activate( void ); + +void S_UpdateBackgroundTrack( void ); + + +#ifdef USE_VOIP +void S_StartCapture( void ); +int S_AvailableCaptureSamples( void ); +void S_Capture( int samples, byte *data ); +void S_StopCapture( void ); +void S_MasterGain( float gain ); +#endif + diff --git a/src/client/snd_wavelet.c b/src/client/snd_wavelet.c new file mode 100644 index 0000000..e75323c --- /dev/null +++ b/src/client/snd_wavelet.c @@ -0,0 +1,254 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "snd_local.h" + +long myftol( float f ); + +#define C0 0.4829629131445341 +#define C1 0.8365163037378079 +#define C2 0.2241438680420134 +#define C3 -0.1294095225512604 + +void daub4(float b[], unsigned long n, int isign) +{ + float wksp[4097]; + float *a=b-1; // numerical recipies so a[1] = b[0] + + unsigned long nh,nh1,i,j; + + if (n < 4) return; + + nh1=(nh=n >> 1)+1; + if (isign >= 0) { + for (i=1,j=1;j<=n-3;j+=2,i++) { + wksp[i] = C0*a[j]+C1*a[j+1]+C2*a[j+2]+C3*a[j+3]; + wksp[i+nh] = C3*a[j]-C2*a[j+1]+C1*a[j+2]-C0*a[j+3]; + } + wksp[i ] = C0*a[n-1]+C1*a[n]+C2*a[1]+C3*a[2]; + wksp[i+nh] = C3*a[n-1]-C2*a[n]+C1*a[1]-C0*a[2]; + } else { + wksp[1] = C2*a[nh]+C1*a[n]+C0*a[1]+C3*a[nh1]; + wksp[2] = C3*a[nh]-C0*a[n]+C1*a[1]-C2*a[nh1]; + for (i=1,j=3;i= 0) { + for (nn=n;nn>=inverseStartLength;nn>>=1) daub4(a,nn,isign); + } else { + for (nn=inverseStartLength;nn<=n;nn<<=1) daub4(a,nn,isign); + } +} + +/* The number of bits required by each value */ +static unsigned char numBits[] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, +}; + +byte MuLawEncode(short s) { + unsigned long adjusted; + byte sign, exponent, mantissa; + + sign = (s<0)?0:0x80; + + if (s<0) s=-s; + adjusted = (long)s << (16-sizeof(short)*8); + adjusted += 128L + 4L; + if (adjusted > 32767) adjusted = 32767; + exponent = numBits[(adjusted>>7)&0xff] - 1; + mantissa = (adjusted>>(exponent+3))&0xf; + return ~(sign | (exponent<<4) | mantissa); +} + +short MuLawDecode(byte uLaw) { + signed long adjusted; + byte exponent, mantissa; + + uLaw = ~uLaw; + exponent = (uLaw>>4) & 0x7; + mantissa = (uLaw&0xf) + 16; + adjusted = (mantissa << (exponent +3)) - 128 - 4; + + return (uLaw & 0x80)? adjusted : -adjusted; +} + +short mulawToShort[256]; +static qboolean madeTable = qfalse; + +static int NXStreamCount; + +void NXPutc(NXStream *stream, char out) { + stream[NXStreamCount++] = out; +} + + +void encodeWavelet( sfx_t *sfx, short *packets) { + float wksp[4097], temp; + int i, samples, size; + sndBuffer *newchunk, *chunk; + byte *out; + + if (!madeTable) { + for (i=0;i<256;i++) { + mulawToShort[i] = (float)MuLawDecode((byte)i); + } + madeTable = qtrue; + } + chunk = NULL; + + samples = sfx->soundLength; + while(samples>0) { + size = samples; + if (size>(SND_CHUNK_SIZE*2)) { + size = (SND_CHUNK_SIZE*2); + } + + if (size<4) { + size = 4; + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + for(i=0; isndChunk; + + for(i=0;i 32767) temp = 32767; else if (temp<-32768) temp = -32768; + out[i] = MuLawEncode((short)temp); + } + + chunk->size = size; + samples -= size; + } +} + +void decodeWavelet(sndBuffer *chunk, short *to) { + float wksp[4097]; + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for(i=0;isoundLength; + grade = 0; + + while(samples>0) { + size = samples; + if (size>(SND_CHUNK_SIZE*2)) { + size = (SND_CHUNK_SIZE*2); + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + out = (byte *)chunk->sndChunk; + for(i=0; i32767) { + poop = 32767; + } else if (poop<-32768) { + poop = -32768; + } + out[i] = MuLawEncode((short)poop); + grade = poop - mulawToShort[out[i]]; + packets++; + } + chunk->size = size; + samples -= size; + } +} + +void decodeMuLaw(sndBuffer *chunk, short *to) { + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for(i=0;inext ) + { + if( fmn->cookie != FREEMEMCOOKIE ) + Com_Error( ERR_DROP, "BG_Alloc: Memory corruption detected!\n" ); + + if( fmn->size >= allocsize ) + { + // We've got a block + if( fmn->size == allocsize ) + { + // Same size, just remove + + prev = fmn->prev; + next = fmn->next; + if( prev ) + prev->next = next; // Point previous node to next + if( next ) + next->prev = prev; // Point next node to previous + if( fmn == freeHead ) + freeHead = next; // Set head pointer to next + ptr = (int *) fmn; + break; // Stop the loop, this is fine + } + else + { + // Keep track of the smallest free slot + if( fmn->size < smallestsize ) + { + smallest = fmn; + smallestsize = fmn->size; + } + } + } + } + + if( !ptr && smallest ) + { + // We found a slot big enough + smallest->size -= allocsize; + endptr = (char *) smallest + smallest->size; + ptr = (int *) endptr; + } + + if( ptr ) + { + freeMem -= allocsize; + memset( ptr, 0, allocsize ); + *ptr++ = allocsize; // Store a copy of size for deallocation + return( (void *) ptr ); + } + + Com_Error( ERR_DROP, "BG_Alloc: failed on allocation of %i bytes\n", size ); + return( NULL ); +} + +void BG_Free( void *ptr ) +{ + // Release allocated memory, add it to the free list. + + freeMemNode_t *fmn; + char *freeend; + int *freeptr; + + freeptr = ptr; + freeptr--; + + freeMem += *freeptr; + + for( fmn = freeHead; fmn; fmn = fmn->next ) + { + freeend = ((char *) fmn) + fmn->size; + if( freeend == (char *) freeptr ) + { + // Released block can be merged to an existing node + + fmn->size += *freeptr; // Add size of node. + return; + } + } + // No merging, add to head of list + + fmn = (freeMemNode_t *) freeptr; + fmn->size = *freeptr; // Set this first to avoid corrupting *freeptr + fmn->cookie = FREEMEMCOOKIE; + fmn->prev = NULL; + fmn->next = freeHead; + freeHead->prev = fmn; + freeHead = fmn; +} + +void BG_InitMemory( void ) +{ + // Set up the initial node + + freeHead = (freeMemNode_t *)memoryPool; + freeHead->cookie = FREEMEMCOOKIE; + freeHead->size = POOLSIZE; + freeHead->next = NULL; + freeHead->prev = NULL; + freeMem = sizeof( memoryPool ); +} + +void BG_DefragmentMemory( void ) +{ + // If there's a frenzy of deallocation and we want to + // allocate something big, this is useful. Otherwise... + // not much use. + + freeMemNode_t *startfmn, *endfmn, *fmn; + + for( startfmn = freeHead; startfmn; ) + { + endfmn = (freeMemNode_t *)(((char *) startfmn) + startfmn->size); + for( fmn = freeHead; fmn; ) + { + if( fmn->cookie != FREEMEMCOOKIE ) + Com_Error( ERR_DROP, "BG_DefragmentMemory: Memory corruption detected!\n" ); + + if( fmn == endfmn ) + { + // We can add fmn onto startfmn. + + if( fmn->prev ) + fmn->prev->next = fmn->next; + if( fmn->next ) + { + if( !(fmn->next->prev = fmn->prev) ) + freeHead = fmn->next; // We're removing the head node + } + startfmn->size += fmn->size; + memset( fmn, 0, sizeof(freeMemNode_t) ); // A redundant call, really. + + startfmn = freeHead; + endfmn = fmn = NULL; // Break out of current loop + } + else + fmn = fmn->next; + } + + if( endfmn ) + startfmn = startfmn->next; // endfmn acts as a 'restart' flag here + } +} + +void BG_MemoryInfo( void ) +{ + // Give a breakdown of memory + + freeMemNode_t *fmn = (freeMemNode_t *)memoryPool; + int size, chunks; + freeMemNode_t *end = (freeMemNode_t *)( memoryPool + POOLSIZE ); + void *p; + + Com_Printf( "%p-%p: %d out of %d bytes allocated\n", + fmn, end, POOLSIZE - freeMem, POOLSIZE ); + + while( fmn < end ) + { + size = chunks = 0; + p = fmn; + while( fmn < end && fmn->cookie == FREEMEMCOOKIE ) + { + size += fmn->size; + chunks++; + fmn = (freeMemNode_t *)( (char *)fmn + fmn->size ); + } + if( size ) + Com_Printf( " %p: %d bytes free (%d chunks)\n", p, size, chunks ); + size = chunks = 0; + p = fmn; + while( fmn < end && fmn->cookie != FREEMEMCOOKIE ) + { + size += *(int *)fmn; + chunks++; + fmn = (freeMemNode_t *)( (size_t)fmn + *(int *)fmn ); + } + if( size ) + Com_Printf( " %p: %d bytes allocated (%d chunks)\n", p, size, chunks ); + } +} diff --git a/src/game/bg_lib.c b/src/game/bg_lib.c new file mode 100644 index 0000000..bdace78 --- /dev/null +++ b/src/game/bg_lib.c @@ -0,0 +1,2816 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// bg_lib.c -- standard C library replacement routines used by code +// compiled for the virtual machine + + +#ifdef Q3_VM + +#include "../qcommon/q_shared.h" + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "bg_lib.h" + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$Id: bg_lib.c 2248 2011-08-08 22:39:07Z cschwarz $"; +#endif /* LIBC_SCCS and not lint */ + +static char* med3(char *, char *, char *, cmp_t *); +static void swapfunc(char *, char *, int, int); + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + register TYPE *pi = (TYPE *) (parmi); \ + register TYPE *pj = (TYPE *) (parmj); \ + do { \ + register TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static void +swapfunc(a, b, n, swaptype) + char *a, *b; + int n, swaptype; +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static char * +med3(a, b, c, cmp) + char *a, *b, *c; + cmp_t *cmp; +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void +qsort(a, n, es, cmp) + void *a; + size_t n, es; + cmp_t *cmp; +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} + +//================================================================================== + + +size_t strlen( const char *string ) +{ + const char *s; + + s = string; + while( *s ) + s++; + + return s - string; +} + + +char *strcat( char *strDestination, const char *strSource ) +{ + char *s; + + s = strDestination; + while( *s ) + s++; + + while( *strSource ) + *s++ = *strSource++; + + *s = 0; + return strDestination; +} + +char *strcpy( char *strDestination, const char *strSource ) +{ + char *s; + + s = strDestination; + + while( *strSource ) + *s++ = *strSource++; + + *s = 0; + return strDestination; +} + + +int strcmp( const char *string1, const char *string2 ) +{ + while( *string1 == *string2 && *string1 && *string2 ) + { + string1++; + string2++; + } + + return *string1 - *string2; +} + +char *strrchr( const char *string, int c ) +{ + int i, length = strlen( string ); + char *p; + + for( i = length - 1; i >= 0; i-- ) + { + p = (char *)&string[ i ]; + + if( *p == c ) + return (char *)p; + } + + return (char *)0; +} + +char *strchr( const char *string, int c ) +{ + while( *string ) + { + if( *string == c ) + return ( char * )string; + + string++; + } + return (char *)0; +} + +char *strstr( const char *string, const char *strCharSet ) +{ + while( *string ) + { + int i; + + for( i = 0; strCharSet[ i ]; i++ ) + { + if( string[ i ] != strCharSet[ i ] ) + break; + } + + if( !strCharSet[ i ] ) + return (char *)string; + + string++; + } + return (char *)0; +} + +int tolower( int c ) +{ + if( c >= 'A' && c <= 'Z' ) + c += 'a' - 'A'; + + return c; +} + + +int toupper( int c ) +{ + if( c >= 'a' && c <= 'z' ) + c += 'A' - 'a'; + + return c; +} + +void *memmove( void *dest, const void *src, size_t count ) +{ + size_t i; + + if( dest > src ) + { + i = count; + while( i > 0 ) + { + i--; + ((char *)dest)[ i ] = ((char *)src)[ i ]; + } + } + else + { + for( i = 0; i < count; i++ ) + ((char *) dest)[ i ] = ((char *)src)[ i ]; + } + + return dest; +} + + +#if 0 + +double floor( double x ) { + return (int)(x + 0x40000000) - 0x40000000; +} + +void *memset( void *dest, int c, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = c; + } + return dest; +} + +void *memcpy( void *dest, const void *src, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = ((char *)src)[count]; + } + return dest; +} + +char *strncpy( char *strDest, const char *strSource, size_t count ) { + char *s; + + s = strDest; + while ( *strSource && count ) { + *s++ = *strSource++; + count--; + } + while ( count-- ) { + *s++ = 0; + } + return strDest; +} + +double sqrt( double x ) { + float y; + float delta; + float maxError; + + if ( x <= 0 ) { + return 0; + } + + // initial guess + y = x / 2; + + // refine + maxError = x * 0.001; + + do { + delta = ( y * y ) - x; + y -= delta / ( 2 * y ); + } while ( delta > maxError || delta < -maxError ); + + return y; +} + + +float sintable[1024] = { +0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, +0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, +0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, +0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, +0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, +0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, +0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, +0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, +0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, +0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, +0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, +0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, +0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, +0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, +0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, +0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, +0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, +0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, +0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, +0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, +0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, +0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, +0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, +0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, +0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, +0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, +0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, +0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, +0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, +0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, +0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, +0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, +0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, +0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, +0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, +0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, +0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, +0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, +0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, +0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, +0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, +0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, +0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, +0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, +0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, +0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, +0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, +0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, +0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, +0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, +0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, +0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, +0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, +0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, +0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, +0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, +0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, +0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, +0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, +0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, +0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, +0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, +0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, +0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, +0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, +0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, +0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, +0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, +0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, +0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, +0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, +0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, +0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, +0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, +0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, +0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, +0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, +0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, +0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, +0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, +0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, +0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, +0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, +0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, +0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, +0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, +0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, +0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, +0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, +0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, +0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, +0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, +0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, +0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, +0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, +0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, +0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, +0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, +0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, +0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, +0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, +0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, +0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, +0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, +0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, +0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, +0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, +0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, +0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, +0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, +0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, +0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, +0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, +0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, +0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, +0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, +0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, +0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, +0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, +0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, +0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, +0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, +0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, +0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, +0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, +0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, +0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, +0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 +}; + +double sin( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 0: + return sintable[index]; + case 1: + return sintable[1023-index]; + case 2: + return -sintable[index]; + case 3: + return -sintable[1023-index]; + } + return 0; +} + + +double cos( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 3: + return sintable[index]; + case 0: + return sintable[1023-index]; + case 1: + return -sintable[index]; + case 2: + return -sintable[1023-index]; + } + return 0; +} + + +/* +void create_acostable( void ) { + int i; + FILE *fp; + float a; + + fp = fopen("c:\\acostable.txt", "w"); + fprintf(fp, "float acostable[] = {"); + for (i = 0; i < 1024; i++) { + if (!(i & 7)) + fprintf(fp, "\n"); + a = acos( (float) -1 + i / 512 ); + fprintf(fp, "%1.8f,", a); + } + fprintf(fp, "\n}\n"); + fclose(fp); +} +*/ + + +float acostable[] = { +3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, +2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, +2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, +2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, +2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, +2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, +2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, +2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, +2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, +2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, +2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, +2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, +2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, +2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, +2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, +2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, +2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, +2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, +2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, +2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, +2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, +2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, +2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, +2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, +2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, +2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, +2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, +2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, +2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, +2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, +2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, +2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, +2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, +2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, +2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, +2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, +2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, +2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, +1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, +1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, +1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, +1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, +1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, +1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, +1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, +1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, +1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, +1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, +1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, +1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, +1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, +1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, +1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, +1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, +1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, +1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, +1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, +1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, +1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, +1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, +1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, +1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, +1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, +1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, +1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, +1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, +1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, +1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, +1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, +1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, +1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, +1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, +1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, +1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, +1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, +1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, +1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, +1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, +1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, +1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, +1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, +1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, +1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, +1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, +1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, +1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, +1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, +1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, +1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, +1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, +1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, +1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, +1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, +1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, +1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, +1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, +1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, +1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, +1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, +0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, +0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, +0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, +0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, +0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, +0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, +0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, +0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, +0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, +0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, +0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, +0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, +0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, +0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, +0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, +0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, +0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, +0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, +0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, +0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, +0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, +0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, +0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, +0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, +0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, +0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, +0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, +0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, +0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, +}; + +double acos( double x ) { + int index; + + if (x < -1) + x = -1; + if (x > 1) + x = 1; + index = (float) (1.0 + x) * 511.9; + return acostable[index]; +} + + +double atan2( double y, double x ) { + float base; + float temp; + float dir; + float test; + int i; + + if ( x < 0 ) { + if ( y >= 0 ) { + // quad 1 + base = M_PI / 2; + temp = x; + x = y; + y = -temp; + } else { + // quad 2 + base = M_PI; + x = -x; + y = -y; + } + } else { + if ( y < 0 ) { + // quad 3 + base = 3 * M_PI / 2; + temp = x; + x = -y; + y = temp; + } + } + + if ( y > x ) { + base += M_PI/2; + temp = x; + x = y; + y = temp; + dir = -1; + } else { + dir = 1; + } + + // calcualte angle in octant 0 + if ( x == 0 ) { + return base; + } + y /= x; + + for ( i = 0 ; i < 512 ; i++ ) { + test = sintable[i] / sintable[1023-i]; + if ( test > y ) { + break; + } + } + + return base + dir * i * ( M_PI/2048); +} + + +#endif + +/* +=============== +rint +=============== +*/ +double rint( double v ) +{ + if( v >= 0.5f ) + return ceil( v ); + else + return floor( v ); +} + +double tan( double x ) +{ + return sin( x ) / cos( x ); +} + +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +typedef union +{ + float value; + unsigned int word; +} ieee_float_shape_type; + +/* Get a 32 bit int from a float. */ + +#define GET_FLOAT_WORD(i,d) \ +do { \ + ieee_float_shape_type gf_u; \ + gf_u.value = (d); \ + (i) = gf_u.word; \ +} while (0) + +/* Set a float from a 32 bit int. */ + +#define SET_FLOAT_WORD(d,i) \ +do { \ + ieee_float_shape_type sf_u; \ + sf_u.word = (i); \ + (d) = sf_u.value; \ +} while (0) + +/* A union which permits us to convert between a float and a 32 bit + int. */ + +//acos +static const float +pi = 3.1415925026e+00, /* 0x40490fda */ +pio2_hi = 1.5707962513e+00, /* 0x3fc90fda */ +pio2_lo = 7.5497894159e-08, /* 0x33a22168 */ +pS0 = 1.6666667163e-01, /* 0x3e2aaaab */ +pS1 = -3.2556581497e-01, /* 0xbea6b090 */ +pS2 = 2.0121252537e-01, /* 0x3e4e0aa8 */ +pS3 = -4.0055535734e-02, /* 0xbd241146 */ +pS4 = 7.9153501429e-04, /* 0x3a4f7f04 */ +pS5 = 3.4793309169e-05, /* 0x3811ef08 */ +qS1 = -2.4033949375e+00, /* 0xc019d139 */ +qS2 = 2.0209457874e+00, /* 0x4001572d */ +qS3 = -6.8828397989e-01, /* 0xbf303361 */ +qS4 = 7.7038154006e-02; /* 0x3d9dc62e */ + +/* +================== +acos +================== +*/ +double acos( double x ) +{ + float z, subp, p, q, r, w, s, c, df; + int hx, ix; + + GET_FLOAT_WORD( hx, x ); + ix = hx & 0x7fffffff; + + if( ix == 0x3f800000 ) + { // |x|==1 + if( hx > 0 ) + return 0.0; // acos(1) = 0 + else + return pi + (float)2.0 * pio2_lo; // acos(-1)= pi + } + else if( ix > 0x3f800000 ) + { // |x| >= 1 + return (x-x)/(x-x); // acos(|x|>1) is NaN + } + + if( ix < 0x3f000000 ) + { // |x| < 0.5 + if( ix <= 0x23000000 ) + return pio2_hi + pio2_lo;//if|x|<2**-57 + + z = x * x; + subp = pS3 + z * ( pS4 + z * pS5 ); + // chop up expression to keep mac register based stack happy + p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) ); + q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) ); + r = p / q; + return pio2_hi - ( x - ( pio2_lo - x * r ) ); + } + else if( hx < 0 ) + { // x < -0.5 + z = ( 1.0 + x ) * (float)0.5; + subp = pS3 + z * ( pS4 + z * pS5 ); + // chop up expression to keep mac register based stack happy + p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) ); + q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) ); + s = sqrt( z ); + r = p / q; + w = r * s - pio2_lo; + return pi - (float)2.0 * ( s + w ); + } + else + { // x > 0.5 + int idf; + z = ( 1.0 - x ) * (float)0.5; + s = sqrt( z ); + df = s; + GET_FLOAT_WORD( idf, df ); + SET_FLOAT_WORD( df, idf & 0xfffff000 ); + c = ( z - df * df ) / ( s + df ); + subp = pS3 + z * ( pS4 + z * pS5 ); + // chop up expression to keep mac register based stack happy + p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) ); + q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) ); + r = p / q; + w = r * s + c; + return (double)( 2.0 * ( df + w ) ); + } +} + +//pow +static const float +bp[ ] = { 1.0, 1.5, }, +dp_h[ ] = { 0.0, 5.84960938e-01, }, /* 0x3f15c000 */ +dp_l[ ] = { 0.0, 1.56322085e-06, }, /* 0x35d1cfdc */ +huge = 1.0e+30, +tiny = 1.0e-30, +zero = 0.0, +one = 1.0, +two = 2.0, +two24 = 16777216.0, /* 0x4b800000 */ +two25 = 3.355443200e+07, /* 0x4c000000 */ +twom25 = 2.9802322388e-08, /* 0x33000000 */ + /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */ +L1 = 6.0000002384e-01, /* 0x3f19999a */ +L2 = 4.2857143283e-01, /* 0x3edb6db7 */ +L3 = 3.3333334327e-01, /* 0x3eaaaaab */ +L4 = 2.7272811532e-01, /* 0x3e8ba305 */ +L5 = 2.3066075146e-01, /* 0x3e6c3255 */ +L6 = 2.0697501302e-01, /* 0x3e53f142 */ +P1 = 1.6666667163e-01, /* 0x3e2aaaab */ +P2 = -2.7777778450e-03, /* 0xbb360b61 */ +P3 = 6.6137559770e-05, /* 0x388ab355 */ +P4 = -1.6533901999e-06, /* 0xb5ddea0e */ +P5 = 4.1381369442e-08, /* 0x3331bb4c */ +lg2 = 6.9314718246e-01, /* 0x3f317218 */ +lg2_h = 6.93145752e-01, /* 0x3f317200 */ +lg2_l = 1.42860654e-06, /* 0x35bfbe8c */ +ovt = 4.2995665694e-08, /* -(128-log2(ovfl+.5ulp)) */ +cp = 9.6179670095e-01, /* 0x3f76384f =2/(3ln2) */ +cp_h = 9.6179199219e-01, /* 0x3f763800 =head of cp */ +cp_l = 4.7017383622e-06, /* 0x369dc3a0 =tail of cp_h */ +ivln2 = 1.4426950216e+00, /* 0x3fb8aa3b =1/ln2 */ +ivln2_h = 1.4426879883e+00, /* 0x3fb8aa00 =16b 1/ln2*/ +ivln2_l = 7.0526075433e-06; /* 0x36eca570 =1/ln2 tail*/ + +/* +================== +copysignf +================== +*/ +static float copysignf( float x, float y ) +{ + unsigned int ix, iy; + + GET_FLOAT_WORD( ix, x ); + GET_FLOAT_WORD( iy, y ); + SET_FLOAT_WORD( x, ( ix & 0x7fffffff ) | ( iy & 0x80000000 ) ); + return x; +} + +/* +================== +__scalbnf +================== +*/ +static float __scalbnf( float x, int n ) +{ + int k, ix; + + GET_FLOAT_WORD( ix, x ); + + k = ( ix & 0x7f800000 ) >> 23; /* extract exponent */ + + if( k == 0 ) + { /* 0 or subnormal x */ + if( ( ix & 0x7fffffff ) == 0 ) + return x; /* +-0 */ + + x *= two25; + GET_FLOAT_WORD( ix, x ); + k = ( ( ix & 0x7f800000 ) >> 23 ) - 25; + } + if( k == 0xff ) + return x+x; /* NaN or Inf */ + + k = k + n; + + if( n > 50000 || k > 0xfe ) + return huge * copysignf( huge, x ); /* overflow */ + if ( n < -50000 ) + return tiny * copysignf( tiny, x ); /*underflow*/ + if( k > 0 ) /* normal result */ + { + SET_FLOAT_WORD( x, ( ix & 0x807fffff ) | ( k << 23 ) ); + return x; + } + if( k <= -25 ) + return tiny * copysignf( tiny, x ); /*underflow*/ + + k += 25; /* subnormal result */ + SET_FLOAT_WORD( x, ( ix & 0x807fffff ) | ( k << 23 ) ); + return x * twom25; +} + +/* +================== +pow +================== +*/ +float pow( float x, float y ) +{ + float z, ax, z_h, z_l, p_h, p_l; + float y1, subt1, t1, t2, subr, r, s, t, u, v, w; + int i, j, k, yisint, n; + int hx, hy, ix, iy, is; + + /*TA: for some reason the Q3 VM goes apeshit when x = 1.0 + and y > 1.0. Curiously this doesn't happen with gcc + hence this hack*/ + if( x == 1.0 ) + return x; + + GET_FLOAT_WORD( hx, x ); + GET_FLOAT_WORD( hy, y ); + ix = hx & 0x7fffffff; + iy = hy & 0x7fffffff; + + /* y==zero: x**0 = 1 */ + if( iy == 0 ) + return one; + + /* +-NaN return x+y */ + if( ix > 0x7f800000 || iy > 0x7f800000 ) + return x + y; + + /* determine if y is an odd int when x < 0 + * yisint = 0 ... y is not an integer + * yisint = 1 ... y is an odd int + * yisint = 2 ... y is an even int + */ + yisint = 0; + if( hx < 0 ) + { + if( iy >= 0x4b800000 ) + yisint = 2; /* even integer y */ + else if( iy >= 0x3f800000 ) + { + k = ( iy >> 23 ) - 0x7f; /* exponent */ + j = iy >> ( 23 - k ); + if( ( j << ( 23 - k ) ) == iy ) + yisint = 2 - ( j & 1 ); + } + } + + /* special value of y */ + if( iy == 0x7f800000 ) + { /* y is +-inf */ + if( ix == 0x3f800000 ) + return y - y; /* inf**+-1 is NaN */ + else if( ix > 0x3f800000 )/* (|x|>1)**+-inf = inf,0 */ + return ( hy >= 0 ) ? y : zero; + else /* (|x|<1)**-,+inf = inf,0 */ + return ( hy < 0 ) ? -y : zero; + } + + if( iy == 0x3f800000 ) + { /* y is +-1 */ + if( hy < 0 ) + return one / x; + else + return x; + } + + if( hy == 0x40000000 ) + return x * x; /* y is 2 */ + + if( hy == 0x3f000000 ) + { /* y is 0.5 */ + if( hx >= 0 ) /* x >= +0 */ + return sqrt( x ); + } + + ax = fabs( x ); + + /* special value of x */ + if( ix == 0x7f800000 || ix == 0 || ix == 0x3f800000 ) + { + z = ax; /*x is +-0,+-inf,+-1*/ + if( hy < 0 ) + z = one / z; /* z = (1/|x|) */ + if( hx < 0 ) + { + if( ( ( ix - 0x3f800000 ) | yisint ) == 0 ) + z = ( z - z ) / ( z - z ); /* (-1)**non-int is NaN */ + else if( yisint == 1 ) + z = -z; /* (x<0)**odd = -(|x|**odd) */ + } + + return z; + } + + /* (x<0)**(non-int) is NaN */ + if( ( ( ( (unsigned int)hx >> 31 ) - 1 ) | yisint ) == 0 ) + return ( x - x ) / ( x - x ); + + /* |y| is huge */ + if( iy > 0x4d000000 ) + { /* if |y| > 2**27 */ + /* over/underflow if x is not close to one */ + if( ix < 0x3f7ffff8 ) + return ( hy < 0 ) ? huge * huge : tiny * tiny; + + if( ix > 0x3f800007 ) + return ( hy > 0 ) ? huge * huge : tiny * tiny; + /* now |1-x| is tiny <= 2**-20, suffice to compute + log(x) by x-x^2/2+x^3/3-x^4/4 */ + t = x - 1; /* t has 20 trailing zeros */ + w = ( t * t ) * ( (float)0.5 - t * ( (float)0.333333333333 - t * (float)0.25 ) ); + u = ivln2_h * t; /* ivln2_h has 16 sig. bits */ + v = t * ivln2_l - w * ivln2; + t1 = u + v; + GET_FLOAT_WORD( is, t1 ); + SET_FLOAT_WORD( t1, is & 0xfffff000 ); + t2 = v - ( t1 - u ); + } + else + { + float s2, s_h, s_l, t_h, t_l; + n = 0; + /* take care subnormal number */ + if( ix < 0x00800000 ) + { + ax *= two24; + n -= 24; + GET_FLOAT_WORD( ix, ax ); + } + + n += ( ( ix ) >> 23 ) - 0x7f; + j = ix & 0x007fffff; + + /* determine interval */ + ix = j | 0x3f800000; /* normalize ix */ + if( j <= 0x1cc471 ) + k = 0; /* |x|> 1 ) | 0x20000000 ) + 0x0040000 + ( k << 21 ) ); + t_l = ax - ( t_h - bp[ k ] ); + s_l = v * ( ( u - s_h * t_h ) - s_h * t_l ); + /* compute log(ax) */ + s2 = s * s; + subr = L3 + s2 * ( L4 + s2 * ( L5 + s2 * L6 ) ); + // chop up expression to keep mac register based stack happy + r = s2 * s2 * ( L1 + s2 * ( L2 + s2 * subr ) ); + r += s_l * ( s_h + s ); + s2 = s_h * s_h; + t_h = (float)3.0 + s2 + r; + GET_FLOAT_WORD( is, t_h ); + SET_FLOAT_WORD( t_h, is & 0xfffff000 ); + t_l = r - ( ( t_h - (float)3.0 ) - s2 ); + /* u+v = s*(1+...) */ + u = s_h * t_h; + v = s_l * t_h + t_l * s; + /* 2/(3log2)*(s+...) */ + p_h = u + v; + GET_FLOAT_WORD( is, p_h ); + SET_FLOAT_WORD( p_h, is & 0xfffff000 ); + p_l = v - ( p_h - u ); + z_h = cp_h * p_h; /* cp_h+cp_l = 2/(3*log2) */ + z_l = cp_l * p_h + p_l * cp + dp_l[ k ]; + /* log2(ax) = (s+..)*2/(3*log2) = n + dp_h + z_h + z_l */ + t = (float)n; + t1 = ( ( ( z_h + z_l ) + dp_h[ k ] ) + t ); + GET_FLOAT_WORD( is, t1 ); + SET_FLOAT_WORD( t1, is & 0xfffff000 ); + t2 = z_l - ( ( ( t1 - t ) - dp_h[ k ] ) - z_h ); + } + + s = one; /* s (sign of result -ve**odd) = -1 else = 1 */ + if( ( ( ( (unsigned int)hx >> 31 ) - 1 ) | ( yisint - 1 ) ) == 0 ) + s = -one; /* (-ve)**(odd int) */ + + /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */ + GET_FLOAT_WORD( is, y ); + SET_FLOAT_WORD( y1, is & 0xfffff000 ); + p_l = ( y - y1 ) * t1 + y * t2; + p_h = y1 * t1; + z = p_l + p_h; + GET_FLOAT_WORD( j, z ); + + if( j > 0x43000000 ) /* if z > 128 */ + return s * huge * huge; /* overflow */ + else if( j == 0x43000000 ) + { /* if z == 128 */ + if( p_l + ovt > z - p_h ) + return s * huge * huge; /* overflow */ + } + else if( ( j & 0x7fffffff ) > 0x43160000 ) /* z <= -150 */ + return s * tiny * tiny; /* underflow */ + else if( (unsigned int)j == 0xc3160000 ) + { /* z == -150 */ + if( p_l <= z - p_h ) + return s * tiny * tiny; /* underflow */ + } + + /* + * compute 2**(p_h+p_l) + */ + i = j & 0x7fffffff; + k = ( i >> 23 ) - 0x7f; + n = 0; + + if( i > 0x3f000000 ) + { /* if |z| > 0.5, set n = [z+0.5] */ + n = j + ( 0x00800000 >> ( k + 1 ) ); + k = ( ( n & 0x7fffffff ) >> 23 ) - 0x7f; /* new k for n */ + SET_FLOAT_WORD( t, n & ~( 0x007fffff >> k ) ); + n = ( ( n & 0x007fffff ) | 0x00800000 ) >> ( 23 - k ); + + if( j < 0 ) + n = -n; + + p_h -= t; + } + + t = p_l + p_h; + GET_FLOAT_WORD( is, t ); + SET_FLOAT_WORD( t, is & 0xfffff000 ); + u = t * lg2_h; + v = ( p_l - ( t - p_h ) ) * lg2 + t * lg2_l; + z = u + v; + w = v - ( z - u ); + t = z * z; + subt1 = P3 + t * ( P4 + t * P5 ); + // chop up expression to keep mac register based stack happy + t1 = z - t * ( P1 + t * ( P2 + t * subt1 ) ); + r = ( z * t1 ) / ( t1 - two ) - ( w + z * w ); + z = one - ( r - z ); + GET_FLOAT_WORD( j, z ); + j += (n << 23 ); + + if( ( j >> 23 ) <= 0 ) + z = __scalbnf( z, n ); /* subnormal output */ + else + SET_FLOAT_WORD( z, j ); + + return s * z; +} + + + +static int randSeed = 0; + +void srand( unsigned seed ) +{ + randSeed = seed; +} + +int rand( void ) +{ + randSeed = ( 69069 * randSeed + 1 ); + return randSeed & 0x7fff; +} + +double atof( const char *string ) +{ + float sign; + float value; + int c; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + return 0; + + string++; + } + + // check sign + switch( *string ) + { + case '+': + string++; + sign = 1; + break; + + case '-': + string++; + sign = -1; + break; + + default: + sign = 1; + break; + } + + // read digits + value = 0; + c = string[ 0 ]; + + if( c != '.' ) + { + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value = value * 10 + c; + } while( 1 ); + } + else + string++; + + // check for decimal point + if( c == '.' ) + { + double fraction; + + fraction = 0.1; + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while( 1 ); + + } + + // not handling 10e10 notation... + + return value * sign; +} + +/* +============== +strtod + +Without an errno variable, this is a fair bit less useful than it is in libc +but it's still a fair bit more capable than atof or _atof +Handles inf[inity], nan (ignoring case), hexadecimals, and decimals +Handles decimal exponents like 10e10 and hex exponents like 0x7f8p20 +10e10 == 10000000000 (power of ten) +0x7f8p20 == 0x7f800000 (decimal power of two) +The variable pointed to by endptr will hold the location of the first character +in the nptr string that was not used in the conversion +============== +*/ +double strtod( const char *nptr, char **endptr ) +{ + double res; + qboolean neg = qfalse; + + // skip whitespace + while( isspace( *nptr ) ) + nptr++; + + // special string parsing + if( Q_stricmpn( nptr, "nan", 3 ) == 0 ) + { + floatint_t nan; + + if( endptr ) + *endptr = (char *)&nptr[3]; + + // nan can be followed by a bracketed number (in hex, octal, + // or decimal) which is then put in the mantissa + // this can be used to generate signalling or quiet NaNs, for + // example (though I doubt it'll ever be used) + // note that nan(0) is infinity! + if( nptr[3] == '(' ) + { + char *end; + int mantissa = strtol( &nptr[4], &end, 0 ); + + if( *end == ')' ) + { + nan.ui = 0x7f800000 | ( mantissa & 0x7fffff ); + + if( endptr ) + *endptr = &end[1]; + return nan.f; + } + } + + nan.ui = 0x7fffffff; + return nan.f; + } + + if( Q_stricmpn( nptr, "inf", 3 ) == 0 ) + { + floatint_t inf; + inf.ui = 0x7f800000; + + if( endptr == NULL ) + return inf.f; + + if( Q_stricmpn( &nptr[3], "inity", 5 ) == 0 ) + *endptr = (char *)&nptr[8]; + else + *endptr = (char *)&nptr[3]; + + return inf.f; + } + + // normal numeric parsing + // sign + if( *nptr == '-' ) + { + nptr++; + neg = qtrue; + } + else if( *nptr == '+' ) + nptr++; + + // hex + if( Q_stricmpn( nptr, "0x", 2 ) == 0 ) + { + // track if we use any digits + const char *s = &nptr[1], *end = s; + nptr += 2; + + for( res = 0;; ) + { + if( isdigit( *nptr ) ) + res = 16 * res + ( *nptr++ - '0' ); + else if( *nptr >= 'A' && *nptr <= 'F' ) + res = 16 * res + 10 + *nptr++ - 'A'; + else if( *nptr >= 'a' && *nptr <= 'f' ) + res = 16 * res + 10 + *nptr++ - 'a'; + else + break; + } + + // if nptr moved, save it + if( end + 1 < nptr ) + end = nptr; + + if( *nptr == '.' ) + { + float place; + nptr++; + + // 1.0 / 16.0 == 0.0625 + // I don't expect the float accuracy to hold out for + // very long but since we need to know the length of + // the string anyway we keep on going regardless + for( place = 0.0625;; place /= 16.0 ) + { + if( isdigit( *nptr ) ) + res += place * ( *nptr++ - '0' ); + else if( *nptr >= 'A' && *nptr <= 'F' ) + res += place * ( 10 + *nptr++ - 'A' ); + else if( *nptr >= 'a' && *nptr <= 'f' ) + res += place * ( 10 + *nptr++ - 'a' ); + else + break; + } + + if( end < nptr ) + end = nptr; + } + + // parse an optional exponent, representing multiplication + // by a power of two + // exponents are only valid if we encountered at least one + // digit already (and have therefore set end to something) + if( end != s && tolower( *nptr ) == 'p' ) + { + int exp; + float res2; + // apparently (confusingly) the exponent should be + // decimal + exp = strtol( &nptr[1], (char **)&end, 10 ); + if( &nptr[1] == end ) + { + // no exponent + if( endptr ) + *endptr = (char *)nptr; + return res; + } + if( exp > 0 ) + { + while( exp-- > 0 ) + { + res2 = res * 2; + // check for infinity + if( res2 <= res ) + break; + res = res2; + } + } + else + { + while( exp++ < 0 ) + { + res2 = res / 2; + // check for underflow + if( res2 >= res ) + break; + res = res2; + } + } + } + if( endptr ) + *endptr = (char *)end; + return res; + } + // decimal + else + { + // track if we find any digits + const char *end = nptr, *p = nptr; + // this is most of the work + for( res = 0; isdigit( *nptr ); + res = 10 * res + *nptr++ - '0' ); + // if nptr moved, we read something + if( end < nptr ) + end = nptr; + if( *nptr == '.' ) + { + // fractional part + float place; + nptr++; + for( place = 0.1; isdigit( *nptr ); place /= 10.0 ) + res += ( *nptr++ - '0' ) * place; + // if nptr moved, we read something + if( end + 1 < nptr ) + end = nptr; + } + // exponent + // meaningless without having already read digits, so check + // we've set end to something + if( p != end && tolower( *nptr ) == 'e' ) + { + int exp; + float res10; + exp = strtol( &nptr[1], (char **)&end, 10 ); + if( &nptr[1] == end ) + { + // no exponent + if( endptr ) + *endptr = (char *)nptr; + return res; + } + if( exp > 0 ) + { + while( exp-- > 0 ) + { + res10 = res * 10; + // check for infinity to save us time + if( res10 <= res ) + break; + res = res10; + } + } + else if( exp < 0 ) + { + while( exp++ < 0 ) + { + res10 = res / 10; + // check for underflow + // (test for 0 would probably be just + // as good) + if( res10 >= res ) + break; + res = res10; + } + } + } + if( endptr ) + *endptr = (char *)end; + return res; + } +} + +double _atof( const char **stringPtr ) +{ + const char *string; + float sign; + float value; + int c = '0'; + + string = *stringPtr; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + { + *stringPtr = string; + return 0; + } + + string++; + } + + // check sign + switch( *string ) + { + case '+': + string++; + sign = 1; + break; + + case '-': + string++; + sign = -1; + break; + + default: + sign = 1; + break; + } + + // read digits + value = 0; + if( string[ 0 ] != '.' ) + { + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value = value * 10 + c; + } while( 1 ); + } + + // check for decimal point + if( c == '.' ) + { + double fraction; + + fraction = 0.1; + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while( 1 ); + + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + +/* +============== +strtol + +Handles any base from 2 to 36. If base is 0 then it guesses +decimal, hex, or octal based on the format of the number (leading 0 or 0x) +Will not overflow - returns LONG_MIN or LONG_MAX as appropriate +*endptr is set to the location of the first character not used +============== +*/ +long strtol( const char *nptr, char **endptr, int base ) +{ + long res; + qboolean pos = qtrue; + + if( endptr ) + *endptr = (char *)nptr; + + // bases other than 0, 2, 8, 16 are very rarely used, but they're + // not much extra effort to support + if( base < 0 || base == 1 || base > 36 ) + return 0; + + // skip leading whitespace + while( isspace( *nptr ) ) + nptr++; + + // sign + if( *nptr == '-' ) + { + nptr++; + pos = qfalse; + } + else if( *nptr == '+' ) + nptr++; + + // look for base-identifying sequences e.g. 0x for hex, 0 for octal + if( nptr[0] == '0' ) + { + nptr++; + + // 0 is always a valid digit + if( endptr ) + *endptr = (char *)nptr; + + if( *nptr == 'x' || *nptr == 'X' ) + { + if( base != 0 && base != 16 ) + { + // can't be hex, reject x (accept 0) + if( endptr ) + *endptr = (char *)nptr; + return 0; + } + + nptr++; + base = 16; + } + else if( base == 0 ) + base = 8; + } + else if( base == 0 ) + base = 10; + + for( res = 0;; ) + { + int val; + + if( isdigit( *nptr ) ) + val = *nptr - '0'; + else if( islower( *nptr ) ) + val = 10 + *nptr - 'a'; + else if( isupper( *nptr ) ) + val = 10 + *nptr - 'A'; + else + break; + + if( val >= base ) + break; + + // we go negative because LONG_MIN is further from 0 than + // LONG_MAX + if( res < ( LONG_MIN + val ) / base ) + res = LONG_MIN; // overflow + else + res = res * base - val; + + nptr++; + + if( endptr ) + *endptr = (char *)nptr; + } + if( pos ) + { + // can't represent LONG_MIN positive + if( res == LONG_MIN ) + res = LONG_MAX; + else + res = -res; + } + return res; +} + +int atoi( const char *string ) +{ + int sign; + int value; + int c; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + return 0; + + string++; + } + + // check sign + switch( *string ) + { + case '+': + string++; + sign = 1; + break; + + case '-': + string++; + sign = -1; + break; + + default: + sign = 1; + break; + } + + // read digits + value = 0; + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value = value * 10 + c; + } while( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + + +int _atoi( const char **stringPtr ) +{ + int sign; + int value; + int c; + const char *string; + + string = *stringPtr; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + return 0; + + string++; + } + + // check sign + switch( *string ) + { + case '+': + string++; + sign = 1; + break; + + case '-': + string++; + sign = -1; + break; + + default: + sign = 1; + break; + } + + // read digits + value = 0; + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value = value * 10 + c; + } while( 1 ); + + // not handling 10e10 notation... + + *stringPtr = string; + + return value * sign; +} + +int abs( int n ) +{ + return n < 0 ? -n : n; +} + +double fabs( double x ) +{ + return x < 0 ? -x : x; +} + +unsigned int _hextoi( const char **stringPtr ) +{ + unsigned int value; + int c; + int i; + const char *string; + + string = *stringPtr; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + return 0; + + string++; + } + + value = 0; + i = 0; + while( i++ < 8 && ( c = *string++ ) ) + { + if ( c >= '0' && c <= '9' ) + { + value = value * 16 + c - '0'; + continue; + } + else if ( c >= 'a' && c <= 'f' ) + { + value = value * 16 + 10 + c - 'a'; + continue; + } + else if ( c >= 'A' && c <= 'F' ) + { + value = value * 16 + 10 + c - 'A'; + continue; + } + else + break; + } + *stringPtr = string; + return value; +} + +//========================================================= + +/* + * New implementation by Patrick Powell and others for vsnprintf. + * Supports length checking in strings. + */ + +/* + * Copyright Patrick Powell 1995 + * This code is based on code written by Patrick Powell (papowell@astart.com) + * It may be used for any purpose as long as this notice remains intact + * on all source code distributions + */ + +/************************************************************** + * Original: + * Patrick Powell Tue Apr 11 09:48:21 PDT 1995 + * A bombproof version of doprnt (dopr) included. + * Sigh. This sort of thing is always nasty do deal with. Note that + * the version here does not include floating point... + * + * snprintf() is used instead of sprintf() as it does limit checks + * for string length. This covers a nasty loophole. + * + * The other functions are there to prevent NULL pointers from + * causing nast effects. + * + * More Recently: + * Brandon Long 9/15/96 for mutt 0.43 + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything + * from the normal C string format, at least as far as I can tell from + * the Solaris 2.5 printf(3S) man page. + * + * Brandon Long 10/22/97 for mutt 0.87.1 + * Ok, added some minimal floating point support, which means this + * probably requires libm on most operating systems. Don't yet + * support the exponent (e,E) and sigfig (g,G). Also, fmtint() + * was pretty badly broken, it just wasn't being exercised in ways + * which showed it, so that's been fixed. Also, formated the code + * to mutt conventions, and removed dead code left over from the + * original. Also, there is now a builtin-test, just compile with: + * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm + * and run snprintf for results. + * + * Thomas Roessler 01/27/98 for mutt 0.89i + * The PGP code was using unsigned hexadecimal formats. + * Unfortunately, unsigned formats simply didn't work. + * + * Michael Elkins 03/05/98 for mutt 0.90.8 + * The original code assumed that both snprintf() and vsnprintf() were + * missing. Some systems only have snprintf() but not vsnprintf(), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + * Andrew Tridgell (tridge@samba.org) Oct 1998 + * fixed handling of %.0f + * added test for HAVE_LONG_DOUBLE + * + * Russ Allbery 2000-08-26 + * fixed return value to comply with C99 + * fixed handling of snprintf(NULL, ...) + * + * Hrvoje Niksic 2000-11-04 + * include instead of "config.h". + * moved TEST_SNPRINTF stuff out of HAVE_SNPRINTF ifdef. + * include for NULL. + * added support and test cases for long long. + * don't declare argument types to (v)snprintf if stdarg is not used. + * use int instead of short int as 2nd arg to va_arg. + * + **************************************************************/ + +/* BDR 2002-01-13 %e and %g were being ignored. Now do something, + if not necessarily correctly */ + +#if (SIZEOF_LONG_DOUBLE > 0) +/* #ifdef HAVE_LONG_DOUBLE */ +#define LDOUBLE long double +#else +#define LDOUBLE double +#endif + +#if (SIZEOF_LONG_LONG > 0) +/* #ifdef HAVE_LONG_LONG */ +# define LLONG long long +#else +# define LLONG long +#endif + +static int dopr (char *buffer, size_t maxlen, const char *format, + va_list args); +static int fmtstr (char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max); +static int fmtint (char *buffer, size_t *currlen, size_t maxlen, + LLONG value, int base, int min, int max, int flags); +static int fmtfp (char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags); +static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c ); + +/* + * dopr(): poor man's version of doprintf + */ + +/* format read states */ +#define DP_S_DEFAULT 0 +#define DP_S_FLAGS 1 +#define DP_S_MIN 2 +#define DP_S_DOT 3 +#define DP_S_MAX 4 +#define DP_S_MOD 5 +#define DP_S_MOD_L 6 +#define DP_S_CONV 7 +#define DP_S_DONE 8 + +/* format flags - Bits */ +#define DP_F_MINUS (1 << 0) +#define DP_F_PLUS (1 << 1) +#define DP_F_SPACE (1 << 2) +#define DP_F_NUM (1 << 3) +#define DP_F_ZERO (1 << 4) +#define DP_F_UP (1 << 5) +#define DP_F_UNSIGNED (1 << 6) + +/* Conversion Flags */ +#define DP_C_SHORT 1 +#define DP_C_LONG 2 +#define DP_C_LLONG 3 +#define DP_C_LDOUBLE 4 + +#define char_to_int(p) (p - '0') + +static int dopr (char *buffer, size_t maxlen, const char *format, va_list args) +{ + char ch; + LLONG value; + LDOUBLE fvalue; + char *strvalue; + int min; + int max; + int state; + int flags; + int cflags; + int total; + size_t currlen; + + state = DP_S_DEFAULT; + currlen = flags = cflags = min = 0; + max = -1; + ch = *format++; + total = 0; + + while (state != DP_S_DONE) + { + if (ch == '\0') + state = DP_S_DONE; + + switch(state) + { + case DP_S_DEFAULT: + if (ch == '%') + state = DP_S_FLAGS; + else + total += dopr_outch (buffer, &currlen, maxlen, ch); + ch = *format++; + break; + case DP_S_FLAGS: + switch (ch) + { + case '-': + flags |= DP_F_MINUS; + ch = *format++; + break; + case '+': + flags |= DP_F_PLUS; + ch = *format++; + break; + case ' ': + flags |= DP_F_SPACE; + ch = *format++; + break; + case '#': + flags |= DP_F_NUM; + ch = *format++; + break; + case '0': + flags |= DP_F_ZERO; + ch = *format++; + break; + default: + state = DP_S_MIN; + break; + } + break; + case DP_S_MIN: + if ('0' <= ch && ch <= '9') + { + min = 10*min + char_to_int (ch); + ch = *format++; + } + else if (ch == '*') + { + min = va_arg (args, int); + ch = *format++; + state = DP_S_DOT; + } + else + state = DP_S_DOT; + break; + case DP_S_DOT: + if (ch == '.') + { + state = DP_S_MAX; + ch = *format++; + } + else + state = DP_S_MOD; + break; + case DP_S_MAX: + if ('0' <= ch && ch <= '9') + { + if (max < 0) + max = 0; + max = 10*max + char_to_int (ch); + ch = *format++; + } + else if (ch == '*') + { + max = va_arg (args, int); + ch = *format++; + state = DP_S_MOD; + } + else + state = DP_S_MOD; + break; + case DP_S_MOD: + switch (ch) + { + case 'h': + cflags = DP_C_SHORT; + ch = *format++; + break; + case 'l': + cflags = DP_C_LONG; + ch = *format++; + break; + case 'L': + cflags = DP_C_LDOUBLE; + ch = *format++; + break; + default: + break; + } + if (cflags != DP_C_LONG) + state = DP_S_CONV; + else + state = DP_S_MOD_L; + break; + case DP_S_MOD_L: + switch (ch) + { + case 'l': + cflags = DP_C_LLONG; + ch = *format++; + break; + default: + break; + } + state = DP_S_CONV; + break; + case DP_S_CONV: + switch (ch) + { + case 'd': + case 'i': + if (cflags == DP_C_SHORT) + value = (short int)va_arg (args, int); + else if (cflags == DP_C_LONG) + value = va_arg (args, long int); + else if (cflags == DP_C_LLONG) + value = va_arg (args, LLONG); + else + value = va_arg (args, int); + total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); + break; + case 'o': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + // value = (unsigned short int) va_arg (args, unsigned short int); // Thilo: This does not work because the rcc compiler cannot do that cast correctly. + value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); // Using this workaround instead. + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else if (cflags == DP_C_LLONG) + value = va_arg (args, unsigned LLONG); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags); + break; + case 'u': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else if (cflags == DP_C_LLONG) + value = va_arg (args, unsigned LLONG); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); + break; + case 'X': + flags |= DP_F_UP; + case 'x': + flags |= DP_F_UNSIGNED; + if (cflags == DP_C_SHORT) + value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); + else if (cflags == DP_C_LONG) + value = va_arg (args, unsigned long int); + else if (cflags == DP_C_LLONG) + value = va_arg (args, unsigned LLONG); + else + value = va_arg (args, unsigned int); + total += fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags); + break; + case 'f': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + /* um, floating point? */ + total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); + break; + case 'E': + flags |= DP_F_UP; + case 'e': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + /* um, floating point? */ + total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); + break; + case 'G': + flags |= DP_F_UP; + case 'g': + if (cflags == DP_C_LDOUBLE) + fvalue = va_arg (args, LDOUBLE); + else + fvalue = va_arg (args, double); + /* um, floating point? */ + total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); + break; + case 'c': + total += dopr_outch (buffer, &currlen, maxlen, va_arg (args, int)); + break; + case 's': + strvalue = va_arg (args, char *); + total += fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max); + break; + case 'p': + strvalue = va_arg (args, void *); + total += fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, + max, flags); + break; + case 'n': + if (cflags == DP_C_SHORT) + { + short int *num; + num = va_arg (args, short int *); + *num = currlen; + } + else if (cflags == DP_C_LONG) + { + long int *num; + num = va_arg (args, long int *); + *num = currlen; + } + else if (cflags == DP_C_LLONG) + { + LLONG *num; + num = va_arg (args, LLONG *); + *num = currlen; + } + else + { + int *num; + num = va_arg (args, int *); + *num = currlen; + } + break; + case '%': + total += dopr_outch (buffer, &currlen, maxlen, ch); + break; + case 'w': + /* not supported yet, treat as next char */ + ch = *format++; + break; + default: + /* Unknown, skip */ + break; + } + ch = *format++; + state = DP_S_DEFAULT; + flags = cflags = min = 0; + max = -1; + break; + case DP_S_DONE: + break; + default: + /* hmm? */ + break; /* some picky compilers need this */ + } + } + if (maxlen > 0) + buffer[currlen] = '\0'; + return total; +} + +static int fmtstr (char *buffer, size_t *currlen, size_t maxlen, + char *value, int flags, int min, int max) +{ + int padlen, strln; /* amount to pad */ + int cnt = 0; + int total = 0; + + if (value == 0) + { + value = ""; + } + + for (strln = 0; value[strln]; ++strln); /* strlen */ + if (max >= 0 && max < strln) + strln = max; + padlen = min - strln; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justify */ + + while (padlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + while (*value && ((max < 0) || (cnt < max))) + { + total += dopr_outch (buffer, currlen, maxlen, *value++); + ++cnt; + } + while (padlen < 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } + return total; +} + +/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ + +static int fmtint (char *buffer, size_t *currlen, size_t maxlen, + LLONG value, int base, int min, int max, int flags) +{ + int signvalue = 0; + unsigned LLONG uvalue; + char convert[24]; + int place = 0; + int spadlen = 0; /* amount to space pad */ + int zpadlen = 0; /* amount to zero pad */ + const char *digits; + int total = 0; + + if (max < 0) + max = 0; + + uvalue = value; + + if(!(flags & DP_F_UNSIGNED)) + { + if( value < 0 ) { + signvalue = '-'; + uvalue = -value; + } + else + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else + if (flags & DP_F_SPACE) + signvalue = ' '; + } + + if (flags & DP_F_UP) + /* Should characters be upper case? */ + digits = "0123456789ABCDEF"; + else + digits = "0123456789abcdef"; + + do { + convert[place++] = digits[uvalue % (unsigned)base]; + uvalue = (uvalue / (unsigned)base ); + } while(uvalue && (place < sizeof (convert))); + if (place == sizeof (convert)) place--; + convert[place] = 0; + + zpadlen = max - place; + spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); + if (zpadlen < 0) zpadlen = 0; + if (spadlen < 0) spadlen = 0; + if (flags & DP_F_ZERO) + { + zpadlen = MAX(zpadlen, spadlen); + spadlen = 0; + } + if (flags & DP_F_MINUS) + spadlen = -spadlen; /* Left Justifty */ + +#ifdef DEBUG_SNPRINTF + dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n", + zpadlen, spadlen, min, max, place)); +#endif + + /* Spaces */ + while (spadlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + --spadlen; + } + + /* Sign */ + if (signvalue) + total += dopr_outch (buffer, currlen, maxlen, signvalue); + + /* Zeros */ + if (zpadlen > 0) + { + while (zpadlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '0'); + --zpadlen; + } + } + + /* Digits */ + while (place > 0) + total += dopr_outch (buffer, currlen, maxlen, convert[--place]); + + /* Left Justified spaces */ + while (spadlen < 0) { + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++spadlen; + } + + return total; +} + +static LDOUBLE abs_val (LDOUBLE value) +{ + LDOUBLE result = value; + + if (value < 0) + result = -value; + + return result; +} + +static LDOUBLE pow10 (int exp) +{ + LDOUBLE result = 1; + + while (exp) + { + result *= 10; + exp--; + } + + return result; +} + +static long round (LDOUBLE value) +{ + long intpart; + + intpart = value; + value = value - intpart; + if (value >= 0.5) + intpart++; + + return intpart; +} + +static int fmtfp (char *buffer, size_t *currlen, size_t maxlen, + LDOUBLE fvalue, int min, int max, int flags) +{ + int signvalue = 0; + LDOUBLE ufvalue; + char iconvert[20]; + char fconvert[20]; + int iplace = 0; + int fplace = 0; + int padlen = 0; /* amount to pad */ + int zpadlen = 0; + int caps = 0; + int total = 0; + long intpart; + long fracpart; + + /* + * AIX manpage says the default is 0, but Solaris says the default + * is 6, and sprintf on AIX defaults to 6 + */ + if (max < 0) + max = 6; + + ufvalue = abs_val (fvalue); + + if (fvalue < 0) + signvalue = '-'; + else + if (flags & DP_F_PLUS) /* Do a sign (+/i) */ + signvalue = '+'; + else + if (flags & DP_F_SPACE) + signvalue = ' '; + +#if 0 + if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ +#endif + + intpart = ufvalue; + + /* + * Sorry, we only support 9 digits past the decimal because of our + * conversion method + */ + if (max > 9) + max = 9; + + /* We "cheat" by converting the fractional part to integer by + * multiplying by a factor of 10 + */ + fracpart = round ((pow10 (max)) * (ufvalue - intpart)); + + if (fracpart >= pow10 (max)) + { + intpart++; + fracpart -= pow10 (max); + } + +#ifdef DEBUG_SNPRINTF + dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart)); +#endif + + /* Convert integer part */ + do { + iconvert[iplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10]; + intpart = (intpart / 10); + } while(intpart && (iplace < 20)); + if (iplace == 20) iplace--; + iconvert[iplace] = 0; + + /* Convert fractional part */ + do { + fconvert[fplace++] = + (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10]; + fracpart = (fracpart / 10); + } while(fracpart && (fplace < 20)); + if (fplace == 20) fplace--; + fconvert[fplace] = 0; + + /* -1 for decimal point, another -1 if we are printing a sign */ + padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); + zpadlen = max - fplace; + if (zpadlen < 0) + zpadlen = 0; + if (padlen < 0) + padlen = 0; + if (flags & DP_F_MINUS) + padlen = -padlen; /* Left Justifty */ + + if ((flags & DP_F_ZERO) && (padlen > 0)) + { + if (signvalue) + { + total += dopr_outch (buffer, currlen, maxlen, signvalue); + --padlen; + signvalue = 0; + } + while (padlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '0'); + --padlen; + } + } + while (padlen > 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + --padlen; + } + if (signvalue) + total += dopr_outch (buffer, currlen, maxlen, signvalue); + + while (iplace > 0) + total += dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]); + + /* + * Decimal point. This should probably use locale to find the correct + * char to print out. + */ + if (max > 0) + { + total += dopr_outch (buffer, currlen, maxlen, '.'); + + while (zpadlen-- > 0) + total += dopr_outch (buffer, currlen, maxlen, '0'); + + while (fplace > 0) + total += dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]); + } + + while (padlen < 0) + { + total += dopr_outch (buffer, currlen, maxlen, ' '); + ++padlen; + } + + return total; +} + +static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c) +{ + if (*currlen + 1 < maxlen) + buffer[(*currlen)++] = c; + return 1; +} + +int Q_vsnprintf(char *str, size_t length, const char *fmt, va_list args) +{ + return dopr(str, length, fmt, args); +} + +int Q_snprintf(char *str, size_t length, const char *fmt, ...) +{ + va_list ap; + int retval; + + va_start(ap, fmt); + retval = Q_vsnprintf(str, length, fmt, ap); + va_end(ap); + + return retval; +} + +/* this is really crappy */ +int sscanf( const char *buffer, const char *fmt, ... ) +{ + int cmd; + va_list ap; + int count; + size_t len; + + va_start( ap, fmt ); + count = 0; + + while( *fmt ) + { + if( fmt[ 0 ] != '%' ) + { + fmt++; + continue; + } + + fmt++; + cmd = *fmt; + + if( isdigit( cmd ) ) + { + len = (size_t)_atoi( &fmt ); + cmd = *( fmt - 1 ); + } + else + { + len = MAX_STRING_CHARS - 1; + fmt++; + } + + switch( cmd ) + { + case 'i': + case 'd': + case 'u': + *( va_arg( ap, int * ) ) = _atoi( &buffer ); + break; + case 'f': + *( va_arg( ap, float * ) ) = _atof( &buffer ); + break; + case 'x': + *( va_arg( ap, unsigned int * ) ) = _hextoi( &buffer ); + break; + case 's': + { + char *s = va_arg( ap, char * ); + while( isspace( *buffer ) ) + buffer++; + while( *buffer && !isspace( *buffer) && len-- > 0 ) + *s++ = *buffer++; + *s++ = '\0'; + break; + } + } + } + + va_end( ap ); + return count; +} + +void *bsearch( const void *key, const void *base, size_t nmemb, size_t size, + cmp_t *compar ) +{ + size_t low = 0, high = nmemb, mid; + int comp; + void *ptr; + + while( low < high ) + { + mid = low + (high - low) / 2; + ptr = (void *)((char *)base + ( mid * size )); + comp = compar (key, ptr); + if( comp < 0 ) + high = mid; + else if( comp > 0 ) + low = mid + 1; + else + return ptr; + } + return NULL; +} + +#endif diff --git a/src/game/bg_lib.h b/src/game/bg_lib.h new file mode 100644 index 0000000..3eb6e19 --- /dev/null +++ b/src/game/bg_lib.h @@ -0,0 +1,137 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// bg_lib.h -- standard C library replacement routines used by code +// compiled for the virtual machine + +// This file is NOT included on native builds +#if !defined( BG_LIB_H ) && defined( Q3_VM ) +#define BG_LIB_H + +#ifndef NULL +#define NULL ((void *)0) +#endif + +typedef unsigned int size_t; + +typedef char * va_list; +#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) +#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) +#define va_end(ap) ( ap = (va_list)0 ) + +#define CHAR_BIT 8 /* number of bits in a char */ +#define SCHAR_MAX 0x7f /* maximum signed char value */ +#define SCHAR_MIN (-SCHAR_MAX - 1)/* minimum signed char value */ +#define UCHAR_MAX 0xff /* maximum unsigned char value */ + +#define SHRT_MAX 0x7fff /* maximum (signed) short value */ +#define SHRT_MIN (-SHRT_MAX - 1) /* minimum (signed) short value */ +#define USHRT_MAX 0xffff /* maximum unsigned short value */ +#define INT_MAX 0x7fffffff /* maximum (signed) int value */ +#define INT_MIN (-INT_MAX - 1) /* minimum (signed) int value */ +#define UINT_MAX 0xffffffff /* maximum unsigned int value */ +#define LONG_MAX 0x7fffffffL /* maximum (signed) long value */ +#define LONG_MIN (-LONG_MAX - 1) /* minimum (signed) long value */ +#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed long int32_t; +typedef unsigned long uint32_t; + +#define isalnum(c) (isalpha(c) || isdigit(c)) +#define isalpha(c) (isupper(c) || islower(c)) +#define isascii(c) ((c) > 0 && (c) <= 0x7f) +#define iscntrl(c) (((c) >= 0) && (((c) <= 0x1f) || ((c) == 0x7f))) +#define isdigit(c) ((c) >= '0' && (c) <= '9') +#define isgraph(c) ((c) != ' ' && isprint(c)) +#define islower(c) ((c) >= 'a' && (c) <= 'z') +#define isprint(c) ((c) >= ' ' && (c) <= '~') +#define ispunct(c) (((c) > ' ' && (c) <= '~') && !isalnum(c)) +#define isspace(c) ((c) == ' ' || (c) == '\f' || (c) == '\n' || (c) == '\r' || \ + (c) == '\t' || (c) == '\v') +#define isupper(c) ((c) >= 'A' && (c) <= 'Z') +#define isxdigit(c) (isxupper(c) || isxlower(c)) +#define isxlower(c) (isdigit(c) || (c >= 'a' && c <= 'f')) +#define isxupper(c) (isdigit(c) || (c >= 'A' && c <= 'F')) + +// Misc functions +#define assert( expr )\ + if( !( expr ) )\ + Com_Error( ERR_DROP, "%s:%d: Assertion `%s' failed",\ + __FILE__, __LINE__, #expr ) +typedef int cmp_t( const void *, const void * ); +void qsort( void *a, size_t n, size_t es, cmp_t *cmp ); +#define RAND_MAX 0x7fff +void srand( unsigned seed ); +int rand( void ); +void *bsearch( const void *key, const void *base, size_t nmemb, + size_t size, cmp_t *compar ); + +// String functions +size_t strlen( const char *string ); +char *strcat( char *strDestination, const char *strSource ); +char *strcpy( char *strDestination, const char *strSource ); +int strcmp( const char *string1, const char *string2 ); +char *strchr( const char *string, int c ); +char *strrchr( const char *string, int c ); +char *strstr( const char *string, const char *strCharSet ); +char *strncpy( char *strDest, const char *strSource, size_t count ); +int tolower( int c ); +int toupper( int c ); + +double atof( const char *string ); +double _atof( const char **stringPtr ); +double strtod( const char *nptr, char **endptr ); +int atoi( const char *string ); +int _atoi( const char **stringPtr ); +long strtol( const char *nptr, char **endptr, int base ); + +int Q_vsnprintf( char *buffer, size_t length, const char *fmt, va_list argptr ); +int Q_snprintf( char *buffer, size_t length, const char *fmt, ... ) __attribute__ ((format (printf, 3, 4))); + +int sscanf( const char *buffer, const char *fmt, ... ); + +// Memory functions +void *memmove( void *dest, const void *src, size_t count ); +void *memset( void *dest, int c, size_t count ); +void *memcpy( void *dest, const void *src, size_t count ); + +// Math functions +double ceil( double x ); +double floor( double x ); +double sqrt( double x ); +double sin( double x ); +double cos( double x ); +double atan2( double y, double x ); +double tan( double x ); +int abs( int n ); +double fabs( double x ); +double acos( double x ); +float pow( float x, float y ); +double rint( double v ); + +#endif // BG_LIB_H diff --git a/src/game/bg_local.h b/src/game/bg_local.h new file mode 100644 index 0000000..6a3fd72 --- /dev/null +++ b/src/game/bg_local.h @@ -0,0 +1,88 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// bg_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes + +#define STEPSIZE 18 + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) +#define TIMER_ATTACK 500 //nonsegmented models + +#define OVERCLIP 1.001f + +#define FALLING_THRESHOLD -900.0f //what vertical speed to start falling sound at + + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server +typedef struct +{ + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + qboolean ladder; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +// movement parameters +extern float pm_stopspeed; +extern float pm_duckScale; +extern float pm_swimScale; +extern float pm_wadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_flightfriction; + +extern int c_pmove; + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepEvent( vec3_t from, vec3_t to, vec3_t normal ); +qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive ); +qboolean PM_PredictStepMove( void ); diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c new file mode 100644 index 0000000..5940a57 --- /dev/null +++ b/src/game/bg_misc.c @@ -0,0 +1,4491 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// bg_misc.c -- both games misc functions, all completely stateless + +#include "../qcommon/q_shared.h" +#include "bg_public.h" + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); + +static const buildableAttributes_t bg_buildableList[ ] = +{ + { + BA_A_SPAWN, //int buildNum; + "eggpod", //char *buildName; + "Egg", //char *humanName; + "The most basic alien structure. It allows aliens to spawn " + "and protect the Overmind. Without any of these, the Overmind " + "is left nearly defenseless and defeat is imminent.", + "team_alien_spawn", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + ASPAWN_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ASPAWN_HEALTH, //int health; + ASPAWN_REGEN, //int regenRate; + ASPAWN_SPLASHDAMAGE, //int splashDamage; + ASPAWN_SPLASHRADIUS, //int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + ASPAWN_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.5f, //float minNormal; + qtrue, //qboolean invertNormal; + qfalse, //qboolean creepTest; + ASPAWN_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + ASPAWN_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_A_OVERMIND, //int buildNum; + "overmind", //char *buildName; + "Overmind", //char *humanName; + "A collective consciousness that controls all the alien structures " + "in its vicinity. It must be protected at all costs, since its " + "death will render alien structures defenseless.", + "team_alien_overmind", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + OVERMIND_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + OVERMIND_HEALTH, //int health; + OVERMIND_REGEN, //int regenRate; + OVERMIND_SPLASHDAMAGE, //int splashDamage; + OVERMIND_SPLASHRADIUS, //int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + OVERMIND_ATTACK_REPEAT,//int nextthink; + OVERMIND_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + OVERMIND_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qtrue, //qboolean uniqueTest; + OVERMIND_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_A_BARRICADE, //int buildNum; + "barricade", //char *buildName; + "Barricade", //char *humanName; + "Used to obstruct corridors and doorways, hindering humans from " + "threatening the spawns and Overmind. Barricades will shrink " + "to allow aliens to pass over them, however.", + "team_alien_barricade",//char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + BARRICADE_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + BARRICADE_HEALTH, //int health; + BARRICADE_REGEN, //int regenRate; + BARRICADE_SPLASHDAMAGE,//int splashDamage; + BARRICADE_SPLASHRADIUS,//int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + BARRICADE_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.707f, //float minNormal; + qfalse, //qboolean invertNormal; + qtrue, //qboolean creepTest; + BARRICADE_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + BARRICADE_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_A_ACIDTUBE, //int buildNum; + "acid_tube", //char *buildName; + "Acid Tube", //char *humanName; + "Ejects lethal poisonous acid at an approaching human. These " + "are highly effective when used in conjunction with a trapper " + "to hold the victim in place.", + "team_alien_acid_tube",//char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + ACIDTUBE_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ACIDTUBE_HEALTH, //int health; + ACIDTUBE_REGEN, //int regenRate; + ACIDTUBE_SPLASHDAMAGE, //int splashDamage; + ACIDTUBE_SPLASHRADIUS, //int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 200, //int nextthink; + ACIDTUBE_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.0f, //float minNormal; + qtrue, //qboolean invertNormal; + qtrue, //qboolean creepTest; + ACIDTUBE_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + ACIDTUBE_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_A_TRAPPER, //int buildNum; + "trapper", //char *buildName; + "Trapper", //char *humanName; + "Fires a blob of adhesive spit at any non-alien in its line of " + "sight. This hinders their movement, making them an easy target " + "for other defensive structures or aliens.", + "team_alien_trapper", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + TRAPPER_BP, //int buildPoints; + ( 1 << S2 )|( 1 << S3 ), //int stages //NEEDS ADV BUILDER SO S2 AND UP + TRAPPER_HEALTH, //int health; + TRAPPER_REGEN, //int regenRate; + TRAPPER_SPLASHDAMAGE, //int splashDamage; + TRAPPER_SPLASHRADIUS, //int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + TRAPPER_BT, //int buildTime; + qfalse, //qboolean usable; + TRAPPER_RANGE, //int turretRange; + TRAPPER_REPEAT, //int turretFireSpeed; + WP_LOCKBLOB_LAUNCHER, //weapon_t turretProjType; + 0.0f, //float minNormal; + qtrue, //qboolean invertNormal; + qtrue, //qboolean creepTest; + TRAPPER_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qtrue, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + TRAPPER_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_A_BOOSTER, //int buildNum; + "booster", //char *buildName; + "Booster", //char *humanName; + "Laces the attacks of any alien that touches it with a poison " + "that will gradually deal damage to any humans exposed to it. " + "The booster also increases the rate of health regeneration for " + "any nearby aliens.", + "team_alien_booster", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + BOOSTER_BP, //int buildPoints; + ( 1 << S2 )|( 1 << S3 ), //int stages + BOOSTER_HEALTH, //int health; + BOOSTER_REGEN, //int regenRate; + BOOSTER_SPLASHDAMAGE, //int splashDamage; + BOOSTER_SPLASHRADIUS, //int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + BOOSTER_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.707f, //float minNormal; + qfalse, //qboolean invertNormal; + qtrue, //qboolean creepTest; + BOOSTER_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qtrue, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + BOOSTER_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_A_HIVE, //int buildNum; + "hive", //char *buildName; + "Hive", //char *humanName; + "Houses millions of tiny insectoid aliens. When a human " + "approaches this structure, the insectoids attack.", + "team_alien_hive", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + HIVE_BP, //int buildPoints; + ( 1 << S3 ), //int stages + HIVE_HEALTH, //int health; + HIVE_REGEN, //int regenRate; + HIVE_SPLASHDAMAGE, //int splashDamage; + HIVE_SPLASHRADIUS, //int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 500, //int nextthink; + HIVE_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_HIVE, //weapon_t turretProjType; + 0.0f, //float minNormal; + qtrue, //qboolean invertNormal; + qtrue, //qboolean creepTest; + HIVE_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + HIVE_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_SPAWN, //int buildNum; + "telenode", //char *buildName; + "Telenode", //char *humanName; + "The most basic human structure. It provides a means for humans " + "to enter the battle arena. Without any of these the humans " + "cannot spawn and defeat is imminent.", + "team_human_spawn", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + HSPAWN_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + HSPAWN_HEALTH, //int health; + 0, //int regenRate; + HSPAWN_SPLASHDAMAGE, //int splashDamage; + HSPAWN_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + HSPAWN_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qtrue, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + HSPAWN_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_MGTURRET, //int buildNum; + "mgturret", //char *buildName; + "Machinegun Turret", //char *humanName; + "Automated base defense that is effective against large targets " + "but slow to begin firing. Should always be " + "backed up by physical support.", + "team_human_mgturret", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + MGTURRET_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + MGTURRET_HEALTH, //int health; + 0, //int regenRate; + MGTURRET_SPLASHDAMAGE, //int splashDamage; + MGTURRET_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 50, //int nextthink; + MGTURRET_BT, //int buildTime; + qfalse, //qboolean usable; + MGTURRET_RANGE, //int turretRange; + MGTURRET_REPEAT, //int turretFireSpeed; + WP_MGTURRET, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qtrue, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + MGTURRET_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_TESLAGEN, //int buildNum; + "tesla", //char *buildName; + "Tesla Generator", //char *humanName; + "A structure equipped with a strong electrical attack that fires " + "instantly and always hits its target. It is effective against smaller " + "aliens and for consolidating basic defense.", + "team_human_tesla", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + TESLAGEN_BP, //int buildPoints; + ( 1 << S3 ), //int stages + TESLAGEN_HEALTH, //int health; + 0, //int regenRate; + TESLAGEN_SPLASHDAMAGE, //int splashDamage; + TESLAGEN_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 150, //int nextthink; + TESLAGEN_BT, //int buildTime; + qfalse, //qboolean usable; + TESLAGEN_RANGE, //int turretRange; + TESLAGEN_REPEAT, //int turretFireSpeed; + WP_TESLAGEN, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qtrue, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + TESLAGEN_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_ARMOURY, //int buildNum; + "arm", //char *buildName; + "Armoury", //char *humanName; + "An essential part of the human base, providing a means " + "to upgrade the basic human equipment. A range of upgrades " + "and weapons are available for sale from the armoury.", + "team_human_armoury", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + ARMOURY_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ARMOURY_HEALTH, //int health; + 0, //int regenRate; + ARMOURY_SPLASHDAMAGE, //int splashDamage; + ARMOURY_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + ARMOURY_BT, //int buildTime; + qtrue, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + ARMOURY_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_DCC, //int buildNum; + "dcc", //char *buildName; + "Defence Computer", //char *humanName; + "A structure that enables self-repair functionality in " + "human structures. Each Defence Computer built increases " + "repair rate slightly.", + "team_human_dcc", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + DC_BP, //int buildPoints; + ( 1 << S2 )|( 1 << S3 ), //int stages + DC_HEALTH, //int health; + 0, //int regenRate; + DC_SPLASHDAMAGE, //int splashDamage; + DC_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + DC_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + DC_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_MEDISTAT, //int buildNum; + "medistat", //char *buildName; + "Medistation", //char *humanName; + "A structure that automatically restores " + "the health and stamina of any human that stands on it. " + "It may only be used by one person at a time. This structure " + "also issues medkits.", + "team_human_medistat", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + MEDISTAT_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + MEDISTAT_HEALTH, //int health; + 0, //int regenRate; + MEDISTAT_SPLASHDAMAGE, //int splashDamage; + MEDISTAT_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + MEDISTAT_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qtrue, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + MEDISTAT_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_REACTOR, //int buildNum; + "reactor", //char *buildName; + "Reactor", //char *humanName; + "All structures except the telenode rely on a reactor to operate. " + "The reactor provides power for all the human structures either " + "directly or via repeaters. Only one reactor can be built at a time.", + "team_human_reactor", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + REACTOR_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + REACTOR_HEALTH, //int health; + 0, //int regenRate; + REACTOR_SPLASHDAMAGE, //int splashDamage; + REACTOR_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + REACTOR_ATTACK_DCC_REPEAT, //int nextthink; + REACTOR_BT, //int buildTime; + qtrue, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qtrue, //qboolean uniqueTest; + REACTOR_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_REPEATER, //int buildNum; + "repeater", //char *buildName; + "Repeater", //char *humanName; + "A power distributor that transmits power from the reactor " + "to remote locations, so that bases may be built far " + "from the reactor.", + "team_human_repeater", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + REPEATER_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + REPEATER_HEALTH, //int health; + 0, //int regenRate; + REPEATER_SPLASHDAMAGE, //int splashDamage; + REPEATER_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + REPEATER_BT, //int buildTime; + qtrue, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + REPEATER_VALUE, //int value; + qfalse //qboolean cuboid; + }, + { + BA_H_CUBOID1, //int buildNum; + "concrete", //char *buildName; + "Concrete", //char *humanName; + "A strong and solid cuboid used either as a " + "protection from alien units or as a bearer for " + "greater structures.", + "team_human_hcuboid1", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + 0, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 0, //int health; + 0, //int regenRate; + 0, //int splashDamage; + 0, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + 0, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 1.00f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + 0, //int value; + qtrue //qboolean cuboid; + }, + { + BA_H_CUBOID2, //int buildNum; + "glass", //char *buildName; + "Glass", //char *humanName; + "A cuboid made of a transparent chemical compound. " + "Its durability is low compared to other glass-like " + "materials. However it has shown increased strenght " + "when formed into a thin pane shape.", + "team_human_hcuboid2", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + 0, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 0, //int health; + 0, //int regenRate; + 0, //int splashDamage; + 0, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + 0, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 1.00f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + 0, //int value; + qtrue //qboolean cuboid; + }, + { + BA_H_CUBOID3, //int buildNum; + "ladder", //char *buildName; + "Ladder", //char *humanName; + "A cuboid made of a durable metal alloy. " + "Purpose-built hollows allow humans to easily " + "climb on its surface thus making it a good " + "substitute for unreliable standard ladders.", + "team_human_hcuboid3", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + 0, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 0, //int health; + 0, //int regenRate; + 0, //int splashDamage; + 0, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + 0, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 1.00f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + 0, //int value; + qtrue //qboolean cuboid; + }, + { + BA_A_CUBOID1, //int buildNum; + "cells", //char *buildName; + "Cells", //char *humanName; + "A solid wall made out of alien cells. " + "Used primarly for protecting bases " + "and outposts.", + "team_alien_acuboid1", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + 0, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 0, //int health; + 0, //int regenRate; + 0, //int splashDamage; + 0, //int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + 0, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.00f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + 0, //int value; + qtrue //qboolean cuboid; + }, + { + BA_A_CUBOID2, //int buildNum; + "slime", //char *buildName; + "Slime", //char *humanName; + "A rigid chunk of the finest quality " + "mixture of various types of acids and " + "slippery chemical compunds. Dangerous " + "to human units but completely safe for " + "aliens.", + "team_alien_acuboid2", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + 0, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 0, //int health; + 0, //int regenRate; + 0, //int splashDamage; + 0, //int splashRadius; + MOD_ASPAWN, //int meansOfDeath; + TEAM_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + 0, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.00f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + 0, //int value; + qtrue //qboolean cuboid; + } +}; + +int bg_numBuildables = sizeof( bg_buildableList ) / sizeof( bg_buildableList[ 0 ] ); + +static const buildableAttributes_t nullBuildable = { 0 }; + +/* +============== +BG_BuildableByName +============== +*/ +const buildableAttributes_t *BG_BuildableByName( const char *name ) +{ + int i; + + for( i = 0; i < bg_numBuildables; i++ ) + { + if( !Q_stricmp( bg_buildableList[ i ].name, name ) ) + return &bg_buildableList[ i ]; + } + + return &nullBuildable; +} + +/* +============== +BG_BuildableByEntityName +============== +*/ +const buildableAttributes_t *BG_BuildableByEntityName( const char *name ) +{ + int i; + + for( i = 0; i < bg_numBuildables; i++ ) + { + if( !Q_stricmp( bg_buildableList[ i ].entityName, name ) ) + return &bg_buildableList[ i ]; + } + + return &nullBuildable; +} + +/* +============== +BG_Buildable +============== +*/ + +const buildableAttributes_t *BG_Buildable( buildable_t buildable, const vec3_t cuboidSize ) +{ + const buildableAttributes_t *attr; + static buildableAttributes_t cuboidAttr; + const cuboidAttributes_t *cuboid; + float volume; + vec3_t sorted; + + if(buildable>BA_NONE&&buildablecuboid) + { + volume=cuboidSize[0]*cuboidSize[1]*cuboidSize[2]*5.5306341e-5; // NOTE: cubic quake units -> cubic meters ( 1qu = 0.0381m ) + memcpy(&cuboidAttr,attr,sizeof(buildableAttributes_t)); + cuboid=BG_CuboidAttributes(buildable); + + switch(cuboid->hpt) + { + case CBHPT_PLAIN: + cuboidAttr.health=ceil(volume*cuboid->hppv); + break; + case CBHPT_CUBES: + BG_CuboidSortSize(cuboidSize,sorted); + cuboidAttr.health=sqrt(sorted[2]*sorted[0])*sqrt(sorted[1]*sorted[0])*(float)sorted[0]*5.5306341e-5*cuboid->hppv; + break; + case CBHPT_PANES: + BG_CuboidSortSize(cuboidSize,sorted); + cuboidAttr.health=(float)sorted[2]*(float)sorted[1]*((float)sorted[0]+(float)sorted[2])/2.0f*5.5306341e-5*cuboid->hppv; + break; + } + + if(cuboidAttr.health<1) + cuboidAttr.health=1; + cuboidAttr.splashDamage=0; + cuboidAttr.splashRadius=0; + if(cuboid->regen) + cuboidAttr.regenRate=cuboid->regenrate; + cuboidAttr.buildTime=MAX((float)cuboidAttr.health/(float)cuboid->buildrate*1e3,(float)cuboid->minbt); + cuboidAttr.buildPoints=MAX(ceil((float)cuboidAttr.health/cuboid->hppv*cuboid->bppv),1); + return &cuboidAttr; + } + return attr; +} + +/* +============== +BG_BuildableAllowedInStage +============== +*/ +qboolean BG_BuildableAllowedInStage( buildable_t buildable, + stage_t stage ) +{ + int stages = BG_Buildable( buildable, NULL )->stages; + + if( stages & ( 1 << stage ) ) + return qtrue; + else + return qfalse; +} + +static buildableConfig_t bg_buildableConfigList[ BA_NUM_BUILDABLES ]; + +/* +============== +BG_BuildableConfig +============== +*/ +buildableConfig_t *BG_BuildableConfig( buildable_t buildable ) +{ + return &bg_buildableConfigList[ buildable ]; +} + +/* +============== +BG_BuildableBoundingBox +============== +*/ +void BG_BuildableBoundingBox( buildable_t buildable, + vec3_t mins, vec3_t maxs ) +{ + buildableConfig_t *buildableConfig = BG_BuildableConfig( buildable ); + + if( mins != NULL ) + VectorCopy( buildableConfig->mins, mins ); + + if( maxs != NULL ) + VectorCopy( buildableConfig->maxs, maxs ); +} + +/* +====================== +BG_ParseBuildableFile + +Parses a configuration file describing a buildable +====================== +*/ +static qboolean BG_ParseBuildableFile( const char *filename, buildableConfig_t *bc ) +{ + char *text_p; + int i; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + float scale; + int defined = 0; + enum + { + MODEL = 1 << 0, + MODELSCALE = 1 << 1, + MINS = 1 << 2, + MAXS = 1 << 3, + ZOFFSET = 1 << 4 + }; + + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + { + Com_Printf( S_COLOR_RED "ERROR: Buildable file %s doesn't exist\n", filename ); + return qfalse; + } + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + Com_Printf( S_COLOR_RED "ERROR: Buildable file %s is %s\n", filename, + len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "model" ) ) + { + int index = 0; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + index = atoi( token ); + + if( index < 0 ) + index = 0; + else if( index > 3 ) + index = 3; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( bc->models[ index ], token, sizeof( bc->models[ 0 ] ) ); + + defined |= MODEL; + continue; + } + else if( !Q_stricmp( token, "modelScale" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + scale = atof( token ); + + if( scale < 0.0f ) + scale = 0.0f; + + bc->modelScale = scale; + + defined |= MODELSCALE; + continue; + } + else if( !Q_stricmp( token, "mins" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + bc->mins[ i ] = atof( token ); + } + + defined |= MINS; + continue; + } + else if( !Q_stricmp( token, "maxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + bc->maxs[ i ] = atof( token ); + } + + defined |= MAXS; + continue; + } + else if( !Q_stricmp( token, "zOffset" ) ) + { + float offset; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + offset = atof( token ); + + bc->zOffset = offset; + + defined |= ZOFFSET; + continue; + } + + + Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); + return qfalse; + } + + if( !( defined & MODEL ) ) token = "model"; + else if( !( defined & MODELSCALE ) ) token = "modelScale"; + else if( !( defined & MINS ) ) token = "mins"; + else if( !( defined & MAXS ) ) token = "maxs"; + else if( !( defined & ZOFFSET ) ) token = "zOffset"; + else token = ""; + + if( strlen( token ) > 0 ) + { + Com_Printf( S_COLOR_RED "ERROR: %s not defined in %s\n", + token, filename ); + return qfalse; + } + + return qtrue; +} + +/* +=============== +BG_InitBuildableConfigs +=============== +*/ +void BG_InitBuildableConfigs( void ) +{ + int i; + buildableConfig_t *bc; + + for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) + { + bc = BG_BuildableConfig( i ); + Com_Memset( bc, 0, sizeof( buildableConfig_t ) ); + + if(BG_Buildable(i,NULL)->cuboid) + continue; + + BG_ParseBuildableFile( va( "configs/buildables/%s.cfg", + BG_Buildable( i, NULL )->name ), bc ); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +static const classAttributes_t bg_classList[ ] = +{ + { + PCL_NONE, //int classnum; + "spectator", //char *className; + "", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 0, //int health; + 0.0f, //float fallDamage; + 0.0f, //float regenRate; + 0, //int abilities; + WP_NONE, //weapon_t startWeapon + 0.0f, //float buildDist; + 90, //int fov; + 0.000f, //float bob; + 1.0f, //float bobCycle; + 0, //int steptime; + 600, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + 0, //int cost; + 0 //int value; + }, + { + PCL_ALIEN_BUILDER0, //int classnum; + "builder", //char *className; + "Responsible for building and maintaining all the alien structures. " + "Has a weak melee slash attack.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ABUILDER_HEALTH, //int health; + 0.2f, //float fallDamage; + ABUILDER_REGEN, //float regenRate; + SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_ALIENSENSE,//int abilities; + WP_ABUILD, //weapon_t startWeapon + 95.0f, //float buildDist; + 110, //int fov; + 0.001f, //float bob; + 2.0f, //float bobCycle; + 150, //int steptime; + ABUILDER_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 195.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_ALIEN_BUILDER0_UPG, PCL_ALIEN_LEVEL0, PCL_NONE }, //int children[ 3 ]; + ABUILDER_COST, //int cost; + ABUILDER_VALUE //int value; + }, + { + PCL_ALIEN_BUILDER0_UPG, //int classnum; + "builderupg", //char *classname; + "Similar to the base Granger, except that in addition to " + "being able to build structures it has a spit attack " + "that slows victims and the ability to crawl on walls.", + ( 1 << S2 )|( 1 << S3 ), //int stages + ABUILDER_UPG_HEALTH, //int health; + 0.2f, //float fallDamage; + ABUILDER_UPG_REGEN, //float regenRate; + SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; + WP_ABUILD2, //weapon_t startWeapon + 105.0f, //float buildDist; + 110, //int fov; + 0.001f, //float bob; + 2.0f, //float bobCycle; + 100, //int steptime; + ABUILDER_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_ALIEN_LEVEL0, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + ABUILDER_UPG_COST, //int cost; + ABUILDER_UPG_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL0, //int classnum; + "level0", //char *classname; + "Has a lethal reflexive bite and the ability to crawl on " + "walls and ceilings.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + LEVEL0_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL0_REGEN, //float regenRate; + SCA_WALLCLIMBER|SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL0, //weapon_t startWeapon + 0.0f, //float buildDist; + 140, //int fov; + 0.0f, //float bob; + 2.5f, //float bobCycle; + 25, //int steptime; + LEVEL0_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 400.0f, //float stopSpeed; + 250.0f, //float jumpMagnitude; + 2.0f, //float knockbackScale; + { PCL_ALIEN_LEVEL1, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL0_COST, //int cost; + LEVEL0_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL1, //int classnum; + "level1", //char *classname; + "A support class able to crawl on walls and ceilings. Its melee " + "attack is most effective when combined with the ability to grab " + "and hold its victims in place. Provides a weak healing aura " + "that accelerates the healing rate of nearby aliens.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + LEVEL1_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL1_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL1, //weapon_t startWeapon + 0.0f, //float buildDist; + 120, //int fov; + 0.001f, //float bob; + 1.8f, //float bobCycle; + 60, //int steptime; + LEVEL1_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 300.0f, //float stopSpeed; + 310.0f, //float jumpMagnitude; + 1.2f, //float knockbackScale; + { PCL_ALIEN_LEVEL2, PCL_ALIEN_LEVEL1_UPG, PCL_NONE }, //int children[ 3 ]; + LEVEL1_COST, //int cost; + LEVEL1_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL1_UPG, //int classnum; + "level1upg", //char *classname; + "In addition to the basic Basilisk abilities, the Advanced " + "Basilisk sprays a poisonous gas which disorients any " + "nearby humans. Has a strong healing aura that " + "that accelerates the healing rate of nearby aliens.", + ( 1 << S2 )|( 1 << S3 ), //int stages + LEVEL1_UPG_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL1_UPG_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL1_UPG, //weapon_t startWeapon + 0.0f, //float buildDist; + 120, //int fov; + 0.001f, //float bob; + 1.8f, //float bobCycle; + 60, //int steptime; + LEVEL1_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 300.0f, //float stopSpeed; + 310.0f, //float jumpMagnitude; + 1.1f, //float knockbackScale; + { PCL_ALIEN_LEVEL2, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL1_UPG_COST, //int cost; + LEVEL1_UPG_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL2, //int classnum; + "level2", //char *classname; + "Has a melee attack and the ability to jump off walls. This " + "allows the Marauder to gather great speed in enclosed areas.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + LEVEL2_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL2_REGEN, //float regenRate; + SCA_WALLJUMPER|SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL2, //weapon_t startWeapon + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.5f, //float bobCycle; + 80, //int steptime; + LEVEL2_SPEED, //float speed; + 10.0f, //float acceleration; + 3.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 380.0f, //float jumpMagnitude; + 0.8f, //float knockbackScale; + { PCL_ALIEN_LEVEL3, PCL_ALIEN_LEVEL2_UPG, PCL_NONE }, //int children[ 3 ]; + LEVEL2_COST, //int cost; + LEVEL2_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL2_UPG, //int classnum; + "level2upg", //char *classname; + "The Advanced Marauder has all the abilities of the basic Marauder " + "with the addition of an area effect electric shock attack.", + ( 1 << S2 )|( 1 << S3 ), //int stages + LEVEL2_UPG_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL2_UPG_REGEN, //float regenRate; + SCA_WALLJUMPER|SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL2_UPG, //weapon_t startWeapon + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.5f, //float bobCycle; + 80, //int steptime; + LEVEL2_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 3.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 380.0f, //float jumpMagnitude; + 0.7f, //float knockbackScale; + { PCL_ALIEN_LEVEL3, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL2_UPG_COST, //int cost; + LEVEL2_UPG_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL3, //int classnum; + "level3", //char *classname; + "Possesses a melee attack and the pounce ability, which may " + "be used as both an attack and a means to reach remote " + "locations inaccessible from the ground.", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + LEVEL3_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL3_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL3, //weapon_t startWeapon + 0.0f, //float buildDist; + 110, //int fov; + 0.0005f, //float bob; + 1.3f, //float bobCycle; + 90, //int steptime; + LEVEL3_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 200.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 0.5f, //float knockbackScale; + { PCL_ALIEN_LEVEL4, PCL_ALIEN_LEVEL3_UPG, PCL_NONE }, //int children[ 3 ]; + LEVEL3_COST, //int cost; + LEVEL3_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL3_UPG, //int classnum; + "level3upg", //char *classname; + "In addition to the basic Dragoon abilities, the Advanced " + "Dragoon has 3 barbs which may be used to attack humans " + "from a distance.", + ( 1 << S2 )|( 1 << S3 ), //int stages + LEVEL3_UPG_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL3_UPG_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL3_UPG, //weapon_t startWeapon + 0.0f, //float buildDist; + 110, //int fov; + 0.0005f, //float bob; + 1.3f, //float bobCycle; + 90, //int steptime; + LEVEL3_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 200.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 0.4f, //float knockbackScale; + { PCL_ALIEN_LEVEL4, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL3_UPG_COST, //int cost; + LEVEL3_UPG_VALUE //int value; + }, + { + PCL_ALIEN_LEVEL4, //int classnum; + "level4", //char *classname; + "A large alien with a strong melee attack, this class can " + "also charge at enemy humans and structures, inflicting " + "great damage. Any humans or their structures caught under " + "a falling Tyrant will be crushed by its weight.", + ( 1 << S3 ), //int stages + LEVEL4_HEALTH, //int health; + 0.0f, //float fallDamage; + LEVEL4_REGEN, //float regenRate; + SCA_FOVWARPS|SCA_ALIENSENSE, //int abilities; + WP_ALEVEL4, //weapon_t startWeapon + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.1f, //float bobCycle; + 100, //int steptime; + LEVEL4_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 170.0f, //float jumpMagnitude; + 0.1f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL4_COST, //int cost; + LEVEL4_VALUE //int value; + }, + { + PCL_HUMAN, //int classnum; + "human_base", //char *classname; + "", + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 100, //int health; + 1.0f, //float fallDamage; + 0.0f, //float regenRate; + SCA_TAKESFALLDAMAGE|SCA_CANUSELADDERS, //int abilities; + WP_NONE, //special-cased in g_client.c //weapon_t startWeapon + 110.0f, //float buildDist; + 90, //int fov; + 0.002f, //float bob; + 1.0f, //float bobCycle; + 100, //int steptime; + 1.0f, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 220.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + 0, //int cost; + ALIEN_CREDITS_PER_KILL //int value; + }, + { + PCL_HUMAN_BSUIT, //int classnum; + "human_bsuit", //char *classname; + "", + ( 1 << S3 ), //int stages + 100, //int health; + 1.0f, //float fallDamage; + 0.0f, //float regenRate; + SCA_TAKESFALLDAMAGE| + SCA_CANUSELADDERS, //int abilities; + WP_NONE, //special-cased in g_client.c //weapon_t startWeapon + 110.0f, //float buildDist; + 90, //int fov; + 0.002f, //float bob; + 1.0f, //float bobCycle; + 100, //int steptime; + 1.0f, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 220.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + 0, //int cost; + ALIEN_CREDITS_PER_KILL //int value; + } +}; + +int bg_numClasses = sizeof( bg_classList ) / sizeof( bg_classList[ 0 ] ); + +static const classAttributes_t nullClass = { 0 }; + +/* +============== +BG_ClassByName +============== +*/ +const classAttributes_t *BG_ClassByName( const char *name ) +{ + int i; + + for( i = 0; i < bg_numClasses; i++ ) + { + if( !Q_stricmp( bg_classList[ i ].name, name ) ) + return &bg_classList[ i ]; + } + + return &nullClass; +} + +/* +============== +BG_Class +============== +*/ +const classAttributes_t *BG_Class( class_t class ) +{ + return ( class >= PCL_NONE && class < PCL_NUM_CLASSES ) ? + &bg_classList[ class ] : &nullClass; +} + +/* +============== +BG_ClassAllowedInStage +============== +*/ +qboolean BG_ClassAllowedInStage( class_t class, + stage_t stage ) +{ + int stages = BG_Class( class )->stages; + + return stages & ( 1 << stage ); +} + +static classConfig_t bg_classConfigList[ PCL_NUM_CLASSES ]; + +/* +============== +BG_ClassConfig +============== +*/ +classConfig_t *BG_ClassConfig( class_t class ) +{ + return &bg_classConfigList[ class ]; +} + +/* +============== +BG_ClassBoundingBox +============== +*/ +void BG_ClassBoundingBox( class_t class, + vec3_t mins, vec3_t maxs, + vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs ) +{ + classConfig_t *classConfig = BG_ClassConfig( class ); + + if( mins != NULL ) + VectorCopy( classConfig->mins, mins ); + + if( maxs != NULL ) + VectorCopy( classConfig->maxs, maxs ); + + if( cmaxs != NULL ) + VectorCopy( classConfig->crouchMaxs, cmaxs ); + + if( dmins != NULL ) + VectorCopy( classConfig->deadMins, dmins ); + + if( dmaxs != NULL ) + VectorCopy( classConfig->deadMaxs, dmaxs ); +} + +/* +============== +BG_ClassHasAbility +============== +*/ +qboolean BG_ClassHasAbility( class_t class, int ability ) +{ + int abilities = BG_Class( class )->abilities; + + return abilities & ability; +} + +/* +============== +BG_ClassCanEvolveFromTo +============== +*/ +int BG_ClassCanEvolveFromTo( class_t fclass, + class_t tclass, + int credits, int stage, + int cost ) +{ + int i, j, best, value; + + if( credits < cost || fclass == PCL_NONE || tclass == PCL_NONE || + fclass == tclass ) + return -1; + + for( i = 0; i < bg_numClasses; i++ ) + { + if( bg_classList[ i ].number != fclass ) + continue; + + best = credits + 1; + for( j = 0; j < 3; j++ ) + { + int thruClass, evolveCost; + + thruClass = bg_classList[ i ].children[ j ]; + if( thruClass == PCL_NONE || !BG_ClassAllowedInStage( thruClass, stage ) || + !BG_ClassIsAllowed( thruClass ) ) + continue; + + evolveCost = BG_Class( thruClass )->cost * ALIEN_CREDITS_PER_KILL; + if( thruClass == tclass ) + value = cost + evolveCost; + else + value = BG_ClassCanEvolveFromTo( thruClass, tclass, credits, stage, + cost + evolveCost ); + + if( value >= 0 && value < best ) + best = value; + } + + return best <= credits ? best : -1; + } + + Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_ClassCanEvolveFromTo\n" ); + return -1; +} + +/* +============== +BG_AlienCanEvolve +============== +*/ +qboolean BG_AlienCanEvolve( class_t class, int credits, int stage ) +{ + int i, j, tclass; + + for( i = 0; i < bg_numClasses; i++ ) + { + if( bg_classList[ i ].number != class ) + continue; + + for( j = 0; j < 3; j++ ) + { + tclass = bg_classList[ i ].children[ j ]; + if( tclass != PCL_NONE && BG_ClassAllowedInStage( tclass, stage ) && + BG_ClassIsAllowed( tclass ) && + credits >= BG_Class( tclass )->cost * ALIEN_CREDITS_PER_KILL ) + return qtrue; + } + + return qfalse; + } + + Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_AlienCanEvolve\n" ); + return qfalse; +} + +/* +====================== +BG_ParseClassFile + +Parses a configuration file describing a class +====================== +*/ +static qboolean BG_ParseClassFile( const char *filename, classConfig_t *cc ) +{ + char *text_p; + int i; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + float scale = 0.0f; + int defined = 0; + enum + { + MODEL = 1 << 0, + SKIN = 1 << 1, + HUD = 1 << 2, + MODELSCALE = 1 << 3, + SHADOWSCALE = 1 << 4, + MINS = 1 << 5, + MAXS = 1 << 6, + DEADMINS = 1 << 7, + DEADMAXS = 1 << 8, + CROUCHMAXS = 1 << 9, + VIEWHEIGHT = 1 << 10, + CVIEWHEIGHT = 1 << 11, + ZOFFSET = 1 << 12, + NAME = 1 << 13, + SHOULDEROFFSETS = 1 << 14 + }; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + Com_Printf( S_COLOR_RED "ERROR: Class file %s is %s\n", filename, + len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "model" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cc->modelName, token, sizeof( cc->modelName ) ); + + defined |= MODEL; + continue; + } + else if( !Q_stricmp( token, "skin" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cc->skinName, token, sizeof( cc->skinName ) ); + + defined |= SKIN; + continue; + } + else if( !Q_stricmp( token, "hud" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cc->hudName, token, sizeof( cc->hudName ) ); + + defined |= HUD; + continue; + } + else if( !Q_stricmp( token, "modelScale" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + scale = atof( token ); + + if( scale < 0.0f ) + scale = 0.0f; + + cc->modelScale = scale; + + defined |= MODELSCALE; + continue; + } + else if( !Q_stricmp( token, "shadowScale" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + scale = atof( token ); + + if( scale < 0.0f ) + scale = 0.0f; + + cc->shadowScale = scale; + + defined |= SHADOWSCALE; + continue; + } + else if( !Q_stricmp( token, "mins" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->mins[ i ] = atof( token ); + } + + defined |= MINS; + continue; + } + else if( !Q_stricmp( token, "maxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->maxs[ i ] = atof( token ); + } + + defined |= MAXS; + continue; + } + else if( !Q_stricmp( token, "deadMins" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->deadMins[ i ] = atof( token ); + } + + defined |= DEADMINS; + continue; + } + else if( !Q_stricmp( token, "deadMaxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->deadMaxs[ i ] = atof( token ); + } + + defined |= DEADMAXS; + continue; + } + else if( !Q_stricmp( token, "crouchMaxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->crouchMaxs[ i ] = atof( token ); + } + + defined |= CROUCHMAXS; + continue; + } + else if( !Q_stricmp( token, "viewheight" ) ) + { + token = COM_Parse( &text_p ); + cc->viewheight = atoi( token ); + defined |= VIEWHEIGHT; + continue; + } + else if( !Q_stricmp( token, "crouchViewheight" ) ) + { + token = COM_Parse( &text_p ); + cc->crouchViewheight = atoi( token ); + defined |= CVIEWHEIGHT; + continue; + } + else if( !Q_stricmp( token, "zOffset" ) ) + { + float offset; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + offset = atof( token ); + + cc->zOffset = offset; + + defined |= ZOFFSET; + continue; + } + else if( !Q_stricmp( token, "name" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cc->humanName, token, sizeof( cc->humanName ) ); + + defined |= NAME; + continue; + } + else if( !Q_stricmp( token, "shoulderOffsets" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cc->shoulderOffsets[ i ] = atof( token ); + } + + defined |= SHOULDEROFFSETS; + continue; + } + + Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); + return qfalse; + } + + if( !( defined & MODEL ) ) token = "model"; + else if( !( defined & SKIN ) ) token = "skin"; + else if( !( defined & HUD ) ) token = "hud"; + else if( !( defined & MODELSCALE ) ) token = "modelScale"; + else if( !( defined & SHADOWSCALE ) ) token = "shadowScale"; + else if( !( defined & MINS ) ) token = "mins"; + else if( !( defined & MAXS ) ) token = "maxs"; + else if( !( defined & DEADMINS ) ) token = "deadMins"; + else if( !( defined & DEADMAXS ) ) token = "deadMaxs"; + else if( !( defined & CROUCHMAXS ) ) token = "crouchMaxs"; + else if( !( defined & VIEWHEIGHT ) ) token = "viewheight"; + else if( !( defined & CVIEWHEIGHT ) ) token = "crouchViewheight"; + else if( !( defined & ZOFFSET ) ) token = "zOffset"; + else if( !( defined & NAME ) ) token = "name"; + else if( !( defined & SHOULDEROFFSETS ) ) token = "shoulderOffsets"; + else token = ""; + + if( strlen( token ) > 0 ) + { + Com_Printf( S_COLOR_RED "ERROR: %s not defined in %s\n", + token, filename ); + return qfalse; + } + + return qtrue; +} + +/* +=============== +BG_InitClassConfigs +=============== +*/ +void BG_InitClassConfigs( void ) +{ + int i; + classConfig_t *cc; + + for( i = PCL_NONE; i < PCL_NUM_CLASSES; i++ ) + { + cc = BG_ClassConfig( i ); + + BG_ParseClassFile( va( "configs/classes/%s.cfg", + BG_Class( i )->name ), cc ); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +static const weaponAttributes_t bg_weapons[ ] = +{ + { + WP_ALEVEL0, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "level0", //char *weaponName; + "Bite", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL0_BITE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL0_BITE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_ALEVEL1, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "level1", //char *weaponName; + "Claws", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL1_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL1_CLAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_ALEVEL1_UPG, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "level1upg", //char *weaponName; + "Claws Upgrade", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL1_CLAW_U_REPEAT, //int repeatRate1; + LEVEL1_PCLOUD_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL1_CLAW_U_K_SCALE,//float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_ALEVEL2, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "level2", //char *weaponName; + "Bite", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL2_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL2_CLAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_ALEVEL2_UPG, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "level2upg", //char *weaponName; + "Zap", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL2_CLAW_U_REPEAT, //int repeatRate1; + LEVEL2_AREAZAP_REPEAT,//int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL2_CLAW_U_K_SCALE,//float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_ALEVEL3, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "level3", //char *weaponName; + "Pounce", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL3_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL3_CLAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_ALEVEL3_UPG, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "level3upg", //char *weaponName; + "Pounce (upgrade)", //char *humanName; + "", + 3, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL3_CLAW_U_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + LEVEL3_BOUNCEBALL_REPEAT,//int repeatRate3; + 0, //int reloadTime; + LEVEL3_CLAW_U_K_SCALE,//float knockbackScale; + qfalse, //qboolean hasAltMode; + qtrue, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_ALEVEL4, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "level4", //char *weaponName; + "Charge", //char *humanName; + "", + 1, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL4_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + LEVEL4_CLAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qtrue, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_BLASTER, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 0, //int slots; + "blaster", //char *weaponName; + "Blaster", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + BLASTER_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + BLASTER_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_MACHINEGUN, //int weaponNum; + RIFLE_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "rifle", //char *weaponName; + "Rifle", //char *humanName; + "Basic weapon. Cased projectile weapon, with a slow clip based " + "reload system.", + RIFLE_CLIPSIZE, //int maxAmmo; + RIFLE_MAXCLIPS, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + RIFLE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + RIFLE_RELOAD, //int reloadTime; + RIFLE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_PAIN_SAW, //int weaponNum; + PAINSAW_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "psaw", //char *weaponName; + "Pain Saw", //char *humanName; + "Similar to a chainsaw, but instead of a chain it has an " + "electric arc capable of dealing a great deal of damage at " + "close range.", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + PAINSAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + PAINSAW_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_SHOTGUN, //int weaponNum; + SHOTGUN_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "shotgun", //char *weaponName; + "Shotgun", //char *humanName; + "Close range weapon that is useful against larger foes. " + "It has a slow repeat rate, but can be devastatingly " + "effective.", + SHOTGUN_SHELLS, //int maxAmmo; + SHOTGUN_MAXCLIPS, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + SHOTGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + SHOTGUN_RELOAD, //int reloadTime; + SHOTGUN_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_LAS_GUN, //int weaponNum; + LASGUN_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "lgun", //char *weaponName; + "Las Gun", //char *humanName; + "Slightly more powerful than the basic rifle, rapidly fires " + "small packets of energy.", + LASGUN_AMMO, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + LASGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + LASGUN_RELOAD, //int reloadTime; + LASGUN_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_MASS_DRIVER, //int weaponNum; + MDRIVER_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "mdriver", //char *weaponName; + "Mass Driver", //char *humanName; + "A portable particle accelerator which causes minor nuclear " + "reactions at the point of impact. It has a very large " + "payload, but fires slowly.", + MDRIVER_CLIPSIZE, //int maxAmmo; + MDRIVER_MAXCLIPS, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + MDRIVER_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + MDRIVER_RELOAD, //int reloadTime; + MDRIVER_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qtrue, //qboolean canZoom; + 20.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_CHAINGUN, //int weaponNum; + CHAINGUN_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "chaingun", //char *weaponName; + "Chaingun", //char *humanName; + "Belt drive, cased projectile weapon. It has a high repeat " + "rate but a wide firing angle and is therefore relatively " + "inaccurate.", + CHAINGUN_BULLETS, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + CHAINGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + CHAINGUN_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_FLAMER, //int weaponNum; + FLAMER_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "flamer", //char *weaponName; + "Flame Thrower", //char *humanName; + "Sprays fire at its target. It is powered by compressed " + "gas. The relatively low rate of fire means this weapon is most " + "effective against static targets.", + FLAMER_GAS, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + FLAMER_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + FLAMER_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_PULSE_RIFLE, //int weaponNum; + PRIFLE_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "prifle", //char *weaponName; + "Pulse Rifle", //char *humanName; + "An energy weapon that fires rapid pulses of concentrated energy.", + PRIFLE_CLIPS, //int maxAmmo; + PRIFLE_MAXCLIPS, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + PRIFLE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + PRIFLE_RELOAD, //int reloadTime; + PRIFLE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_LUCIFER_CANNON, //int weaponNum; + LCANNON_PRICE, //int price; + ( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "lcannon", //char *weaponName; + "Lucifer Cannon", //char *humanName; + "Blaster technology scaled up to deliver devastating power. " + "Primary fire must be charged before firing. It has a quick " + "secondary attack that does not require charging.", + LCANNON_AMMO, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + LCANNON_REPEAT, //int repeatRate1; + LCANNON_SECONDARY_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + LCANNON_RELOAD, //int reloadTime; + LCANNON_K_SCALE, //float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qtrue, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_GRENADE, //int weaponNum; + GRENADE_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_NONE, //int slots; + "grenade", //char *weaponName; + "Grenade", //char *humanName; + "", + 1, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + GRENADE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + GRENADE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_LOCKBLOB_LAUNCHER, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "lockblob", //char *weaponName; + "Lock Blob", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 500, //int repeatRate1; + 500, //int repeatRate2; + 500, //int repeatRate3; + 0, //int reloadTime; + LOCKBLOB_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_HIVE, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "hive", //char *weaponName; + "Hive", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 500, //int repeatRate1; + 500, //int repeatRate2; + 500, //int repeatRate3; + 0, //int reloadTime; + HIVE_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_TESLAGEN, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "teslagen", //char *weaponName; + "Tesla Generator", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qtrue, //int usesEnergy; + 500, //int repeatRate1; + 500, //int repeatRate2; + 500, //int repeatRate3; + 0, //int reloadTime; + TESLAGEN_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_MGTURRET, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "mgturret", //char *weaponName; + "Machinegun Turret", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 0, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + MGTURRET_K_SCALE, //float knockbackScale; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; + }, + { + WP_ABUILD, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "abuild", //char *weaponName; + "Alien build weapon", //char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + ABUILDER_BUILD_REPEAT,//int repeatRate1; + ABUILDER_CLAW_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + ABUILDER_CLAW_K_SCALE,//float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_ABUILD2, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "abuildupg", //char *weaponName; + "Alien build weapon2",//char *humanName; + "", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + ABUILDER_BUILD_REPEAT,//int repeatRate1; + ABUILDER_CLAW_REPEAT, //int repeatRate2; + ABUILDER_BLOB_REPEAT, //int repeatRate3; + 0, //int reloadTime; + ABUILDER_CLAW_K_SCALE,//float knockbackScale; + qtrue, //qboolean hasAltMode; + qtrue, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_ALIENS //team_t team; + }, + { + WP_HBUILD, //int weaponNum; + HBUILD_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "ckit", //char *weaponName; + "Construction Kit", //char *humanName; + "Used for building structures. This includes " + "spawns, power and basic defense. More structures become " + "available with new stages.", + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + HBUILD_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + 0.0f, //float knockbackScale; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + qfalse, //qboolean longRanged; + TEAM_HUMANS //team_t team; + } +}; + +int bg_numWeapons = sizeof( bg_weapons ) / sizeof( bg_weapons[ 0 ] ); + +static const weaponAttributes_t nullWeapon = { 0 }; + +/* +============== +BG_WeaponByName +============== +*/ +const weaponAttributes_t *BG_WeaponByName( const char *name ) +{ + int i; + + for( i = 0; i < bg_numWeapons; i++ ) + { + if( !Q_stricmp( bg_weapons[ i ].name, name ) ) + { + return &bg_weapons[ i ]; + } + } + + return &nullWeapon; +} + +/* +============== +BG_Weapon +============== +*/ +const weaponAttributes_t *BG_Weapon( weapon_t weapon ) +{ + return ( weapon > WP_NONE && weapon < WP_NUM_WEAPONS ) ? + &bg_weapons[ weapon - 1 ] : &nullWeapon; +} + +/* +============== +BG_WeaponAllowedInStage +============== +*/ +qboolean BG_WeaponAllowedInStage( weapon_t weapon, stage_t stage ) +{ + int stages = BG_Weapon( weapon )->stages; + + return stages & ( 1 << stage ); +} + +//////////////////////////////////////////////////////////////////////////////// + +static const upgradeAttributes_t bg_upgrades[ ] = +{ + { + UP_LIGHTARMOUR, //int upgradeNum; + LIGHTARMOUR_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_TORSO|SLOT_ARMS|SLOT_LEGS, //int slots; + "larmour", //char *upgradeName; + "Light Armour", //char *humanName; + "Protective armour that helps to defend against light alien melee " + "attacks.", + "icons/iconu_larmour", + qtrue, //qboolean purchasable + qfalse, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_HELMET_MK1, //int upgradeNum; + HELMET_MK1_PRICE, //int price; + ( 1 << S1 ), //int stages + SLOT_HEAD, //int slots; + "helmet_mk1", //char *upgradeName; + "Helmet Mk1", //char *humanName; + "Headgear that protects your head from injuries.", + "icons/iconu_helmet", + qtrue, //qboolean purchasable + qfalse, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_HELMET_MK2, //int upgradeNum; + HELMET_MK2_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ),//int stages + SLOT_HEAD, //int slots; + "helmet_mk2", //char *upgradeName; + "Helmet Mk2", //char *humanName; + "In addition to protecting your head, the helmet provides a " + "scanner indicating the presence of any friendly or hostile " + "lifeforms and structures in your immediate vicinity.", + "icons/iconu_helmet_mk2", + qtrue, //qboolean purchasable + qfalse, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_MEDKIT, //int upgradeNum; + MEDKIT_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_NONE, //int slots; + "medkit", //char *upgradeName; + "Medkit", //char *humanName; + "", + "icons/iconu_atoxin", + qfalse, //qboolean purchasable + qtrue, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_BATTPACK, //int upgradeNum; + BATTPACK_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_BACKPACK, //int slots; + "battpack", //char *upgradeName; + "Battery Pack", //char *humanName; + "Back-mounted battery pack that permits storage of one and a half " + "times the normal energy capacity for energy weapons.", + "icons/iconu_battpack", + qtrue, //qboolean purchasable + qfalse, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_JETPACK, //int upgradeNum; + JETPACK_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_BACKPACK, //int slots; + "jetpack", //char *upgradeName; + "Jet Pack", //char *humanName; + "Back-mounted jet pack that enables the user to fly to remote " + "locations. It is very useful against alien spawns in hard " + "to reach spots.", + "icons/iconu_jetpack", + qtrue, //qboolean purchasable + qtrue, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_BATTLESUIT, //int upgradeNum; + BSUIT_PRICE, //int price; + ( 1 << S3 ), //int stages + SLOT_HEAD|SLOT_TORSO|SLOT_ARMS|SLOT_LEGS|SLOT_BACKPACK, //int slots; + "bsuit", //char *upgradeName; + "Battlesuit", //char *humanName; + "A full body armour that is highly effective at repelling alien attacks. " + "It allows the user to enter hostile situations with a greater degree " + "of confidence.", + "icons/iconu_bsuit", + qtrue, //qboolean purchasable + qfalse, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_GRENADE, //int upgradeNum; + GRENADE_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ),//int stages + SLOT_NONE, //int slots; + "gren", //char *upgradeName; + "Grenade", //char *humanName; + "A small incendinary device ideal for damaging tightly packed " + "alien structures. Has a five second timer.", + 0, + qtrue, //qboolean purchasable + qtrue, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_BIORES, //int upgradeNum; + BIORES_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_NONE, //int slots; + "biores", //char *upgradeName; + "Biores", //char *humanName; + "figure out a description later", + "icons/iconu_biokit", + qtrue, //qboolean purchasable + qfalse, //qboolean usable + TEAM_HUMANS //team_t team; + }, + { + UP_AMMO, //int upgradeNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_NONE, //int slots; + "ammo", //char *upgradeName; + "Ammunition", //char *humanName; + "Ammunition for the currently held weapon.", + 0, + qtrue, //qboolean purchasable + qfalse, //qboolean usable + TEAM_HUMANS //team_t team; + } +}; + +int bg_numUpgrades = sizeof( bg_upgrades ) / sizeof( bg_upgrades[ 0 ] ); + +static const upgradeAttributes_t nullUpgrade = { 0 }; + +/* +============== +BG_UpgradeByName +============== +*/ +const upgradeAttributes_t *BG_UpgradeByName( const char *name ) +{ + int i; + + for( i = 0; i < bg_numUpgrades; i++ ) + { + if( !Q_stricmp( bg_upgrades[ i ].name, name ) ) + { + return &bg_upgrades[ i ]; + } + } + + return &nullUpgrade; +} + +/* +============== +BG_Upgrade +============== +*/ +const upgradeAttributes_t *BG_Upgrade( upgrade_t upgrade ) +{ + return ( upgrade > UP_NONE && upgrade < UP_NUM_UPGRADES ) ? + &bg_upgrades[ upgrade - 1 ] : &nullUpgrade; +} + +/* +============== +BG_UpgradeAllowedInStage +============== +*/ +qboolean BG_UpgradeAllowedInStage( upgrade_t upgrade, stage_t stage ) +{ + int stages = BG_Upgrade( upgrade )->stages; + + return stages & ( 1 << stage ); +} + +//////////////////////////////////////////////////////////////////////////////// + +/* +================ +BG_EvaluateTrajectory + +================ +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) +{ + float deltaTime; + float phase; + + switch( tr->trType ) + { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float)tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + + case TR_LINEAR_STOP: + if( atTime > tr->trTime + tr->trDuration ) + atTime = tr->trTime + tr->trDuration; + + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if( deltaTime < 0 ) + deltaTime = 0; + + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[ 2 ] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + + case TR_BUOYANCY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[ 2 ] += 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +================ +BG_EvaluateTrajectoryDelta + +For determining velocity at a given time +================ +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) +{ + float deltaTime; + float phase; + + switch( tr->trType ) + { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float)tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + + case TR_LINEAR_STOP: + if( atTime > tr->trTime + tr->trDuration ) + { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[ 2 ] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + + case TR_BUOYANCY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[ 2 ] += DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +char *eventnames[ ] = +{ + "EV_NONE", + + "EV_FOOTSTEP", + "EV_FOOTSTEP_METAL", + "EV_FOOTSTEP_SQUELCH", + "EV_FOOTSPLASH", + "EV_FOOTWADE", + "EV_SWIM", + + "EV_STEP_4", + "EV_STEP_8", + "EV_STEP_12", + "EV_STEP_16", + + "EV_STEPDN_4", + "EV_STEPDN_8", + "EV_STEPDN_12", + "EV_STEPDN_16", + + "EV_FALL_SHORT", + "EV_FALL_MEDIUM", + "EV_FALL_FAR", + "EV_FALLING", + + "EV_JUMP", + "EV_WATER_TOUCH", // foot touches + "EV_WATER_LEAVE", // foot leaves + "EV_WATER_UNDER", // head touches + "EV_WATER_CLEAR", // head leaves + + "EV_NOAMMO", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + "EV_FIRE_WEAPON2", + "EV_FIRE_WEAPON3", + + "EV_PLAYER_RESPAWN", // for fovwarp effects + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + + "EV_BULLET_HIT_FLESH", + "EV_BULLET_HIT_WALL", + + "EV_SHOTGUN", + "EV_MASS_DRIVER", + + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_MISSILE_MISS_METAL", + "EV_TESLATRAIL", + "EV_BULLET", // otherEntity is the shooter + + "EV_LEV1_GRAB", + "EV_LEV4_TRAMPLE_PREPARE", + "EV_LEV4_TRAMPLE_START", + + "EV_PAIN", + "EV_DEATH1", + "EV_DEATH2", + "EV_DEATH3", + "EV_OBITUARY", + + "EV_GIB_PLAYER", // gib a previously living player + + "EV_BUILD_CONSTRUCT", + "EV_BUILD_DESTROY", + "EV_BUILD_DELAY", // can't build yet + "EV_BUILD_REPAIR", // repairing buildable + "EV_BUILD_REPAIRED", // buildable has full health + "EV_HUMAN_BUILDABLE_EXPLOSION", + "EV_ALIEN_BUILDABLE_EXPLOSION", + "EV_ALIEN_ACIDTUBE", + + "EV_MEDKIT_USED", + + "EV_ALIEN_EVOLVE", + "EV_ALIEN_EVOLVE_FAILED", + + "EV_DEBUG_LINE", + "EV_STOPLOOPINGSOUND", + "EV_TAUNT", + + "EV_OVERMIND_ATTACK", // overmind under attack + "EV_OVERMIND_DYING", // overmind close to death + "EV_OVERMIND_SPAWNS", // overmind needs spawns + + "EV_DCC_ATTACK", // dcc under attack + + "EV_MGTURRET_SPINUP", // trigger a sound + + "EV_RPTUSE_SOUND", // trigger a sound + "EV_LEV2_ZAP" +}; + +/* +=============== +BG_EventName +=============== +*/ +const char *BG_EventName( int num ) +{ + if( num < 0 || num >= sizeof( eventnames ) / sizeof( char * ) ) + return "UNKNOWN"; + + return eventnames[ num ]; +} + +/* +=============== +BG_AddPredictableEventToPlayerstate + +Handles the sequence numbers +=============== +*/ + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) +{ +#ifdef _DEBUG + { + char buf[ 256 ]; + trap_Cvar_VariableStringBuffer( "showevents", buf, sizeof( buf ) ); + + if( atof( buf ) != 0 ) + { +#ifdef GAME + Com_Printf( " game event svt %5d -> %5d: num = %20s parm %d\n", + ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, + BG_EventName( newEvent ), eventParm ); +#else + Com_Printf( "Cgame event svt %5d -> %5d: num = %20s parm %d\n", + ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, + BG_EventName( newEvent ), eventParm ); +#endif + } + } +#endif + ps->events[ ps->eventSequence & ( MAX_PS_EVENTS - 1 ) ] = newEvent; + ps->eventParms[ ps->eventSequence & ( MAX_PS_EVENTS - 1 ) ] = eventParm; + ps->eventSequence++; +} + + +/* +======================== +BG_PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) +{ + int i; + + if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE ) + s->eType = ET_INVISIBLE; + else if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + s->eType = ET_INVISIBLE; + else + s->eType = ET_PLAYER; + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + VectorCopy( ps->origin, s->pos.trBase ); + + if( snap ) + SnapVector( s->pos.trBase ); + + //set the trDelta for flag direction + VectorCopy( ps->velocity, s->pos.trDelta ); + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + + if( snap ) + SnapVector( s->apos.trBase ); + + s->time2 = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->weaponAnim = ps->weaponAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + if( ps->stats[STAT_HEALTH] <= 0 ) + s->eFlags |= EF_DEAD; + else + s->eFlags &= ~EF_DEAD; + + if( ps->stats[ STAT_STATE ] & SS_BLOBLOCKED ) + s->eFlags |= EF_BLOBLOCKED; + else + s->eFlags &= ~EF_BLOBLOCKED; + + if( ps->externalEvent ) + { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } + else if( ps->entityEventSequence < ps->eventSequence ) + { + int seq; + + if( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS ) + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + + seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + //store items held and active items in modelindex and modelindex2 + s->modelindex = 0; + s->modelindex2 = 0; + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, ps->stats ) ) + { + s->modelindex |= 1 << i; + + if( BG_UpgradeIsActive( i, ps->stats ) ) + s->modelindex2 |= 1 << i; + } + } + + // use misc field to store team/class info: + s->misc = ps->stats[ STAT_TEAM ] | ( ps->stats[ STAT_CLASS ] << 8 ); + + // have to get the surfNormal through somehow... + VectorCopy( ps->grapplePoint, s->angles2 ); + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + + if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES ) + s->generic1 = WPM_PRIMARY; + + s->otherEntityNum = ps->otherEntityNum; +} + + +/* +======================== +BG_PlayerStateToEntityStateExtraPolate + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) +{ + int i; + + if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE ) + s->eType = ET_INVISIBLE; + else if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + s->eType = ET_INVISIBLE; + else + s->eType = ET_PLAYER; + + s->number = ps->clientNum; + + s->pos.trType = TR_LINEAR_STOP; + VectorCopy( ps->origin, s->pos.trBase ); + + if( snap ) + SnapVector( s->pos.trBase ); + + // set the trDelta for flag direction and linear prediction + VectorCopy( ps->velocity, s->pos.trDelta ); + // set the time for linear prediction + s->pos.trTime = time; + // set maximum extra polation time + s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if( snap ) + SnapVector( s->apos.trBase ); + + s->time2 = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->weaponAnim = ps->weaponAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + + if( ps->stats[STAT_HEALTH] <= 0 ) + s->eFlags |= EF_DEAD; + else + s->eFlags &= ~EF_DEAD; + + if( ps->stats[ STAT_STATE ] & SS_BLOBLOCKED ) + s->eFlags |= EF_BLOBLOCKED; + else + s->eFlags &= ~EF_BLOBLOCKED; + + if( ps->externalEvent ) + { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } + else if( ps->entityEventSequence < ps->eventSequence ) + { + int seq; + + if( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS ) + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + + seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + //store items held and active items in modelindex and modelindex2 + s->modelindex = 0; + s->modelindex2 = 0; + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, ps->stats ) ) + { + s->modelindex |= 1 << i; + + if( BG_UpgradeIsActive( i, ps->stats ) ) + s->modelindex2 |= 1 << i; + } + } + + // use misc field to store team/class info: + s->misc = ps->stats[ STAT_TEAM ] | ( ps->stats[ STAT_CLASS ] << 8 ); + + // have to get the surfNormal through somehow... + VectorCopy( ps->grapplePoint, s->angles2 ); + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + + if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES ) + s->generic1 = WPM_PRIMARY; + + s->otherEntityNum = ps->otherEntityNum; +} + +/* +======================== +BG_WeaponMaxAmmo +======================== +*/ +int BG_WeaponMaxAmmo( weapon_t weapon, int *stats) +{ + if( BG_InventoryContainsUpgrade( UP_BATTPACK, stats ) ) + return (int)( (float)BG_Weapon(weapon)->maxAmmo * BATTPACK_MODIFIER ); + else + return BG_Weapon(weapon)->maxAmmo; +} + +/* +======================== +BG_WeaponIsFull + +Check if a weapon has full ammo +======================== +*/ +qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips ) +{ + int maxAmmo, maxClips; + + maxAmmo = BG_Weapon( weapon )->maxAmmo; + maxClips = BG_Weapon( weapon )->maxClips; + + if( BG_InventoryContainsUpgrade( UP_BATTPACK, stats ) ) + maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); + + return ( maxAmmo == ammo ) && ( maxClips == clips ); +} + +/* +======================== +BG_InventoryContainsWeapon + +Does the player hold a weapon? +======================== +*/ +qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] ) +{ + // humans always have a blaster + if( stats[ STAT_TEAM ] == TEAM_HUMANS && weapon == WP_BLASTER ) + return qtrue; + + return ( stats[ STAT_WEAPON ] == weapon ); +} + +/* +======================== +BG_SlotsForInventory + +Calculate the slots used by an inventory and warn of conflicts +======================== +*/ +int BG_SlotsForInventory( int stats[ ] ) +{ + int i, slot, slots; + + slots = BG_Weapon( stats[ STAT_WEAPON ] )->slots; + if( stats[ STAT_TEAM ] == TEAM_HUMANS ) + slots |= BG_Weapon( WP_BLASTER )->slots; + + for( i = UP_NONE; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, stats ) ) + { + slot = BG_Upgrade( i )->slots; + + // this check should never be true + if( slots & slot ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: held item %d conflicts with " + "inventory slot %d\n", i, slot ); + } + + slots |= slot; + } + } + + return slots; +} + +/* +======================== +BG_AddUpgradeToInventory + +Give the player an upgrade +======================== +*/ +void BG_AddUpgradeToInventory( int item, int stats[ ] ) +{ + stats[ STAT_ITEMS ] |= ( 1 << item ); +} + +/* +======================== +BG_RemoveUpgradeFromInventory + +Take an upgrade from the player +======================== +*/ +void BG_RemoveUpgradeFromInventory( int item, int stats[ ] ) +{ + stats[ STAT_ITEMS ] &= ~( 1 << item ); +} + +/* +======================== +BG_InventoryContainsUpgrade + +Does the player hold an upgrade? +======================== +*/ +qboolean BG_InventoryContainsUpgrade( int item, int stats[ ] ) +{ + return( stats[ STAT_ITEMS ] & ( 1 << item ) ); +} + +/* +======================== +BG_ActivateUpgrade + +Activates an upgrade +======================== +*/ +void BG_ActivateUpgrade( int item, int stats[ ] ) +{ + if( item != UP_JETPACK || stats[ STAT_FUEL ] > 0) + stats[ STAT_ACTIVEITEMS ] |= ( 1 << item ); +} + +/* +======================== +BG_DeactivateUpgrade + +Deactivates an upgrade +======================== +*/ +void BG_DeactivateUpgrade( int item, int stats[ ] ) +{ + stats[ STAT_ACTIVEITEMS ] &= ~( 1 << item ); +} + +/* +======================== +BG_UpgradeIsActive + +Is this upgrade active? +======================== +*/ +qboolean BG_UpgradeIsActive( int item, int stats[ ] ) +{ + return( stats[ STAT_ACTIVEITEMS ] & ( 1 << item ) ); +} + +/* +=============== +BG_RotateAxis + +Shared axis rotation function +=============== +*/ +qboolean BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ], + vec3_t outAxis[ 3 ], qboolean inverse, qboolean ceiling ) +{ + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f }; + vec3_t localNormal, xNormal; + float rotAngle; + + //the grapplePoint being a surfNormal rotation Normal hack... see above :) + if( ceiling ) + { + VectorCopy( ceilingNormal, localNormal ); + VectorCopy( surfNormal, xNormal ); + } + else + { + //cross the reference normal and the surface normal to get the rotation axis + VectorCopy( surfNormal, localNormal ); + CrossProduct( localNormal, refNormal, xNormal ); + VectorNormalize( xNormal ); + } + + //can't rotate with no rotation vector + if( VectorLength( xNormal ) != 0.0f ) + { + rotAngle = RAD2DEG( acos( DotProduct( localNormal, refNormal ) ) ); + + if( inverse ) + rotAngle = -rotAngle; + + AngleNormalize180( rotAngle ); + + //hmmm could get away with only one rotation and some clever stuff later... but i'm lazy + RotatePointAroundVector( outAxis[ 0 ], xNormal, inAxis[ 0 ], -rotAngle ); + RotatePointAroundVector( outAxis[ 1 ], xNormal, inAxis[ 1 ], -rotAngle ); + RotatePointAroundVector( outAxis[ 2 ], xNormal, inAxis[ 2 ], -rotAngle ); + } + else + return qfalse; + + return qtrue; +} + +/* +=============== +BG_GetClientNormal + +Get the normal for the surface the client is walking on +=============== +*/ +void BG_GetClientNormal( const playerState_t *ps, vec3_t normal ) +{ + if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) + { + if( ps->eFlags & EF_WALLCLIMBCEILING ) + VectorSet( normal, 0.0f, 0.0f, -1.0f ); + else + VectorCopy( ps->grapplePoint, normal ); + } + else + VectorSet( normal, 0.0f, 0.0f, 1.0f ); +} + +/* +=============== +BG_GetClientViewOrigin + +Get the position of the client's eye, based on the client's position, the surface's normal, and client's view height +=============== +*/ +void BG_GetClientViewOrigin( const playerState_t *ps, vec3_t viewOrigin ) +{ + vec3_t normal; + BG_GetClientNormal( ps, normal ); + VectorMA( ps->origin, ps->viewheight, normal, viewOrigin ); +} + +/* +=============== +BG_PositionBuildableRelativeToPlayer + +Find a place to build a buildable +=============== +*/ + +#define DIAGONAL2(a,b) sqrt((a)*(a)+(b)*(b)) +qboolean BG_PositionBuildableRelativeToPlayer( const playerState_t *ps, qboolean iscuboid, + const vec3_t mins, const vec3_t maxs, + void (*trace)( trace_t *, const vec3_t, const vec3_t, + const vec3_t, const vec3_t, int, int ), + vec3_t outOrigin, vec3_t outAngles, trace_t *tr ) +{ + vec3_t forward, normal, view, traceA, traceB, faces[6]; + float buildDist, maxDist, normalOffset=0.0f, zOffset, dist, min=10e6; + int i; + vec3_t cuboid; + + BG_GetClientNormal(ps,normal); + AngleVectors(ps->viewangles,forward,NULL,NULL); + buildDist=BG_Class(ps->stats[STAT_CLASS])->buildDist; + if(iscuboid) + { + BG_GetClientViewOrigin(ps,view); + VectorNormalize(forward); + VectorSubtract(maxs,mins,cuboid); + maxDist=buildDist+VectorLength(cuboid); + VectorMA(view,maxDist,forward,traceB); + VectorMA(view,0.0f,forward,traceA); + (*trace)(tr,traceA,NULL,NULL,traceB,ps->clientNum,MASK_PLAYERSOLID); + if(COMPARE_FLOAT_EPSILON(tr->fraction,1.0f)) + return qfalse; + zOffset=-cuboid[2]*0.5f; + if(COMPARE_VECTOR_EPSILON(tr->plane.normal,0.0f,0.0f,1.0f)) + zOffset=0.0f; + else if(COMPARE_VECTOR_EPSILON(tr->plane.normal,0.0f,0.0f,-1.0f)) + zOffset=-cuboid[2]; + else if(COMPARE_VECTOR_EPSILON(tr->plane.normal,1.0f,0.0f,0.0f) || + COMPARE_VECTOR_EPSILON(tr->plane.normal,-1.0f,0.0f,0.0f)) + normalOffset=cuboid[0]*0.5f; + else if(COMPARE_VECTOR_EPSILON(tr->plane.normal,0.0f,1.0f,0.0f) || + COMPARE_VECTOR_EPSILON(tr->plane.normal,0.0f,-1.0f,0.0f)) + normalOffset=cuboid[1]*0.5f; + else + return qfalse; + VectorMA(tr->endpos,normalOffset,tr->plane.normal,outOrigin); + outOrigin[2]+=zOffset; + for(i=0;i<6;i++) + VectorCopy(outOrigin,faces[i]); + faces[0][0]+=cuboid[0]/2.0f; + faces[1][1]+=cuboid[1]/2.0f; + faces[2][0]-=cuboid[0]/2.0f; + faces[3][1]-=cuboid[1]/2.0f; + faces[4][2]+=cuboid[2]; + for(i=0;i<6;i++) + if((dist=Distance(view,faces[i]))256) + return qfalse; + return qtrue; + } + vectoangles(forward,outAngles); + ProjectPointOnPlane(forward,forward,normal); + VectorNormalize(forward); + VectorMA(ps->origin,buildDist,forward,traceA); + VectorMA(traceA,-128,normal,traceB); + VectorMA(traceA,32,normal,traceA); + (*trace)(tr,traceA,mins,maxs,traceB,ps->clientNum,MASK_PLAYERSOLID); + VectorCopy(tr->endpos,outOrigin); + return qtrue; +} + +/* +=============== +BG_GetValueOfPlayer + +Returns the credit value of a player +=============== +*/ +int BG_GetValueOfPlayer( playerState_t *ps ) +{ + int i, worth = 0; + + worth = BG_Class( ps->stats[ STAT_CLASS ] )->value; + + // Humans have worth from their equipment as well + if( ps->stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, ps->stats ) ) + worth += BG_Upgrade( i )->price; + } + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( BG_InventoryContainsWeapon( i, ps->stats ) ) + worth += BG_Weapon( i )->price; + } + } + + return worth; +} + +/* +================= +BG_PlayerCanChangeWeapon +================= +*/ +qboolean BG_PlayerCanChangeWeapon( playerState_t *ps ) +{ + // Do not allow Lucifer Cannon "canceling" via weapon switch + if( ps->weapon == WP_LUCIFER_CANNON && + ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_MIN ) + return qfalse; + + return ps->weaponTime <= 0 || ps->weaponstate != WEAPON_FIRING; +} + +/* +================= +BG_PlayerPoisonCloudTime +================= +*/ +int BG_PlayerPoisonCloudTime( playerState_t *ps ) +{ + int time = LEVEL1_PCLOUD_TIME; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ps->stats ) ) + time -= BSUIT_PCLOUD_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, ps->stats ) ) + time -= HELMET_MK1_PCLOUD_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, ps->stats ) ) + time -= HELMET_MK2_PCLOUD_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, ps->stats ) ) + time -= LIGHTARMOUR_PCLOUD_PROTECTION; + + return time; +} + +/* +================= +BG_GetPlayerWeapon + +Returns the players current weapon or the weapon they are switching to. +Only needs to be used for human weapons. +================= +*/ +weapon_t BG_GetPlayerWeapon( playerState_t *ps ) +{ + if( ps->persistant[ PERS_NEWWEAPON ] ) + return ps->persistant[ PERS_NEWWEAPON ]; + + return ps->weapon; +} + +/* +=============== +atof_neg + +atof with an allowance for negative values +=============== +*/ +float atof_neg( char *token, qboolean allowNegative ) +{ + float value; + + value = atof( token ); + + if( !allowNegative && value < 0.0f ) + value = 1.0f; + + return value; +} + +/* +=============== +atoi_neg + +atoi with an allowance for negative values +=============== +*/ +int atoi_neg( char *token, qboolean allowNegative ) +{ + int value; + + value = atoi( token ); + + if( !allowNegative && value < 0 ) + value = 1; + + return value; +} + +#define MAX_NUM_PACKED_ENTITY_NUMS 10 + +/* +=============== +BG_PackEntityNumbers + +Pack entity numbers into an entityState_t +=============== +*/ +void BG_PackEntityNumbers( entityState_t *es, const int *entityNums, int count ) +{ + int i; + + if( count > MAX_NUM_PACKED_ENTITY_NUMS ) + { + count = MAX_NUM_PACKED_ENTITY_NUMS; + Com_Printf( S_COLOR_YELLOW "WARNING: A maximum of %d entity numbers can be " + "packed, but BG_PackEntityNumbers was passed %d entities", + MAX_NUM_PACKED_ENTITY_NUMS, count ); + } + + es->misc = es->time = es->time2 = es->constantLight = 0; + + for( i = 0; i < MAX_NUM_PACKED_ENTITY_NUMS; i++ ) + { + int entityNum; + + if( i < count ) + entityNum = entityNums[ i ]; + else + entityNum = ENTITYNUM_NONE; + + if( entityNum & ~GENTITYNUM_MASK ) + { + Com_Error( ERR_FATAL, "BG_PackEntityNumbers passed an entity number (%d) which " + "exceeds %d bits", entityNum, GENTITYNUM_BITS ); + } + + switch( i ) + { + case 0: es->misc |= entityNum; break; + case 1: es->time |= entityNum; break; + case 2: es->time |= entityNum << GENTITYNUM_BITS; break; + case 3: es->time |= entityNum << (GENTITYNUM_BITS * 2); break; + case 4: es->time2 |= entityNum; break; + case 5: es->time2 |= entityNum << GENTITYNUM_BITS; break; + case 6: es->time2 |= entityNum << (GENTITYNUM_BITS * 2); break; + case 7: es->constantLight |= entityNum; break; + case 8: es->constantLight |= entityNum << GENTITYNUM_BITS; break; + case 9: es->constantLight |= entityNum << (GENTITYNUM_BITS * 2); break; + default: Com_Error( ERR_FATAL, "Entity index %d not handled", i ); break; + } + } +} + +/* +=============== +BG_UnpackEntityNumbers + +Unpack entity numbers from an entityState_t +=============== +*/ +int BG_UnpackEntityNumbers( entityState_t *es, int *entityNums, int count ) +{ + int i; + + if( count > MAX_NUM_PACKED_ENTITY_NUMS ) + count = MAX_NUM_PACKED_ENTITY_NUMS; + + for( i = 0; i < count; i++ ) + { + int *entityNum = &entityNums[ i ]; + + switch( i ) + { + case 0: *entityNum = es->misc; break; + case 1: *entityNum = es->time; break; + case 2: *entityNum = (es->time >> GENTITYNUM_BITS); break; + case 3: *entityNum = (es->time >> (GENTITYNUM_BITS * 2)); break; + case 4: *entityNum = es->time2; break; + case 5: *entityNum = (es->time2 >> GENTITYNUM_BITS); break; + case 6: *entityNum = (es->time2 >> (GENTITYNUM_BITS * 2)); break; + case 7: *entityNum = es->constantLight; break; + case 8: *entityNum = (es->constantLight >> GENTITYNUM_BITS); break; + case 9: *entityNum = (es->constantLight >> (GENTITYNUM_BITS * 2)); break; + default: Com_Error( ERR_FATAL, "Entity index %d not handled", i ); break; + } + + *entityNum &= GENTITYNUM_MASK; + + if( *entityNum == ENTITYNUM_NONE ) + break; + } + + return i; +} + +/* +=============== +BG_ParseCSVEquipmentList +=============== +*/ +void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weaponsSize, + upgrade_t *upgrades, int upgradesSize ) +{ + char buffer[ MAX_STRING_CHARS ]; + int i = 0, j = 0; + char *p, *q; + qboolean EOS = qfalse; + + Q_strncpyz( buffer, string, MAX_STRING_CHARS ); + + p = q = buffer; + + while( *p != '\0' ) + { + //skip to first , or EOS + while( *p != ',' && *p != '\0' ) + p++; + + if( *p == '\0' ) + EOS = qtrue; + + *p = '\0'; + + //strip leading whitespace + while( *q == ' ' ) + q++; + + if( weaponsSize ) + weapons[ i ] = BG_WeaponByName( q )->number; + + if( upgradesSize ) + upgrades[ j ] = BG_UpgradeByName( q )->number; + + if( weaponsSize && weapons[ i ] == WP_NONE && + upgradesSize && upgrades[ j ] == UP_NONE ) + Com_Printf( S_COLOR_YELLOW "WARNING: unknown equipment %s\n", q ); + else if( weaponsSize && weapons[ i ] != WP_NONE ) + i++; + else if( upgradesSize && upgrades[ j ] != UP_NONE ) + j++; + + if( !EOS ) + { + p++; + q = p; + } + else + break; + + if( i == ( weaponsSize - 1 ) || j == ( upgradesSize - 1 ) ) + break; + } + + if( weaponsSize ) + weapons[ i ] = WP_NONE; + + if( upgradesSize ) + upgrades[ j ] = UP_NONE; +} + +/* +=============== +BG_ParseCSVClassList +=============== +*/ +void BG_ParseCSVClassList( const char *string, class_t *classes, int classesSize ) +{ + char buffer[ MAX_STRING_CHARS ]; + int i = 0; + char *p, *q; + qboolean EOS = qfalse; + + Q_strncpyz( buffer, string, MAX_STRING_CHARS ); + + p = q = buffer; + + while( *p != '\0' && i < classesSize - 1 ) + { + //skip to first , or EOS + while( *p != ',' && *p != '\0' ) + p++; + + if( *p == '\0' ) + EOS = qtrue; + + *p = '\0'; + + //strip leading whitespace + while( *q == ' ' ) + q++; + + classes[ i ] = BG_ClassByName( q )->number; + + if( classes[ i ] == PCL_NONE ) + Com_Printf( S_COLOR_YELLOW "WARNING: unknown class %s\n", q ); + else + i++; + + if( !EOS ) + { + p++; + q = p; + } + else + break; + } + + classes[ i ] = PCL_NONE; +} + +/* +=============== +BG_ParseCSVBuildableList +=============== +*/ +void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int buildablesSize ) +{ + char buffer[ MAX_STRING_CHARS ]; + int i = 0; + char *p, *q; + qboolean EOS = qfalse; + + Q_strncpyz( buffer, string, MAX_STRING_CHARS ); + + p = q = buffer; + + while( *p != '\0' && i < buildablesSize - 1 ) + { + //skip to first , or EOS + while( *p != ',' && *p != '\0' ) + p++; + + if( *p == '\0' ) + EOS = qtrue; + + *p = '\0'; + + //strip leading whitespace + while( *q == ' ' ) + q++; + + buildables[ i ] = BG_BuildableByName( q )->number; + + if( buildables[ i ] == BA_NONE ) + Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable %s\n", q ); + else + i++; + + if( !EOS ) + { + p++; + q = p; + } + else + break; + } + + buildables[ i ] = BA_NONE; +} + +typedef struct gameElements_s +{ + buildable_t buildables[ BA_NUM_BUILDABLES ]; + class_t classes[ PCL_NUM_CLASSES ]; + weapon_t weapons[ WP_NUM_WEAPONS ]; + upgrade_t upgrades[ UP_NUM_UPGRADES ]; + int cuboidMode; +} gameElements_t; + +static gameElements_t bg_disabledGameElements; + +/* +============ +BG_InitAllowedGameElements +============ +*/ +void BG_InitAllowedGameElements( void ) +{ + char cvar[ MAX_CVAR_VALUE_STRING ]; + + trap_Cvar_VariableStringBuffer( "g_disabledEquipment", + cvar, MAX_CVAR_VALUE_STRING ); + + BG_ParseCSVEquipmentList( cvar, + bg_disabledGameElements.weapons, WP_NUM_WEAPONS, + bg_disabledGameElements.upgrades, UP_NUM_UPGRADES ); + + trap_Cvar_VariableStringBuffer( "g_disabledClasses", + cvar, MAX_CVAR_VALUE_STRING ); + + BG_ParseCSVClassList( cvar, + bg_disabledGameElements.classes, PCL_NUM_CLASSES ); + + trap_Cvar_VariableStringBuffer( "g_disabledBuildables", + cvar, MAX_CVAR_VALUE_STRING ); + + BG_ParseCSVBuildableList( cvar, + bg_disabledGameElements.buildables, BA_NUM_BUILDABLES ); + + trap_Cvar_VariableStringBuffer( "g_cuboidMode", + cvar, MAX_CVAR_VALUE_STRING ); + + bg_disabledGameElements.cuboidMode = atoi_neg(cvar,qfalse); + +} + +/* +============ +BG_WeaponIsAllowed +============ +*/ +qboolean BG_WeaponIsAllowed( weapon_t weapon ) +{ + int i; + + for( i = 0; i < WP_NUM_WEAPONS && + bg_disabledGameElements.weapons[ i ] != WP_NONE; i++ ) + { + if( bg_disabledGameElements.weapons[ i ] == weapon ) + return qfalse; + } + + return qtrue; +} + +/* +============ +BG_UpgradeIsAllowed +============ +*/ +qboolean BG_UpgradeIsAllowed( upgrade_t upgrade ) +{ + int i; + + for( i = 0; i < UP_NUM_UPGRADES && + bg_disabledGameElements.upgrades[ i ] != UP_NONE; i++ ) + { + if( bg_disabledGameElements.upgrades[ i ] == upgrade ) + return qfalse; + } + + return qtrue; +} + +/* +============ +BG_ClassIsAllowed +============ +*/ +qboolean BG_ClassIsAllowed( class_t class ) +{ + int i; + + for( i = 0; i < PCL_NUM_CLASSES && + bg_disabledGameElements.classes[ i ] != PCL_NONE; i++ ) + { + if( bg_disabledGameElements.classes[ i ] == class ) + return qfalse; + } + + return qtrue; +} + +/* +============ +BG_BuildableIsAllowed +============ +*/ +qboolean BG_BuildableIsAllowed( buildable_t buildable ) +{ + int i; + + for( i = 0; i < BA_NUM_BUILDABLES && + bg_disabledGameElements.buildables[ i ] != BA_NONE; i++ ) + { + if( bg_disabledGameElements.buildables[ i ] == buildable ) + return qfalse; + } + + + return qtrue; +} + +/* +============ +BG_PrimaryWeapon +============ +*/ +weapon_t BG_PrimaryWeapon( int stats[ ] ) +{ + int i; + + for( i = WP_NONE; i < WP_NUM_WEAPONS; i++ ) + { + if( BG_Weapon( i )->slots != SLOT_WEAPON ) + continue; + if( BG_InventoryContainsWeapon( i, stats ) ) + return i; + } + + if( BG_InventoryContainsWeapon( WP_BLASTER, stats ) ) + return WP_BLASTER; + + return WP_NONE; +} + +/* +============ +BG_LoadEmoticons +============ +*/ +int BG_LoadEmoticons( emoticon_t *emoticons, int num ) +{ + int numFiles; + char fileList[ MAX_EMOTICONS * ( MAX_EMOTICON_NAME_LEN + 9 ) ] = {""}; + int i; + char *filePtr; + int fileLen; + int count; + + numFiles = trap_FS_GetFileList( "emoticons", "x1.tga", fileList, + sizeof( fileList ) ); + + if( numFiles < 1 ) + return 0; + + filePtr = fileList; + fileLen = 0; + count = 0; + for( i = 0; i < numFiles && count < num; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + if( fileLen < 9 || filePtr[ fileLen - 8 ] != '_' || + filePtr[ fileLen - 7 ] < '1' || filePtr[ fileLen - 7 ] > '9' ) + { + Com_Printf( S_COLOR_YELLOW "skipping invalidly named emoticon \"%s\"\n", + filePtr ); + continue; + } + if( fileLen - 8 > MAX_EMOTICON_NAME_LEN ) + { + Com_Printf( S_COLOR_YELLOW "emoticon file name \"%s\" too long (>%d)\n", + filePtr, MAX_EMOTICON_NAME_LEN + 8 ); + continue; + } + if( !trap_FS_FOpenFile( va( "emoticons/%s", filePtr ), NULL, FS_READ ) ) + { + Com_Printf( S_COLOR_YELLOW "could not open \"emoticons/%s\"\n", filePtr ); + continue; + } + + Q_strncpyz( emoticons[ count ].name, filePtr, fileLen - 8 + 1 ); +#ifndef GAME + emoticons[ count ].width = filePtr[ fileLen - 7 ] - '0'; +#endif + count++; + } + + Com_Printf( "Loaded %d of %d emoticons (MAX_EMOTICONS is %d)\n", + count, numFiles, MAX_EMOTICONS ); + return count; +} + +/* +============ +BG_TeamName +============ +*/ +char *BG_TeamName( team_t team ) +{ + if( team == TEAM_NONE ) + return "spectator"; + if( team == TEAM_ALIENS ) + return "alien"; + if( team == TEAM_HUMANS ) + return "human"; + return ""; +} + +int cmdcmp( const void *a, const void *b ) +{ + return Q_stricmp( (const char *)a, ((dummyCmd_t *)b)->name ); +} + +/* +============ +cuboid stuff +============ +*/ + +const cuboidAttributes_t BG_CuboidTypes [] = +{ + { + qtrue, // qboolean genericAnim + "concrete", // const char* file + "[concrete]", // const char* icon + { // cuboidSoundset_t pain { + 3, // int numsounds + }, // } + { // cuboidSoundset_t dstr { + 1, // int numsounds + {0} // float volumes[6] + }, // } + 180, // float hppv + CBHPT_CUBES, // int hpt + 3.0, // float bppv + 40, // int buildrate + 1500, // int minbt + qfalse, // qboolean regen; + 0, // int regenspeed + qfalse, // qboolean zappable + qfalse, // qboolean repairable + qfalse, // qboolean detectable + qfalse, // qboolean needpower + 0 // int surfaceFlags + }, + { + qtrue, // qboolean genericAnim + "glass", // const char* file + "[glass]", // const char* icon + { // cuboidSoundset_t pain { + 3, // int numsounds + }, // } + { // cuboidSoundset_t dstr { + 3, // int numsounds + {0, 0.5, 6} // float volumes[6] + }, // } + 52, // float hppv + CBHPT_PANES, // int hpt + 1.5, // float bppv + 55, // int buildrate + 1200, // int minbt + qfalse, // qboolean regen; + 0, // int regenspeed + qfalse, // qboolean zappable + qfalse, // qboolean repairable + qfalse, // qboolean detectable + qfalse, // qboolean needpower + 0 // int surfaceFlags + }, + { + qtrue, // qboolean genericAnim + "ladder", // const char* file + "[ladder]", // const char* icon + { // cuboidSoundset_t pain { + 2, // int numsounds + }, // } + { // cuboidSoundset_t dstr { + 1, // int numsounds + {0} // float volumes[6] + }, // } + 50, // float hppv + CBHPT_PLAIN, // int hpt + 4.0, // float bppv + 26, // int buildrate + 1800, // int minbt + qfalse, // qboolean regen; + 0, // int regenspeed + qfalse, // qboolean zappable + qtrue, // qboolean repairable + qfalse, // qboolean detectable + qfalse, // qboolean needpower + SURF_LADDER | SURF_METALSTEPS // int surfaceFlags + }, + { + qfalse, // qboolean genericAnim + "organic", // const char* file + "[organic]", // const char* icon + { // cuboidSoundset_t pain { + 3, // int numsounds + }, // } + { // cuboidSoundset_t dstr { + 3, // int numsounds + {0,2.0,4.5} // float volumes[6] + }, // } + 50, // float hppv + CBHPT_PLAIN, // int hpt + 1.2, // float bppv + 83, // int buildrate + 750, // int minbt + qtrue, // qboolean regen; + 45, // int regenspeed + qfalse, // qboolean zappable + qfalse, // qboolean repairable + qfalse, // qboolean detectable + qfalse, // qboolean needpower + SURF_FLESH // int surfaceFlags + }, + { + qfalse, // qboolean genericAnim + "slime", // const char* file + "[slime]", // const char* icon + { // cuboidSoundset_t pain { + 2, // int numsounds + }, // } + { // cuboidSoundset_t dstr { + 2, // int numsounds + {0,2.0} // float volumes[6] + }, // } + 20, // float hppv + CBHPT_PLAIN, // int hpt + 1.5, // float bppv + 45, // int buildrate + 1300, // int minbt + qtrue, // qboolean regen; + 90, // int regenspeed + qfalse, // qboolean zappable + qfalse, // qboolean repairable + qfalse, // qboolean detectable + qfalse, // qboolean needpower + SURF_SLICK // int surfaceFlags + } +}; + +const cuboidAttributes_t *BG_CuboidAttributes( buildable_t buildable ) +{ + if( buildable>=CUBOID_FIRST && buildable<=CUBOID_LAST ) + return &BG_CuboidTypes[ buildable-CUBOID_FIRST ]; + return &BG_CuboidTypes[ 0 ]; +} + +qboolean BG_IsCuboid(buildable_t buildable) +{ + return ( buildable >= CUBOID_FIRST && buildable <= CUBOID_LAST ); +} + +void BG_CuboidBBox(const vec3_t dims, vec3_t mins, vec3_t maxs) +{ + mins[ 0 ] = -0.5 * dims[0]; + mins[ 1 ] = -0.5 * dims[1]; + maxs[ 0 ] = 0.5 * dims[0]; + maxs[ 1 ] = 0.5 * dims[1]; + mins[ 2 ] = 0.0; + maxs[ 2 ] = dims[2]; +} + +/* Since there is no space in entityState_t designed for cuboid-specific data, + * existing fields that are not in use by a cuboid are used: + * + * vec3_t angles - cuboid dimensions (NOTE: not sure if there are no precision losses) + * int constantLight - cuboid health (because generic1 is too small to fit the huge health values) + */ + +int BG_CuboidUnpackHealth(entityState_t *es) +{ + return es->constantLight; +} + +void BG_CuboidPackHealth(entityState_t *es, int health) +{ + es->constantLight = health; +} + +void BG_CuboidPackHealthSafe(buildable_t buildable, entityState_t *es, int health) +{ + if(!BG_Buildable(buildable,NULL)->cuboid) + return; + BG_CuboidPackHealth(es,health); +} + +int BG_CuboidMode(void) +{ + return bg_disabledGameElements.cuboidMode; +} + +// The g_cuboidMode cvar governs whether building cuboids is allowed or not. +// It was introduced to disable cuboids on maps on which balance could be +// heavily harmed by the ability to construct cuboids. + +// g_cuboidMode 0 - allowed +// g_cuboidMode 1 - allowed, except for Stage 1 +// g_cuboidMode 2 - not allowed at all + +qboolean BG_CuboidAllowed(int stage) +{ + int mode = bg_disabledGameElements.cuboidMode; + if(mode==0) + return qtrue; + if(mode==1) + return (stage>S1); + return qfalse; +} + +void BG_CuboidSortSize(const vec3_t a, vec3_t b) +{ + if(a[0]numsounds-1;i>=0;i--) + if(set->volumes[i]ps ); +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt( int entityNum ) +{ + int i; + + if( entityNum == ENTITYNUM_WORLD ) + return; + + if( pm->numtouch == MAXTOUCH ) + return; + + // see if it is already added + for( i = 0 ; i < pm->numtouch ; i++ ) + { + if( pm->touchents[ i ] == entityNum ) + return; + } + + // add it + pm->touchents[ pm->numtouch ] = entityNum; + pm->numtouch++; +} + +/* +=================== +PM_StartTorsoAnim +=================== +*/ +void PM_StartTorsoAnim( int anim ) +{ + if( PM_Paralyzed( pm->ps->pm_type ) ) + return; + + pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +/* +=================== +PM_StartWeaponAnim +=================== +*/ +static void PM_StartWeaponAnim( int anim ) +{ + if( PM_Paralyzed( pm->ps->pm_type ) ) + return; + + pm->ps->weaponAnim = ( ( pm->ps->weaponAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +/* +=================== +PM_StartLegsAnim +=================== +*/ +static void PM_StartLegsAnim( int anim ) +{ + if( PM_Paralyzed( pm->ps->pm_type ) ) + return; + + //legsTimer is clamped too tightly for nonsegmented models + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->legsTimer > 0 ) + return; // a high priority animation is running + } + else + { + if( pm->ps->torsoTimer > 0 ) + return; // a high priority animation is running + } + + pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +/* +=================== +PM_ContinueLegsAnim +=================== +*/ +static void PM_ContinueLegsAnim( int anim ) +{ + if( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) + return; + + //legsTimer is clamped too tightly for nonsegmented models + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->legsTimer > 0 ) + return; // a high priority animation is running + } + else + { + if( pm->ps->torsoTimer > 0 ) + return; // a high priority animation is running + } + + PM_StartLegsAnim( anim ); +} + +/* +=================== +PM_ContinueTorsoAnim +=================== +*/ +static void PM_ContinueTorsoAnim( int anim ) +{ + if( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) + return; + + if( pm->ps->torsoTimer > 0 ) + return; // a high priority animation is running + + PM_StartTorsoAnim( anim ); +} + +/* +=================== +PM_ContinueWeaponAnim +=================== +*/ +static void PM_ContinueWeaponAnim( int anim ) +{ + if( ( pm->ps->weaponAnim & ~ANIM_TOGGLEBIT ) == anim ) + return; + + PM_StartWeaponAnim( anim ); +} + +/* +=================== +PM_ForceLegsAnim +=================== +*/ +static void PM_ForceLegsAnim( int anim ) +{ + //legsTimer is clamped too tightly for nonsegmented models + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + pm->ps->legsTimer = 0; + else + pm->ps->torsoTimer = 0; + + PM_StartLegsAnim( anim ); +} + + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) +{ + float backoff; + float change; + int i; + + backoff = DotProduct( in, normal ); + + //Com_Printf( "%1.0f ", backoff ); + + if( backoff < 0 ) + backoff *= overbounce; + else + backoff /= overbounce; + + for( i = 0; i < 3; i++ ) + { + change = normal[ i ] * backoff; + //Com_Printf( "%1.0f ", change ); + out[ i ] = in[ i ] - change; + } + + //Com_Printf( " " ); +} + + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +static void PM_Friction( void ) +{ + vec3_t vec; + float *vel; + float speed, newspeed, control; + float drop; + + vel = pm->ps->velocity; + + // make sure vertical velocity is NOT set to zero when wall climbing + VectorCopy( vel, vec ); + if( pml.walking && !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) + vec[ 2 ] = 0; // ignore slope movement + + speed = VectorLength( vec ); + + if( speed < 1 ) + { + vel[ 0 ] = 0; + vel[ 1 ] = 0; // allow sinking underwater + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + // apply ground friction + if( pm->waterlevel <= 1 ) + { + if( ( pml.walking || pml.ladder ) && !( pml.groundTrace.surfaceFlags & SURF_SLICK ) ) + { + // if getting knocked back, no friction + if( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) + { + float stopSpeed = BG_Class( pm->ps->stats[ STAT_CLASS ] )->stopSpeed; + float friction = BG_Class( pm->ps->stats[ STAT_CLASS ] )->friction; + + control = speed < stopSpeed ? stopSpeed : speed; + drop += control * friction * pml.frametime; + } + } + } + + // apply water friction even if just wading + if( pm->waterlevel ) + drop += speed * pm_waterfriction * pm->waterlevel * pml.frametime; + + // apply flying friction + if( pm->ps->pm_type == PM_JETPACK ) + drop += speed * pm_flightfriction * pml.frametime; + + if( pm->ps->pm_type == PM_SPECTATOR ) + drop += speed * pm_spectatorfriction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if( newspeed < 0 ) + newspeed = 0; + + newspeed /= speed; + + vel[ 0 ] = vel[ 0 ] * newspeed; + vel[ 1 ] = vel[ 1 ] * newspeed; + vel[ 2 ] = vel[ 2 ] * newspeed; +} + + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) +{ +#if 1 + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct( pm->ps->velocity, wishdir ); + addspeed = wishspeed - currentspeed; + if( addspeed <= 0 ) + return; + + accelspeed = accel * pml.frametime * wishspeed; + if( accelspeed > addspeed ) + accelspeed = addspeed; + + for( i = 0; i < 3; i++ ) + pm->ps->velocity[ i ] += accelspeed * wishdir[ i ]; +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + vec3_t wishVelocity; + vec3_t pushDir; + float pushLen; + float canPush; + + VectorScale( wishdir, wishspeed, wishVelocity ); + VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); + pushLen = VectorNormalize( pushDir ); + + canPush = accel * pml.frametime * wishspeed; + if( canPush > pushLen ) + canPush = pushLen; + + VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); +#endif +} + + + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +static float PM_CmdScale( usercmd_t *cmd ) +{ + int max; + float total; + float scale; + float modifier = 1.0f; + + if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS && pm->ps->pm_type == PM_NORMAL ) + { + qboolean wasSprinting; + qboolean sprint; + wasSprinting = sprint = pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST; + + if( pm->ps->persistant[ PERS_STATE ] & PS_SPRINTTOGGLE ) + { + if( cmd->buttons & BUTTON_SPRINT && + !( pm->ps->pm_flags & PMF_SPRINTHELD ) ) + { + sprint = !sprint; + pm->ps->pm_flags |= PMF_SPRINTHELD; + } + else if( pm->ps->pm_flags & PMF_SPRINTHELD && + !( cmd->buttons & BUTTON_SPRINT ) ) + pm->ps->pm_flags &= ~PMF_SPRINTHELD; + } + else + sprint = cmd->buttons & BUTTON_SPRINT; + + if( sprint ) + pm->ps->stats[ STAT_STATE ] |= SS_SPEEDBOOST; + else if( wasSprinting && !sprint ) + pm->ps->stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; + + // Walk overrides sprint. We keep the state that we want to be sprinting + // (above), but don't apply the modifier, and in g_active we skip taking + // the stamina too. + if( sprint && !( cmd->buttons & BUTTON_WALKING ) ) + modifier *= HUMAN_SPRINT_MODIFIER; + else + modifier *= HUMAN_JOG_MODIFIER; + + if( cmd->forwardmove < 0 ) + { + //can't run backwards + modifier *= HUMAN_BACK_MODIFIER; + } + else if( cmd->rightmove ) + { + //can't move that fast sideways + modifier *= HUMAN_SIDE_MODIFIER; + } + + //must have have stamina or a jetpack to jump + if( pm->ps->stats[ STAT_STAMINA ] < STAMINA_SLOW_LEVEL + STAMINA_JUMP_TAKE && + ( !BG_InventoryContainsUpgrade( UP_JETPACK, pm->ps->stats ) || pm->ps->stats[ STAT_FUEL ] < JETPACK_FUEL_JUMP ) ) + cmd->upmove = 0; + + //slow down once stamina depletes + if( pm->ps->stats[ STAT_STAMINA ] <= STAMINA_SLOW_LEVEL ) + modifier *= (float)( pm->ps->stats[ STAT_STAMINA ] + STAMINA_MAX ) / (float)(STAMINA_SLOW_LEVEL + STAMINA_MAX); + + if( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED ) + { + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, pm->ps->stats ) || + BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) + modifier *= CREEP_ARMOUR_MODIFIER; + else + modifier *= CREEP_MODIFIER; + } + if( pm->ps->eFlags & EF_POISONCLOUDED ) + { + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, pm->ps->stats ) || + BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) + modifier *= PCLOUD_ARMOUR_MODIFIER; + else + modifier *= PCLOUD_MODIFIER; + } + } + + if( pm->ps->weapon == WP_ALEVEL4 && pm->ps->pm_flags & PMF_CHARGE ) + modifier *= 1.0f + ( pm->ps->stats[ STAT_MISC ] * + ( LEVEL4_TRAMPLE_SPEED - 1.0f ) / + LEVEL4_TRAMPLE_DURATION ); + + //slow player if charging up for a pounce + if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) && + cmd->buttons & BUTTON_ATTACK2 ) + modifier *= LEVEL3_POUNCE_SPEED_MOD; + + //slow the player if slow locked + if( pm->ps->stats[ STAT_STATE ] & SS_SLOWLOCKED ) + modifier *= ABUILDER_BLOB_SPEED_MOD; + + if( pm->ps->pm_type == PM_GRABBED ) + modifier = 0.0f; + + if( pm->ps->pm_type != PM_SPECTATOR && pm->ps->pm_type != PM_NOCLIP ) + { + if( BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude == 0.0f ) + cmd->upmove = 0; + + //prevent speed distortions for non ducking classes + if( !( pm->ps->pm_flags & PMF_DUCKED ) && pm->ps->pm_type != PM_JETPACK && cmd->upmove < 0 ) + cmd->upmove = 0; + } + + max = abs( cmd->forwardmove ); + if( abs( cmd->rightmove ) > max ) + max = abs( cmd->rightmove ); + + if( abs( cmd->upmove ) > max ) + max = abs( cmd->upmove ); + + if( !max ) + return 0; + + total = sqrt( cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); + + scale = (float)pm->ps->speed * max / ( 127.0 * total ) * modifier; + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) +{ + if( pm->cmd.forwardmove || pm->cmd.rightmove ) + { + if( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) + pm->ps->movementDir = 0; + else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) + pm->ps->movementDir = 1; + else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) + pm->ps->movementDir = 2; + else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) + pm->ps->movementDir = 3; + else if( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) + pm->ps->movementDir = 4; + else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) + pm->ps->movementDir = 5; + else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) + pm->ps->movementDir = 6; + else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) + pm->ps->movementDir = 7; + } + else + { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if( pm->ps->movementDir == 2 ) + pm->ps->movementDir = 1; + else if( pm->ps->movementDir == 6 ) + pm->ps->movementDir = 7; + } +} + + +/* +============= +PM_CheckCharge +============= +*/ +static void PM_CheckCharge( void ) +{ + if( pm->ps->weapon != WP_ALEVEL4 ) + return; + + if( pm->cmd.buttons & BUTTON_ATTACK2 && + !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) ) + { + pm->ps->pm_flags &= ~PMF_CHARGE; + return; + } + + if( pm->ps->stats[ STAT_MISC ] > 0 ) + pm->ps->pm_flags |= PMF_CHARGE; + else + pm->ps->pm_flags &= ~PMF_CHARGE; +} + +/* +============= +PM_CheckPounce +============= +*/ +static qboolean PM_CheckPounce( void ) +{ + int jumpMagnitude; + + if( pm->ps->weapon != WP_ALEVEL3 && + pm->ps->weapon != WP_ALEVEL3_UPG ) + return qfalse; + + // We were pouncing, but we've landed + if( pm->ps->groundEntityNum != ENTITYNUM_NONE && + ( pm->ps->pm_flags & PMF_CHARGE ) ) + { + pm->ps->pm_flags &= ~PMF_CHARGE; + pm->ps->weaponTime += LEVEL3_POUNCE_REPEAT; + return qfalse; + } + + // We're building up for a pounce + if( pm->cmd.buttons & BUTTON_ATTACK2 ) + { + pm->ps->pm_flags &= ~PMF_CHARGE; + return qfalse; + } + + // Can't start a pounce + if( ( pm->ps->pm_flags & PMF_CHARGE ) || + pm->ps->stats[ STAT_MISC ] < LEVEL3_POUNCE_TIME_MIN || + pm->ps->groundEntityNum == ENTITYNUM_NONE ) + return qfalse; + + // Give the player forward velocity and simulate a jump + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_CHARGE; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + if( pm->ps->weapon == WP_ALEVEL3 ) + jumpMagnitude = pm->ps->stats[ STAT_MISC ] * + LEVEL3_POUNCE_JUMP_MAG / LEVEL3_POUNCE_TIME; + else + jumpMagnitude = pm->ps->stats[ STAT_MISC ] * + LEVEL3_POUNCE_JUMP_MAG_UPG / LEVEL3_POUNCE_TIME_UPG; + VectorMA( pm->ps->velocity, jumpMagnitude, pml.forward, pm->ps->velocity ); + PM_AddEvent( EV_JUMP ); + + // Play jumping animation + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->pmext->pouncePayload = pm->ps->stats[ STAT_MISC ]; + pm->ps->stats[ STAT_MISC ] = 0; + + return qtrue; +} + +/* +============= +PM_CheckWallJump +============= +*/ +static qboolean PM_CheckWallJump( void ) +{ + vec3_t dir, forward, right, movedir, point; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + float normalFraction = 1.5f; + float cmdFraction = 1.0f; + float upFraction = 1.5f; + trace_t trace; + + if( !( BG_Class( pm->ps->stats[ STAT_CLASS ] )->abilities & SCA_WALLJUMPER ) ) + return qfalse; + + ProjectPointOnPlane( movedir, pml.forward, refNormal ); + VectorNormalize( movedir ); + + if( pm->cmd.forwardmove < 0 ) + VectorNegate( movedir, movedir ); + + //allow strafe transitions + if( pm->cmd.rightmove ) + { + VectorCopy( pml.right, movedir ); + + if( pm->cmd.rightmove < 0 ) + VectorNegate( movedir, movedir ); + } + + //trace into direction we are moving + VectorMA( pm->ps->origin, 0.25f, movedir, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + if( trace.fraction < 1.0f && + !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && + trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) + { + if( !VectorCompare( trace.plane.normal, pm->ps->grapplePoint ) ) + { + VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); + } + } + else + return qfalse; + + if( pm->ps->pm_flags & PMF_RESPAWNED ) + return qfalse; // don't allow jump until all buttons are up + + if( pm->cmd.upmove < 10 ) + // not holding jump + return qfalse; + + if( pm->ps->pm_flags & PMF_TIME_WALLJUMP ) + return qfalse; + + // must wait for jump to be released + if( pm->ps->pm_flags & PMF_JUMP_HELD && + pm->ps->grapplePoint[ 2 ] == 1.0f ) + { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + pm->ps->pm_flags |= PMF_TIME_WALLJUMP; + pm->ps->pm_time = 200; + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + + ProjectPointOnPlane( forward, pml.forward, pm->ps->grapplePoint ); + ProjectPointOnPlane( right, pml.right, pm->ps->grapplePoint ); + + VectorScale( pm->ps->grapplePoint, normalFraction, dir ); + + if( pm->cmd.forwardmove > 0 ) + VectorMA( dir, cmdFraction, forward, dir ); + else if( pm->cmd.forwardmove < 0 ) + VectorMA( dir, -cmdFraction, forward, dir ); + + if( pm->cmd.rightmove > 0 ) + VectorMA( dir, cmdFraction, right, dir ); + else if( pm->cmd.rightmove < 0 ) + VectorMA( dir, -cmdFraction, right, dir ); + + VectorMA( dir, upFraction, refNormal, dir ); + VectorNormalize( dir ); + + VectorMA( pm->ps->velocity, BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude, + dir, pm->ps->velocity ); + + //for a long run of wall jumps the velocity can get pretty large, this caps it + if( VectorLength( pm->ps->velocity ) > LEVEL2_WALLJUMP_MAXSPEED ) + { + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, LEVEL2_WALLJUMP_MAXSPEED, pm->ps->velocity ); + } + + PM_AddEvent( EV_JUMP ); + + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) +{ + vec3_t normal; + qboolean jetjump; +//ZdrytchX: Uhh... Okay then... + int jumpvel; + + if( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + return qfalse; + + if( BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude == 0.0f ) + return qfalse; + + //can't jump and pounce at the same time + if( ( pm->ps->weapon == WP_ALEVEL3 || + pm->ps->weapon == WP_ALEVEL3_UPG ) && + pm->ps->stats[ STAT_MISC ] > 0 ) + return qfalse; + + //can't jump and charge at the same time + if( ( pm->ps->weapon == WP_ALEVEL4 ) && + pm->ps->stats[ STAT_MISC ] > 0 ) + return qfalse; + + if( ( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS ) && + ( pm->ps->stats[ STAT_STAMINA ] < STAMINA_SLOW_LEVEL + STAMINA_JUMP_TAKE ) && + ( !BG_InventoryContainsUpgrade( UP_JETPACK, pm->ps->stats ) || pm->ps->stats[ STAT_FUEL ] < JETPACK_FUEL_JUMP ) ) + return qfalse; + + //no bunny hopping off a dodge + if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS && + pm->ps->pm_time ) + return qfalse; + + if( pm->ps->pm_flags & PMF_RESPAWNED ) + return qfalse; // don't allow jump until all buttons are up + + if( pm->cmd.upmove < 10 ) + // not holding jump + return qfalse; + + //can't jump whilst grabbed + if( pm->ps->pm_type == PM_GRABBED ) + { + pm->cmd.upmove = 0; + return qfalse; + } + + // must wait for jump to be released + if( pm->ps->pm_flags & PMF_JUMP_HELD ) + { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + //don't allow walljump for a short while after jumping from the ground + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) + { + pm->ps->pm_flags |= PMF_TIME_WALLJUMP; + pm->ps->pm_time = 200; + } + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + + // true if using the jetpack to jump (doesn't use stamina, only fuel) + jetjump = ( BG_InventoryContainsUpgrade( UP_JETPACK, pm->ps->stats ) && + pm->ps->stats[ STAT_FUEL ] >= JETPACK_FUEL_JUMP ); + + if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + if( !jetjump ) + pm->ps->stats[ STAT_STAMINA ] -= STAMINA_JUMP_TAKE; + else + pm->ps->stats[ STAT_FUEL ] -= JETPACK_FUEL_JUMP; + } + +//Alright, let's start the jumping process. + jumpvel = BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude; + + //ZdrytchX: check for double-jump + if(pm->ps->stats[ STAT_STAMINA ] > 0 //derp doesn't have STAMINA_MIN_TO_JUMP :/ + || pm->ps->stats[ STAT_TEAM ] == pm->ps->stats[ STAT_TEAM ] == TEAM_ALIENS + && !BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) + //Trust me. You don't want marauders flying out of the map from 3 wall jumps. + if (cpm_pm_jump_z) { + if (pm->ps->persistant[PERS_JUMPTIME] > 0) { + jumpvel += (cpm_pm_jump_z * BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude); + //Adding requires me to multiply by class vel again + } + pm->ps->persistant[PERS_JUMPTIME] = 400; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + + // jump away from wall + BG_GetClientNormal( pm->ps, normal ); + + if( pm->ps->velocity[ 2 ] < 0 ) + pm->ps->velocity[ 2 ] = 0; + + VectorMA( pm->ps->velocity, jumpvel, + normal, pm->ps->velocity ); + + if( jetjump ) + PM_AddEvent( EV_JETJUMP ); + else + PM_AddEvent( EV_JUMP ); + + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) +{ + vec3_t spot; + int cont; + vec3_t flatforward; + + if( pm->ps->pm_time ) + return qfalse; + + // check for water jump + if( pm->waterlevel != 2 ) + return qfalse; + + flatforward[ 0 ] = pml.forward[ 0 ]; + flatforward[ 1 ] = pml.forward[ 1 ]; + flatforward[ 2 ] = 0; + VectorNormalize( flatforward ); + + VectorMA( pm->ps->origin, 30, flatforward, spot ); + spot[ 2 ] += 4; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + + if( !( cont & CONTENTS_SOLID ) ) + return qfalse; + + spot[ 2 ] += 16; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + + if( cont ) + return qfalse; + + // jump out of water + VectorScale( pml.forward, 200, pm->ps->velocity ); + pm->ps->velocity[ 2 ] = 350; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) +{ + // waterjump has no control, but falls + + PM_StepSlideMove( qtrue, qfalse ); + + pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime; + if( pm->ps->velocity[ 2 ] < 0 ) + { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +static void PM_WaterMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if( PM_CheckWaterJump( ) ) + { + PM_WaterJumpMove(); + return; + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction( ); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if( !scale ) + { + wishvel[ 0 ] = 0; + wishvel[ 1 ] = 0; + wishvel[ 2 ] = -60; // sink towards bottom + } + else + { + for( i = 0; i < 3; i++ ) + wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; + + wishvel[ 2 ] += scale * pm->cmd.upmove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + if( wishspeed > pm->ps->speed * pm_swimScale ) + wishspeed = pm->ps->speed * pm_swimScale; + + PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate ); + + // make sure we can go up slopes easily under water + if( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) + { + vel = VectorLength( pm->ps->velocity ); + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + } + + PM_SlideMove( qfalse ); +} + +/* +=================== +PM_JetPackMove + +Only with the jetpack +=================== +*/ +static void PM_JetPackMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + //normal slowdown + PM_Friction( ); + + scale = PM_CmdScale( &pm->cmd ); + + // user intentions + for( i = 0; i < 2; i++ ) + wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; + + if( pm->cmd.upmove > 0.0f ) + wishvel[ 2 ] = JETPACK_FLOAT_SPEED; + else if( pm->cmd.upmove < 0.0f ) + wishvel[ 2 ] = -JETPACK_SINK_SPEED; + else + wishvel[ 2 ] = 0.0f; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); + + PM_StepSlideMove( qfalse, qfalse ); + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_LAND ); + else + PM_ContinueLegsAnim( NSPA_LAND ); +} + + + + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +static void PM_FlyMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + // normal slowdown + PM_Friction( ); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if( !scale ) + { + wishvel[ 0 ] = 0; + wishvel[ 1 ] = 0; + wishvel[ 2 ] = 0; + } + else + { + for( i = 0; i < 3; i++ ) + wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; + + wishvel[ 2 ] += scale * pm->cmd.upmove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); + + PM_StepSlideMove( qfalse, qfalse ); +} + +/* +=================== +PM_AirControl + +=================== +*/ +static void PM_AirControl(vec3_t wishdir, float wishspeed) +{ + float zspeed, speed, dot, k; + int i; + + if((pm->ps->movementDir && pm->ps->movementDir != 4) || wishspeed == 0.0) + return; // can't control movement if not moving forward or backward + + zspeed = pm->ps->velocity[2]; + pm->ps->velocity[2] = 0; + speed = VectorNormalize(pm->ps->velocity); + + dot = DotProduct(pm->ps->velocity, wishdir); + k = 32; + k *= pm_airControlAmount * BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration * dot * dot * pml.frametime; + + if(dot > 0) + { + // we can't change direction while slowing down + for(i = 0; i < 2; i++) + pm->ps->velocity[i] = pm->ps->velocity[i] * speed + wishdir[i] * k; + VectorNormalize(pm->ps->velocity); + } + + for(i = 0; i < 2; i++) + pm->ps->velocity[i] *= speed; + + pm->ps->velocity[2] = zspeed; +} + +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + + float accel; // CPM + float wishspeed2; // CPM + float velscale;//classes temp.var + + velscale = BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration; + + PM_CheckWallJump( ); + PM_Friction( ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir( ); + + // project moves down to flat plane + pml.forward[ 2 ] = 0; + pml.right[ 2 ] = 0; + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + for( i = 0; i < 2; i++ ) + wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove; + + wishvel[ 2 ] = 0; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + if (DotProduct(pm->ps->velocity, wishdir) < 0 && (pm_airaccelerate * velscale) < cpm_pm_airstopaccelerate )// Stop marauders from climbing walls easily + accel = cpm_pm_airstopaccelerate * velscale; + +//ZdrytchX: Use side strafes for Q1 style turning + if (pm->ps->movementDir == 2 || pm->ps->movementDir == 6 ) //Detect for side strafe buttons + { + wishspeed = cpm_pm_wishspeed * velscale; + accel = cpm_pm_strafeaccelerate * velscale; + PM_Accelerate (wishdir, wishspeed, accel); + } + else //accelerte normally + { + PM_Accelerate( wishdir, wishspeed, velscale ); + } + + // cpma air control + PM_AirControl (wishdir, wishspeed); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if( pml.groundPlane ) + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + PM_StepSlideMove( qtrue, qfalse ); +} + +/* +=================== +PM_ClimbMove + +=================== +*/ +static void PM_ClimbMove( void ) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + + if( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) + { + // begin swimming + PM_WaterMove( ); + return; + } + + + if( PM_CheckJump( ) || PM_CheckPounce( ) ) + { + // jumped away + if( pm->waterlevel > 1 ) + PM_WaterMove( ); + else + PM_AirMove( ); + + return; + } + + PM_Friction( ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir( ); + + // project the forward and right directions onto the ground plane + PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + for( i = 0; i < 3; i++ ) + wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove; + + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // clamp the speed lower if ducking + if( pm->ps->pm_flags & PMF_DUCKED ) + { + if( wishspeed > pm->ps->speed * pm_duckScale ) + wishspeed = pm->ps->speed * pm_duckScale; + } + + // clamp the speed lower if wading or walking on the bottom + if( pm->waterlevel ) + { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if( wishspeed > pm->ps->speed * waterScale ) + wishspeed = pm->ps->speed * waterScale; + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration; + else + accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->acceleration; + + PM_Accelerate( wishdir, wishspeed, accelerate ); + + if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime; + + vel = VectorLength( pm->ps->velocity ); + + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + + // don't do anything if standing still + if( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] && !pm->ps->velocity[ 2 ] ) + return; + + PM_StepSlideMove( qfalse, qfalse ); +} + + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( void ) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + + if( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) + { + // begin swimming + PM_WaterMove( ); + return; + } + + if( PM_CheckJump( ) || PM_CheckPounce( ) ) + { + // jumped away + if( pm->waterlevel > 1 ) + PM_WaterMove( ); + else + PM_AirMove( ); + + return; + } + + //charging + PM_CheckCharge( ); + + PM_Friction( ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir( ); + + // project moves down to flat plane + pml.forward[ 2 ] = 0; + pml.right[ 2 ] = 0; + + // project the forward and right directions onto the ground plane + PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + for( i = 0; i < 3; i++ ) + wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove; + + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // clamp the speed lower if ducking + if( pm->ps->pm_flags & PMF_DUCKED ) + { + if( wishspeed > pm->ps->speed * pm_duckScale ) + wishspeed = pm->ps->speed * pm_duckScale; + } + + // clamp the speed lower if wading or walking on the bottom + if( pm->waterlevel ) + { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if( wishspeed > pm->ps->speed * waterScale ) + wishspeed = pm->ps->speed * waterScale; + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration; + else + accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->acceleration; + + PM_Accelerate( wishdir, wishspeed, accelerate ); + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); + + if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime; + else + { + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + } + + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't do anything if standing still + if( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] ) + return; + + PM_StepSlideMove( qfalse, qfalse ); + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); + +} + + +/* +=================== +PM_LadderMove + +Basically a rip of PM_WaterMove with a few changes +=================== +*/ +static void PM_LadderMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + PM_Friction( ); + + scale = PM_CmdScale( &pm->cmd ); + + for( i = 0; i < 3; i++ ) + wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; + + wishvel[ 2 ] += scale * pm->cmd.upmove; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + if( wishspeed > pm->ps->speed * pm_swimScale ) + wishspeed = pm->ps->speed * pm_swimScale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + //slanty ladders + if( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0.0f ) + { + vel = VectorLength( pm->ps->velocity ); + + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + } + + PM_SlideMove( qfalse ); +} + + +/* +============= +PM_CheckLadder + +Check to see if the player is on a ladder or not +============= +*/ +static void PM_CheckLadder( void ) +{ + vec3_t forward, end; + trace_t trace; + + //test if class can use ladders + if( !BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_CANUSELADDERS ) ) + { + pml.ladder = qfalse; + return; + } + + VectorCopy( pml.forward, forward ); + forward[ 2 ] = 0.0f; + + VectorMA( pm->ps->origin, 1.0f, forward, end ); + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, MASK_PLAYERSOLID ); + + if( ( trace.fraction < 1.0f ) && ( trace.surfaceFlags & SURF_LADDER ) ) + pml.ladder = qtrue; + else + pml.ladder = qfalse; +} + + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) +{ + float forward; + + if( !pml.walking ) + return; + + // extra friction + + forward = VectorLength( pm->ps->velocity ); + forward -= 20; + + if( forward <= 0 ) + VectorClear( pm->ps->velocity ); + else + { + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, forward, pm->ps->velocity ); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) +{ + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + // friction + + speed = VectorLength( pm->ps->velocity ); + + if( speed < 1 ) + { + VectorCopy( vec3_origin, pm->ps->velocity ); + } + else + { + drop = 0; + + friction = pm_friction * 1.5; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control * friction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + + if( newspeed < 0 ) + newspeed = 0; + + newspeed /= speed; + + VectorScale( pm->ps->velocity, newspeed, pm->ps->velocity ); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for( i = 0; i < 3; i++ ) + wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove; + + wishvel[ 2 ] += pm->cmd.upmove; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA( pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin ); +} + +//============================================================================ + +/* +================ +PM_FootstepForSurface + +Returns an event number apropriate for the groundsurface +================ +*/ +static int PM_FootstepForSurface( void ) +{ + if( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED ) + return EV_FOOTSTEP_SQUELCH; + + if( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) + return 0; + + if( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) + return EV_FOOTSTEP_METAL; + + return EV_FOOTSTEP; +} + + +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ +static void PM_CrashLand( void ) +{ + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + + // decide which landing animation to use + if( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_LANDB ); + else + PM_ForceLegsAnim( NSPA_LANDBACK ); + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_LAND ); + else + PM_ForceLegsAnim( NSPA_LAND ); + } + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + pm->ps->legsTimer = TIMER_LAND; + else + pm->ps->torsoTimer = TIMER_LAND; + + // calculate the exact velocity on landing + dist = pm->ps->origin[ 2 ] - pml.previous_origin[ 2 ]; + vel = pml.previous_velocity[ 2 ]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if( den < 0 ) + return; + + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta*delta * 0.0001; + + // never take falling damage if completely underwater + if( pm->waterlevel == 3 ) + return; + + // reduce falling damage if there is standing water + if( pm->waterlevel == 2 ) + delta *= 0.25; + + if( pm->waterlevel == 1 ) + delta *= 0.5; + + if( delta < 1 ) + return; + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if( !( pml.groundTrace.surfaceFlags & SURF_NODAMAGE ) ) + { + pm->ps->stats[ STAT_FALLDIST ] = delta; + + if( delta > AVG_FALL_DISTANCE ) + { + if( PM_Live( pm->ps->pm_type ) ) + PM_AddEvent( EV_FALL_FAR ); + } + else if( delta > MIN_FALL_DISTANCE ) + { + if( PM_Live( pm->ps->pm_type ) ) + PM_AddEvent( EV_FALL_MEDIUM ); + } + else + { + if( delta > 7 ) + PM_AddEvent( EV_FALL_SHORT ); + else + PM_AddEvent( PM_FootstepForSurface( ) ); + } + } + + // start footstep cycle over + pm->ps->bobCycle = 0; +} + + +/* +============= +PM_CorrectAllSolid +============= +*/ +static int PM_CorrectAllSolid( trace_t *trace ) +{ + int i, j, k; + vec3_t point; + + if( pm->debugLevel ) + Com_Printf("%i:allsolid\n", c_pmove); + + // jitter around + for( i = -1; i <= 1; i++ ) + { + for( j = -1; j <= 1; j++ ) + { + for( k = -1; k <= 1; k++ ) + { + VectorCopy( pm->ps->origin, point ); + point[ 0 ] += (float)i; + point[ 1 ] += (float)j; + point[ 2 ] += (float)k; + pm->trace( trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + if( !trace->allsolid ) + { + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] - 0.25; + + pm->trace( trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + pml.groundTrace = *trace; + return qtrue; + } + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + + +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) +{ + trace_t trace; + vec3_t point; + + if( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + // we just transitioned into freefall + if( pm->debugLevel ) + Com_Printf( "%i:lift\n", c_pmove ); + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[ 2 ] -= 64.0f; + + pm->trace( &trace, pm->ps->origin, NULL, NULL, point, pm->ps->clientNum, pm->tracemask ); + if( trace.fraction == 1.0f ) + { + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + } + } + + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_TAKESFALLDAMAGE ) ) + { + if( pm->ps->velocity[ 2 ] < FALLING_THRESHOLD && pml.previous_velocity[ 2 ] >= FALLING_THRESHOLD ) + PM_AddEvent( EV_FALLING ); + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + +/* +============= +PM_GroundClimbTrace +============= +*/ +static void PM_GroundClimbTrace( void ) +{ + vec3_t surfNormal, movedir, lookdir, point; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f }; + vec3_t toAngles, surfAngles; + trace_t trace; + int i; + const float eps = 0.000001f; + + //used for delta correction + vec3_t traceCROSSsurf, traceCROSSref, surfCROSSref; + float traceDOTsurf, traceDOTref, surfDOTref, rTtDOTrTsTt; + float traceANGsurf, traceANGref, surfANGref; + vec3_t horizontal = { 1.0f, 0.0f, 0.0f }; //arbituary vector perpendicular to refNormal + vec3_t refTOtrace, refTOsurfTOtrace, tempVec; + int rTtANGrTsTt; + float ldDOTtCs, d; + vec3_t abc; + + BG_GetClientNormal( pm->ps, surfNormal ); + + //construct a vector which reflects the direction the player is looking wrt the surface normal + ProjectPointOnPlane( movedir, pml.forward, surfNormal ); + VectorNormalize( movedir ); + + VectorCopy( movedir, lookdir ); + + if( pm->cmd.forwardmove < 0 ) + VectorNegate( movedir, movedir ); + + //allow strafe transitions + if( pm->cmd.rightmove ) + { + VectorCopy( pml.right, movedir ); + + if( pm->cmd.rightmove < 0 ) + VectorNegate( movedir, movedir ); + } + + for( i = 0; i <= 4; i++ ) + { + switch ( i ) + { + case 0: + //we are going to step this frame so skip the transition test + if( PM_PredictStepMove( ) ) + continue; + + //trace into direction we are moving + VectorMA( pm->ps->origin, 0.25f, movedir, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + break; + + case 1: + //trace straight down anto "ground" surface + //mask out CONTENTS_BODY to not hit other players and avoid the camera flipping out when + // wallwalkers touch + VectorMA( pm->ps->origin, -0.25f, surfNormal, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask /*& ~CONTENTS_BODY*/ ); + break; + + case 2: + if( pml.groundPlane != qfalse && PM_PredictStepMove( ) ) + { + //step down + VectorMA( pm->ps->origin, -STEPSIZE, surfNormal, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + } + else + continue; + break; + + case 3: + //trace "underneath" BBOX so we can traverse angles > 180deg + if( pml.groundPlane != qfalse ) + { + VectorMA( pm->ps->origin, -16.0f, surfNormal, point ); + VectorMA( point, -16.0f, movedir, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + } + else + continue; + break; + + case 4: + //fall back so we don't have to modify PM_GroundTrace too much + VectorCopy( pm->ps->origin, point ); + point[ 2 ] = pm->ps->origin[ 2 ] - 0.25f; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + break; + } + + //if we hit something + if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) /*&& + !( trace.entityNum != ENTITYNUM_WORLD && i != 4 )*/ ) + { + if( i == 2 || i == 3 ) + { + if( i == 2 ) + PM_StepEvent( pm->ps->origin, trace.endpos, surfNormal ); + + VectorCopy( trace.endpos, pm->ps->origin ); + } + + //calculate a bunch of stuff... + CrossProduct( trace.plane.normal, surfNormal, traceCROSSsurf ); + VectorNormalize( traceCROSSsurf ); + + CrossProduct( trace.plane.normal, refNormal, traceCROSSref ); + VectorNormalize( traceCROSSref ); + + CrossProduct( surfNormal, refNormal, surfCROSSref ); + VectorNormalize( surfCROSSref ); + + //calculate angle between surf and trace + traceDOTsurf = DotProduct( trace.plane.normal, surfNormal ); + traceANGsurf = RAD2DEG( acos( traceDOTsurf ) ); + + if( traceANGsurf > 180.0f ) + traceANGsurf -= 180.0f; + + //calculate angle between trace and ref + traceDOTref = DotProduct( trace.plane.normal, refNormal ); + traceANGref = RAD2DEG( acos( traceDOTref ) ); + + if( traceANGref > 180.0f ) + traceANGref -= 180.0f; + + //calculate angle between surf and ref + surfDOTref = DotProduct( surfNormal, refNormal ); + surfANGref = RAD2DEG( acos( surfDOTref ) ); + + if( surfANGref > 180.0f ) + surfANGref -= 180.0f; + + //if the trace result and old surface normal are different then we must have transided to a new + //surface... do some stuff... + if( !VectorCompareEpsilon( trace.plane.normal, surfNormal, eps ) ) + { + //if the trace result or the old vector is not the floor or ceiling correct the YAW angle + if( !VectorCompareEpsilon( trace.plane.normal, refNormal, eps ) && !VectorCompareEpsilon( surfNormal, refNormal, eps ) && + !VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) && !VectorCompareEpsilon( surfNormal, ceilingNormal, eps ) ) + { + //behold the evil mindfuck from hell + //it has fucked mind like nothing has fucked mind before + + //calculate reference rotated through to trace plane + RotatePointAroundVector( refTOtrace, traceCROSSref, horizontal, -traceANGref ); + + //calculate reference rotated through to surf plane then to trace plane + RotatePointAroundVector( tempVec, surfCROSSref, horizontal, -surfANGref ); + RotatePointAroundVector( refTOsurfTOtrace, traceCROSSsurf, tempVec, -traceANGsurf ); + + //calculate angle between refTOtrace and refTOsurfTOtrace + rTtDOTrTsTt = DotProduct( refTOtrace, refTOsurfTOtrace ); + rTtANGrTsTt = ANGLE2SHORT( RAD2DEG( acos( rTtDOTrTsTt ) ) ); + + if( rTtANGrTsTt > 32768 ) + rTtANGrTsTt -= 32768; + + CrossProduct( refTOtrace, refTOsurfTOtrace, tempVec ); + VectorNormalize( tempVec ); + if( DotProduct( trace.plane.normal, tempVec ) > 0.0f ) + rTtANGrTsTt = -rTtANGrTsTt; + + //phew! - correct the angle + pm->ps->delta_angles[ YAW ] -= rTtANGrTsTt; + } + + //construct a plane dividing the surf and trace normals + CrossProduct( traceCROSSsurf, surfNormal, abc ); + VectorNormalize( abc ); + d = DotProduct( abc, pm->ps->origin ); + + //construct a point representing where the player is looking + VectorAdd( pm->ps->origin, lookdir, point ); + + //check whether point is on one side of the plane, if so invert the correction angle + if( ( abc[ 0 ] * point[ 0 ] + abc[ 1 ] * point[ 1 ] + abc[ 2 ] * point[ 2 ] - d ) > 0 ) + traceANGsurf = -traceANGsurf; + + //find the . product of the lookdir and traceCROSSsurf + if( ( ldDOTtCs = DotProduct( lookdir, traceCROSSsurf ) ) < 0.0f ) + { + VectorInverse( traceCROSSsurf ); + ldDOTtCs = DotProduct( lookdir, traceCROSSsurf ); + } + + //set the correction angle + traceANGsurf *= 1.0f - ldDOTtCs; + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_WALLCLIMBINGFOLLOW ) ) + { + //correct the angle + pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( traceANGsurf ); + } + + //transition from wall to ceiling + //normal for subsequent viewangle rotations + if( VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) ) + { + CrossProduct( surfNormal, trace.plane.normal, pm->ps->grapplePoint ); + VectorNormalize( pm->ps->grapplePoint ); + pm->ps->eFlags |= EF_WALLCLIMBCEILING; + } + + //transition from ceiling to wall + //we need to do some different angle correction here cos GPISROTVEC + if( VectorCompareEpsilon( surfNormal, ceilingNormal, eps ) ) + { + vectoangles( trace.plane.normal, toAngles ); + vectoangles( pm->ps->grapplePoint, surfAngles ); + + pm->ps->delta_angles[ 1 ] -= ANGLE2SHORT( ( ( surfAngles[ 1 ] - toAngles[ 1 ] ) * 2 ) - 180.0f ); + } + } + + pml.groundTrace = trace; + + //so everything knows where we're wallclimbing (ie client side) + pm->ps->eFlags |= EF_WALLCLIMB; + + //if we're not stuck to the ceiling then set grapplePoint to be a surface normal + if( !VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) ) + { + //so we know what surface we're stuck to + VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); + pm->ps->eFlags &= ~EF_WALLCLIMBCEILING; + } + + //IMPORTANT: break out of the for loop if we've hit something + break; + } + else if( trace.allsolid ) + { + // do something corrective if the trace starts in a solid... + if( !PM_CorrectAllSolid( &trace ) ) + return; + } + } + + if( trace.fraction >= 1.0f ) + { + // if the trace didn't hit anything, we are in free fall + PM_GroundTraceMissed( ); + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->eFlags &= ~EF_WALLCLIMB; + + //just transided from ceiling to floor... apply delta correction + if( pm->ps->eFlags & EF_WALLCLIMBCEILING ) + { + vec3_t forward, rotated, angles; + + AngleVectors( pm->ps->viewangles, forward, NULL, NULL ); + + RotatePointAroundVector( rotated, pm->ps->grapplePoint, forward, 180.0f ); + vectoangles( rotated, angles ); + + pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( angles[ YAW ] - pm->ps->viewangles[ YAW ] ); + } + + pm->ps->eFlags &= ~EF_WALLCLIMBCEILING; + + //we get very bizarre effects if we don't do this :0 + VectorCopy( refNormal, pm->ps->grapplePoint ); + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) + { + pm->ps->pm_flags &= ~PMF_TIME_WATERJUMP; + pm->ps->pm_time = 0; + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) +{ + vec3_t point; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + trace_t trace; + + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) ) + { + if( pm->ps->persistant[ PERS_STATE ] & PS_WALLCLIMBINGTOGGLE ) + { + //toggle wall climbing if holding crouch + if( pm->cmd.upmove < 0 && !( pm->ps->pm_flags & PMF_CROUCH_HELD ) ) + { + if( !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) + pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBING; + else if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + + pm->ps->pm_flags |= PMF_CROUCH_HELD; + } + else if( pm->cmd.upmove >= 0 ) + pm->ps->pm_flags &= ~PMF_CROUCH_HELD; + } + else + { + if( pm->cmd.upmove < 0 ) + pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBING; + else if( pm->cmd.upmove >= 0 ) + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + } + + if( pm->ps->pm_type == PM_DEAD ) + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + + if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) + { + PM_GroundClimbTrace( ); + return; + } + + //just transided from ceiling to floor... apply delta correction + if( pm->ps->eFlags & EF_WALLCLIMBCEILING ) + { + vec3_t forward, rotated, angles; + + AngleVectors( pm->ps->viewangles, forward, NULL, NULL ); + + RotatePointAroundVector( rotated, pm->ps->grapplePoint, forward, 180.0f ); + vectoangles( rotated, angles ); + + pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( angles[ YAW ] - pm->ps->viewangles[ YAW ] ); + } + } + + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + pm->ps->eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); + + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] - 0.25f; + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if( trace.allsolid ) + if( !PM_CorrectAllSolid( &trace ) ) + return; + + //make sure that the surfNormal is reset to the ground + VectorCopy( refNormal, pm->ps->grapplePoint ); + + // if the trace didn't hit anything, we are in free fall + if( trace.fraction == 1.0f ) + { + qboolean steppedDown = qfalse; + + // try to step down + if( pml.groundPlane != qfalse && PM_PredictStepMove( ) && pm->ps->velocity[ 2 ] > 0.0f ) + { + //step down + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] - STEPSIZE; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + //if we hit something + if( trace.fraction < 1.0f ) + { + PM_StepEvent( pm->ps->origin, trace.endpos, refNormal ); + VectorCopy( trace.endpos, pm->ps->origin ); + steppedDown = qtrue; + } + } + + if( !steppedDown ) + { + PM_GroundTraceMissed( ); + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return; + } + } + + // check if getting thrown off the ground + if( pm->ps->velocity[ 2 ] > 0.0f && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10.0f ) + { + if( pm->debugLevel ) + Com_Printf( "%i:kickoff\n", c_pmove ); + + // go into jump animation + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if( trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) + { + if( pm->debugLevel ) + Com_Printf( "%i:steep\n", c_pmove ); + + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) + { + pm->ps->pm_flags &= ~PMF_TIME_WATERJUMP; + pm->ps->pm_time = 0; + } + + if( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + // just hit the ground + if( pm->debugLevel ) + Com_Printf( "%i:Land\n", c_pmove ); + + // communicate the fall velocity to the server + pm->pmext->fallVelocity = pml.previous_velocity[ 2 ]; + + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_TAKESFALLDAMAGE ) ) + PM_CrashLand( ); + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving +============= +*/ +static void PM_SetWaterLevel( void ) +{ + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + 1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if( cont & MASK_WATER ) + { + sample2 = pm->ps->viewheight - MINS_Z; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + sample1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if( cont & MASK_WATER ) + { + pm->waterlevel = 2; + point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + sample2; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if( cont & MASK_WATER ) + pm->waterlevel = 3; + } + } +} + + + +/* +============== +PM_SetViewheight +============== +*/ +static void PM_SetViewheight( void ) +{ + pm->ps->viewheight = ( pm->ps->pm_flags & PMF_DUCKED ) + ? BG_ClassConfig( pm->ps->stats[ STAT_CLASS ] )->crouchViewheight + : BG_ClassConfig( pm->ps->stats[ STAT_CLASS ] )->viewheight; +} + +/* +============== +PM_CheckDuck + +Sets mins and maxs, and calls PM_SetViewheight +============== +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + vec3_t PCmins, PCmaxs, PCcmaxs; + + BG_ClassBoundingBox( pm->ps->stats[ STAT_CLASS ], PCmins, PCmaxs, PCcmaxs, NULL, NULL ); + + pm->mins[ 0 ] = PCmins[ 0 ]; + pm->mins[ 1 ] = PCmins[ 1 ]; + + pm->maxs[ 0 ] = PCmaxs[ 0 ]; + pm->maxs[ 1 ] = PCmaxs[ 1 ]; + + pm->mins[ 2 ] = PCmins[ 2 ]; + + if( pm->ps->pm_type == PM_DEAD ) + { + pm->maxs[ 2 ] = -8; + pm->ps->viewheight = PCmins[ 2 ] + DEAD_VIEWHEIGHT; + return; + } + + // If the standing and crouching bboxes are the same the class can't crouch + if( ( pm->cmd.upmove < 0 ) && !VectorCompare( PCmaxs, PCcmaxs ) && + pm->ps->pm_type != PM_JETPACK ) + { + // duck + pm->ps->pm_flags |= PMF_DUCKED; + } + else + { + // stand up if possible + if( pm->ps->pm_flags & PMF_DUCKED ) + { + // try to stand up + pm->maxs[ 2 ] = PCmaxs[ 2 ]; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if( !trace.allsolid ) + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + + if( pm->ps->pm_flags & PMF_DUCKED ) + pm->maxs[ 2 ] = PCcmaxs[ 2 ]; + else + pm->maxs[ 2 ] = PCmaxs[ 2 ]; + + PM_SetViewheight( ); +} + + + +//=================================================================== + + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) +{ + float bobmove; + int old; + qboolean footstep; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) && ( pml.groundPlane ) ) + { + // FIXME: yes yes i know this is wrong + pm->xyspeed = sqrt( pm->ps->velocity[ 0 ] * pm->ps->velocity[ 0 ] + + pm->ps->velocity[ 1 ] * pm->ps->velocity[ 1 ] + + pm->ps->velocity[ 2 ] * pm->ps->velocity[ 2 ] ); + } + else + pm->xyspeed = sqrt( pm->ps->velocity[ 0 ] * pm->ps->velocity[ 0 ] + + pm->ps->velocity[ 1 ] * pm->ps->velocity[ 1 ] ); + + if( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + // airborne leaves position in cycle intact, but doesn't advance + if( pm->waterlevel > 1 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_SWIM ); + else + PM_ContinueLegsAnim( NSPA_SWIM ); + } + + return; + } + + // if not trying to move + if( !pm->cmd.forwardmove && !pm->cmd.rightmove ) + { + if( pm->xyspeed < 5 ) + { + pm->ps->bobCycle = 0; // start at beginning of cycle again + if( pm->ps->pm_flags & PMF_DUCKED ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_IDLECR ); + else + PM_ContinueLegsAnim( NSPA_STAND ); + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_IDLE ); + else + PM_ContinueLegsAnim( NSPA_STAND ); + } + } + return; + } + + + footstep = qfalse; + + if( pm->ps->pm_flags & PMF_DUCKED ) + { + bobmove = 0.5; // ducked characters bob much faster + + if( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_BACKCR ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKLEFT ); + else + PM_ContinueLegsAnim( NSPA_WALKBACK ); + } + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_WALKCR ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKLEFT ); + else + PM_ContinueLegsAnim( NSPA_WALK ); + } + } + + // ducked characters never play footsteps + } + else + { + if( !( pm->cmd.buttons & BUTTON_WALKING ) ) + { + bobmove = 0.4f; // faster speeds bob faster + + if( pm->ps->weapon == WP_ALEVEL4 && pm->ps->pm_flags & PMF_CHARGE ) + PM_ContinueLegsAnim( NSPA_CHARGE ); + else if( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_BACK ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_RUNRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_RUNLEFT ); + else + PM_ContinueLegsAnim( NSPA_RUNBACK ); + } + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_RUN ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_RUNRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_RUNLEFT ); + else + PM_ContinueLegsAnim( NSPA_RUN ); + } + } + + footstep = qtrue; + } + else + { + bobmove = 0.3f; // walking bobs slow + if( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_BACKWALK ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKLEFT ); + else + PM_ContinueLegsAnim( NSPA_WALKBACK ); + } + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_WALK ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKLEFT ); + else + PM_ContinueLegsAnim( NSPA_WALK ); + } + } + } + } + + bobmove *= BG_Class( pm->ps->stats[ STAT_CLASS ] )->bobCycle; + + if( pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST ) + bobmove *= HUMAN_SPRINT_MODIFIER; + + // check for footstep / splash sounds + old = pm->ps->bobCycle; + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) + { + if( pm->waterlevel == 0 ) + { + // on ground will only play sounds if running + if( footstep && !pm->noFootsteps ) + PM_AddEvent( PM_FootstepForSurface( ) ); + } + else if( pm->waterlevel == 1 ) + { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } + else if( pm->waterlevel == 2 ) + { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } + else if( pm->waterlevel == 3 ) + { + // no sound when completely underwater + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) +{ + // FIXME? + // + // if just entered a water volume, play a sound + // + if( !pml.previous_waterlevel && pm->waterlevel ) + PM_AddEvent( EV_WATER_TOUCH ); + + // + // if just completely exited a water volume, play a sound + // + if( pml.previous_waterlevel && !pm->waterlevel ) + PM_AddEvent( EV_WATER_LEAVE ); + + // + // check for head just going under water + // + if( pml.previous_waterlevel != 3 && pm->waterlevel == 3 ) + PM_AddEvent( EV_WATER_UNDER ); + + // + // check for head just coming out of water + // + if( pml.previous_waterlevel == 3 && pm->waterlevel != 3 ) + PM_AddEvent( EV_WATER_CLEAR ); +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +static void PM_BeginWeaponChange( int weapon ) +{ + if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) + return; + + if( !BG_InventoryContainsWeapon( weapon, pm->ps->stats ) ) + return; + + if( pm->ps->weaponstate == WEAPON_DROPPING ) + return; + + // cancel a reload + pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD; + if( pm->ps->weaponstate == WEAPON_RELOADING ) + pm->ps->weaponTime = 0; + + //special case to prevent storing a charged up lcannon + if( pm->ps->weapon == WP_LUCIFER_CANNON ) + pm->ps->stats[ STAT_MISC ] = 0; + + pm->ps->weaponstate = WEAPON_DROPPING; + pm->ps->weaponTime += 200; + pm->ps->persistant[ PERS_NEWWEAPON ] = weapon; + + //reset build weapon + pm->ps->stats[ STAT_BUILDABLE ] = BA_NONE; + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + PM_StartTorsoAnim( TORSO_DROP ); + PM_StartWeaponAnim( WANIM_DROP ); + } +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +static void PM_FinishWeaponChange( void ) +{ + int weapon; + + PM_AddEvent( EV_CHANGE_WEAPON ); + weapon = pm->ps->persistant[ PERS_NEWWEAPON ]; + if( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) + weapon = WP_NONE; + + if( !BG_InventoryContainsWeapon( weapon, pm->ps->stats ) ) + weapon = WP_NONE; + + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + PM_StartTorsoAnim( TORSO_RAISE ); + PM_StartWeaponAnim( WANIM_RAISE ); + } +} + + +/* +============== +PM_TorsoAnimation + +============== +*/ +static void PM_TorsoAnimation( void ) +{ + if( pm->ps->weaponstate == WEAPON_READY ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->weapon == WP_BLASTER ) + PM_ContinueTorsoAnim( TORSO_STAND2 ); + else + PM_ContinueTorsoAnim( TORSO_STAND ); + } + + PM_ContinueWeaponAnim( WANIM_IDLE ); + } +} + + +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ +static void PM_Weapon( void ) +{ + int addTime = 200; //default addTime - should never be used + qboolean attack1 = pm->cmd.buttons & BUTTON_ATTACK; + qboolean attack2 = pm->cmd.buttons & BUTTON_ATTACK2; + qboolean attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; + + // Ignore weapons in some cases + if( pm->ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) + return; + + // Check for dead player + if( pm->ps->stats[ STAT_HEALTH ] <= 0 ) + { + pm->ps->weapon = WP_NONE; + return; + } + + // Charging for a pounce or canceling a pounce + if( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) + { + int max; + + max = pm->ps->weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME : + LEVEL3_POUNCE_TIME_UPG; + if( pm->cmd.buttons & BUTTON_ATTACK2 ) + pm->ps->stats[ STAT_MISC ] += pml.msec; + else + pm->ps->stats[ STAT_MISC ] -= pml.msec; + + if( pm->ps->stats[ STAT_MISC ] > max ) + pm->ps->stats[ STAT_MISC ] = max; + else if( pm->ps->stats[ STAT_MISC ] < 0 ) + pm->ps->stats[ STAT_MISC ] = 0; + } + + // Trample charge mechanics + if( pm->ps->weapon == WP_ALEVEL4 ) + { + // Charging up + if( !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) ) + { + // Charge button held + if( pm->ps->stats[ STAT_MISC ] < LEVEL4_TRAMPLE_CHARGE_TRIGGER && + ( pm->cmd.buttons & BUTTON_ATTACK2 ) ) + { + pm->ps->stats[ STAT_STATE ] &= ~SS_CHARGING; + if( pm->cmd.forwardmove > 0 ) + { + int charge = pml.msec; + vec3_t dir,vel; + + AngleVectors( pm->ps->viewangles, dir, NULL, NULL ); + VectorCopy( pm->ps->velocity, vel ); + vel[2] = 0; + dir[2] = 0; + VectorNormalize( vel ); + VectorNormalize( dir ); + + charge *= DotProduct( dir, vel ); + + pm->ps->stats[ STAT_MISC ] += charge; + } + else + pm->ps->stats[ STAT_MISC ] = 0; + } + + // Charge button released + else if( !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) ) + { + if( pm->ps->stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MIN ) + { + if( pm->ps->stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MAX ) + pm->ps->stats[ STAT_MISC ] = LEVEL4_TRAMPLE_CHARGE_MAX; + pm->ps->stats[ STAT_MISC ] = pm->ps->stats[ STAT_MISC ] * + LEVEL4_TRAMPLE_DURATION / + LEVEL4_TRAMPLE_CHARGE_MAX; + pm->ps->stats[ STAT_STATE ] |= SS_CHARGING; + PM_AddEvent( EV_LEV4_TRAMPLE_START ); + } + else + pm->ps->stats[ STAT_MISC ] -= pml.msec; + } + } + + // Discharging + else + { + if( pm->ps->stats[ STAT_MISC ] < LEVEL4_TRAMPLE_CHARGE_MIN ) + pm->ps->stats[ STAT_MISC ] = 0; + else + pm->ps->stats[ STAT_MISC ] -= pml.msec; + + // If the charger has stopped moving take a chunk of charge away + if( VectorLength( pm->ps->velocity ) < 64.0f || pm->cmd.rightmove ) + pm->ps->stats[ STAT_MISC ] -= LEVEL4_TRAMPLE_STOP_PENALTY * pml.msec; + } + + // Charge is over + if( pm->ps->stats[ STAT_MISC ] <= 0 || pm->cmd.forwardmove <= 0 ) + { + pm->ps->stats[ STAT_MISC ] = 0; + pm->ps->stats[ STAT_STATE ] &= ~SS_CHARGING; + } + } + + // Charging up a Lucifer Cannon + pm->ps->eFlags &= ~EF_WARN_CHARGE; + if( pm->ps->weapon == WP_LUCIFER_CANNON ) + { + // Charging up + if( !pm->ps->weaponTime && pm->ps->weaponstate != WEAPON_NEEDS_RESET && + ( pm->cmd.buttons & BUTTON_ATTACK ) ) + { + pm->ps->stats[ STAT_MISC ] += pml.msec; + if( pm->ps->stats[ STAT_MISC ] >= LCANNON_CHARGE_TIME_MAX ) + pm->ps->stats[ STAT_MISC ] = LCANNON_CHARGE_TIME_MAX; + if( pm->ps->stats[ STAT_MISC ] > pm->ps->ammo * LCANNON_CHARGE_TIME_MAX / + LCANNON_CHARGE_AMMO ) + pm->ps->stats[ STAT_MISC ] = pm->ps->ammo * LCANNON_CHARGE_TIME_MAX / + LCANNON_CHARGE_AMMO; + } + + // Set overcharging flag so other players can hear the warning beep + if( pm->ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_WARN ) + pm->ps->eFlags |= EF_WARN_CHARGE; + } + + // don't allow attack until all buttons are up + if( pm->ps->pm_flags & PMF_RESPAWNED ) + return; + + // no bite during pounce + if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) + && ( pm->cmd.buttons & BUTTON_ATTACK ) + && ( pm->ps->pm_flags & PMF_CHARGE ) ) + return; + + // pump weapon delays (repeat times etc) + if( pm->ps->weaponTime > 0 ) + pm->ps->weaponTime -= pml.msec; + if( pm->ps->weaponTime < 0 ) + pm->ps->weaponTime = 0; + + // no slash during charge + if( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) + return; + + // check for weapon change + // can't change if weapon is firing, but can change + // again if lowering or raising + if( BG_PlayerCanChangeWeapon( pm->ps ) ) + { + // must press use to switch weapons + if( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) + { + if( !( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) + { + if( pm->cmd.weapon < 32 ) + { + //if trying to select a weapon, select it + if( pm->ps->weapon != pm->cmd.weapon ) + PM_BeginWeaponChange( pm->cmd.weapon ); + } + else + { + //if trying to toggle an upgrade, toggle it + if( BG_InventoryContainsUpgrade( pm->cmd.weapon - 32, pm->ps->stats ) ) //sanity check + { + if( BG_UpgradeIsActive( pm->cmd.weapon - 32, pm->ps->stats ) ) + BG_DeactivateUpgrade( pm->cmd.weapon - 32, pm->ps->stats ); + else + BG_ActivateUpgrade( pm->cmd.weapon - 32, pm->ps->stats ); + } + } + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + } + } + else + pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; + + //something external thinks a weapon change is necessary + if( pm->ps->pm_flags & PMF_WEAPON_SWITCH ) + { + pm->ps->pm_flags &= ~PMF_WEAPON_SWITCH; + if( pm->ps->weapon != WP_NONE ) + { + // drop the current weapon + PM_BeginWeaponChange( pm->ps->persistant[ PERS_NEWWEAPON ] ); + } + else + { + // no current weapon, so just raise the new one + PM_FinishWeaponChange( ); + } + } + } + + if( pm->ps->weaponTime > 0 ) + return; + + // change weapon if time + if( pm->ps->weaponstate == WEAPON_DROPPING ) + { + PM_FinishWeaponChange( ); + return; + } + + // Set proper animation + if( pm->ps->weaponstate == WEAPON_RAISING ) + { + pm->ps->weaponstate = WEAPON_READY; + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->weapon == WP_BLASTER ) + PM_ContinueTorsoAnim( TORSO_STAND2 ); + else + PM_ContinueTorsoAnim( TORSO_STAND ); + } + + PM_ContinueWeaponAnim( WANIM_IDLE ); + + return; + } + + // check for out of ammo + if( !pm->ps->ammo && !pm->ps->clips && !BG_Weapon( pm->ps->weapon )->infiniteAmmo ) + { + if( attack1 || + ( BG_Weapon( pm->ps->weapon )->hasAltMode && attack2 ) || + ( BG_Weapon( pm->ps->weapon )->hasThirdMode && attack3 ) ) + { + PM_AddEvent( EV_NOAMMO ); + pm->ps->weaponTime += 500; + } + + if( pm->ps->weaponstate == WEAPON_FIRING ) + pm->ps->weaponstate = WEAPON_READY; + + return; + } + + //done reloading so give em some ammo + if( pm->ps->weaponstate == WEAPON_RELOADING ) + { + pm->ps->clips--; + pm->ps->ammo = BG_Weapon( pm->ps->weapon )->maxAmmo; + + if( BG_Weapon( pm->ps->weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, pm->ps->stats ) ) + pm->ps->ammo *= BATTPACK_MODIFIER; + + //allow some time for the weapon to be raised + pm->ps->weaponstate = WEAPON_RAISING; + PM_StartTorsoAnim( TORSO_RAISE ); + pm->ps->weaponTime += 250; + return; + } + + // check for end of clip + if( !BG_Weapon( pm->ps->weapon )->infiniteAmmo && + ( pm->ps->ammo <= 0 || ( pm->ps->pm_flags & PMF_WEAPON_RELOAD ) ) && + pm->ps->clips > 0 ) + { + pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD; + pm->ps->weaponstate = WEAPON_RELOADING; + + //drop the weapon + PM_StartTorsoAnim( TORSO_DROP ); + PM_StartWeaponAnim( WANIM_RELOAD ); + + pm->ps->weaponTime += BG_Weapon( pm->ps->weapon )->reloadTime; + return; + } + + //check if non-auto primary/secondary attacks are permited + switch( pm->ps->weapon ) + { + case WP_ALEVEL0: + //venom is only autohit + return; + + case WP_ALEVEL3: + case WP_ALEVEL3_UPG: + //pouncing has primary secondary AND autohit procedures + // pounce is autohit + if( !attack1 && !attack2 && !attack3 ) + return; + break; + + case WP_LUCIFER_CANNON: + attack3 = qfalse; + + // Prevent firing of the Lucifer Cannon after an overcharge + if( pm->ps->weaponstate == WEAPON_NEEDS_RESET ) + { + if( attack1 ) + return; + pm->ps->weaponstate = WEAPON_READY; + } + + // Can't fire secondary while primary is charging + if( attack1 || pm->ps->stats[ STAT_MISC ] > 0 ) + attack2 = qfalse; + + if( ( attack1 || pm->ps->stats[ STAT_MISC ] == 0 ) && !attack2 ) + { + pm->ps->weaponTime = 0; + + // Charging + if( pm->ps->stats[ STAT_MISC ] < LCANNON_CHARGE_TIME_MAX ) + { + pm->ps->weaponstate = WEAPON_READY; + return; + } + + // Overcharge + pm->ps->weaponstate = WEAPON_NEEDS_RESET; + } + + if( pm->ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_MIN ) + { + // Fire primary attack + attack1 = qtrue; + attack2 = qfalse; + } + else if( pm->ps->stats[ STAT_MISC ] > 0 ) + { + // Not enough charge + pm->ps->stats[ STAT_MISC ] = 0; + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + else if( !attack2 ) + { + // Idle + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + break; + + case WP_MASS_DRIVER: + attack2 = attack3 = qfalse; + // attack2 is handled on the client for zooming (cg_view.c) + + if( !attack1 ) + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + break; + + default: + if( !attack1 && !attack2 && !attack3 ) + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + break; + } + + // fire events for non auto weapons + if( attack3 ) + { + if( BG_Weapon( pm->ps->weapon )->hasThirdMode ) + { + //hacky special case for slowblob + if( ( pm->ps->weapon == WP_ALEVEL3_UPG || + pm->ps->weapon == WP_ALEVEL4 ) + && !pm->ps->ammo ) + { + pm->ps->weaponTime += 200; + return; + } + + pm->ps->generic1 = WPM_TERTIARY; + PM_AddEvent( EV_FIRE_WEAPON3 ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate3; + } + else + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + pm->ps->generic1 = WPM_NOTFIRING; + return; + } + } + else if( attack2 ) + { + if( BG_Weapon( pm->ps->weapon )->hasAltMode ) + { + pm->ps->generic1 = WPM_SECONDARY; + PM_AddEvent( EV_FIRE_WEAPON2 ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate2; + } + else + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + pm->ps->generic1 = WPM_NOTFIRING; + return; + } + } + else if( attack1 ) + { + pm->ps->generic1 = WPM_PRIMARY; + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate1; + } + + // fire events for autohit weapons + if( pm->autoWeaponHit[ pm->ps->weapon ] ) + { + switch( pm->ps->weapon ) + { + case WP_ALEVEL0: + pm->ps->generic1 = WPM_PRIMARY; + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate1; + break; + + case WP_ALEVEL3: + case WP_ALEVEL3_UPG: + pm->ps->generic1 = WPM_SECONDARY; + PM_AddEvent( EV_FIRE_WEAPON2 ); + addTime = BG_Weapon( pm->ps->weapon )->repeatRate2; + break; + + default: + break; + } + } + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + //FIXME: this should be an option in the client weapon.cfg + switch( pm->ps->weapon ) + { + case WP_FLAMER: + if( pm->ps->weaponstate == WEAPON_READY ) + { + PM_StartTorsoAnim( TORSO_ATTACK ); + PM_StartWeaponAnim( WANIM_ATTACK1 ); + } + break; + + case WP_BLASTER: + PM_StartTorsoAnim( TORSO_ATTACK2 ); + PM_StartWeaponAnim( WANIM_ATTACK1 ); + break; + + default: + PM_StartTorsoAnim( TORSO_ATTACK ); + PM_StartWeaponAnim( WANIM_ATTACK1 ); + break; + } + } + else + { + int num = rand( ); + + //FIXME: it would be nice to have these hard coded policies in + // weapon.cfg + switch( pm->ps->weapon ) + { + case WP_ALEVEL1_UPG: + case WP_ALEVEL1: + if( attack1 ) + { + num /= RAND_MAX / 6 + 1; + PM_ForceLegsAnim( NSPA_ATTACK1 ); + PM_StartWeaponAnim( WANIM_ATTACK1 + num ); + } + break; + + case WP_ALEVEL2_UPG: + if( attack2 ) + { + PM_ForceLegsAnim( NSPA_ATTACK2 ); + PM_StartWeaponAnim( WANIM_ATTACK7 ); + } + case WP_ALEVEL2: + if( attack1 ) + { + num /= RAND_MAX / 6 + 1; + PM_ForceLegsAnim( NSPA_ATTACK1 ); + PM_StartWeaponAnim( WANIM_ATTACK1 + num ); + } + break; + + case WP_ALEVEL4: + num /= RAND_MAX / 3 + 1; + PM_ForceLegsAnim( NSPA_ATTACK1 + num ); + PM_StartWeaponAnim( WANIM_ATTACK1 + num ); + break; + + default: + if( attack1 ) + { + PM_ForceLegsAnim( NSPA_ATTACK1 ); + PM_StartWeaponAnim( WANIM_ATTACK1 ); + } + else if( attack2 ) + { + PM_ForceLegsAnim( NSPA_ATTACK2 ); + PM_StartWeaponAnim( WANIM_ATTACK2 ); + } + else if( attack3 ) + { + PM_ForceLegsAnim( NSPA_ATTACK3 ); + PM_StartWeaponAnim( WANIM_ATTACK3 ); + } + break; + } + + pm->ps->torsoTimer = TIMER_ATTACK; + } + + if( pm->ps->weaponstate != WEAPON_NEEDS_RESET ) + pm->ps->weaponstate = WEAPON_FIRING; + + // take an ammo away if not infinite + if( !BG_Weapon( pm->ps->weapon )->infiniteAmmo || + ( pm->ps->weapon == WP_ALEVEL3_UPG && attack3 ) || + ( pm->ps->weapon == WP_ALEVEL4 && attack3 )) + { + // Special case for lcannon + if( pm->ps->weapon == WP_LUCIFER_CANNON && attack1 && !attack2 ) + pm->ps->ammo -= ( pm->ps->stats[ STAT_MISC ] * LCANNON_CHARGE_AMMO + + LCANNON_CHARGE_TIME_MAX - 1 ) / LCANNON_CHARGE_TIME_MAX; + else + pm->ps->ammo--; + + // Stay on the safe side + if( pm->ps->ammo < 0 ) + pm->ps->ammo = 0; + } + + //FIXME: predicted angles miss a problem?? + if( pm->ps->weapon == WP_CHAINGUN ) + { + if( pm->ps->pm_flags & PMF_DUCKED || + BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) + { + pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( ( ( random() * 0.5 ) - 0.125 ) * ( 30 / (float)addTime ) ); + pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( ( ( random() * 0.5 ) - 0.25 ) * ( 30.0 / (float)addTime ) ); + } + else + { + pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( ( ( random() * 8 ) - 2 ) * ( 30.0 / (float)addTime ) ); + pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( ( ( random() * 8 ) - 4 ) * ( 30.0 / (float)addTime ) ); + } + } + + pm->ps->weaponTime += addTime; +} + +/* +================ +PM_Animate +================ +*/ +static void PM_Animate( void ) +{ + if( PM_Paralyzed( pm->ps->pm_type ) ) + return; + + if( pm->cmd.buttons & BUTTON_GESTURE ) + { + if( pm->ps->tauntTimer > 0 ) + return; + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->torsoTimer == 0 ) + { + PM_StartTorsoAnim( TORSO_GESTURE ); + pm->ps->torsoTimer = TIMER_GESTURE; + pm->ps->tauntTimer = TIMER_GESTURE; + + PM_AddEvent( EV_TAUNT ); + } + } + else + { + if( pm->ps->torsoTimer == 0 ) + { + PM_ForceLegsAnim( NSPA_GESTURE ); + pm->ps->torsoTimer = TIMER_GESTURE; + pm->ps->tauntTimer = TIMER_GESTURE; + + PM_AddEvent( EV_TAUNT ); + } + } + } +} + + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) +{ + // drop misc timing counter + if( pm->ps->pm_time ) + { + if( pml.msec >= pm->ps->pm_time ) + { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } + else + pm->ps->pm_time -= pml.msec; + } + + // drop animation counter + if( pm->ps->legsTimer > 0 ) + { + pm->ps->legsTimer -= pml.msec; + + if( pm->ps->legsTimer < 0 ) + pm->ps->legsTimer = 0; + } + + if( pm->ps->torsoTimer > 0 ) + { + pm->ps->torsoTimer -= pml.msec; + + if( pm->ps->torsoTimer < 0 ) + pm->ps->torsoTimer = 0; + } + + if( pm->ps->tauntTimer > 0 ) + { + pm->ps->tauntTimer -= pml.msec; + + if( pm->ps->tauntTimer < 0 ) + { + pm->ps->tauntTimer = 0; + } + } +} + + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated instead of a full move +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) +{ + short temp[ 3 ]; + int i; + vec3_t axis[ 3 ], rotaxis[ 3 ]; + vec3_t tempang; + + if( ps->pm_type == PM_INTERMISSION ) + return; // no view changes at all + + if( ps->pm_type != PM_SPECTATOR && ps->stats[ STAT_HEALTH ] <= 0 ) + return; // no view changes at all + + // circularly clamp the angles with deltas + for( i = 0; i < 3; i++ ) + { + if( i == ROLL ) + { + // Guard against speed hack + temp[ i ] = ps->delta_angles[ i ]; + +#ifdef CGAME + // Assert here so that if cmd->angles[ i ] becomes non-zero + // for a legitimate reason we can tell where and why it's + // being ignored + assert( cmd->angles[ i ] == 0 ); +#endif + + } + else + temp[ i ] = cmd->angles[ i ] + ps->delta_angles[ i ]; + + if( i == PITCH ) + { + // don't let the player look up or down more than 90 degrees + if( temp[ i ] > 16000 ) + { + ps->delta_angles[ i ] = 16000 - cmd->angles[ i ]; + temp[ i ] = 16000; + } + else if( temp[ i ] < -16000 ) + { + ps->delta_angles[ i ] = -16000 - cmd->angles[ i ]; + temp[ i ] = -16000; + } + } + tempang[ i ] = SHORT2ANGLE( temp[ i ] ); + } + + //convert viewangles -> axis + AnglesToAxis( tempang, axis ); + + if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) || + !BG_RotateAxis( ps->grapplePoint, axis, rotaxis, qfalse, + ps->eFlags & EF_WALLCLIMBCEILING ) ) + AxisCopy( axis, rotaxis ); + + //convert the new axis back to angles + AxisToAngles( rotaxis, tempang ); + + //force angles to -180 <= x <= 180 + for( i = 0; i < 3; i++ ) + { + while( tempang[ i ] > 180.0f ) + tempang[ i ] -= 360.0f; + + while( tempang[ i ] < -180.0f ) + tempang[ i ] += 360.0f; + } + + //actually set the viewangles + for( i = 0; i < 3; i++ ) + ps->viewangles[ i ] = tempang[ i ]; + + //pull the view into the lock point + if( ps->pm_type == PM_GRABBED && !BG_InventoryContainsUpgrade( UP_BATTLESUIT, ps->stats ) ) + { + vec3_t dir, angles; + + ByteToDir( ps->stats[ STAT_VIEWLOCK ], dir ); + vectoangles( dir, angles ); + + for( i = 0; i < 3; i++ ) + { + float diff = AngleSubtract( ps->viewangles[ i ], angles[ i ] ); + + while( diff > 180.0f ) + diff -= 360.0f; + while( diff < -180.0f ) + diff += 360.0f; + + if( diff < -90.0f ) + ps->delta_angles[ i ] += ANGLE2SHORT( fabs( diff ) - 90.0f ); + else if( diff > 90.0f ) + ps->delta_angles[ i ] -= ANGLE2SHORT( fabs( diff ) - 90.0f ); + + if( diff < 0.0f ) + ps->delta_angles[ i ] += ANGLE2SHORT( fabs( diff ) * 0.05f ); + else if( diff > 0.0f ) + ps->delta_angles[ i ] -= ANGLE2SHORT( fabs( diff ) * 0.05f ); + } + } +} + + +/* +================ +PmoveSingle + +================ +*/ +void trap_SnapVector( float *v ); + +void PmoveSingle( pmove_t *pmove ) +{ + pm = pmove; + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; + pm->watertype = 0; + pm->waterlevel = 0; + + if( pm->ps->stats[ STAT_HEALTH ] <= 0 ) + pm->tracemask &= ~CONTENTS_BODY; + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + if( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) + pm->cmd.buttons &= ~BUTTON_WALKING; + + // set the firing flag for continuous beam weapons + if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION && + ( pm->cmd.buttons & BUTTON_ATTACK ) && + ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) ) + pm->ps->eFlags |= EF_FIRING; + else + pm->ps->eFlags &= ~EF_FIRING; + + // set the firing flag for continuous beam weapons + if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION && + ( pm->cmd.buttons & BUTTON_ATTACK2 ) && + ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) ) + pm->ps->eFlags |= EF_FIRING2; + else + pm->ps->eFlags &= ~EF_FIRING2; + + // set the firing flag for continuous beam weapons + if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION && + ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) && + ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) ) + pm->ps->eFlags |= EF_FIRING3; + else + pm->ps->eFlags &= ~EF_FIRING3; + + + // clear the respawned flag if attack and use are cleared + if( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) ) + pm->ps->pm_flags &= ~PMF_RESPAWNED; + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if( pmove->cmd.buttons & BUTTON_TALK ) + { + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + + if( pmove->cmd.upmove > 0 ) + pmove->cmd.upmove = 0; + } + + // clear all pmove local vars + memset( &pml, 0, sizeof( pml ) ); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + + if( pml.msec < 1 ) + pml.msec = 1; + else if( pml.msec > 200 ) + pml.msec = 200; + + pm->ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy( pm->ps->origin, pml.previous_origin ); + + // save old velocity for crashlanding + VectorCopy( pm->ps->velocity, pml.previous_velocity ); + + pml.frametime = pml.msec * 0.001; + + AngleVectors( pm->ps->viewangles, pml.forward, pml.right, pml.up ); + + if( pm->cmd.upmove < 10 ) + { + // not holding jump + pm->ps->pm_flags &= ~PMF_JUMP_HELD; + } + + // decide if backpedaling animations should be used + if( pm->cmd.forwardmove < 0 ) + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + else if( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) + pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; + + if( PM_Paralyzed( pm->ps->pm_type ) ) + { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + if( pm->ps->pm_type == PM_SPECTATOR ) + { + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + PM_CheckDuck( ); + PM_FlyMove( ); + PM_DropTimers( ); + return; + } + + if( pm->ps->pm_type == PM_NOCLIP ) + { + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + PM_NoclipMove( ); + PM_SetViewheight( ); + PM_Weapon( ); + PM_DropTimers( ); + return; + } + + if( pm->ps->pm_type == PM_FREEZE) + return; // no movement at all + + if( pm->ps->pm_type == PM_INTERMISSION ) + return; // no movement at all + + // set watertype, and waterlevel + PM_SetWaterLevel( ); + pml.previous_waterlevel = pmove->waterlevel; + + // set mins, maxs, and viewheight + PM_CheckDuck( ); + + PM_CheckLadder( ); + + // set groundentity + PM_GroundTrace( ); + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + if( pm->ps->pm_type == PM_DEAD || pm->ps->pm_type == PM_GRABBED ) + PM_DeadMove( ); + + PM_DropTimers( ); + //Incremenet jumptime status + if (pm->ps->persistant[PERS_JUMPTIME] > 0) pm->ps->persistant[PERS_JUMPTIME] -= pml.msec; + + if( pm->ps->pm_type == PM_JETPACK ) + PM_JetPackMove( ); + else if( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) + PM_WaterJumpMove( ); + else if( pm->waterlevel > 1 ) + PM_WaterMove( ); + else if( pml.ladder ) + PM_LadderMove( ); + else if( pml.walking ) + { + if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) && + ( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) + PM_ClimbMove( ); // walking on any surface + else + PM_WalkMove( ); // walking on ground + } + else + PM_AirMove( ); + + PM_Animate( ); + + // set groundentity, watertype, and waterlevel + PM_GroundTrace( ); + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + PM_SetWaterLevel( ); + + // weapons + PM_Weapon( ); + + // torso animation + PM_TorsoAnimation( ); + + // footstep events / legs animations + PM_Footsteps( ); + + // entering / leaving water splashes + PM_WaterEvents( ); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity ); +} + + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove( pmove_t *pmove ) +{ + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if( finalTime < pmove->ps->commandTime ) + return; // should not happen + + if( finalTime > pmove->ps->commandTime + 1000 ) + pmove->ps->commandTime = finalTime - 1000; + + pmove->ps->pmove_framecount = ( pmove->ps->pmove_framecount + 1 ) & ( ( 1 << PS_PMOVEFRAMECOUNTBITS ) - 1 ); + + // chop the move up if it is too long, to prevent framerate + // dependent behavior + while( pmove->ps->commandTime != finalTime ) + { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if( pmove->pmove_fixed ) + { + if( msec > pmove->pmove_msec ) + msec = pmove->pmove_msec; + } + else + { + if( msec > 66 ) + msec = 66; + } + + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + PmoveSingle( pmove ); + + if( pmove->ps->pm_flags & PMF_JUMP_HELD ) + pmove->cmd.upmove = 20; + } +} diff --git a/src/game/bg_public.h b/src/game/bg_public.h new file mode 100644 index 0000000..9355ac6 --- /dev/null +++ b/src/game/bg_public.h @@ -0,0 +1,1409 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// bg_public.h -- definitions shared by both the server game and client game modules + +//tremulous balance header +#include "tremulous.h" + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame +#define GAME_VERSION "base" + +#define DEFAULT_GRAVITY 800 + +#define VOTE_TIME 30000 // 30 seconds before vote times out + +#define MINS_Z -24 +#define DEFAULT_VIEWHEIGHT 26 +#define CROUCH_VIEWHEIGHT 12 +#define DEAD_VIEWHEIGHT 4 // height from ground + +#define COMPARE_FLOAT_EPSILON(a,b) ((a)>(b)-0.0001f&&(a)<(b)+0.0001f) +#define COMPARE_VECTOR_EPSILON(v,x,y,z) (COMPARE_FLOAT_EPSILON((v)[0],(x))&&COMPARE_FLOAT_EPSILON((v)[1],(y))&&COMPARE_FLOAT_EPSILON((v)[2],(z))) + +// player teams +typedef enum +{ + TEAM_NONE, + TEAM_ALIENS, + TEAM_HUMANS, + + NUM_TEAMS +} team_t; + +// +// config strings are a general means of communicating variable length strings +// from the server to all connected clients. +// + +// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h +enum +{ + CS_MUSIC = 2, + CS_MESSAGE, // from the map worldspawn's message field + CS_MOTD, // g_motd string for server message of the day + CS_WARMUP, // server time when the match will be restarted + + CS_VOTE_TIME, // Vote stuff each needs NUM_TEAMS slots + CS_VOTE_STRING = CS_VOTE_TIME + NUM_TEAMS, + CS_VOTE_YES = CS_VOTE_STRING + NUM_TEAMS, + CS_VOTE_NO = CS_VOTE_YES + NUM_TEAMS, + CS_VOTE_CALLER = CS_VOTE_NO + NUM_TEAMS, + + CS_GAME_VERSION = CS_VOTE_CALLER + NUM_TEAMS, + CS_LEVEL_START_TIME, // so the timer only shows the current level + CS_INTERMISSION, // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two + CS_WINNER , // string indicating round winner + CS_SHADERSTATE, + CS_BOTINFO, + CS_CLIENTS_READY, + + CS_ALIEN_STAGES, + CS_HUMAN_STAGES, + + CS_MODELS, + CS_SOUNDS = CS_MODELS + MAX_MODELS, + CS_SHADERS = CS_SOUNDS + MAX_SOUNDS, + CS_PARTICLE_SYSTEMS = CS_SHADERS + MAX_GAME_SHADERS, + CS_PLAYERS = CS_PARTICLE_SYSTEMS + MAX_GAME_PARTICLE_SYSTEMS, + CS_LOCATIONS = CS_PLAYERS + MAX_CLIENTS, + + CS_MAX = CS_LOCATIONS + MAX_LOCATIONS +}; + +#if CS_MAX > MAX_CONFIGSTRINGS +#error overflow: CS_MAX > MAX_CONFIGSTRINGS +#endif + +typedef enum +{ + GENDER_MALE, + GENDER_FEMALE, + GENDER_NEUTER +} gender_t; + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + +typedef enum +{ + PM_NORMAL, // can accelerate and turn + PM_NOCLIP, // noclip movement + PM_SPECTATOR, // still run into walls + PM_JETPACK, // jetpack physics + PM_GRABBED, // like dead, but for when the player is still live + PM_DEAD, // no acceleration or turning, but free falling + PM_FREEZE, // stuck in place with no control + PM_INTERMISSION // no movement or status bar +} pmtype_t; + +// pmtype_t categories +#define PM_Paralyzed( x ) ( (x) == PM_DEAD || (x) == PM_FREEZE ||\ + (x) == PM_INTERMISSION ) +#define PM_Live( x ) ( (x) == PM_NORMAL || (x) == PM_JETPACK ||\ + (x) == PM_GRABBED ) + +typedef enum +{ + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_FIRING, + WEAPON_RELOADING, + WEAPON_NEEDS_RESET, +} weaponstate_t; + +// pmove->pm_flags +#define PMF_DUCKED 0x000001 +#define PMF_JUMP_HELD 0x000002 +#define PMF_CROUCH_HELD 0x000004 +#define PMF_BACKWARDS_JUMP 0x000008 // go into backwards land +#define PMF_BACKWARDS_RUN 0x000010 // coast down to backwards run +#define PMF_TIME_LAND 0x000020 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 0x000040 // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP 0x000080 // pm_time is waterjump +#define PMF_RESPAWNED 0x000100 // clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD 0x000200 +#define PMF_WEAPON_RELOAD 0x000400 // force a weapon switch +#define PMF_FOLLOW 0x000800 // spectate following another player +#define PMF_QUEUED 0x001000 // player is queued +#define PMF_TIME_WALLJUMP 0x002000 // for limiting wall jumping +#define PMF_CHARGE 0x004000 // keep track of pouncing +#define PMF_WEAPON_SWITCH 0x008000 // force a weapon switch +#define PMF_SPRINTHELD 0x010000 + + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK|PMF_TIME_WALLJUMP) + +typedef struct +{ + int pouncePayload; + float fallVelocity; +} pmoveExt_t; + +#define MAXTOUCH 32 +typedef struct pmove_s +{ + // state (in / out) + playerState_t *ps; + pmoveExt_t *pmext; + // command (in) + usercmd_t cmd; + int tracemask; // collide against these types of surfaces + int debugLevel; // if set, diagnostic output will be printed + qboolean noFootsteps; // if the game is setup for no footsteps by the server + qboolean autoWeaponHit[ 32 ]; + + int framecount; + + // results (out) + int numtouch; + int touchents[ MAXTOUCH ]; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + float xyspeed; + + // for fixed msec Pmove + int pmove_fixed; + int pmove_msec; + + // callbacks to test the world + // these will be different functions during game and cgame + /*void (*trace)( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask );*/ + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, + const vec3_t end, int passEntityNum, int contentMask ); + + + int (*pointcontents)( const vec3_t point, int passEntityNum ); +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove( pmove_t *pmove ); + +//=================================================================================== + + +// player_state->stats[] indexes +typedef enum +{ + STAT_HEALTH, + STAT_ITEMS, + STAT_ACTIVEITEMS, + STAT_WEAPON, // current primary weapon + STAT_MAX_HEALTH,// health / armor limit, changable by handicap + STAT_CLASS, // player class (for aliens AND humans) + STAT_TEAM, // player team + STAT_STAMINA, // stamina (human only) + STAT_STATE, // client states e.g. wall climbing + STAT_MISC, // for uh...misc stuff (pounce, trample, lcannon) + STAT_BUILDABLE, // which ghost model to display for building + STAT_FALLDIST, // the distance the player fell + STAT_VIEWLOCK, // direction to lock the view in + STAT_FUEL // jetpacks + // netcode has space for 2 more +} statIndex_t; + +#define SCA_WALLCLIMBER 0x00000001 +#define SCA_TAKESFALLDAMAGE 0x00000002 +#define SCA_CANZOOM 0x00000004 +#define SCA_FOVWARPS 0x00000008 +#define SCA_ALIENSENSE 0x00000010 +#define SCA_CANUSELADDERS 0x00000020 +#define SCA_WALLJUMPER 0x00000040 + +#define SS_WALLCLIMBING 0x00000001 +#define SS_CREEPSLOWED 0x00000002 +#define SS_SPEEDBOOST 0x00000004 +#define SS_GRABBED 0x00000008 +#define SS_BLOBLOCKED 0x00000010 +#define SS_POISONED 0x00000020 +#define SS_BOOSTED 0x00000040 +#define SS_BOOSTEDWARNING 0x00000080 // booster poison is running out +#define SS_SLOWLOCKED 0x00000100 +#define SS_CHARGING 0x00000200 +#define SS_HEALING_ACTIVE 0x00000400 // medistat for humans, creep for aliens +#define SS_HEALING_2X 0x00000800 // medkit or double healing rate +#define SS_HEALING_3X 0x00001000 // triple healing rate + +#define SB_VALID_TOGGLEBIT 0x00002000 + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +typedef enum +{ + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_HITS, // total points damage inflicted so damage beeps can sound on change + PERS_SPAWNS, // how many spawns your team has + PERS_SPECSTATE, + PERS_SPAWN_COUNT, // incremented every respawn + PERS_ATTACKER, // clientnum of last damage inflicter + PERS_KILLED, // count of the number of times you died + + PERS_STATE, + PERS_CREDIT, // human credit + PERS_QUEUEPOS, // position in the spawn queue + PERS_NEWWEAPON, // weapon to switch to + PERS_BP, + PERS_MARKEDBP, + //zdrytchx: no space in stats, use persistant. This meanas we risk doing a double jump upon spawning but death animations are 1700 msecs long, so technically it's impossible anyway + PERS_JUMPTIME, + PERS_SPAWNS_IMPLANTED + // netcode has space for 1 more +} persEnum_t; + +#define PS_WALLCLIMBINGFOLLOW 0x00000001 +#define PS_WALLCLIMBINGTOGGLE 0x00000002 +#define PS_NONSEGMODEL 0x00000004 +#define PS_SPRINTTOGGLE 0x00000008 + +// entityState_t->eFlags +// notice that some flags are overlapped, so their meaning depends on context +#define EF_DEAD 0x0001 // don't draw a foe marker over players with EF_DEAD +#define EF_TELEPORT_BIT 0x0002 // toggled every time the origin abruptly changes +#define EF_PLAYER_EVENT 0x0004 // only used for eType > ET_EVENTS + +// for missiles: +#define EF_BOUNCE 0x0008 // for missiles +#define EF_BOUNCE_HALF 0x0010 // for missiles +#define EF_NO_BOUNCE_SOUND 0x0020 // for missiles + +// buildable flags: +#define EF_B_SPAWNED 0x0008 +#define EF_B_POWERED 0x0010 +#define EF_B_MARKED 0x0020 + +#define EF_WARN_CHARGE 0x0020 // Lucifer Cannon is about to overcharge +#define EF_WALLCLIMB 0x0040 // wall walking +#define EF_WALLCLIMBCEILING 0x0080 // wall walking ceiling hack +#define EF_NODRAW 0x0100 // may have an event, but no model (unspawned items) +#define EF_FIRING 0x0200 // for lightning gun +#define EF_FIRING2 0x0400 // alt fire +#define EF_FIRING3 0x0800 // third fire +#define EF_MOVER_STOP 0x1000 // will push otherwise +#define EF_POISONCLOUDED 0x2000 // player hit with basilisk gas +#define EF_CONNECTION 0x4000 // draw a connection trouble sprite +#define EF_BLOBLOCKED 0x8000 // caught by a trapper + +typedef enum +{ + HI_NONE, + + HI_TELEPORTER, + HI_MEDKIT, + + HI_NUM_HOLDABLE +} holdable_t; + +typedef enum +{ + WPM_NONE, + + WPM_PRIMARY, + WPM_SECONDARY, + WPM_TERTIARY, + + WPM_NOTFIRING, + + WPM_NUM_WEAPONMODES +} weaponMode_t; + +typedef enum +{ + WP_NONE, + + WP_ALEVEL0, + WP_ALEVEL1, + WP_ALEVEL1_UPG, + WP_ALEVEL2, + WP_ALEVEL2_UPG, + WP_ALEVEL3, + WP_ALEVEL3_UPG, + WP_ALEVEL4, + + WP_BLASTER, + WP_MACHINEGUN, + WP_PAIN_SAW, + WP_SHOTGUN, + WP_LAS_GUN, + WP_MASS_DRIVER, + WP_CHAINGUN, + WP_FLAMER, + WP_PULSE_RIFLE, + WP_LUCIFER_CANNON, + WP_GRENADE, + + WP_LOCKBLOB_LAUNCHER, + WP_HIVE, + WP_TESLAGEN, + WP_MGTURRET, + + //build weapons must remain in a block + WP_ABUILD, + WP_ABUILD2, + WP_HBUILD, + //ok? + + WP_NUM_WEAPONS +} weapon_t; + +typedef enum +{ + UP_NONE, + + UP_LIGHTARMOUR, + UP_HELMET_MK1, + UP_HELMET_MK2, + UP_MEDKIT, + UP_BATTPACK, + UP_JETPACK, + UP_BATTLESUIT, + UP_GRENADE, + UP_BIORES, + + UP_AMMO, + + UP_NUM_UPGRADES +} upgrade_t; + +// bitmasks for upgrade slots +#define SLOT_NONE 0x00000000 +#define SLOT_HEAD 0x00000001 +#define SLOT_TORSO 0x00000002 +#define SLOT_ARMS 0x00000004 +#define SLOT_LEGS 0x00000008 +#define SLOT_BACKPACK 0x00000010 +#define SLOT_WEAPON 0x00000020 +#define SLOT_SIDEARM 0x00000040 + +typedef enum +{ + BA_NONE, + + BA_A_SPAWN, + BA_A_OVERMIND, + + BA_A_BARRICADE, + BA_A_ACIDTUBE, + BA_A_TRAPPER, + BA_A_BOOSTER, + BA_A_HIVE, + + BA_H_SPAWN, + + BA_H_MGTURRET, + BA_H_TESLAGEN, + + BA_H_ARMOURY, + BA_H_DCC, + BA_H_MEDISTAT, + + BA_H_REACTOR, + BA_H_REPEATER, + + //cuboids must stay in a block +#define CUBOID_FIRST BA_H_CUBOID1 + BA_H_CUBOID1, + BA_H_CUBOID2, + BA_H_CUBOID3, + BA_A_CUBOID1, + BA_A_CUBOID2, +#define CUBOID_LAST BA_A_CUBOID2 +#define CUBOID_TYPES (CUBOID_LAST-CUBOID_FIRST+1) + + BA_NUM_BUILDABLES +} buildable_t; + +// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) +#define PLAYEREVENT_DENIEDREWARD 0x0001 +#define PLAYEREVENT_GAUNTLETREWARD 0x0002 +#define PLAYEREVENT_HOLYSHIT 0x0004 + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) + +#define EVENT_VALID_MSEC 300 + +const char *BG_EventName( int num ); + +typedef enum +{ + EV_NONE, + + EV_FOOTSTEP, + EV_FOOTSTEP_METAL, + EV_FOOTSTEP_SQUELCH, + EV_FOOTSPLASH, + EV_FOOTWADE, + EV_SWIM, + + EV_STEP_4, + EV_STEP_8, + EV_STEP_12, + EV_STEP_16, + + EV_STEPDN_4, + EV_STEPDN_8, + EV_STEPDN_12, + EV_STEPDN_16, + + EV_FALL_SHORT, + EV_FALL_MEDIUM, + EV_FALL_FAR, + EV_FALLING, + + EV_JUMP, + EV_JETJUMP, + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + EV_FIRE_WEAPON2, + EV_FIRE_WEAPON3, + + EV_PLAYER_RESPAWN, // for fovwarp effects + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + + EV_BULLET_HIT_FLESH, + EV_BULLET_HIT_WALL, + + EV_SHOTGUN, + EV_MASS_DRIVER, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + EV_MISSILE_MISS_METAL, + EV_TESLATRAIL, + EV_BULLET, // otherEntity is the shooter + + EV_LEV1_GRAB, + EV_LEV4_TRAMPLE_PREPARE, + EV_LEV4_TRAMPLE_START, + + EV_PAIN, + EV_DEATH1, + EV_DEATH2, + EV_DEATH3, + EV_OBITUARY, + + EV_GIB_PLAYER, // gib a previously living player + + EV_BUILD_CONSTRUCT, + EV_BUILD_DESTROY, + EV_BUILD_DELAY, // can't build yet + EV_BUILD_REPAIR, // repairing buildable + EV_BUILD_REPAIRED, // buildable has full health + EV_HUMAN_BUILDABLE_EXPLOSION, + EV_ALIEN_BUILDABLE_EXPLOSION, + EV_CUBOID_EXPLOSION, + EV_ALIEN_ACIDTUBE, + + EV_MEDKIT_USED, + + EV_ALIEN_EVOLVE, + EV_ALIEN_EVOLVE_FAILED, + + EV_DEBUG_LINE, + EV_STOPLOOPINGSOUND, + EV_TAUNT, + + EV_OVERMIND_ATTACK, // overmind under attack + EV_OVERMIND_DYING, // overmind close to death + EV_OVERMIND_SPAWNS, // overmind needs spawns + + EV_DCC_ATTACK, // dcc under attack + + EV_MGTURRET_SPINUP, // turret spinup sound should play + + EV_RPTUSE_SOUND, // trigger a sound + EV_LEV2_ZAP, + + EV_ALIEN_HATCH, // when an alien pops out of a human's chest + EV_ALIEN_HATCH_FAILURE, // when it doesns't work + + EV_JETPACK_DEACTIVATE, + EV_JETPACK_REFUEL + +} entity_event_t; + +typedef enum +{ + MN_NONE, + + MN_TEAM, + MN_A_TEAMFULL, + MN_H_TEAMFULL, + MN_A_TEAMLOCKED, + MN_H_TEAMLOCKED, + MN_PLAYERLIMIT, + MN_WARMUP, + + // cmd stuff + MN_CMD_CHEAT, + MN_CMD_CHEAT_TEAM, + MN_CMD_TEAM, + MN_CMD_SPEC, + MN_CMD_ALIEN, + MN_CMD_HUMAN, + MN_CMD_LIVING, + + //alien stuff + MN_A_CLASS, + MN_A_BUILD, + MN_A_INFEST, + MN_A_NOEROOM, + MN_A_TOOCLOSE, + MN_A_NOOVMND_EVOLVE, + MN_A_EVOLVEBUILDTIMER, + MN_A_CANTEVOLVE, + MN_A_EVOLVEWALLWALK, + MN_A_UNKNOWNCLASS, + MN_A_CLASSNOTSPAWN, + MN_A_CLASSNOTALLOWED, + MN_A_CLASSNOTATSTAGE, + + //shared build + MN_B_NOROOM, + MN_B_NORMAL, + MN_B_CANNOT, + MN_B_LASTSPAWN, + MN_B_SUDDENDEATH, + MN_B_REVOKED, + MN_B_SURRENDER, + MN_B_NOSURF, + MN_B_CUBOID_MODE1, + MN_B_CUBOID_MODE2, + MN_B_TOODENSE, + + //alien build + MN_A_ONEOVERMIND, + MN_A_NOBP, + MN_A_NOCREEP, + MN_A_NOOVMND, + + //human stuff + MN_H_SPAWN, + MN_H_BUILD, + MN_H_ARMOURY, + MN_H_UNKNOWNITEM, + MN_H_NOSLOTS, + MN_H_NOFUNDS, + MN_H_ITEMHELD, + MN_H_NOARMOURYHERE, + MN_H_NOENERGYAMMOHERE, + MN_H_NOROOMBSUITON, + MN_H_NOROOMBSUITOFF, + MN_H_ARMOURYBUILDTIMER, + MN_H_DEADTOCLASS, + MN_H_UNKNOWNSPAWNITEM, + + //human build + MN_H_NOPOWERHERE, + MN_H_NOBP, + MN_H_NOTPOWERED, + MN_H_NODCC, + MN_H_ONEREACTOR, + MN_H_RPTPOWERHERE, +} dynMenu_t; + +// animations +typedef enum +{ + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATH3, + BOTH_DEAD3, + + TORSO_GESTURE, + + TORSO_ATTACK, + TORSO_ATTACK2, + + TORSO_DROP, + TORSO_RAISE, + + TORSO_STAND, + TORSO_STAND2, + + LEGS_WALKCR, + LEGS_WALK, + LEGS_RUN, + LEGS_BACK, + LEGS_SWIM, + + LEGS_JUMP, + LEGS_LAND, + + LEGS_JUMPB, + LEGS_LANDB, + + LEGS_IDLE, + LEGS_IDLECR, + + LEGS_TURN, + + TORSO_GETFLAG, + TORSO_GUARDBASE, + TORSO_PATROL, + TORSO_FOLLOWME, + TORSO_AFFIRMATIVE, + TORSO_NEGATIVE, + + MAX_PLAYER_ANIMATIONS, + + LEGS_BACKCR, + LEGS_BACKWALK, + FLAG_RUN, + FLAG_STAND, + FLAG_STAND2RUN, + + MAX_PLAYER_TOTALANIMATIONS +} playerAnimNumber_t; + +// nonsegmented animations +typedef enum +{ + NSPA_STAND, + + NSPA_GESTURE, + + NSPA_WALK, + NSPA_RUN, + NSPA_RUNBACK, + NSPA_CHARGE, + + NSPA_RUNLEFT, + NSPA_WALKLEFT, + NSPA_RUNRIGHT, + NSPA_WALKRIGHT, + + NSPA_SWIM, + + NSPA_JUMP, + NSPA_LAND, + NSPA_JUMPBACK, + NSPA_LANDBACK, + + NSPA_TURN, + + NSPA_ATTACK1, + NSPA_ATTACK2, + NSPA_ATTACK3, + + NSPA_PAIN1, + NSPA_PAIN2, + + NSPA_DEATH1, + NSPA_DEAD1, + NSPA_DEATH2, + NSPA_DEAD2, + NSPA_DEATH3, + NSPA_DEAD3, + + MAX_NONSEG_PLAYER_ANIMATIONS, + + NSPA_WALKBACK, + + MAX_NONSEG_PLAYER_TOTALANIMATIONS +} nonSegPlayerAnimNumber_t; + +// for buildable animations +typedef enum +{ + BANIM_NONE, + + BANIM_CONSTRUCT1, + BANIM_CONSTRUCT2, + + BANIM_IDLE1, + BANIM_IDLE2, + BANIM_IDLE3, + + BANIM_ATTACK1, + BANIM_ATTACK2, + + BANIM_SPAWN1, + BANIM_SPAWN2, + + BANIM_PAIN1, + BANIM_PAIN2, + + BANIM_DESTROY1, + BANIM_DESTROY2, + BANIM_DESTROYED, + + MAX_BUILDABLE_ANIMATIONS +} buildableAnimNumber_t; + +typedef enum +{ + WANIM_NONE, + + WANIM_IDLE, + + WANIM_DROP, + WANIM_RELOAD, + WANIM_RAISE, + + WANIM_ATTACK1, + WANIM_ATTACK2, + WANIM_ATTACK3, + WANIM_ATTACK4, + WANIM_ATTACK5, + WANIM_ATTACK6, + WANIM_ATTACK7, + WANIM_ATTACK8, + + MAX_WEAPON_ANIMATIONS +} weaponAnimNumber_t; + +typedef struct animation_s +{ + int firstFrame; + int numFrames; + int loopFrames; // 0 to numFrames + int frameLerp; // msec between frames + int initialLerp; // msec to get to first frame + int reversed; // true if animation is reversed + int flipflop; // true if animation should flipflop back to base +} animation_t; + + +// flip the togglebit every time an animation +// changes so a restart of the same anim can be detected +#define ANIM_TOGGLEBIT 0x80 +#define ANIM_FORCEBIT 0x40 + +// Time between location updates +#define TEAM_LOCATION_UPDATE_TIME 500 + +// player classes +typedef enum +{ + PCL_NONE, + + //builder classes + PCL_ALIEN_BUILDER0, + PCL_ALIEN_BUILDER0_UPG, + + //offensive classes + PCL_ALIEN_LEVEL0, + PCL_ALIEN_LEVEL1, + PCL_ALIEN_LEVEL1_UPG, + PCL_ALIEN_LEVEL2, + PCL_ALIEN_LEVEL2_UPG, + PCL_ALIEN_LEVEL3, + PCL_ALIEN_LEVEL3_UPG, + PCL_ALIEN_LEVEL4, + + //human class + PCL_HUMAN, + PCL_HUMAN_BSUIT, + + PCL_NUM_CLASSES +} class_t; + +// spectator state +typedef enum +{ + SPECTATOR_NOT, + SPECTATOR_FREE, + SPECTATOR_LOCKED, + SPECTATOR_FOLLOW, + SPECTATOR_SCOREBOARD +} spectatorState_t; + +// modes of text communication +typedef enum +{ + SAY_ALL, + SAY_TEAM, + SAY_PRIVMSG, + SAY_TPRIVMSG, + SAY_AREA, + SAY_ADMINS, + SAY_ADMINS_PUBLIC, + SAY_RAW +} saymode_t; + +// means of death +typedef enum +{ + MOD_UNKNOWN, + MOD_SHOTGUN, + MOD_BLASTER, + MOD_PAINSAW, + MOD_MACHINEGUN, + MOD_CHAINGUN, + MOD_PRIFLE, + MOD_MDRIVER, + MOD_LASGUN, + MOD_LCANNON, + MOD_LCANNON_SPLASH, + MOD_FLAMER, + MOD_FLAMER_SPLASH, + MOD_GRENADE, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, + + MOD_ABUILDER_CLAW, + MOD_LEVEL0_BITE, + MOD_LEVEL1_CLAW, + MOD_LEVEL1_PCLOUD, + MOD_LEVEL3_CLAW, + MOD_LEVEL3_POUNCE, + MOD_LEVEL3_BOUNCEBALL, + MOD_LEVEL2_CLAW, + MOD_LEVEL2_ZAP, + MOD_LEVEL4_CLAW, + MOD_LEVEL4_TRAMPLE, + MOD_LEVEL4_CRUSH, + MOD_LEVEL4_BOMB, + + MOD_SLOWBLOB, + MOD_POISON, + MOD_SWARM, + + MOD_HSPAWN, + MOD_TESLAGEN, + MOD_MGTURRET, + MOD_REACTOR, + + MOD_ASPAWN, + MOD_ATUBE, + MOD_OVERMIND, + MOD_DECONSTRUCT, + MOD_REPLACE, + MOD_NOCREEP, + + MOD_ALIEN_HATCH, + MOD_ALIEN_HATCH_FAILED + +} meansOfDeath_t; + + +//--------------------------------------------------------- + +// player class record +typedef struct +{ + class_t number; + + char *name; + char *info; + + int stages; + + int health; + float fallDamage; + float regenRate; + + int abilities; + + weapon_t startWeapon; + + float buildDist; + + int fov; + float bob; + float bobCycle; + int steptime; + + float speed; + float acceleration; + float airAcceleration; + float friction; + float stopSpeed; + float jumpMagnitude; + float knockbackScale; + + int children[ 3 ]; + int cost; + int value; +} classAttributes_t; + +typedef struct +{ + char modelName[ MAX_QPATH ]; + float modelScale; + char skinName[ MAX_QPATH ]; + float shadowScale; + char hudName[ MAX_QPATH ]; + char humanName[ MAX_STRING_CHARS ]; + + vec3_t mins; + vec3_t maxs; + vec3_t crouchMaxs; + vec3_t deadMins; + vec3_t deadMaxs; + int viewheight; + int crouchViewheight; + float zOffset; + vec3_t shoulderOffsets; +} classConfig_t; + +//stages +typedef enum +{ + S1, + S2, + S3 +} stage_t; + +#define MAX_BUILDABLE_MODELS 4 + +// buildable item record +typedef struct +{ + buildable_t number; + + char *name; + char *humanName; + char *info; + char *entityName; + + trType_t traj; + float bounce; + + int buildPoints; + int stages; + + int health; + int regenRate; + + int splashDamage; + int splashRadius; + + int meansOfDeath; + + team_t team; + weapon_t buildWeapon; + + int idleAnim; + + int nextthink; + int buildTime; + qboolean usable; + + int turretRange; + int turretFireSpeed; + weapon_t turretProjType; + + float minNormal; + qboolean invertNormal; + + qboolean creepTest; + int creepSize; + + qboolean dccTest; + qboolean transparentTest; + qboolean uniqueTest; + + int value; + + qboolean cuboid; +} buildableAttributes_t; + +typedef struct +{ + char models[ MAX_BUILDABLE_MODELS ][ MAX_QPATH ]; + + float modelScale; + vec3_t mins; + vec3_t maxs; + float zOffset; +} buildableConfig_t; + +// weapon record +typedef struct +{ + weapon_t number; + + int price; + int stages; + + int slots; + + char *name; + char *humanName; + char *info; + + int maxAmmo; + int maxClips; + qboolean infiniteAmmo; + qboolean usesEnergy; + + int repeatRate1; + int repeatRate2; + int repeatRate3; + int reloadTime; + float knockbackScale; + + qboolean hasAltMode; + qboolean hasThirdMode; + + qboolean canZoom; + float zoomFov; + + qboolean purchasable; + qboolean longRanged; + + team_t team; +} weaponAttributes_t; + +// upgrade record +typedef struct +{ + upgrade_t number; + + int price; + int stages; + + int slots; + + char *name; + char *humanName; + char *info; + + char *icon; + + qboolean purchasable; + qboolean usable; + + team_t team; +} upgradeAttributes_t; + +qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips ); +int BG_WeaponMaxAmmo( weapon_t weapon, int *stats); +qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] ); +int BG_SlotsForInventory( int stats[ ] ); +void BG_AddUpgradeToInventory( int item, int stats[ ] ); +void BG_RemoveUpgradeFromInventory( int item, int stats[ ] ); +qboolean BG_InventoryContainsUpgrade( int item, int stats[ ] ); +void BG_ActivateUpgrade( int item, int stats[ ] ); +void BG_DeactivateUpgrade( int item, int stats[ ] ); +qboolean BG_UpgradeIsActive( int item, int stats[ ] ); +qboolean BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ], + vec3_t outAxis[ 3 ], qboolean inverse, qboolean ceiling ); +void BG_GetClientNormal( const playerState_t *ps, vec3_t normal ); +void BG_GetClientViewOrigin( const playerState_t *ps, vec3_t viewOrigin ); +qboolean BG_PositionBuildableRelativeToPlayer( const playerState_t *ps, qboolean cuboid, + const vec3_t mins, const vec3_t maxs, + void (*trace)( trace_t *, const vec3_t, const vec3_t, + const vec3_t, const vec3_t, int, int ), + vec3_t outOrigin, vec3_t outAngles, trace_t *tr ); +int BG_GetValueOfPlayer( playerState_t *ps ); +qboolean BG_PlayerCanChangeWeapon( playerState_t *ps ); +int BG_PlayerPoisonCloudTime( playerState_t *ps ); +weapon_t BG_GetPlayerWeapon( playerState_t *ps ); +qboolean BG_HasEnergyWeapon( playerState_t *ps ); + +void BG_PackEntityNumbers( entityState_t *es, const int *entityNums, int count ); +int BG_UnpackEntityNumbers( entityState_t *es, int *entityNums, int count ); + +const buildableAttributes_t *BG_BuildableByName( const char *name ); +const buildableAttributes_t *BG_BuildableByEntityName( const char *name ); +const buildableAttributes_t *BG_Buildable( buildable_t buildable, const vec3_t cuboidSize ); +qboolean BG_BuildableAllowedInStage( buildable_t buildable, + stage_t stage ); + +buildableConfig_t *BG_BuildableConfig( buildable_t buildable ); +void BG_BuildableBoundingBox( buildable_t buildable, + vec3_t mins, vec3_t maxs ); +void BG_InitBuildableConfigs( void ); + +const classAttributes_t *BG_ClassByName( const char *name ); +const classAttributes_t *BG_Class( class_t class ); +qboolean BG_ClassAllowedInStage( class_t class, + stage_t stage ); + +classConfig_t *BG_ClassConfig( class_t class ); + +void BG_ClassBoundingBox( class_t class, vec3_t mins, + vec3_t maxs, vec3_t cmaxs, + vec3_t dmins, vec3_t dmaxs ); +qboolean BG_ClassHasAbility( class_t class, int ability ); +int BG_ClassCanEvolveFromTo( class_t fclass, + class_t tclass, + int credits, int alienStage, int num ); +qboolean BG_AlienCanEvolve( class_t class, int credits, int alienStage ); + +void BG_InitClassConfigs( void ); + +const weaponAttributes_t *BG_WeaponByName( const char *name ); +const weaponAttributes_t *BG_Weapon( weapon_t weapon ); +qboolean BG_WeaponAllowedInStage( weapon_t weapon, + stage_t stage ); + +const upgradeAttributes_t *BG_UpgradeByName( const char *name ); +const upgradeAttributes_t *BG_Upgrade( upgrade_t upgrade ); +qboolean BG_UpgradeAllowedInStage( upgrade_t upgrade, + stage_t stage ); +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +//#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_DEADSOLID MASK_PLAYERSOLID +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY) + +// +// entityState_t->eType +// +typedef enum +{ + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + + ET_BUILDABLE, // buildable type + + ET_LOCATION, + + ET_MISSILE, + ET_MOVER, + ET_BEAM, + ET_PORTAL, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_INVISIBLE, + ET_GRAPPLE, // grapple hooked on wall + + ET_CORPSE, + ET_PARTICLE_SYSTEM, + ET_ANIMMAPOBJ, + ET_MODELDOOR, + ET_LIGHTFLARE, + ET_LEV2_ZAP_CHAIN, + + ET_EVENTS // any of the EV_* events can be added freestanding + // by setting eType to ET_EVENTS + eventNum + // this avoids having to set eFlags and eventNum +} entityType_t; + +void *BG_Alloc( int size ); +void BG_InitMemory( void ); +void BG_Free( void *ptr ); +void BG_DefragmentMemory( void ); +void BG_MemoryInfo( void ); + +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); + +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + +float atof_neg( char *token, qboolean allowNegative ); +int atoi_neg( char *token, qboolean allowNegative ); + +void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weaponsSize, + upgrade_t *upgrades, int upgradesSize ); +void BG_ParseCSVClassList( const char *string, class_t *classes, int classesSize ); +void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int buildablesSize ); +void BG_InitAllowedGameElements( void ); +qboolean BG_WeaponIsAllowed( weapon_t weapon ); +qboolean BG_UpgradeIsAllowed( upgrade_t upgrade ); +qboolean BG_ClassIsAllowed( class_t class ); +qboolean BG_BuildableIsAllowed( buildable_t buildable ); +weapon_t BG_PrimaryWeapon( int stats[ ] ); + +// Friendly Fire Flags +#define FFF_HUMANS 1 +#define FFF_ALIENS 2 +#define FFF_BUILDABLES 4 + +// bg_voice.c +#define MAX_VOICES 8 +#define MAX_VOICE_NAME_LEN 16 +#define MAX_VOICE_CMD_LEN 16 +#define VOICE_ENTHUSIASM_DECAY 0.5f // enthusiasm lost per second + +typedef enum +{ + VOICE_CHAN_ALL, + VOICE_CHAN_TEAM , + VOICE_CHAN_LOCAL, + + VOICE_CHAN_NUM_CHANS +} voiceChannel_t; + +typedef struct voiceTrack_s +{ +#ifdef CGAME + sfxHandle_t track; + int duration; +#endif + char *text; + int enthusiasm; + int team; + int class; + int weapon; + struct voiceTrack_s *next; +} voiceTrack_t; + + +typedef struct voiceCmd_s +{ + char cmd[ MAX_VOICE_CMD_LEN ]; + voiceTrack_t *tracks; + struct voiceCmd_s *next; +} voiceCmd_t; + +typedef struct voice_s +{ + char name[ MAX_VOICE_NAME_LEN ]; + voiceCmd_t *cmds; + struct voice_s *next; +} voice_t; + +voice_t *BG_VoiceInit( void ); +void BG_PrintVoices( voice_t *voices, int debugLevel ); + +voice_t *BG_VoiceByName( voice_t *head, char *name ); +voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum ); +voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num); +voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num ); +voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team, + class_t class, weapon_t weapon, + int enthusiasm, int *trackNum ); + +int BG_LoadEmoticons( emoticon_t *emoticons, int num ); + +char *BG_TeamName( team_t team ); + +typedef struct +{ + const char *name; +} dummyCmd_t; +int cmdcmp( const void *a, const void *b ); + + +//cuboid health point calculation types +enum +{ + CBHPT_PLAIN, // hp depends only on volume + CBHPT_CUBES, // more efficient as a cube + CBHPT_PANES // more efficient as a thin pane +}; + +// sound selection based on cuboid's volume +typedef struct +{ + int numsounds; // number of sounds + float volumes[6]; // minimum cuboid's volume at which sound plays (IN CUBIC METRES) +} cuboidVolsnds_t; + +// sound selection based on randomness +typedef struct +{ + int numsounds; +} cuboidRandsnds_t; + +typedef struct +{ + qboolean genericAnim; // use generic animation instead of loading four shaders + const char* file; // cuboid's name in files + const char* icon; // cuboid's [emoticon] + cuboidRandsnds_t pain;// paint sound stuff + cuboidVolsnds_t dstr; // destruction sound stuff + float hppv; // health per volume + int hpt; // health calculation type + float bppv; // build points usage per volume + int buildrate; // build rate (hp per sec) + int minbt; // minimum build time + qboolean regen; // does it regenerate? + int regenrate; // regeneration speed (hp per sec) + qboolean zappable; // can it be zapped by an Adv. Marauder? + qboolean repairable; // can it be repaired by a Construction Kit? + qboolean detectable; // do we want it to be visible on radars? + qboolean needpower; // does it need power? + int surfaceFlags; // surface flags (ladder, slick, etc.) +} cuboidAttributes_t; + +qboolean BG_IsCuboid(buildable_t buildable); +void BG_CuboidBBox(const vec3_t size, vec3_t mins, vec3_t maxs); +const cuboidAttributes_t *BG_CuboidAttributes(buildable_t buildable); +qboolean BG_CuboidAllowed(int stage); +int BG_CuboidMode(void); +int BG_CuboidUnpackHealth(entityState_t *es); +void BG_CuboidPackHealth(entityState_t *es, int health); +void BG_CuboidPackHealthSafe(buildable_t buildable, entityState_t *es, int health); +void BG_CuboidSortSize(const vec3_t a, vec3_t b); +int BG_SoundForVolume(const cuboidVolsnds_t *set, const vec3_t size); diff --git a/src/game/bg_slidemove.c b/src/game/bg_slidemove.c new file mode 100644 index 0000000..bd61401 --- /dev/null +++ b/src/game/bg_slidemove.c @@ -0,0 +1,408 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// bg_slidemove.c -- part of bg_pmove functionality + +#include "../qcommon/q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +/* + +input: origin, velocity, bounds, groundPlane, trace function + +output: origin, velocity, impacts, stairup boolean + +*/ + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +qboolean PM_SlideMove( qboolean gravity ) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + + numbumps = 4; + + VectorCopy( pm->ps->velocity, primal_velocity ); + + if( gravity ) + { + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[ 2 ] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[ 2 ] = ( pm->ps->velocity[ 2 ] + endVelocity[ 2 ] ) * 0.5; + primal_velocity[ 2 ] = endVelocity[ 2 ]; + + if( pml.groundPlane ) + { + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if( pml.groundPlane ) + { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[ 0 ] ); + } + else + numplanes = 0; + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[ numplanes ] ); + numplanes++; + + for( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) + { + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask ); + + if( trace.allsolid ) + { + // entity is completely trapped in another solid + pm->ps->velocity[ 2 ] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if( trace.fraction > 0 ) + { + // actually covered some distance + VectorCopy( trace.endpos, pm->ps->origin ); + } + + if( trace.fraction == 1 ) + break; // moved the entire distance + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if( numplanes >= MAX_CLIP_PLANES ) + { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for( i = 0 ; i < numplanes ; i++ ) + { + if( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) + { + VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + + if( i < numplanes ) + continue; + + VectorCopy( trace.plane.normal, planes[ numplanes ] ); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for( i = 0; i < numplanes; i++ ) + { + into = DotProduct( pm->ps->velocity, planes[ i ] ); + if( into >= 0.1 ) + continue; // move doesn't interact with the plane + + // see how hard we are hitting things + if( -into > pml.impactSpeed ) + pml.impactSpeed = -into; + + // slide along the plane + PM_ClipVelocity( pm->ps->velocity, planes[ i ], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity( endVelocity, planes[ i ], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for( j = 0; j < numplanes; j++ ) + { + if( j == i ) + continue; + + if( DotProduct( clipVelocity, planes[ j ] ) >= 0.1 ) + continue; // move doesn't interact with the plane + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[ j ], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[ j ], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if( DotProduct( clipVelocity, planes[ i ] ) >= 0 ) + continue; + + // slide the original velocity along the crease + CrossProduct( planes[ i ], planes[ j ], dir ); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct( planes[ i ], planes[ j ], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for( k = 0; k < numplanes; k++ ) + { + if( k == i || k == j ) + continue; + + if( DotProduct( clipVelocity, planes[ k ] ) >= 0.1 ) + continue; // move doesn't interact with the plane + + // stop dead at a tripple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if( gravity ) + VectorCopy( endVelocity, pm->ps->velocity ); + + // don't change velocity if in a timer (FIXME: is this correct?) + if( pm->ps->pm_time ) + VectorCopy( primal_velocity, pm->ps->velocity ); + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepEvent +================== +*/ +void PM_StepEvent( vec3_t from, vec3_t to, vec3_t normal ) +{ + float size; + vec3_t delta, dNormal; + + VectorSubtract( from, to, delta ); + VectorCopy( delta, dNormal ); + VectorNormalize( dNormal ); + + size = DotProduct( normal, dNormal ) * VectorLength( delta ); + + if( size > 0.0f ) + { + if( size > 2.0f ) + { + if( size < 7.0f ) + PM_AddEvent( EV_STEPDN_4 ); + else if( size < 11.0f ) + PM_AddEvent( EV_STEPDN_8 ); + else if( size < 15.0f ) + PM_AddEvent( EV_STEPDN_12 ); + else + PM_AddEvent( EV_STEPDN_16 ); + } + } + else + { + size = fabs( size ); + + if( size > 2.0f ) + { + if( size < 7.0f ) + PM_AddEvent( EV_STEP_4 ); + else if( size < 11.0f ) + PM_AddEvent( EV_STEP_8 ); + else if( size < 15.0f ) + PM_AddEvent( EV_STEP_12 ); + else + PM_AddEvent( EV_STEP_16 ); + } + } + + if( pm->debugLevel ) + Com_Printf( "%i:stepped\n", c_pmove ); +} + +/* +================== +PM_StepSlideMove +================== +*/ +qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive ) +{ + vec3_t start_o, start_v; + vec3_t down_o, down_v; + trace_t trace; + vec3_t normal; + vec3_t step_v, step_vNormal; + vec3_t up, down; + float stepSize; + qboolean stepped = qfalse; + + BG_GetClientNormal( pm->ps, normal ); + + VectorCopy( pm->ps->origin, start_o ); + VectorCopy( pm->ps->velocity, start_v ); + + if( PM_SlideMove( gravity ) == 0 ) + { + VectorCopy( start_o, down ); + VectorMA( down, -STEPSIZE, normal, down ); + pm->trace( &trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask ); + + //we can step down + if( trace.fraction > 0.01f && trace.fraction < 1.0f && + !trace.allsolid && pml.groundPlane != qfalse ) + { + if( pm->debugLevel ) + Com_Printf( "%d: step down\n", c_pmove ); + + stepped = qtrue; + } + } + else + { + VectorCopy( start_o, down ); + VectorMA( down, -STEPSIZE, normal, down ); + pm->trace( &trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask ); + // never step up when you still have up velocity + if( DotProduct( trace.plane.normal, pm->ps->velocity ) > 0.0f && + ( trace.fraction == 1.0f || DotProduct( trace.plane.normal, normal ) < 0.7f ) ) + { + return stepped; + } + + VectorCopy( pm->ps->origin, down_o ); + VectorCopy( pm->ps->velocity, down_v ); + + VectorCopy( start_o, up ); + VectorMA( up, STEPSIZE, normal, up ); + + // test the player position if they were a stepheight higher + pm->trace( &trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask ); + if( trace.allsolid ) + { + if( pm->debugLevel ) + Com_Printf( "%i:bend can't step\n", c_pmove ); + + return stepped; // can't step up + } + + VectorSubtract( trace.endpos, start_o, step_v ); + VectorCopy( step_v, step_vNormal ); + VectorNormalize( step_vNormal ); + + stepSize = DotProduct( normal, step_vNormal ) * VectorLength( step_v ); + // try slidemove from this position + VectorCopy( trace.endpos, pm->ps->origin ); + VectorCopy( start_v, pm->ps->velocity ); + + if( PM_SlideMove( gravity ) == 0 ) + { + if( pm->debugLevel ) + Com_Printf( "%d: step up\n", c_pmove ); + + stepped = qtrue; + } + + // push down the final amount + VectorCopy( pm->ps->origin, down ); + VectorMA( down, -stepSize, normal, down ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask ); + + if( !trace.allsolid ) + VectorCopy( trace.endpos, pm->ps->origin ); + + if( trace.fraction < 1.0f ) + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + + if( !predictive && stepped ) + PM_StepEvent( start_o, pm->ps->origin, normal ); + + return stepped; +} + +/* +================== +PM_PredictStepMove +================== +*/ +qboolean PM_PredictStepMove( void ) +{ + vec3_t velocity, origin; + float impactSpeed; + qboolean stepped = qfalse; + + VectorCopy( pm->ps->velocity, velocity ); + VectorCopy( pm->ps->origin, origin ); + impactSpeed = pml.impactSpeed; + + if( PM_StepSlideMove( qfalse, qtrue ) ) + stepped = qtrue; + + VectorCopy( velocity, pm->ps->velocity ); + VectorCopy( origin, pm->ps->origin ); + pml.impactSpeed = impactSpeed; + + return stepped; +} diff --git a/src/game/bg_voice.c b/src/game/bg_voice.c new file mode 100644 index 0000000..c0764dd --- /dev/null +++ b/src/game/bg_voice.c @@ -0,0 +1,652 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2008 Tony J. White + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// bg_voice.c -- both games voice functions +#include "../qcommon/q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int trap_Parse_LoadSource( const char *filename ); +int trap_Parse_FreeSource( int handle ); +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ); +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ); + +#ifdef CGAME +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); +int trap_S_SoundDuration( sfxHandle_t handle ); +#endif + + +/* +============ +BG_VoiceParseError +============ +*/ +static void BG_VoiceParseError( fileHandle_t handle, char *err ) +{ + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + trap_Parse_FreeSource( handle ); + Com_Error( ERR_FATAL, "%s on line %d of %s\n", err, line, filename ); +} + +/* +============ +BG_VoiceList +============ +*/ +static voice_t *BG_VoiceList( void ) +{ + char fileList[ MAX_VOICES * ( MAX_VOICE_NAME_LEN + 8 ) ] = {""}; + int numFiles, i, fileLen = 0; + int count = 0; + char *filePtr; + voice_t *voices = NULL; + voice_t *top = NULL; + + numFiles = trap_FS_GetFileList( "voice", ".voice", fileList, + sizeof( fileList ) ); + + if( numFiles < 1 ) + return NULL; + + // special case for default.voice. this file is REQUIRED and will + // always be loaded first in the event of overflow of voice definitions + if( !trap_FS_FOpenFile( "voice/default.voice", NULL, FS_READ ) ) + { + Com_Printf( "voice/default.voice missing, voice system disabled." ); + return NULL; + } + + voices = (voice_t*)BG_Alloc( sizeof( voice_t ) ); + Q_strncpyz( voices->name, "default", sizeof( voices->name ) ); + voices->cmds = NULL; + voices->next = NULL; + count = 1; + + top = voices; + + filePtr = fileList; + for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + + // accounted for above + if( !Q_stricmp( filePtr, "default.voice" ) ) + continue; + + if( fileLen > MAX_VOICE_NAME_LEN + 8 ) { + Com_Printf( S_COLOR_YELLOW "WARNING: MAX_VOICE_NAME_LEN is %d. " + "skipping \"%s\", filename too long", MAX_VOICE_NAME_LEN, filePtr ); + continue; + } + + // trap_FS_GetFileList() buffer has overflowed + if( !trap_FS_FOpenFile( va( "voice/%s", filePtr ), NULL, FS_READ ) ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceList(): detected " + "an invalid .voice file \"%s\" in directory listing. You have " + "probably named one or more .voice files with outrageously long " + "names. gjbs", filePtr ); + break; + } + + if( count >= MAX_VOICES ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: .voice file overflow. " + "%d of %d .voice files loaded. MAX_VOICES is %d", + count, numFiles, MAX_VOICES ); + break; + } + + voices->next = (voice_t*)BG_Alloc( sizeof( voice_t ) ); + voices = voices->next; + + Q_strncpyz( voices->name, filePtr, sizeof( voices->name ) ); + // strip extension + voices->name[ fileLen - 6 ] = '\0'; + voices->cmds = NULL; + voices->next = NULL; + count++; + } + return top; +} + +/* +============ +BG_VoiceParseTrack +============ +*/ +static qboolean BG_VoiceParseTrack( int handle, voiceTrack_t *voiceTrack ) +{ + pc_token_t token; + qboolean found = qfalse; + qboolean foundText = qfalse; + qboolean foundToken = qfalse; + + foundToken = trap_Parse_ReadToken( handle, &token ); + while( foundToken ) + { + if( token.string[ 0 ] == '}' ) + { + if( foundText ) + return qtrue; + else + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing text attribute for track" ); + } + } + else if( !Q_stricmp( token.string, "team" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->team < 0 ) + voiceTrack->team = 0; + voiceTrack->team |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"team\" value" ); + } + continue; + } + else if( !Q_stricmp( token.string, "class" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->class < 0 ) + voiceTrack->class = 0; + voiceTrack->class |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"class\" value" ); + } + continue; + } + else if( !Q_stricmp( token.string, "weapon" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->weapon < 0 ) + voiceTrack->weapon = 0; + voiceTrack->weapon |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"weapon\" value"); + } + continue; + } + else if( !Q_stricmp( token.string, "text" ) ) + { + if( foundText ) + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "duplicate \"text\" definition for track" ); + } + foundToken = trap_Parse_ReadToken( handle, &token ); + if( !foundToken ) + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing \"text\" value" ); + } + foundText = qtrue; + if( strlen( token.string ) >= MAX_SAY_TEXT ) + { + BG_VoiceParseError( handle, va( "BG_VoiceParseTrack(): " + "\"text\" value " "\"%s\" exceeds MAX_SAY_TEXT length", + token.string ) ); + } + + voiceTrack->text = (char *)BG_Alloc( strlen( token.string ) + 1 ); + Q_strncpyz( voiceTrack->text, token.string, strlen( token.string ) + 1 ); + foundToken = trap_Parse_ReadToken( handle, &token ); + continue; + } + else if( !Q_stricmp( token.string, "enthusiasm" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + if( token.type == TT_NUMBER ) + { + voiceTrack->enthusiasm = token.intvalue; + } + else + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing \"enthusiasm\" value" ); + } + foundToken = trap_Parse_ReadToken( handle, &token ); + continue; + } + else + { + BG_VoiceParseError( handle, va( "BG_VoiceParseTrack():" + " unknown token \"%s\"", token.string ) ); + } + } + return qfalse; +} + +/* +============ +BG_VoiceParseCommand +============ +*/ +static voiceTrack_t *BG_VoiceParseCommand( int handle ) +{ + pc_token_t token; + qboolean parsingTrack = qfalse; + voiceTrack_t *voiceTracks = NULL; + voiceTrack_t *top = NULL; + + while( trap_Parse_ReadToken( handle, &token ) ) + { + if( !parsingTrack && token.string[ 0 ] == '}' ) + return top; + + if( parsingTrack ) + { + if( token.string[ 0 ] == '{' ) + { + BG_VoiceParseTrack( handle, voiceTracks ); + parsingTrack = qfalse; + continue; + + } + else + { + BG_VoiceParseError( handle, va( "BG_VoiceParseCommand(): " + "parse error at \"%s\"", token.string ) ); + } + } + + + if( top == NULL ) + { + voiceTracks = BG_Alloc( sizeof( voiceTrack_t ) ); + top = voiceTracks; + } + else + { + voiceTracks->next = BG_Alloc( sizeof( voiceCmd_t ) ); + voiceTracks = voiceTracks->next; + } + + if( !trap_FS_FOpenFile( token.string, NULL, FS_READ ) ) + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceParseCommand(): " + "track \"%s\" referenced on line %d of %s does not exist\n", + token.string, line, filename ); + } + else + { +#ifdef CGAME + voiceTracks->track = trap_S_RegisterSound( token.string, qfalse ); + voiceTracks->duration = trap_S_SoundDuration( voiceTracks->track ); +#endif + } + + voiceTracks->team = -1; + voiceTracks->class = -1; + voiceTracks->weapon = -1; + voiceTracks->enthusiasm = 0; + voiceTracks->text = NULL; + voiceTracks->next = NULL; + parsingTrack = qtrue; + + } + return NULL; +} + +/* +============ +BG_VoiceParse +============ +*/ +static voiceCmd_t *BG_VoiceParse( char *name ) +{ + voiceCmd_t *voiceCmds = NULL; + voiceCmd_t *top = NULL; + pc_token_t token; + qboolean parsingCmd = qfalse; + int handle; + + handle = trap_Parse_LoadSource( va( "voice/%s.voice", name ) ); + if( !handle ) + return NULL; + + while( trap_Parse_ReadToken( handle, &token ) ) + { + if( parsingCmd ) + { + if( token.string[ 0 ] == '{' ) + { + voiceCmds->tracks = BG_VoiceParseCommand( handle ); + parsingCmd = qfalse; + continue; + } + else + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Error( ERR_FATAL, "BG_VoiceParse(): " + "parse error on line %d of %s\n", line, filename ); + } + } + + if( strlen( token.string ) >= MAX_VOICE_CMD_LEN ) + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Error( ERR_FATAL, "BG_VoiceParse(): " + "command \"%s\" exceeds MAX_VOICE_CMD_LEN (%d) on line %d of %s\n", + token.string, MAX_VOICE_CMD_LEN, line, filename ); + } + + if( top == NULL ) + { + voiceCmds = BG_Alloc( sizeof( voiceCmd_t ) ); + top = voiceCmds; + } + else + { + voiceCmds->next = BG_Alloc( sizeof( voiceCmd_t ) ); + voiceCmds = voiceCmds->next; + } + + Q_strncpyz( voiceCmds->cmd, token.string, sizeof( voiceCmds->cmd ) ); + voiceCmds->next = NULL; + parsingCmd = qtrue; + + } + + trap_Parse_FreeSource( handle ); + + return top; +} + +/* +============ +BG_VoiceInit +============ +*/ +voice_t *BG_VoiceInit( void ) +{ + voice_t *voices; + voice_t *voice; + + voices = BG_VoiceList(); + + voice = voices; + while( voice ) + { + voice->cmds = BG_VoiceParse( voice->name ); + voice = voice->next; + } + + return voices; +} + + +/* +============ +BG_PrintVoices +============ +*/ +void BG_PrintVoices( voice_t *voices, int debugLevel ) +{ + voice_t *voice = voices; + voiceCmd_t *voiceCmd; + voiceTrack_t *voiceTrack; + + int cmdCount; + int trackCount; + + if( voice == NULL ) + { + Com_Printf( "voice list is empty\n" ); + return; + } + + while( voice != NULL ) + { + if( debugLevel > 0 ) + Com_Printf( "voice \"%s\"\n", voice->name ); + voiceCmd = voice->cmds; + cmdCount = 0; + trackCount = 0; + while( voiceCmd != NULL ) + { + if( debugLevel > 0 ) + Com_Printf( " %s\n", voiceCmd->cmd ); + voiceTrack = voiceCmd->tracks; + cmdCount++; + while ( voiceTrack != NULL ) + { + if( debugLevel > 1 ) + Com_Printf( " text -> %s\n", voiceTrack->text ); + if( debugLevel > 2 ) + { + Com_Printf( " team -> %d\n", voiceTrack->team ); + Com_Printf( " class -> %d\n", voiceTrack->class ); + Com_Printf( " weapon -> %d\n", voiceTrack->weapon ); + Com_Printf( " enthusiasm -> %d\n", voiceTrack->enthusiasm ); +#ifdef CGAME + Com_Printf( " duration -> %d\n", voiceTrack->duration ); +#endif + } + if( debugLevel > 1 ) + Com_Printf( "\n" ); + trackCount++; + voiceTrack = voiceTrack->next; + } + voiceCmd = voiceCmd->next; + } + + if( !debugLevel ) + { + Com_Printf( "voice \"%s\": %d commands, %d tracks\n", + voice->name, cmdCount, trackCount ); + } + voice = voice->next; + } +} + +/* +============ +BG_VoiceByName +============ +*/ +voice_t *BG_VoiceByName( voice_t *head, char *name ) +{ + voice_t *v = head; + + while( v ) + { + if( !Q_stricmp( v->name, name ) ) + return v; + v = v->next; + } + return NULL; +} + +/* +============ +BG_VoiceCmdFind +============ +*/ +voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum ) +{ + voiceCmd_t *vc = head; + int i = 0; + + while( vc ) + { + i++; + if( !Q_stricmp( vc->cmd, name ) ) + { + *cmdNum = i; + return vc; + } + vc = vc->next; + } + return NULL; +} + +/* +============ +BG_VoiceCmdByNum +============ +*/ +voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num ) +{ + voiceCmd_t *vc = head; + int i = 0; + + while( vc ) + { + i++; + if( i == num ) + return vc; + vc = vc->next; + } + return NULL; +} + +/* +============ +BG_VoiceTrackByNum +============ +*/ +voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num ) +{ + voiceTrack_t *vt = head; + int i = 0; + + while( vt ) + { + i++; + if( i == num ) + return vt; + vt = vt->next; + } + return NULL; +} + +/* +============ +BG_VoiceTrackFind +============ +*/ +voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team, + class_t class, weapon_t weapon, + int enthusiasm, int *trackNum ) +{ + voiceTrack_t *vt = head; + int highestMatch = 0; + int matchCount = 0; + int selectedMatch = 0; + int i = 0; + int j = 0; + + // find highest enthusiasm without going over + while( vt ) + { + if( ( vt->team >= 0 && !( vt->team & ( 1 << team ) ) ) || + ( vt->class >= 0 && !( vt->class & ( 1 << class ) ) ) || + ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) || + vt->enthusiasm > enthusiasm ) + { + vt = vt->next; + continue; + } + + if( vt->enthusiasm > highestMatch ) + { + matchCount = 0; + highestMatch = vt->enthusiasm; + } + if( vt->enthusiasm == highestMatch ) + matchCount++; + vt = vt->next; + } + + if( !matchCount ) + return NULL; + + // return randomly selected match + selectedMatch = rand() / ( RAND_MAX / matchCount + 1 ); + vt = head; + i = 0; + j = 0; + while( vt ) + { + j++; + if( ( vt->team >= 0 && !( vt->team & ( 1 << team ) ) ) || + ( vt->class >= 0 && !( vt->class & ( 1 << class ) ) ) || + ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) || + vt->enthusiasm != highestMatch ) + { + vt = vt->next; + continue; + } + if( i == selectedMatch ) + { + *trackNum = j; + return vt; + } + i++; + vt = vt->next; + } + return NULL; +} diff --git a/src/game/g_active.c b/src/game/g_active.c new file mode 100644 index 0000000..a65d9f2 --- /dev/null +++ b/src/game/g_active.c @@ -0,0 +1,1955 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +/* To create useful and interesting cuboids (like ladders), the ability to specify + * custom surface flags for buildings was needed. This is what the following function + * was written for. It replaces the regular tracing function in PM code (also on the + * client side, grep for CG_PMTraceHack). This function also takes into account + * the modelindex of the entity it found and adjusts the surfaceFlags output + * accordingly. This way custom surface flags for buildings can be done without + * messing around with the PM code or the engine itself. + */ +void G_PMTraceHack(trace_t *results,const vec3_t start,const vec3_t mins,const vec3_t maxs,const vec3_t end,int passEntityNum,int contentmask ) +{ + int modelindex; + trap_Trace(results,start,mins,maxs,end,passEntityNum,contentmask); + if(results->entityNum!=ENTITYNUM_NONE) + { + modelindex=g_entities[results->entityNum].s.modelindex; + if(modelindex>=CUBOID_FIRST&&modelindex<=CUBOID_LAST&&(g_entities[results->entityNum].s.eFlags&EF_B_SPAWNED)) + results->surfaceFlags|=BG_CuboidAttributes(modelindex)->surfaceFlags; + } +} + + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) +{ + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if( !PM_Live( client->ps.pm_type ) ) + return; + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if( count == 0 ) + return; // didn't take any damage + + if( count > 255 ) + count = 255; + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if( client->damage_fromWorld ) + { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } + else + { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[ PITCH ] / 360.0 * 256; + client->ps.damageYaw = angles[ YAW ] / 360.0 * 256; + } + + // play an apropriate pain sound + if( ( level.time > player->pain_debounce_time ) && !( player->flags & FL_GODMODE ) ) + { + player->pain_debounce_time = level.time + 700; + G_AddEvent( player, EV_PAIN, player->health > 255 ? 255 : player->health ); + client->ps.damageEvent++; + } + + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + + + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) +{ + int waterlevel; + + if( ent->client->noclip ) + { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + waterlevel = ent->waterlevel; + + // + // check for drowning + // + if( waterlevel == 3 ) + { + // if out of air, start drowning + if( ent->client->airOutTime < level.time) + { + // drown! + ent->client->airOutTime += 1000; + if( ent->health > 0 ) + { + // take more damage the longer underwater + ent->damage += 2; + if( ent->damage > 15 ) + ent->damage = 15; + + // play a gurp sound instead of a normal pain sound + if( ent->health <= ent->damage ) + G_Sound( ent, CHAN_VOICE, G_SoundIndex( "*drown.wav" ) ); + else if( rand( ) < RAND_MAX / 2 + 1 ) + G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp1.wav" ) ); + else + G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp2.wav" ) ); + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage( ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER ); + } + } + } + else + { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if( waterlevel && + ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) + { + if( ent->health > 0 && + ent->pain_debounce_time <= level.time ) + { + if( ent->watertype & CONTENTS_LAVA ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, + 30 * waterlevel, 0, MOD_LAVA ); + } + + if( ent->watertype & CONTENTS_SLIME ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, + 10 * waterlevel, 0, MOD_SLIME ); + } + } + } +} + + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) +{ + if( ent->waterlevel && ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) + ent->client->ps.loopSound = level.snd_fry; + else + ent->client->ps.loopSound = 0; +} + + + +//============================================================== + +/* +============== +ClientShove +============== +*/ +static int GetClientMass( gentity_t *ent ) +{ + int entMass = 100; + + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + entMass = BG_Class( ent->client->pers.classSelection )->health; + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + { + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) ) + entMass *= 2; + } + else + return 0; + return entMass; +} + +static void ClientShove( gentity_t *ent, gentity_t *victim ) +{ + vec3_t dir, push; + float force; + int entMass, vicMass; + + // Don't push if the entity is not trying to move + if( !ent->client->pers.cmd.rightmove && !ent->client->pers.cmd.forwardmove && + !ent->client->pers.cmd.upmove ) + return; + + // Cannot push enemy players unless they are walking on the player + if( !OnSameTeam( ent, victim ) && + victim->client->ps.groundEntityNum != ent - g_entities ) + return; + + // Shove force is scaled by relative mass + entMass = GetClientMass( ent ); + vicMass = GetClientMass( victim ); + if( vicMass <= 0 || entMass <= 0 ) + return; + force = g_shove.value * entMass / vicMass; + if( force < 0 ) + force = 0; + if( force > 150 ) + force = 150; + + // Give the victim some shove velocity + VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, dir ); + VectorNormalizeFast( dir ); + VectorScale( dir, force, push ); + VectorAdd( victim->client->ps.velocity, push, victim->client->ps.velocity ); + + // Set the pmove timer so that the other client can't cancel + // out the movement immediately + if( !victim->client->ps.pm_time ) + { + int time; + + time = force * 2 + 0.5f; + if( time < 50 ) + time = 50; + if( time > 200 ) + time = 200; + victim->client->ps.pm_time = time; + victim->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } +} + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) +{ + int i; + trace_t trace; + gentity_t *other; + + // clear a fake trace struct for touch function + memset( &trace, 0, sizeof( trace ) ); + + for( i = 0; i < pm->numtouch; i++ ) + { + other = &g_entities[ pm->touchents[ i ] ]; + + // see G_UnlaggedDetectCollisions(), this is the inverse of that. + // if our movement is blocked by another player's real position, + // don't use the unlagged position for them because they are + // blocking or server-side Pmove() from reaching it + if( other->client && other->client->unlaggedCalc.used ) + other->client->unlaggedCalc.used = qfalse; + + // tyrant impact attacks + if( ent->client->ps.weapon == WP_ALEVEL4 ) + { + G_ChargeAttack( ent, other ); + G_CrushAttack( ent, other ); + } + + // shove players + if( ent->client && other->client ) + ClientShove( ent, other ); + + // touch triggers + if( other->touch ) + other->touch( other, ent, &trace ); + } +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) +{ + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + vec3_t pmins, pmaxs; + static vec3_t range = { 10, 10, 10 }; + + if( !ent->client ) + return; + + // dead clients don't activate triggers! + if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 ) + return; + + BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], + pmins, pmaxs, NULL, NULL, NULL ); + + VectorAdd( ent->client->ps.origin, pmins, mins ); + VectorAdd( ent->client->ps.origin, pmaxs, maxs ); + + VectorSubtract( mins, range, mins ); + VectorAdd( maxs, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if( !hit->touch && !ent->touch ) + continue; + + if( !( hit->r.contents & CONTENTS_TRIGGER ) ) + continue; + + // ignore most entities if a spectator + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) + { + if( hit->s.eType != ET_TELEPORT_TRIGGER && + // this is ugly but adding a new ET_? type will + // most likely cause network incompatibilities + hit->touch != Touch_DoorTrigger ) + { + //check for manually triggered doors + manualTriggerSpectator( hit, ent ); + continue; + } + } + + if( !trap_EntityContact( mins, maxs, hit ) ) + continue; + + memset( &trace, 0, sizeof( trace ) ); + + if( hit->touch ) + hit->touch( hit, ent, &trace ); + } +} + +/* +================= +G_ClientUpdateSpawnQueue + +Send spawn queue data to a client (used to be in SpectatorThink) +================= +*/ +void G_ClientUpdateSpawnQueue( gclient_t *client ) +{ + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + client->ps.persistant[ PERS_QUEUEPOS ] = + G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); + client->ps.persistant[ PERS_SPAWNS ] = level.numAlienSpawns; + client->ps.persistant[ PERS_SPAWNS_IMPLANTED ] = level.numAlienImplantedSpawns; + } + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + client->ps.persistant[ PERS_QUEUEPOS ] = + G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); + client->ps.persistant[ PERS_SPAWNS ] = level.numHumanSpawns; + client->ps.persistant[ PERS_SPAWNS_IMPLANTED ] = level.numHumanImplantedSpawns; + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) +{ + pmove_t pm; + gclient_t *client; + int clientNum; + qboolean attack1, attack3, following, queued; + + client = ent->client; + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + attack1 = ( client->buttons & BUTTON_ATTACK ) && + !( client->oldbuttons & BUTTON_ATTACK ); + attack3 = ( client->buttons & BUTTON_USE_HOLDABLE ) && + !( client->oldbuttons & BUTTON_USE_HOLDABLE ); + + // We are in following mode only if we are following a non-spectating client + following = client->sess.spectatorState == SPECTATOR_FOLLOW; + if( following ) + { + clientNum = client->sess.spectatorClient; + if( clientNum < 0 || clientNum > level.maxclients || + !g_entities[ clientNum ].client || + g_entities[ clientNum ].client->sess.spectatorState != SPECTATOR_NOT ) + following = qfalse; + } + + // Check to see if we are in the spawn queue + if( client->pers.teamSelection == TEAM_ALIENS ) + queued = G_SearchSpawnQueue( &level.alienSpawnQueue, ent - g_entities ); + else if( client->pers.teamSelection == TEAM_HUMANS ) + queued = G_SearchSpawnQueue( &level.humanSpawnQueue, ent - g_entities ); + else + queued = qfalse; + + // Wants to get out of spawn queue + if( attack1 && queued ) + { + if( client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); + client->pers.classSelection = PCL_NONE; + client->ps.stats[ STAT_CLASS ] = PCL_NONE; + client->ps.pm_flags &= ~PMF_QUEUED; + queued = qfalse; + } + else if( attack1 ) + { + // Wants to get into spawn queue + if( client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + if( client->pers.teamSelection == TEAM_NONE ) + G_TriggerMenu( client->ps.clientNum, MN_TEAM ); + else if( client->pers.teamSelection == TEAM_ALIENS ) + G_TriggerMenu( client->ps.clientNum, MN_A_CLASS ); + else if( client->pers.teamSelection == TEAM_HUMANS ) + G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN ); + } + + // We are either not following anyone or following a spectator + if( !following ) + { + if( client->sess.spectatorState == SPECTATOR_LOCKED || + client->sess.spectatorState == SPECTATOR_FOLLOW ) + client->ps.pm_type = PM_FREEZE; + else if( client->noclip ) + client->ps.pm_type = PM_NOCLIP; + else + client->ps.pm_type = PM_SPECTATOR; + + if( queued ) + client->ps.pm_flags |= PMF_QUEUED; + + client->ps.speed = client->pers.flySpeed; + client->ps.stats[ STAT_STAMINA ] = 0; + client->ps.stats[ STAT_MISC ] = 0; + client->buildTimer=0; + client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + client->ps.stats[ STAT_CLASS ] = PCL_NONE; + client->ps.weapon = WP_NONE; + + // Set up for pmove + memset( &pm, 0, sizeof( pm ) ); + pm.ps = &client->ps; + pm.pmext = &client->pmext; + pm.cmd = *ucmd; + pm.tracemask = MASK_DEADSOLID; // spectators can fly through bodies + pm.trace = G_PMTraceHack; + pm.pointcontents = trap_PointContents; + + // Perform a pmove + Pmove( &pm ); + + // Save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_ClientUpdateSpawnQueue( ent->client ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + } +} + + + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gentity_t *ent ) +{ + gclient_t *client = ent->client; + + if( ! g_inactivity.integer ) + { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } + else if( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + ( client->pers.cmd.buttons & BUTTON_ATTACK ) ) + { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } + else if( !client->pers.localClient ) + { + if( level.time > client->inactivityTime && + !G_admin_permission( ent, ADMF_ACTIVITY ) ) + { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + + if( level.time > client->inactivityTime - 10000 && + !client->inactivityWarning && + !G_admin_permission( ent, ADMF_ACTIVITY ) ) + { + client->inactivityWarning = qtrue; + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) +{ + gclient_t *client; + usercmd_t *ucmd; + int aForward, aRight; + qboolean walking = qfalse, stopped = qfalse, + crouched = qfalse, jumping = qfalse, + strafing = qfalse; + int i; + + ucmd = &ent->client->pers.cmd; + + aForward = abs( ucmd->forwardmove ); + aRight = abs( ucmd->rightmove ); + + if( aForward == 0 && aRight == 0 ) + stopped = qtrue; + else if( aForward <= 64 && aRight <= 64 ) + walking = qtrue; + + if( aRight > 0 ) + strafing = qtrue; + + if( ucmd->upmove > 0 ) + jumping = qtrue; + else if( ent->client->ps.pm_flags & PMF_DUCKED ) + crouched = qtrue; + + client = ent->client; + client->time100 += msec; + client->time1000 += msec; + client->time10000 += msec; + + while ( client->time100 >= 100 ) + { + weapon_t weapon = BG_GetPlayerWeapon( &client->ps ); + + client->time100 -= 100; + + // Restore or subtract stamina + if( stopped || client->ps.pm_type == PM_JETPACK ) + client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE; + else if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && + !( client->buttons & BUTTON_WALKING ) ) // walk overrides sprint + client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; + else if( walking || crouched ) + client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; + + // Check stamina limits + if( client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) + client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + else if( client->ps.stats[ STAT_STAMINA ] < -STAMINA_MAX ) + client->ps.stats[ STAT_STAMINA ] = -STAMINA_MAX; + + if( weapon == WP_ABUILD || weapon == WP_ABUILD2 || + BG_InventoryContainsWeapon( WP_HBUILD, client->ps.stats ) ) + { + client->buildTimer-=MIN(100,client->buildTimer); + G_RecalcBuildTimer(client); + } + + switch( weapon ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + + // Set validity bit on buildable + if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) + { + int dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; + vec3_t dummy, dummy2; + + if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, + dist, dummy, dummy2, client->cuboidSelection ) == IBE_NONE ) + client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; + else + client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT; + + // Let the client know which buildables will be removed by building + for( i = 0; i < MAX_MISC; i++ ) + { + if( i < level.numBuildablesForRemoval ) + client->ps.misc[ i ] = level.markedBuildables[ i ]->s.number; + else + client->ps.misc[ i ] = 0; + } + } + else + { + for( i = 0; i < MAX_MISC; i++ ) + client->ps.misc[ i ] = 0; + } + break; + + default: + break; + } + + if( ent->client->pers.teamSelection == TEAM_HUMANS && + ( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X ) ) + { + int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime ); + + if( remainingStartupTime < 0 ) + { + if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && + ent->client->medKitHealthToRestore && + ent->client->ps.pm_type != PM_DEAD ) + { + ent->client->medKitHealthToRestore--; + ent->health++; + } + else + ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X; + } + else + { + if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && + ent->client->medKitHealthToRestore && + ent->client->ps.pm_type != PM_DEAD ) + { + //partial increase + if( level.time > client->medKitIncrementTime ) + { + ent->client->medKitHealthToRestore--; + ent->health++; + + client->medKitIncrementTime = level.time + + ( remainingStartupTime / MEDKIT_STARTUP_SPEED ); + } + } + else + ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X; + } + } + + //use fuel + if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) && + BG_UpgradeIsActive( UP_JETPACK, ent->client->ps.stats ) && + ent->client->ps.stats[ STAT_FUEL ] > 0 ) + { + ent->client->ps.stats[ STAT_FUEL ] -= JETPACK_FUEL_USAGE; + if( ent->client->ps.stats[ STAT_FUEL ] <= 0 ) + { + ent->client->ps.stats[ STAT_FUEL ] = 0; + BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); + BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &client->ps ); + } + } + + //biores + if( BG_InventoryContainsUpgrade( UP_BIORES, ent->client->ps.stats ) && + ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] ) + { + float hp, dmod; + + dmod = MIN( level.time - ent->lastDamageTime, 3000 ) / 3000.0f; + hp = (float)ent->health / ent->client->ps.stats[ STAT_MAX_HEALTH ]; + ent->client->bioresHealTimer += (BIORES_EQUATION) * 10 * dmod; + } + + if( ent->client->bioresHealTimer >= 100 ) + { + int delta; + + delta = ent->client->bioresHealTimer / 100; + ent->health = MAX( MIN( ent->health+delta, ent->client->ps.stats[ STAT_MAX_HEALTH ] ), 1 ); + + ent->client->bioresHealTimer %= 100; + } + + } + + while( client->time1000 >= 1000 ) + { + client->time1000 -= 1000; + + //client is poisoned + if( client->ps.stats[ STAT_STATE ] & SS_POISONED ) + { + int damage = ALIEN_POISON_DMG; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) + damage -= BSUIT_POISON_PROTECTION; + + if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, client->ps.stats ) ) + damage -= HELMET_MK1_POISON_PROTECTION; + + if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, client->ps.stats ) ) + damage -= HELMET_MK2_POISON_PROTECTION; + + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) + damage -= LIGHTARMOUR_POISON_PROTECTION; + + if( BG_InventoryContainsUpgrade( UP_BIORES, client->ps.stats ) ) + damage *= BIORES_POISON_MODIFIER; + + G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, + 0, damage, 0, MOD_POISON ); + } + + // turn off life support when a team admits defeat + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && + level.surrenderTeam == TEAM_ALIENS ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, + BG_Class( client->ps.stats[ STAT_CLASS ] )->regenRate, + DAMAGE_NO_ARMOR, MOD_SUICIDE ); + } + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + level.surrenderTeam == TEAM_HUMANS ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, 5, DAMAGE_NO_ARMOR, MOD_SUICIDE ); + } + + // lose some voice enthusiasm + if( client->voiceEnthusiasm > 0.0f ) + client->voiceEnthusiasm -= VOICE_ENTHUSIASM_DECAY; + else + client->voiceEnthusiasm = 0.0f; + + client->pers.aliveSeconds++; + if( g_freeFundPeriod.integer > 0 && + client->pers.aliveSeconds % g_freeFundPeriod.integer == 0 ) + { + // Give clients some credit periodically + if( G_TimeTilSuddenDeath( ) > 0 ) + { + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + G_AddCreditToClient( client, FREEKILL_ALIEN, qtrue ); + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + G_AddCreditToClient( client, FREEKILL_HUMAN, qtrue ); + } + } + + G_ClientUpdateSpawnQueue( ent->client ); + + if( client->isImpregnated && !client->isImplantMature && + level.time >= client->impregnationTime + ALIEN_IMPLANT_MATURING_TIME_MIN && + random() <= ALIEN_IMPLANT_MATURING_CHANCE ) + client->isImplantMature = qtrue; + + } + + while( client->time10000 >= 10000 ) + { + client->time10000 -= 10000; + + if( ent->client->ps.weapon == WP_ABUILD || + ent->client->ps.weapon == WP_ABUILD2 ) + { + AddScore( ent, ALIEN_BUILDER_SCOREINC ); + } + else if( ent->client->ps.weapon == WP_HBUILD ) + { + AddScore( ent, HUMAN_BUILDER_SCOREINC ); + } + + // Give score to basis that healed other aliens + if( ent->client->pers.hasHealed ) + { + if( client->ps.weapon == WP_ALEVEL1 ) + AddScore( ent, LEVEL1_REGEN_SCOREINC ); + else if( client->ps.weapon == WP_ALEVEL1_UPG ) + AddScore( ent, LEVEL1_UPG_REGEN_SCOREINC ); + + ent->client->pers.hasHealed = qfalse; + } + } + + // Regenerate Adv. Dragoon barbs + if( client->ps.weapon == WP_ALEVEL3_UPG ) + { + if( client->ps.ammo < BG_Weapon( WP_ALEVEL3_UPG )->maxAmmo ) + { + if( ent->timestamp + LEVEL3_BOUNCEBALL_REGEN < level.time ) + { + client->ps.ammo++; + ent->timestamp = level.time; + } + } + else + ent->timestamp = level.time; + } +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) +{ + client->ps.eFlags &= ~EF_FIRING; + client->ps.eFlags &= ~EF_FIRING2; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + if( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) + client->readyToExit = 1; +} + + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +void ClientEvents( gentity_t *ent, int oldEventSequence ) +{ + int i; + int event; + gclient_t *client; + int damage; + vec3_t dir; + vec3_t point, mins; + float fallDistance; + class_t class; + + client = ent->client; + class = client->ps.stats[ STAT_CLASS ]; + + if( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) + oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; + + for( i = oldEventSequence; i < client->ps.eventSequence; i++ ) + { + event = client->ps.events[ i & ( MAX_PS_EVENTS - 1 ) ]; + + switch( event ) + { + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + if( ent->s.eType != ET_PLAYER ) + break; // not in the player model + + fallDistance = ( (float)client->ps.stats[ STAT_FALLDIST ] - MIN_FALL_DISTANCE ) / + ( MAX_FALL_DISTANCE - MIN_FALL_DISTANCE ); + + if( fallDistance < 0.0f ) + fallDistance = 0.0f; + else if( fallDistance > 1.0f ) + fallDistance = 1.0f; + + damage = (int)( (float)BG_Class( class )->health * + BG_Class( class )->fallDamage * fallDistance ); + + VectorSet( dir, 0, 0, 1 ); + BG_ClassBoundingBox( class, mins, NULL, NULL, NULL, NULL ); + mins[ 0 ] = mins[ 1 ] = 0.0f; + VectorAdd( client->ps.origin, mins, point ); + + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage( ent, NULL, NULL, dir, point, damage, DAMAGE_NO_LOCDAMAGE, MOD_FALLING ); + break; + + case EV_FIRE_WEAPON: + FireWeapon( ent ); + break; + + case EV_FIRE_WEAPON2: + FireWeapon2( ent ); + break; + + case EV_FIRE_WEAPON3: + FireWeapon3( ent ); + break; + + case EV_NOAMMO: + break; + + default: + break; + } + } +} + + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) +{ + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if( ps->entityEventSequence < ps->eventSequence ) + { + // create a temporary entity for this event which is sent to everyone + // except the client who generated the event + seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } +} + +/* +============== + G_UnlaggedStore + + Called on every server frame. Stores position data for the client at that + into client->unlaggedHist[] and the time into level.unlaggedTimes[]. + This data is used by G_UnlaggedCalc() +============== +*/ +void G_UnlaggedStore( void ) +{ + int i = 0; + gentity_t *ent; + unlagged_t *save; + + if( !g_unlagged.integer ) + return; + level.unlaggedIndex++; + if( level.unlaggedIndex >= MAX_UNLAGGED_MARKERS ) + level.unlaggedIndex = 0; + + level.unlaggedTimes[ level.unlaggedIndex ] = level.time; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + save = &ent->client->unlaggedHist[ level.unlaggedIndex ]; + save->used = qfalse; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + VectorCopy( ent->r.mins, save->mins ); + VectorCopy( ent->r.maxs, save->maxs ); + VectorCopy( ent->s.pos.trBase, save->origin ); + save->used = qtrue; + } +} + +/* +============== + G_UnlaggedClear + + Mark all unlaggedHist[] markers for this client invalid. Useful for + preventing teleporting and death. +============== +*/ +void G_UnlaggedClear( gentity_t *ent ) +{ + int i; + + for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) + ent->client->unlaggedHist[ i ].used = qfalse; +} + +/* +============== + G_UnlaggedCalc + + Loops through all active clients and calculates their predicted position + for time then stores it in client->unlaggedCalc +============== +*/ +void G_UnlaggedCalc( int time, gentity_t *rewindEnt ) +{ + int i = 0; + gentity_t *ent; + int startIndex = level.unlaggedIndex; + int stopIndex = -1; + int frameMsec = 0; + float lerp = 0.5f; + + if( !g_unlagged.integer ) + return; + + // clear any calculated values from a previous run + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + ent->client->unlaggedCalc.used = qfalse; + } + + for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) + { + if( level.unlaggedTimes[ startIndex ] <= time ) + break; + stopIndex = startIndex; + if( --startIndex < 0 ) + startIndex = MAX_UNLAGGED_MARKERS - 1; + } + if( i == MAX_UNLAGGED_MARKERS ) + { + // if we searched all markers and the oldest one still isn't old enough + // just use the oldest marker with no lerping + lerp = 0.0f; + } + + // client is on the current frame, no need for unlagged + if( stopIndex == -1 ) + return; + + // lerp between two markers + frameMsec = level.unlaggedTimes[ stopIndex ] - + level.unlaggedTimes[ startIndex ]; + if( frameMsec > 0 ) + { + lerp = ( float )( time - level.unlaggedTimes[ startIndex ] ) / + ( float )frameMsec; + } + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( ent == rewindEnt ) + continue; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + if( !ent->client->unlaggedHist[ startIndex ].used ) + continue; + if( !ent->client->unlaggedHist[ stopIndex ].used ) + continue; + + // between two unlagged markers + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins, + ent->client->unlaggedHist[ stopIndex ].mins, + ent->client->unlaggedCalc.mins ); + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs, + ent->client->unlaggedHist[ stopIndex ].maxs, + ent->client->unlaggedCalc.maxs ); + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin, + ent->client->unlaggedHist[ stopIndex ].origin, + ent->client->unlaggedCalc.origin ); + + ent->client->unlaggedCalc.used = qtrue; + } +} + +/* +============== + G_UnlaggedOff + + Reverses the changes made to all active clients by G_UnlaggedOn() +============== +*/ +void G_UnlaggedOff( void ) +{ + int i = 0; + gentity_t *ent; + + if( !g_unlagged.integer ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( !ent->client->unlaggedBackup.used ) + continue; + VectorCopy( ent->client->unlaggedBackup.mins, ent->r.mins ); + VectorCopy( ent->client->unlaggedBackup.maxs, ent->r.maxs ); + VectorCopy( ent->client->unlaggedBackup.origin, ent->r.currentOrigin ); + ent->client->unlaggedBackup.used = qfalse; + trap_LinkEntity( ent ); + } +} + +/* +============== + G_UnlaggedOn + + Called after G_UnlaggedCalc() to apply the calculated values to all active + clients. Once finished tracing, G_UnlaggedOff() must be called to restore + the clients' position data + + As an optimization, all clients that have an unlagged position that is + not touchable at "range" from "muzzle" will be ignored. This is required + to prevent a huge amount of trap_LinkEntity() calls per user cmd. +============== +*/ + +void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range ) +{ + int i = 0; + gentity_t *ent; + unlagged_t *calc; + + if( !g_unlagged.integer ) + return; + + if( !attacker->client->pers.useUnlagged ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + calc = &ent->client->unlaggedCalc; + + if( !calc->used ) + continue; + if( ent->client->unlaggedBackup.used ) + continue; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( VectorCompare( ent->r.currentOrigin, calc->origin ) ) + continue; + if( muzzle ) + { + float r1 = Distance( calc->origin, calc->maxs ); + float r2 = Distance( calc->origin, calc->mins ); + float maxRadius = ( r1 > r2 ) ? r1 : r2; + + if( Distance( muzzle, calc->origin ) > range + maxRadius ) + continue; + } + + // create a backup of the real positions + VectorCopy( ent->r.mins, ent->client->unlaggedBackup.mins ); + VectorCopy( ent->r.maxs, ent->client->unlaggedBackup.maxs ); + VectorCopy( ent->r.currentOrigin, ent->client->unlaggedBackup.origin ); + ent->client->unlaggedBackup.used = qtrue; + + // move the client to the calculated unlagged position + VectorCopy( calc->mins, ent->r.mins ); + VectorCopy( calc->maxs, ent->r.maxs ); + VectorCopy( calc->origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } +} +/* +============== + G_UnlaggedDetectCollisions + + cgame prediction will predict a client's own position all the way up to + the current time, but only updates other player's positions up to the + postition sent in the most recent snapshot. + + This allows player X to essentially "move through" the position of player Y + when player X's cmd is processed with Pmove() on the server. This is because + player Y was clipping player X's Pmove() on his client, but when the same + cmd is processed with Pmove on the server it is not clipped. + + Long story short (too late): don't use unlagged positions for players who + were blocking this player X's client-side Pmove(). This makes the assumption + that if player X's movement was blocked in the client he's going to still + be up against player Y when the Pmove() is run on the server with the + same cmd. + + NOTE: this must be called after Pmove() and G_UnlaggedCalc() +============== +*/ +static void G_UnlaggedDetectCollisions( gentity_t *ent ) +{ + unlagged_t *calc; + trace_t tr; + float r1, r2; + float range; + + if( !g_unlagged.integer ) + return; + + if( !ent->client->pers.useUnlagged ) + return; + + calc = &ent->client->unlaggedCalc; + + // if the client isn't moving, this is not necessary + if( VectorCompare( ent->client->oldOrigin, ent->client->ps.origin ) ) + return; + + range = Distance( ent->client->oldOrigin, ent->client->ps.origin ); + + // increase the range by the player's largest possible radius since it's + // the players bounding box that collides, not their origin + r1 = Distance( calc->origin, calc->mins ); + r2 = Distance( calc->origin, calc->maxs ); + range += ( r1 > r2 ) ? r1 : r2; + + G_UnlaggedOn( ent, ent->client->oldOrigin, range ); + + trap_Trace(&tr, ent->client->oldOrigin, ent->r.mins, ent->r.maxs, + ent->client->ps.origin, ent->s.number, MASK_PLAYERSOLID ); + if( tr.entityNum >= 0 && tr.entityNum < MAX_CLIENTS ) + g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse; + + G_UnlaggedOff( ); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) +{ + gclient_t *client; + pmove_t pm; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + + client = ent->client; + + // don't think if the client is not yet connected (and thus not yet spawned in) + if( client->pers.connected != CON_CONNECTED ) + return; + + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + // sanity check the command time to prevent speedup cheating + if( ucmd->serverTime > level.time + 200 ) + { + ucmd->serverTime = level.time + 200; +// G_Printf("serverTime <<<<<\n" ); + } + + if( ucmd->serverTime < level.time - 1000 ) + { + ucmd->serverTime = level.time - 1000; +// G_Printf("serverTime >>>>>\n" ); + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) + return; + + if( msec > 200 ) + msec = 200; + + client->unlaggedTime = ucmd->serverTime; + + if( pmove_msec.integer < 8 ) + trap_Cvar_Set( "pmove_msec", "8" ); + else if( pmove_msec.integer > 33 ) + trap_Cvar_Set( "pmove_msec", "33" ); + + if( pmove_fixed.integer || client->pers.pmoveFixed ) + { + ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; + //if (ucmd->serverTime - client->ps.commandTime <= 0) + // return; + } + + // + // check for exiting intermission + // + if( level.intermissiontime ) + { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + if( client->sess.spectatorState != SPECTATOR_NOT ) + { + if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) + return; + + SpectatorThink( ent, ucmd ); + return; + } + + G_namelog_update_score( client ); + + // check for inactivity timer, but never drop the local client of a non-dedicated server + if( !ClientInactivityTimer( ent ) ) + return; + + // calculate where ent is currently seeing all the other active clients + G_UnlaggedCalc( ent->client->unlaggedTime, ent ); + + if( client->noclip ) + client->ps.pm_type = PM_NOCLIP; + else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) + client->ps.pm_type = PM_DEAD; + else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED || + client->ps.stats[ STAT_STATE ] & SS_GRABBED ) + client->ps.pm_type = PM_GRABBED; + else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) + client->ps.pm_type = PM_JETPACK; + else + client->ps.pm_type = PM_NORMAL; + + if( ( client->ps.stats[ STAT_STATE ] & SS_GRABBED ) && + client->grabExpiryTime < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; + + if( ( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) && + client->lastLockTime + LOCKBLOB_LOCKTIME < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; + + if( ( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED ) && + client->lastSlowTime + ABUILDER_BLOB_TIME < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED; + + // Update boosted state flags + client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTEDWARNING; + if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + { + if( level.time - client->boostedTime >= BOOST_TIME ) + client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; + else if( level.time - client->boostedTime >= BOOST_WARN_TIME ) + client->ps.stats[ STAT_STATE ] |= SS_BOOSTEDWARNING; + } + + // Check if poison cloud has worn off + if( ( client->ps.eFlags & EF_POISONCLOUDED ) && + BG_PlayerPoisonCloudTime( &client->ps ) - level.time + + client->lastPoisonCloudedTime <= 0 ) + client->ps.eFlags &= ~EF_POISONCLOUDED; + + if( client->ps.stats[ STAT_STATE ] & SS_POISONED && + client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + + client->ps.gravity = g_gravity.value; + + if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) && + BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) ) + { + //if currently using a medkit or have no need for a medkit now + if( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X || + ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] && + !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) ) + { + BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); + } + else if( client->ps.stats[ STAT_HEALTH ] > 0 ) + { + //remove anti toxin + BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); + BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats ); + + client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; + + client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; + client->lastMedKitTime = level.time; + client->medKitHealthToRestore = + client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; + client->medKitIncrementTime = level.time + + ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED ); + + G_AddEvent( ent, EV_MEDKIT_USED, 0 ); + } + } + + // Replenish alien health + if( level.surrenderTeam != client->pers.teamSelection && + ent->nextRegenTime >= 0 && ent->nextRegenTime < level.time ) + { + float regenRate = + BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->regenRate; + + if( ent->health <= 0 || ent->nextRegenTime < 0 || regenRate == 0 ) + ent->nextRegenTime = -1; // no regen + else + { + int entityList[ MAX_GENTITIES ]; + int i, num; + int count, interval; + vec3_t range, mins, maxs; + float modifier = 1.0f; + + VectorSet( range, REGEN_BOOST_RANGE, REGEN_BOOST_RANGE, + REGEN_BOOST_RANGE ); + VectorAdd( client->ps.origin, range, maxs ); + VectorSubtract( client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + gentity_t *boost = &g_entities[ entityList[ i ] ]; + + if( Distance( client->ps.origin, boost->s.origin ) > REGEN_BOOST_RANGE ) + continue; + + if( modifier < BOOSTER_REGEN_MOD && boost->s.eType == ET_BUILDABLE && + boost->s.modelindex == BA_A_BOOSTER && boost->spawned && + boost->health > 0 && boost->powered ) + { + modifier = BOOSTER_REGEN_MOD; + continue; + } + + if( boost->s.eType == ET_PLAYER && boost->client && + boost->client->pers.teamSelection == + ent->client->pers.teamSelection && boost->health > 0 ) + { + class_t class = boost->client->ps.stats[ STAT_CLASS ]; + qboolean didBoost = qfalse; + + if( class == PCL_ALIEN_LEVEL1 && modifier < LEVEL1_REGEN_MOD ) + { + modifier = LEVEL1_REGEN_MOD; + didBoost = qtrue; + } + else if( class == PCL_ALIEN_LEVEL1_UPG && + modifier < LEVEL1_UPG_REGEN_MOD ) + { + modifier = LEVEL1_UPG_REGEN_MOD; + didBoost = qtrue; + } + + if( didBoost && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] ) + boost->client->pers.hasHealed = qtrue; + } + } + + // Transmit heal rate to the client so it can be displayed on the HUD + client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; + client->ps.stats[ STAT_STATE ] &= ~( SS_HEALING_2X | SS_HEALING_3X ); + if( modifier == 1.0f && !G_FindCreep( ent ) ) + { + client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; + modifier *= ALIEN_REGEN_NOCREEP_MOD; + } + else if( modifier >= 3.0f ) + client->ps.stats[ STAT_STATE ] |= SS_HEALING_3X; + else if( modifier >= 2.0f ) + client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; + + interval = 1000 / ( regenRate * modifier ); + // if recovery interval is less than frametime, compensate + count = 1 + ( level.time - ent->nextRegenTime ) / interval; + + ent->health += count; + ent->nextRegenTime += count * interval; + + // if at max health, clear damage counters + if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] ) + { + ent->health = client->ps.stats[ STAT_MAX_HEALTH ]; + for( i = 0; i < MAX_CLIENTS; i++ ) + ent->credits[ i ] = 0; + } + } + } + + if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) && + BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) ) + { + int lastWeapon = ent->s.weapon; + + //remove grenade + BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats ); + BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats ); + + //M-M-M-M-MONSTER HACK + ent->s.weapon = WP_GRENADE; + FireWeapon( ent ); + ent->s.weapon = lastWeapon; + } + + // set speed + if( client->ps.pm_type == PM_NOCLIP ) + client->ps.speed = client->pers.flySpeed; + else + client->ps.speed = g_speed.value * + BG_Class( client->ps.stats[ STAT_CLASS ] )->speed; + + if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; + + //randomly disable the jet pack if damaged + if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && + BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) + { + if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time ) + { + if( random( ) > JETPACK_DISABLE_CHANCE ) + client->ps.pm_type = PM_NORMAL; + } + + //switch jetpack off if no reactor + //if( !G_Reactor( ) ) + // BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); + } + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset( &pm, 0, sizeof( pm ) ); + + if( ent->flags & FL_FORCE_GESTURE ) + { + ent->flags &= ~FL_FORCE_GESTURE; + ent->client->pers.cmd.buttons |= BUTTON_GESTURE; + } + + // clear fall velocity before every pmove + client->pmext.fallVelocity = 0.0f; + + pm.ps = &client->ps; + pm.pmext = &client->pmext; + pm.cmd = *ucmd; + + if( pm.ps->pm_type == PM_DEAD ) + pm.tracemask = MASK_DEADSOLID; + else + pm.tracemask = MASK_PLAYERSOLID; + + pm.trace = G_PMTraceHack; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + VectorCopy( client->ps.origin, client->oldOrigin ); + + // moved from after Pmove -- potentially the cause of + // future triggering bugs + if( !ent->client->noclip ) + G_TouchTriggers( ent ); + + Pmove( &pm ); + + G_UnlaggedDetectCollisions( ent ); + + // save results of pmove + if( ent->client->ps.eventSequence != oldEventSequence ) + ent->eventTime = level.time; + + if( g_smoothClients.integer ) + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + else + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + + switch( client->ps.weapon ) + { + case WP_ALEVEL0: + if( !CheckVenomAttack( ent ) ) + { + client->ps.weaponstate = WEAPON_READY; + } + else + { + client->ps.generic1 = WPM_PRIMARY; + G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); + } + break; + + case WP_ALEVEL1: + case WP_ALEVEL1_UPG: + CheckGrabAttack( ent ); + break; + + case WP_ALEVEL3: + case WP_ALEVEL3_UPG: + if( !CheckPounceAttack( ent ) ) + { + client->ps.weaponstate = WEAPON_READY; + } + else + { + client->ps.generic1 = WPM_SECONDARY; + G_AddEvent( ent, EV_FIRE_WEAPON2, 0 ); + } + break; + + case WP_ALEVEL4: + // If not currently in a trample, reset the trample bookkeeping data + if( !( client->ps.pm_flags & PMF_CHARGE ) && client->trampleBuildablesHitPos ) + { + ent->client->trampleBuildablesHitPos = 0; + memset( ent->client->trampleBuildablesHit, 0, sizeof( ent->client->trampleBuildablesHit ) ); + } + break; + + case WP_HBUILD: + CheckCkitRepair( ent ); + break; + + default: + break; + } + + SendPendingPredictableEvents( &ent->client->ps ); + + if( !( ent->client->ps.eFlags & EF_FIRING ) ) + client->fireHeld = qfalse; // for grapple + if( !( ent->client->ps.eFlags & EF_FIRING2 ) ) + client->fire2Held = qfalse; + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy( pm.mins, ent->r.mins ); + VectorCopy( pm.maxs, ent->r.maxs ); + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // touch other objects + ClientImpacts( ent, &pm ); + + // execute client events + ClientEvents( ent, oldEventSequence ); + + // link entity now, after any personal teleporters have been used + trap_LinkEntity( ent ); + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + VectorCopy( ent->client->ps.origin, ent->s.origin ); + + // save results of triggers and client events + if( ent->client->ps.eventSequence != oldEventSequence ) + ent->eventTime = level.time; + + // Don't think anymore if dead + if( client->ps.stats[ STAT_HEALTH ] <= 0 ) + return; + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + if( ( client->buttons & BUTTON_USE_EVOLVE ) && !( client->oldbuttons & BUTTON_USE_EVOLVE ) && + client->ps.stats[ STAT_HEALTH ] > 0 ) + { + trace_t trace; + vec3_t eyes, view, point; + gentity_t *traceEnt; + +#define USE_OBJECT_RANGE 64 + + int entityList[ MAX_GENTITIES ]; + vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; + vec3_t mins, maxs; + int i, num; + + // look for object infront of player + AngleVectors( client->ps.viewangles, view, NULL, NULL ); + BG_GetClientViewOrigin( &client->ps, eyes ); // !@#CUBOID + VectorMA( eyes, USE_OBJECT_RANGE, view, point ); + + trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); + traceEnt = &g_entities[ trace.entityNum ]; + + if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) + traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context + else + { + //no entity in front of player - do a small area search + + VectorAdd( client->ps.origin, range, maxs ); + VectorSubtract( client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + traceEnt = &g_entities[ entityList[ i ] ]; + + if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) + { + traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context + break; + } + } + + if( i == num && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + if( BG_AlienCanEvolve( client->ps.stats[ STAT_CLASS ], + client->pers.credit, + g_alienStage.integer ) ) + { + //no nearby objects and alien - show class menu + G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); + } + else + { + //flash frags + G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); + } + } + } + } + + client->ps.persistant[ PERS_BP ] = G_GetBuildPoints( client->ps.origin, + client->ps.stats[ STAT_TEAM ] ); + client->ps.persistant[ PERS_MARKEDBP ] = G_GetMarkedBuildPoints( client->ps.origin, + client->ps.stats[ STAT_TEAM ] ); + + if( client->ps.persistant[ PERS_BP ] < 0 ) + client->ps.persistant[ PERS_BP ] = 0; + + // perform once-a-second actions + ClientTimerActions( ent, msec ); + + if( ent->suicideTime > 0 && ent->suicideTime < level.time ) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; + player_die( ent, ent, ent, 100000, MOD_SUICIDE ); + + ent->suicideTime = 0; + } +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum ) +{ + gentity_t *ent; + + ent = g_entities + clientNum; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if( !g_synchronousClients.integer ) + ClientThink_real( ent ); +} + + +void G_RunClient( gentity_t *ent ) +{ + if( !g_synchronousClients.integer ) + return; + + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + + +/* +================== +SpectatorClientEndFrame + +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) +{ + gclient_t *cl; + int clientNum; + int score, ping; + + // if we are doing a chase cam or a remote view, grab the latest info + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + { + clientNum = ent->client->sess.spectatorClient; + if( clientNum >= 0 && clientNum < level.maxclients ) + { + cl = &level.clients[ clientNum ]; + if( cl->pers.connected == CON_CONNECTED ) + { + score = ent->client->ps.persistant[ PERS_SCORE ]; + ping = ent->client->ps.ping; + + // Copy + ent->client->ps = cl->ps; + + // Restore + ent->client->ps.persistant[ PERS_SCORE ] = score; + ent->client->ps.ping = ping; + + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.pm_flags &= ~PMF_QUEUED; + } + } + } +} + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) +{ + clientPersistant_t *pers; + + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) + { + SpectatorClientEndFrame( ent ); + return; + } + + pers = &ent->client->pers; + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if( level.intermissiontime ) + return; + + // burn from lava, etc + P_WorldEffects( ent ); + + // apply all the damage taken this frame + P_DamageFeedback( ent ); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if( level.time - ent->client->lastCmdTime > 1000 ) + ent->s.eFlags |= EF_CONNECTION; + else + ent->s.eFlags &= ~EF_CONNECTION; + + ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health... + + // respawn if dead + if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 && level.time >= ent->client->respawnTime ) + respawn( ent ); + + G_SetClientSound( ent ); + + // set the latest infor + if( g_smoothClients.integer ) + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + else + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + + SendPendingPredictableEvents( &ent->client->ps ); +} + diff --git a/src/game/g_active.c.orig b/src/game/g_active.c.orig new file mode 100644 index 0000000..cfa6f89 --- /dev/null +++ b/src/game/g_active.c.orig @@ -0,0 +1,1937 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +/* To create useful and interesting cuboids (like ladders), the ability to specify + * custom surface flags for buildings was needed. This is what the following function + * was written for. It replaces the regular tracing function in PM code (also on the + * client side, grep for CG_PMTraceHack). This function also takes into account + * the modelindex of the entity it found and adjusts the surfaceFlags output + * accordingly. This way custom surface flags for buildings can be done without + * messing around with the PM code or the engine itself. + */ +void G_PMTraceHack(trace_t *results,const vec3_t start,const vec3_t mins,const vec3_t maxs,const vec3_t end,int passEntityNum,int contentmask ) +{ + int modelindex; + trap_Trace(results,start,mins,maxs,end,passEntityNum,contentmask); + if(results->entityNum!=ENTITYNUM_NONE) + { + modelindex=g_entities[results->entityNum].s.modelindex; + if(modelindex>=CUBOID_FIRST&&modelindex<=CUBOID_LAST&&(g_entities[results->entityNum].s.eFlags&EF_B_SPAWNED)) + results->surfaceFlags|=BG_CuboidAttributes(modelindex)->surfaceFlags; + } +} + + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) +{ + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if( !PM_Live( client->ps.pm_type ) ) + return; + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if( count == 0 ) + return; // didn't take any damage + + if( count > 255 ) + count = 255; + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if( client->damage_fromWorld ) + { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } + else + { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[ PITCH ] / 360.0 * 256; + client->ps.damageYaw = angles[ YAW ] / 360.0 * 256; + } + + // play an apropriate pain sound + if( ( level.time > player->pain_debounce_time ) && !( player->flags & FL_GODMODE ) ) + { + player->pain_debounce_time = level.time + 700; + G_AddEvent( player, EV_PAIN, player->health > 255 ? 255 : player->health ); + client->ps.damageEvent++; + } + + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + + + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) +{ + int waterlevel; + + if( ent->client->noclip ) + { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + waterlevel = ent->waterlevel; + + // + // check for drowning + // + if( waterlevel == 3 ) + { + // if out of air, start drowning + if( ent->client->airOutTime < level.time) + { + // drown! + ent->client->airOutTime += 1000; + if( ent->health > 0 ) + { + // take more damage the longer underwater + ent->damage += 2; + if( ent->damage > 15 ) + ent->damage = 15; + + // play a gurp sound instead of a normal pain sound + if( ent->health <= ent->damage ) + G_Sound( ent, CHAN_VOICE, G_SoundIndex( "*drown.wav" ) ); + else if( rand( ) < RAND_MAX / 2 + 1 ) + G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp1.wav" ) ); + else + G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp2.wav" ) ); + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage( ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER ); + } + } + } + else + { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if( waterlevel && + ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) + { + if( ent->health > 0 && + ent->pain_debounce_time <= level.time ) + { + if( ent->watertype & CONTENTS_LAVA ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, + 30 * waterlevel, 0, MOD_LAVA ); + } + + if( ent->watertype & CONTENTS_SLIME ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, + 10 * waterlevel, 0, MOD_SLIME ); + } + } + } +} + + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) +{ + if( ent->waterlevel && ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) + ent->client->ps.loopSound = level.snd_fry; + else + ent->client->ps.loopSound = 0; +} + + + +//============================================================== + +/* +============== +ClientShove +============== +*/ +static int GetClientMass( gentity_t *ent ) +{ + int entMass = 100; + + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + entMass = BG_Class( ent->client->pers.classSelection )->health; + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + { + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) ) + entMass *= 2; + } + else + return 0; + return entMass; +} + +static void ClientShove( gentity_t *ent, gentity_t *victim ) +{ + vec3_t dir, push; + float force; + int entMass, vicMass; + + // Don't push if the entity is not trying to move + if( !ent->client->pers.cmd.rightmove && !ent->client->pers.cmd.forwardmove && + !ent->client->pers.cmd.upmove ) + return; + + // Cannot push enemy players unless they are walking on the player + if( !OnSameTeam( ent, victim ) && + victim->client->ps.groundEntityNum != ent - g_entities ) + return; + + // Shove force is scaled by relative mass + entMass = GetClientMass( ent ); + vicMass = GetClientMass( victim ); + if( vicMass <= 0 || entMass <= 0 ) + return; + force = g_shove.value * entMass / vicMass; + if( force < 0 ) + force = 0; + if( force > 150 ) + force = 150; + + // Give the victim some shove velocity + VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, dir ); + VectorNormalizeFast( dir ); + VectorScale( dir, force, push ); + VectorAdd( victim->client->ps.velocity, push, victim->client->ps.velocity ); + + // Set the pmove timer so that the other client can't cancel + // out the movement immediately + if( !victim->client->ps.pm_time ) + { + int time; + + time = force * 2 + 0.5f; + if( time < 50 ) + time = 50; + if( time > 200 ) + time = 200; + victim->client->ps.pm_time = time; + victim->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } +} + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) +{ + int i; + trace_t trace; + gentity_t *other; + + // clear a fake trace struct for touch function + memset( &trace, 0, sizeof( trace ) ); + + for( i = 0; i < pm->numtouch; i++ ) + { + other = &g_entities[ pm->touchents[ i ] ]; + + // see G_UnlaggedDetectCollisions(), this is the inverse of that. + // if our movement is blocked by another player's real position, + // don't use the unlagged position for them because they are + // blocking or server-side Pmove() from reaching it + if( other->client && other->client->unlaggedCalc.used ) + other->client->unlaggedCalc.used = qfalse; + + // tyrant impact attacks + if( ent->client->ps.weapon == WP_ALEVEL4 ) + { + G_ChargeAttack( ent, other ); + G_CrushAttack( ent, other ); + } + + // shove players + if( ent->client && other->client ) + ClientShove( ent, other ); + + // touch triggers + if( other->touch ) + other->touch( other, ent, &trace ); + } +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) +{ + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + vec3_t pmins, pmaxs; + static vec3_t range = { 10, 10, 10 }; + + if( !ent->client ) + return; + + // dead clients don't activate triggers! + if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 ) + return; + + BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], + pmins, pmaxs, NULL, NULL, NULL ); + + VectorAdd( ent->client->ps.origin, pmins, mins ); + VectorAdd( ent->client->ps.origin, pmaxs, maxs ); + + VectorSubtract( mins, range, mins ); + VectorAdd( maxs, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if( !hit->touch && !ent->touch ) + continue; + + if( !( hit->r.contents & CONTENTS_TRIGGER ) ) + continue; + + // ignore most entities if a spectator + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) + { + if( hit->s.eType != ET_TELEPORT_TRIGGER && + // this is ugly but adding a new ET_? type will + // most likely cause network incompatibilities + hit->touch != Touch_DoorTrigger ) + { + //check for manually triggered doors + manualTriggerSpectator( hit, ent ); + continue; + } + } + + if( !trap_EntityContact( mins, maxs, hit ) ) + continue; + + memset( &trace, 0, sizeof( trace ) ); + + if( hit->touch ) + hit->touch( hit, ent, &trace ); + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) +{ + pmove_t pm; + gclient_t *client; + int clientNum; + qboolean attack1, attack3, following, queued; + + client = ent->client; + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + attack1 = ( client->buttons & BUTTON_ATTACK ) && + !( client->oldbuttons & BUTTON_ATTACK ); + attack3 = ( client->buttons & BUTTON_USE_HOLDABLE ) && + !( client->oldbuttons & BUTTON_USE_HOLDABLE ); + + // We are in following mode only if we are following a non-spectating client + following = client->sess.spectatorState == SPECTATOR_FOLLOW; + if( following ) + { + clientNum = client->sess.spectatorClient; + if( clientNum < 0 || clientNum > level.maxclients || + !g_entities[ clientNum ].client || + g_entities[ clientNum ].client->sess.spectatorState != SPECTATOR_NOT ) + following = qfalse; + } + + // Check to see if we are in the spawn queue + if( client->pers.teamSelection == TEAM_ALIENS ) + queued = G_SearchSpawnQueue( &level.alienSpawnQueue, ent - g_entities ); + else if( client->pers.teamSelection == TEAM_HUMANS ) + queued = G_SearchSpawnQueue( &level.humanSpawnQueue, ent - g_entities ); + else + queued = qfalse; + + // Wants to get out of spawn queue + if( attack1 && queued ) + { + if( client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); + client->pers.classSelection = PCL_NONE; + client->ps.stats[ STAT_CLASS ] = PCL_NONE; + client->ps.pm_flags &= ~PMF_QUEUED; + queued = qfalse; + } + else if( attack1 ) + { + // Wants to get into spawn queue + if( client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + if( client->pers.teamSelection == TEAM_NONE ) + G_TriggerMenu( client->ps.clientNum, MN_TEAM ); + else if( client->pers.teamSelection == TEAM_ALIENS ) + G_TriggerMenu( client->ps.clientNum, MN_A_CLASS ); + else if( client->pers.teamSelection == TEAM_HUMANS ) + G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN ); + } + + // We are either not following anyone or following a spectator + if( !following ) + { + if( client->sess.spectatorState == SPECTATOR_LOCKED || + client->sess.spectatorState == SPECTATOR_FOLLOW ) + client->ps.pm_type = PM_FREEZE; + else if( client->noclip ) + client->ps.pm_type = PM_NOCLIP; + else + client->ps.pm_type = PM_SPECTATOR; + + if( queued ) + client->ps.pm_flags |= PMF_QUEUED; + + client->ps.speed = client->pers.flySpeed; + client->ps.stats[ STAT_STAMINA ] = 0; + client->ps.stats[ STAT_MISC ] = 0; + client->buildTimer=0; + client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + client->ps.stats[ STAT_CLASS ] = PCL_NONE; + client->ps.weapon = WP_NONE; + + // Set up for pmove + memset( &pm, 0, sizeof( pm ) ); + pm.ps = &client->ps; + pm.pmext = &client->pmext; + pm.cmd = *ucmd; + pm.tracemask = MASK_DEADSOLID; // spectators can fly through bodies + pm.trace = G_PMTraceHack; + pm.pointcontents = trap_PointContents; + + // Perform a pmove + Pmove( &pm ); + + // Save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + + // Set the queue position and spawn count for the client side + if( client->ps.pm_flags & PMF_QUEUED ) + { + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + client->ps.persistant[ PERS_QUEUEPOS ] = + G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); + client->ps.persistant[ PERS_SPAWNS ] = level.numAlienSpawns; + } + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + client->ps.persistant[ PERS_QUEUEPOS ] = + G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); + client->ps.persistant[ PERS_SPAWNS ] = level.numHumanSpawns; + } + } + } +} + + + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gentity_t *ent ) +{ + gclient_t *client = ent->client; + + if( ! g_inactivity.integer ) + { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } + else if( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + ( client->pers.cmd.buttons & BUTTON_ATTACK ) ) + { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } + else if( !client->pers.localClient ) + { + if( level.time > client->inactivityTime && + !G_admin_permission( ent, ADMF_ACTIVITY ) ) + { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + + if( level.time > client->inactivityTime - 10000 && + !client->inactivityWarning && + !G_admin_permission( ent, ADMF_ACTIVITY ) ) + { + client->inactivityWarning = qtrue; + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) +{ + gclient_t *client; + usercmd_t *ucmd; + int aForward, aRight; + qboolean walking = qfalse, stopped = qfalse, + crouched = qfalse, jumping = qfalse, + strafing = qfalse; + int i; + + ucmd = &ent->client->pers.cmd; + + aForward = abs( ucmd->forwardmove ); + aRight = abs( ucmd->rightmove ); + + if( aForward == 0 && aRight == 0 ) + stopped = qtrue; + else if( aForward <= 64 && aRight <= 64 ) + walking = qtrue; + + if( aRight > 0 ) + strafing = qtrue; + + if( ucmd->upmove > 0 ) + jumping = qtrue; + else if( ent->client->ps.pm_flags & PMF_DUCKED ) + crouched = qtrue; + + client = ent->client; + client->time100 += msec; + client->time1000 += msec; + client->time10000 += msec; + + while ( client->time100 >= 100 ) + { + weapon_t weapon = BG_GetPlayerWeapon( &client->ps ); + + client->time100 -= 100; + + // Restore or subtract stamina + if( stopped || client->ps.pm_type == PM_JETPACK ) + client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE; + else if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && + !( client->buttons & BUTTON_WALKING ) ) // walk overrides sprint + client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; + else if( walking || crouched ) + client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; + + // Check stamina limits + if( client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) + client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + else if( client->ps.stats[ STAT_STAMINA ] < -STAMINA_MAX ) + client->ps.stats[ STAT_STAMINA ] = -STAMINA_MAX; + + if( weapon == WP_ABUILD || weapon == WP_ABUILD2 || + BG_InventoryContainsWeapon( WP_HBUILD, client->ps.stats ) ) + { + client->buildTimer-=MIN(100,client->buildTimer); + G_RecalcBuildTimer(client); + } + + switch( weapon ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + + // Set validity bit on buildable + if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) + { + int dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; + vec3_t dummy, dummy2; + + if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, + dist, dummy, dummy2, client->cuboidSelection ) == IBE_NONE ) + client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; + else + client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT; + + // Let the client know which buildables will be removed by building + for( i = 0; i < MAX_MISC; i++ ) + { + if( i < level.numBuildablesForRemoval ) + client->ps.misc[ i ] = level.markedBuildables[ i ]->s.number; + else + client->ps.misc[ i ] = 0; + } + } + else + { + for( i = 0; i < MAX_MISC; i++ ) + client->ps.misc[ i ] = 0; + } + break; + + default: + break; + } + + if( ent->client->pers.teamSelection == TEAM_HUMANS && + ( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X ) ) + { + int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime ); + + if( remainingStartupTime < 0 ) + { + if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && + ent->client->medKitHealthToRestore && + ent->client->ps.pm_type != PM_DEAD ) + { + ent->client->medKitHealthToRestore--; + ent->health++; + } + else + ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X; + } + else + { + if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && + ent->client->medKitHealthToRestore && + ent->client->ps.pm_type != PM_DEAD ) + { + //partial increase + if( level.time > client->medKitIncrementTime ) + { + ent->client->medKitHealthToRestore--; + ent->health++; + + client->medKitIncrementTime = level.time + + ( remainingStartupTime / MEDKIT_STARTUP_SPEED ); + } + } + else + ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X; + } + } + + //use fuel + if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) && + BG_UpgradeIsActive( UP_JETPACK, ent->client->ps.stats ) && + ent->client->ps.stats[ STAT_FUEL ] > 0 ) + { + ent->client->ps.stats[ STAT_FUEL ] -= JETPACK_FUEL_USAGE; + if( ent->client->ps.stats[ STAT_FUEL ] <= 0 ) + { + ent->client->ps.stats[ STAT_FUEL ] = 0; + BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); + BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &client->ps ); + } + } + + //biores + if( BG_InventoryContainsUpgrade( UP_BIORES, ent->client->ps.stats ) && + ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] ) + { + float hp, dmod; + + dmod = MIN( level.time - ent->lastDamageTime, 3000 ) / 3000.0f; + hp = (float)ent->health / ent->client->ps.stats[ STAT_MAX_HEALTH ]; + ent->client->bioresHealTimer += (BIORES_EQUATION) * 10 * dmod; + } + + if( ent->client->bioresHealTimer >= 100 ) + { + int delta; + + delta = ent->client->bioresHealTimer / 100; + ent->health = MAX( MIN( ent->health+delta, ent->client->ps.stats[ STAT_MAX_HEALTH ] ), 1 ); + + ent->client->bioresHealTimer %= 100; + } + + } + + while( client->time1000 >= 1000 ) + { + client->time1000 -= 1000; + + //client is poisoned + if( client->ps.stats[ STAT_STATE ] & SS_POISONED ) + { + int damage = ALIEN_POISON_DMG; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) + damage -= BSUIT_POISON_PROTECTION; + + if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, client->ps.stats ) ) + damage -= HELMET_MK1_POISON_PROTECTION; + + if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, client->ps.stats ) ) + damage -= HELMET_MK2_POISON_PROTECTION; + + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) + damage -= LIGHTARMOUR_POISON_PROTECTION; + + if( BG_InventoryContainsUpgrade( UP_BIORES, client->ps.stats ) ) + damage *= BIORES_POISON_MODIFIER; + + G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, + 0, damage, 0, MOD_POISON ); + } + + // turn off life support when a team admits defeat + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && + level.surrenderTeam == TEAM_ALIENS ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, + BG_Class( client->ps.stats[ STAT_CLASS ] )->regenRate, + DAMAGE_NO_ARMOR, MOD_SUICIDE ); + } + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + level.surrenderTeam == TEAM_HUMANS ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, 5, DAMAGE_NO_ARMOR, MOD_SUICIDE ); + } + + // lose some voice enthusiasm + if( client->voiceEnthusiasm > 0.0f ) + client->voiceEnthusiasm -= VOICE_ENTHUSIASM_DECAY; + else + client->voiceEnthusiasm = 0.0f; + + client->pers.aliveSeconds++; + if( g_freeFundPeriod.integer > 0 && + client->pers.aliveSeconds % g_freeFundPeriod.integer == 0 ) + { + // Give clients some credit periodically + if( G_TimeTilSuddenDeath( ) > 0 ) + { + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + G_AddCreditToClient( client, FREEKILL_ALIEN, qtrue ); + else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + G_AddCreditToClient( client, FREEKILL_HUMAN, qtrue ); + } + } + } + + while( client->time10000 >= 10000 ) + { + client->time10000 -= 10000; + + if( ent->client->ps.weapon == WP_ABUILD || + ent->client->ps.weapon == WP_ABUILD2 ) + { + AddScore( ent, ALIEN_BUILDER_SCOREINC ); + } + else if( ent->client->ps.weapon == WP_HBUILD ) + { + AddScore( ent, HUMAN_BUILDER_SCOREINC ); + } + + // Give score to basis that healed other aliens + if( ent->client->pers.hasHealed ) + { + if( client->ps.weapon == WP_ALEVEL1 ) + AddScore( ent, LEVEL1_REGEN_SCOREINC ); + else if( client->ps.weapon == WP_ALEVEL1_UPG ) + AddScore( ent, LEVEL1_UPG_REGEN_SCOREINC ); + + ent->client->pers.hasHealed = qfalse; + } + } + + // Regenerate Adv. Dragoon barbs + if( client->ps.weapon == WP_ALEVEL3_UPG ) + { + if( client->ps.ammo < BG_Weapon( WP_ALEVEL3_UPG )->maxAmmo ) + { + if( ent->timestamp + LEVEL3_BOUNCEBALL_REGEN < level.time ) + { + client->ps.ammo++; + ent->timestamp = level.time; + } + } + else + ent->timestamp = level.time; + } +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) +{ + client->ps.eFlags &= ~EF_FIRING; + client->ps.eFlags &= ~EF_FIRING2; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + if( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) + client->readyToExit = 1; +} + + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +void ClientEvents( gentity_t *ent, int oldEventSequence ) +{ + int i; + int event; + gclient_t *client; + int damage; + vec3_t dir; + vec3_t point, mins; + float fallDistance; + class_t class; + + client = ent->client; + class = client->ps.stats[ STAT_CLASS ]; + + if( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) + oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; + + for( i = oldEventSequence; i < client->ps.eventSequence; i++ ) + { + event = client->ps.events[ i & ( MAX_PS_EVENTS - 1 ) ]; + + switch( event ) + { + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + if( ent->s.eType != ET_PLAYER ) + break; // not in the player model + + fallDistance = ( (float)client->ps.stats[ STAT_FALLDIST ] - MIN_FALL_DISTANCE ) / + ( MAX_FALL_DISTANCE - MIN_FALL_DISTANCE ); + + if( fallDistance < 0.0f ) + fallDistance = 0.0f; + else if( fallDistance > 1.0f ) + fallDistance = 1.0f; + + damage = (int)( (float)BG_Class( class )->health * + BG_Class( class )->fallDamage * fallDistance ); + + VectorSet( dir, 0, 0, 1 ); + BG_ClassBoundingBox( class, mins, NULL, NULL, NULL, NULL ); + mins[ 0 ] = mins[ 1 ] = 0.0f; + VectorAdd( client->ps.origin, mins, point ); + + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage( ent, NULL, NULL, dir, point, damage, DAMAGE_NO_LOCDAMAGE, MOD_FALLING ); + break; + + case EV_FIRE_WEAPON: + FireWeapon( ent ); + break; + + case EV_FIRE_WEAPON2: + FireWeapon2( ent ); + break; + + case EV_FIRE_WEAPON3: + FireWeapon3( ent ); + break; + + case EV_NOAMMO: + break; + + default: + break; + } + } +} + + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) +{ + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if( ps->entityEventSequence < ps->eventSequence ) + { + // create a temporary entity for this event which is sent to everyone + // except the client who generated the event + seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } +} + +/* +============== + G_UnlaggedStore + + Called on every server frame. Stores position data for the client at that + into client->unlaggedHist[] and the time into level.unlaggedTimes[]. + This data is used by G_UnlaggedCalc() +============== +*/ +void G_UnlaggedStore( void ) +{ + int i = 0; + gentity_t *ent; + unlagged_t *save; + + if( !g_unlagged.integer ) + return; + level.unlaggedIndex++; + if( level.unlaggedIndex >= MAX_UNLAGGED_MARKERS ) + level.unlaggedIndex = 0; + + level.unlaggedTimes[ level.unlaggedIndex ] = level.time; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + save = &ent->client->unlaggedHist[ level.unlaggedIndex ]; + save->used = qfalse; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + VectorCopy( ent->r.mins, save->mins ); + VectorCopy( ent->r.maxs, save->maxs ); + VectorCopy( ent->s.pos.trBase, save->origin ); + save->used = qtrue; + } +} + +/* +============== + G_UnlaggedClear + + Mark all unlaggedHist[] markers for this client invalid. Useful for + preventing teleporting and death. +============== +*/ +void G_UnlaggedClear( gentity_t *ent ) +{ + int i; + + for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) + ent->client->unlaggedHist[ i ].used = qfalse; +} + +/* +============== + G_UnlaggedCalc + + Loops through all active clients and calculates their predicted position + for time then stores it in client->unlaggedCalc +============== +*/ +void G_UnlaggedCalc( int time, gentity_t *rewindEnt ) +{ + int i = 0; + gentity_t *ent; + int startIndex = level.unlaggedIndex; + int stopIndex = -1; + int frameMsec = 0; + float lerp = 0.5f; + + if( !g_unlagged.integer ) + return; + + // clear any calculated values from a previous run + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + ent->client->unlaggedCalc.used = qfalse; + } + + for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) + { + if( level.unlaggedTimes[ startIndex ] <= time ) + break; + stopIndex = startIndex; + if( --startIndex < 0 ) + startIndex = MAX_UNLAGGED_MARKERS - 1; + } + if( i == MAX_UNLAGGED_MARKERS ) + { + // if we searched all markers and the oldest one still isn't old enough + // just use the oldest marker with no lerping + lerp = 0.0f; + } + + // client is on the current frame, no need for unlagged + if( stopIndex == -1 ) + return; + + // lerp between two markers + frameMsec = level.unlaggedTimes[ stopIndex ] - + level.unlaggedTimes[ startIndex ]; + if( frameMsec > 0 ) + { + lerp = ( float )( time - level.unlaggedTimes[ startIndex ] ) / + ( float )frameMsec; + } + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( ent == rewindEnt ) + continue; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + if( !ent->client->unlaggedHist[ startIndex ].used ) + continue; + if( !ent->client->unlaggedHist[ stopIndex ].used ) + continue; + + // between two unlagged markers + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins, + ent->client->unlaggedHist[ stopIndex ].mins, + ent->client->unlaggedCalc.mins ); + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs, + ent->client->unlaggedHist[ stopIndex ].maxs, + ent->client->unlaggedCalc.maxs ); + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin, + ent->client->unlaggedHist[ stopIndex ].origin, + ent->client->unlaggedCalc.origin ); + + ent->client->unlaggedCalc.used = qtrue; + } +} + +/* +============== + G_UnlaggedOff + + Reverses the changes made to all active clients by G_UnlaggedOn() +============== +*/ +void G_UnlaggedOff( void ) +{ + int i = 0; + gentity_t *ent; + + if( !g_unlagged.integer ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( !ent->client->unlaggedBackup.used ) + continue; + VectorCopy( ent->client->unlaggedBackup.mins, ent->r.mins ); + VectorCopy( ent->client->unlaggedBackup.maxs, ent->r.maxs ); + VectorCopy( ent->client->unlaggedBackup.origin, ent->r.currentOrigin ); + ent->client->unlaggedBackup.used = qfalse; + trap_LinkEntity( ent ); + } +} + +/* +============== + G_UnlaggedOn + + Called after G_UnlaggedCalc() to apply the calculated values to all active + clients. Once finished tracing, G_UnlaggedOff() must be called to restore + the clients' position data + + As an optimization, all clients that have an unlagged position that is + not touchable at "range" from "muzzle" will be ignored. This is required + to prevent a huge amount of trap_LinkEntity() calls per user cmd. +============== +*/ + +void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range ) +{ + int i = 0; + gentity_t *ent; + unlagged_t *calc; + + if( !g_unlagged.integer ) + return; + + if( !attacker->client->pers.useUnlagged ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + calc = &ent->client->unlaggedCalc; + + if( !calc->used ) + continue; + if( ent->client->unlaggedBackup.used ) + continue; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( VectorCompare( ent->r.currentOrigin, calc->origin ) ) + continue; + if( muzzle ) + { + float r1 = Distance( calc->origin, calc->maxs ); + float r2 = Distance( calc->origin, calc->mins ); + float maxRadius = ( r1 > r2 ) ? r1 : r2; + + if( Distance( muzzle, calc->origin ) > range + maxRadius ) + continue; + } + + // create a backup of the real positions + VectorCopy( ent->r.mins, ent->client->unlaggedBackup.mins ); + VectorCopy( ent->r.maxs, ent->client->unlaggedBackup.maxs ); + VectorCopy( ent->r.currentOrigin, ent->client->unlaggedBackup.origin ); + ent->client->unlaggedBackup.used = qtrue; + + // move the client to the calculated unlagged position + VectorCopy( calc->mins, ent->r.mins ); + VectorCopy( calc->maxs, ent->r.maxs ); + VectorCopy( calc->origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } +} +/* +============== + G_UnlaggedDetectCollisions + + cgame prediction will predict a client's own position all the way up to + the current time, but only updates other player's positions up to the + postition sent in the most recent snapshot. + + This allows player X to essentially "move through" the position of player Y + when player X's cmd is processed with Pmove() on the server. This is because + player Y was clipping player X's Pmove() on his client, but when the same + cmd is processed with Pmove on the server it is not clipped. + + Long story short (too late): don't use unlagged positions for players who + were blocking this player X's client-side Pmove(). This makes the assumption + that if player X's movement was blocked in the client he's going to still + be up against player Y when the Pmove() is run on the server with the + same cmd. + + NOTE: this must be called after Pmove() and G_UnlaggedCalc() +============== +*/ +static void G_UnlaggedDetectCollisions( gentity_t *ent ) +{ + unlagged_t *calc; + trace_t tr; + float r1, r2; + float range; + + if( !g_unlagged.integer ) + return; + + if( !ent->client->pers.useUnlagged ) + return; + + calc = &ent->client->unlaggedCalc; + + // if the client isn't moving, this is not necessary + if( VectorCompare( ent->client->oldOrigin, ent->client->ps.origin ) ) + return; + + range = Distance( ent->client->oldOrigin, ent->client->ps.origin ); + + // increase the range by the player's largest possible radius since it's + // the players bounding box that collides, not their origin + r1 = Distance( calc->origin, calc->mins ); + r2 = Distance( calc->origin, calc->maxs ); + range += ( r1 > r2 ) ? r1 : r2; + + G_UnlaggedOn( ent, ent->client->oldOrigin, range ); + + trap_Trace(&tr, ent->client->oldOrigin, ent->r.mins, ent->r.maxs, + ent->client->ps.origin, ent->s.number, MASK_PLAYERSOLID ); + if( tr.entityNum >= 0 && tr.entityNum < MAX_CLIENTS ) + g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse; + + G_UnlaggedOff( ); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) +{ + gclient_t *client; + pmove_t pm; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + + client = ent->client; + + // don't think if the client is not yet connected (and thus not yet spawned in) + if( client->pers.connected != CON_CONNECTED ) + return; + + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + // sanity check the command time to prevent speedup cheating + if( ucmd->serverTime > level.time + 200 ) + { + ucmd->serverTime = level.time + 200; +// G_Printf("serverTime <<<<<\n" ); + } + + if( ucmd->serverTime < level.time - 1000 ) + { + ucmd->serverTime = level.time - 1000; +// G_Printf("serverTime >>>>>\n" ); + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) + return; + + if( msec > 200 ) + msec = 200; + + client->unlaggedTime = ucmd->serverTime; + + if( pmove_msec.integer < 8 ) + trap_Cvar_Set( "pmove_msec", "8" ); + else if( pmove_msec.integer > 33 ) + trap_Cvar_Set( "pmove_msec", "33" ); + + if( pmove_fixed.integer || client->pers.pmoveFixed ) + { + ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; + //if (ucmd->serverTime - client->ps.commandTime <= 0) + // return; + } + + // + // check for exiting intermission + // + if( level.intermissiontime ) + { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + if( client->sess.spectatorState != SPECTATOR_NOT ) + { + if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) + return; + + SpectatorThink( ent, ucmd ); + return; + } + + G_namelog_update_score( client ); + + // check for inactivity timer, but never drop the local client of a non-dedicated server + if( !ClientInactivityTimer( ent ) ) + return; + + // calculate where ent is currently seeing all the other active clients + G_UnlaggedCalc( ent->client->unlaggedTime, ent ); + + if( client->noclip ) + client->ps.pm_type = PM_NOCLIP; + else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) + client->ps.pm_type = PM_DEAD; + else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED || + client->ps.stats[ STAT_STATE ] & SS_GRABBED ) + client->ps.pm_type = PM_GRABBED; + else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) + client->ps.pm_type = PM_JETPACK; + else + client->ps.pm_type = PM_NORMAL; + + if( ( client->ps.stats[ STAT_STATE ] & SS_GRABBED ) && + client->grabExpiryTime < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; + + if( ( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) && + client->lastLockTime + LOCKBLOB_LOCKTIME < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; + + if( ( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED ) && + client->lastSlowTime + ABUILDER_BLOB_TIME < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED; + + // Update boosted state flags + client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTEDWARNING; + if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + { + if( level.time - client->boostedTime >= BOOST_TIME ) + client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; + else if( level.time - client->boostedTime >= BOOST_WARN_TIME ) + client->ps.stats[ STAT_STATE ] |= SS_BOOSTEDWARNING; + } + + // Check if poison cloud has worn off + if( ( client->ps.eFlags & EF_POISONCLOUDED ) && + BG_PlayerPoisonCloudTime( &client->ps ) - level.time + + client->lastPoisonCloudedTime <= 0 ) + client->ps.eFlags &= ~EF_POISONCLOUDED; + + if( client->ps.stats[ STAT_STATE ] & SS_POISONED && + client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + + client->ps.gravity = g_gravity.value; + + if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) && + BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) ) + { + //if currently using a medkit or have no need for a medkit now + if( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X || + ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] && + !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) ) + { + BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); + } + else if( client->ps.stats[ STAT_HEALTH ] > 0 ) + { + //remove anti toxin + BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); + BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats ); + + client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; + + client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; + client->lastMedKitTime = level.time; + client->medKitHealthToRestore = + client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; + client->medKitIncrementTime = level.time + + ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED ); + + G_AddEvent( ent, EV_MEDKIT_USED, 0 ); + } + } + + // Replenish alien health + if( level.surrenderTeam != client->pers.teamSelection && + ent->nextRegenTime >= 0 && ent->nextRegenTime < level.time ) + { + float regenRate = + BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->regenRate; + + if( ent->health <= 0 || ent->nextRegenTime < 0 || regenRate == 0 ) + ent->nextRegenTime = -1; // no regen + else + { + int entityList[ MAX_GENTITIES ]; + int i, num; + int count, interval; + vec3_t range, mins, maxs; + float modifier = 1.0f; + + VectorSet( range, REGEN_BOOST_RANGE, REGEN_BOOST_RANGE, + REGEN_BOOST_RANGE ); + VectorAdd( client->ps.origin, range, maxs ); + VectorSubtract( client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + gentity_t *boost = &g_entities[ entityList[ i ] ]; + + if( Distance( client->ps.origin, boost->s.origin ) > REGEN_BOOST_RANGE ) + continue; + + if( modifier < BOOSTER_REGEN_MOD && boost->s.eType == ET_BUILDABLE && + boost->s.modelindex == BA_A_BOOSTER && boost->spawned && + boost->health > 0 && boost->powered ) + { + modifier = BOOSTER_REGEN_MOD; + continue; + } + + if( boost->s.eType == ET_PLAYER && boost->client && + boost->client->pers.teamSelection == + ent->client->pers.teamSelection && boost->health > 0 ) + { + class_t class = boost->client->ps.stats[ STAT_CLASS ]; + qboolean didBoost = qfalse; + + if( class == PCL_ALIEN_LEVEL1 && modifier < LEVEL1_REGEN_MOD ) + { + modifier = LEVEL1_REGEN_MOD; + didBoost = qtrue; + } + else if( class == PCL_ALIEN_LEVEL1_UPG && + modifier < LEVEL1_UPG_REGEN_MOD ) + { + modifier = LEVEL1_UPG_REGEN_MOD; + didBoost = qtrue; + } + + if( didBoost && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] ) + boost->client->pers.hasHealed = qtrue; + } + } + + // Transmit heal rate to the client so it can be displayed on the HUD + client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; + client->ps.stats[ STAT_STATE ] &= ~( SS_HEALING_2X | SS_HEALING_3X ); + if( modifier == 1.0f && !G_FindCreep( ent ) ) + { + client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; + modifier *= ALIEN_REGEN_NOCREEP_MOD; + } + else if( modifier >= 3.0f ) + client->ps.stats[ STAT_STATE ] |= SS_HEALING_3X; + else if( modifier >= 2.0f ) + client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; + + interval = 1000 / ( regenRate * modifier ); + // if recovery interval is less than frametime, compensate + count = 1 + ( level.time - ent->nextRegenTime ) / interval; + + ent->health += count; + ent->nextRegenTime += count * interval; + + // if at max health, clear damage counters + if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] ) + { + ent->health = client->ps.stats[ STAT_MAX_HEALTH ]; + for( i = 0; i < MAX_CLIENTS; i++ ) + ent->credits[ i ] = 0; + } + } + } + + if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) && + BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) ) + { + int lastWeapon = ent->s.weapon; + + //remove grenade + BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats ); + BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats ); + + //M-M-M-M-MONSTER HACK + ent->s.weapon = WP_GRENADE; + FireWeapon( ent ); + ent->s.weapon = lastWeapon; + } + + // set speed + if( client->ps.pm_type == PM_NOCLIP ) + client->ps.speed = client->pers.flySpeed; + else + client->ps.speed = g_speed.value * + BG_Class( client->ps.stats[ STAT_CLASS ] )->speed; + + if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; + + //randomly disable the jet pack if damaged + if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && + BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) + { + if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time ) + { + if( random( ) > JETPACK_DISABLE_CHANCE ) + client->ps.pm_type = PM_NORMAL; + } + + //switch jetpack off if no reactor + //if( !G_Reactor( ) ) + // BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); + } + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset( &pm, 0, sizeof( pm ) ); + + if( ent->flags & FL_FORCE_GESTURE ) + { + ent->flags &= ~FL_FORCE_GESTURE; + ent->client->pers.cmd.buttons |= BUTTON_GESTURE; + } + + // clear fall velocity before every pmove + client->pmext.fallVelocity = 0.0f; + + pm.ps = &client->ps; + pm.pmext = &client->pmext; + pm.cmd = *ucmd; + + if( pm.ps->pm_type == PM_DEAD ) + pm.tracemask = MASK_DEADSOLID; + else + pm.tracemask = MASK_PLAYERSOLID; + + pm.trace = G_PMTraceHack; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + VectorCopy( client->ps.origin, client->oldOrigin ); + + // moved from after Pmove -- potentially the cause of + // future triggering bugs + if( !ent->client->noclip ) + G_TouchTriggers( ent ); + + Pmove( &pm ); + + G_UnlaggedDetectCollisions( ent ); + + // save results of pmove + if( ent->client->ps.eventSequence != oldEventSequence ) + ent->eventTime = level.time; + + if( g_smoothClients.integer ) + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + else + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + + switch( client->ps.weapon ) + { + case WP_ALEVEL0: + if( !CheckVenomAttack( ent ) ) + { + client->ps.weaponstate = WEAPON_READY; + } + else + { + client->ps.generic1 = WPM_PRIMARY; + G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); + } + break; + + case WP_ALEVEL1: + case WP_ALEVEL1_UPG: + CheckGrabAttack( ent ); + break; + + case WP_ALEVEL3: + case WP_ALEVEL3_UPG: + if( !CheckPounceAttack( ent ) ) + { + client->ps.weaponstate = WEAPON_READY; + } + else + { + client->ps.generic1 = WPM_SECONDARY; + G_AddEvent( ent, EV_FIRE_WEAPON2, 0 ); + } + break; + + case WP_ALEVEL4: + // If not currently in a trample, reset the trample bookkeeping data + if( !( client->ps.pm_flags & PMF_CHARGE ) && client->trampleBuildablesHitPos ) + { + ent->client->trampleBuildablesHitPos = 0; + memset( ent->client->trampleBuildablesHit, 0, sizeof( ent->client->trampleBuildablesHit ) ); + } + break; + + case WP_HBUILD: + CheckCkitRepair( ent ); + break; + + default: + break; + } + + SendPendingPredictableEvents( &ent->client->ps ); + + if( !( ent->client->ps.eFlags & EF_FIRING ) ) + client->fireHeld = qfalse; // for grapple + if( !( ent->client->ps.eFlags & EF_FIRING2 ) ) + client->fire2Held = qfalse; + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy( pm.mins, ent->r.mins ); + VectorCopy( pm.maxs, ent->r.maxs ); + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // touch other objects + ClientImpacts( ent, &pm ); + + // execute client events + ClientEvents( ent, oldEventSequence ); + + // link entity now, after any personal teleporters have been used + trap_LinkEntity( ent ); + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + VectorCopy( ent->client->ps.origin, ent->s.origin ); + + // save results of triggers and client events + if( ent->client->ps.eventSequence != oldEventSequence ) + ent->eventTime = level.time; + + // Don't think anymore if dead + if( client->ps.stats[ STAT_HEALTH ] <= 0 ) + return; + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + if( ( client->buttons & BUTTON_USE_EVOLVE ) && !( client->oldbuttons & BUTTON_USE_EVOLVE ) && + client->ps.stats[ STAT_HEALTH ] > 0 ) + { + trace_t trace; + vec3_t eyes, view, point; + gentity_t *traceEnt; + +#define USE_OBJECT_RANGE 64 + + int entityList[ MAX_GENTITIES ]; + vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; + vec3_t mins, maxs; + int i, num; + + // look for object infront of player + AngleVectors( client->ps.viewangles, view, NULL, NULL ); + BG_GetClientViewOrigin( &client->ps, eyes ); // !@#CUBOID + VectorMA( eyes, USE_OBJECT_RANGE, view, point ); + + trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); + traceEnt = &g_entities[ trace.entityNum ]; + + if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) + traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context + else + { + //no entity in front of player - do a small area search + + VectorAdd( client->ps.origin, range, maxs ); + VectorSubtract( client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + traceEnt = &g_entities[ entityList[ i ] ]; + + if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) + { + traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context + break; + } + } + + if( i == num && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + if( BG_AlienCanEvolve( client->ps.stats[ STAT_CLASS ], + client->pers.credit, + g_alienStage.integer ) ) + { + //no nearby objects and alien - show class menu + G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); + } + else + { + //flash frags + G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); + } + } + } + } + + client->ps.persistant[ PERS_BP ] = G_GetBuildPoints( client->ps.origin, + client->ps.stats[ STAT_TEAM ] ); + client->ps.persistant[ PERS_MARKEDBP ] = G_GetMarkedBuildPoints( client->ps.origin, + client->ps.stats[ STAT_TEAM ] ); + + if( client->ps.persistant[ PERS_BP ] < 0 ) + client->ps.persistant[ PERS_BP ] = 0; + + // perform once-a-second actions + ClientTimerActions( ent, msec ); + + if( ent->suicideTime > 0 && ent->suicideTime < level.time ) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; + player_die( ent, ent, ent, 100000, MOD_SUICIDE ); + + ent->suicideTime = 0; + } +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum ) +{ + gentity_t *ent; + + ent = g_entities + clientNum; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if( !g_synchronousClients.integer ) + ClientThink_real( ent ); +} + + +void G_RunClient( gentity_t *ent ) +{ + if( !g_synchronousClients.integer ) + return; + + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + + +/* +================== +SpectatorClientEndFrame + +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) +{ + gclient_t *cl; + int clientNum; + int score, ping; + + // if we are doing a chase cam or a remote view, grab the latest info + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + { + clientNum = ent->client->sess.spectatorClient; + if( clientNum >= 0 && clientNum < level.maxclients ) + { + cl = &level.clients[ clientNum ]; + if( cl->pers.connected == CON_CONNECTED ) + { + score = ent->client->ps.persistant[ PERS_SCORE ]; + ping = ent->client->ps.ping; + + // Copy + ent->client->ps = cl->ps; + + // Restore + ent->client->ps.persistant[ PERS_SCORE ] = score; + ent->client->ps.ping = ping; + + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.pm_flags &= ~PMF_QUEUED; + } + } + } +} + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) +{ + clientPersistant_t *pers; + + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) + { + SpectatorClientEndFrame( ent ); + return; + } + + pers = &ent->client->pers; + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if( level.intermissiontime ) + return; + + // burn from lava, etc + P_WorldEffects( ent ); + + // apply all the damage taken this frame + P_DamageFeedback( ent ); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if( level.time - ent->client->lastCmdTime > 1000 ) + ent->s.eFlags |= EF_CONNECTION; + else + ent->s.eFlags &= ~EF_CONNECTION; + + ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health... + + // respawn if dead + if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 && level.time >= ent->client->respawnTime ) + respawn( ent ); + + G_SetClientSound( ent ); + + // set the latest infor + if( g_smoothClients.integer ) + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + else + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + + SendPendingPredictableEvents( &ent->client->ps ); +} + diff --git a/src/game/g_admin.c b/src/game/g_admin.c new file mode 100644 index 0000000..e753f19 --- /dev/null +++ b/src/game/g_admin.c @@ -0,0 +1,3277 @@ +/* +=========================================================================== +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +This shrubbot implementation is the original work of Tony J. White. + +Contains contributions from Wesley van Beelen, Chris Bajumpaa, Josh Menke, +and Travis Maurer. + +The functionality of this code mimics the behaviour of the currently +inactive project shrubet (http://www.etstats.com/shrubet/index.php?ver=2) +by Ryan Mannion. However, shrubet was a closed-source project and +none of it's code has been copied, only it's functionality. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +// big ugly global buffer for use with buffered printing of long outputs +static char g_bfb[ 32000 ]; + +// note: list ordered alphabetically +g_admin_cmd_t g_admin_cmds[ ] = + { + {"adjustban", G_admin_adjustban, qfalse, "ban", + "change the duration or reason of a ban. duration is specified as " + "numbers followed by units 'w' (weeks), 'd' (days), 'h' (hours) or " + "'m' (minutes), or seconds if no units are specified. if the duration is" + " preceded by a + or -, the ban duration will be extended or shortened by" + " the specified amount", + "[^3ban#^7] (^5/mask^7) (^5duration^7) (^5reason^7)" + }, + + {"adminhelp", G_admin_adminhelp, qtrue, "adminhelp", + "display admin commands available to you or help on a specific command", + "(^5command^7)" + }, + + {"admintest", G_admin_admintest, qfalse, "admintest", + "display your current admin level", + "" + }, + + {"allowbuild", G_admin_denybuild, qfalse, "denybuild", + "restore a player's ability to build", + "[^3name|slot#^7]" + }, + + {"allready", G_admin_allready, qfalse, "allready", + "makes everyone ready in intermission", + "" + }, + + {"ban", G_admin_ban, qfalse, "ban", + "ban a player by IP and GUID with an optional expiration time and reason." + " duration is specified as numbers followed by units 'w' (weeks), 'd' " + "(days), 'h' (hours) or 'm' (minutes), or seconds if no units are " + "specified", + "[^3name|slot#|IP(/mask)^7] (^5duration^7) (^5reason^7)" + }, + + {"builder", G_admin_builder, qtrue, "builder", + "show who built a structure", + "" + }, + + {"buildlog", G_admin_buildlog, qfalse, "buildlog", + "show buildable log", + "(^5name|slot#^7) (^5id^7)" + }, + + {"cancelvote", G_admin_endvote, qfalse, "cancelvote", + "cancel a vote taking place", + "(^5a|h^7)" + }, + + {"changemap", G_admin_changemap, qfalse, "changemap", + "load a map (and optionally force layout)", + "[^3mapname^7] (^5layout^7)" + }, + + {"denybuild", G_admin_denybuild, qfalse, "denybuild", + "take away a player's ability to build", + "[^3name|slot#^7]" + }, + + {"kick", G_admin_kick, qfalse, "kick", + "kick a player with an optional reason", + "[^3name|slot#^7] (^5reason^7)" + }, + + {"listadmins", G_admin_listadmins, qtrue, "listadmins", + "display a list of all server admins and their levels", + "(^5name^7) (^5start admin#^7)" + }, + + {"listlayouts", G_admin_listlayouts, qtrue, "listlayouts", + "display a list of all available layouts for a map", + "(^5mapname^7)" + }, + + {"listplayers", G_admin_listplayers, qtrue, "listplayers", + "display a list of players, their client numbers and their levels", + "" + }, + + {"lock", G_admin_lock, qfalse, "lock", + "lock a team to prevent anyone from joining it", + "[^3a|h^7]" + }, + + {"mute", G_admin_mute, qfalse, "mute", + "mute a player", + "[^3name|slot#^7]" + }, + + {"namelog", G_admin_namelog, qtrue, "namelog", + "display a list of names used by recently connected players", + "(^5name|IP(/mask)^7) (start namelog#)" + }, + + {"nextmap", G_admin_nextmap, qfalse, "nextmap", + "go to the next map in the cycle", + "" + }, + + {"passvote", G_admin_endvote, qfalse, "passvote", + "pass a vote currently taking place", + "(^5a|h^7)" + }, + + {"pause", G_admin_pause, qfalse, "pause", + "Pause (or unpause) the game.", + "" + }, + + {"putteam", G_admin_putteam, qfalse, "putteam", + "move a player to a specified team", + "[^3name|slot#^7] [^3h|a|s^7]" + }, + + {"readconfig", G_admin_readconfig, qfalse, "readconfig", + "reloads the admin config file and refreshes permission flags", + "" + }, + + {"rename", G_admin_rename, qfalse, "rename", + "rename a player", + "[^3name|slot#^7] [^3new name^7]" + }, + + {"restart", G_admin_restart, qfalse, "restart", + "restart the current map (optionally using named layout or keeping/switching teams)", + "(^5layout^7) (^5keepteams|switchteams|keepteamslock|switchteamslock^7)" + }, + + {"revert", G_admin_revert, qfalse, "revert", + "revert buildables to a given time", + "[^3id^7]" + }, + + {"setlevel", G_admin_setlevel, qfalse, "setlevel", + "sets the admin level of a player", + "[^3name|slot#|admin#^7] [^3level^7]" + }, + + {"showbans", G_admin_showbans, qtrue, "showbans", + "display a (partial) list of active bans", + "(^5name|IP(/mask)^7) (^5start at ban#^7)" + }, + + {"spec999", G_admin_spec999, qfalse, "spec999", + "move 999 pingers to the spectator team", + ""}, + + {"time", G_admin_time, qtrue, "time", + "show the current local server time", + ""}, + + {"unban", G_admin_unban, qfalse, "ban", + "unbans a player specified by the slot as seen in showbans", + "[^3ban#^7]" + }, + + {"unlock", G_admin_lock, qfalse, "lock", + "unlock a locked team", + "[^3a|h^7]" + }, + + {"unmute", G_admin_mute, qfalse, "mute", + "unmute a muted player", + "[^3name|slot#^7]" + } + }; + +static size_t adminNumCmds = sizeof( g_admin_cmds ) / sizeof( g_admin_cmds[ 0 ] ); + +static int admin_level_maxname = 0; +g_admin_level_t *g_admin_levels = NULL; +g_admin_admin_t *g_admin_admins = NULL; +g_admin_ban_t *g_admin_bans = NULL; +g_admin_command_t *g_admin_commands = NULL; + +void G_admin_register_cmds( void ) +{ + int i; + + for( i = 0; i < adminNumCmds; i++ ) + trap_AddCommand( g_admin_cmds[ i ].keyword ); +} + +void G_admin_unregister_cmds( void ) +{ + int i; + + for( i = 0; i < adminNumCmds; i++ ) + trap_RemoveCommand( g_admin_cmds[ i ].keyword ); +} + +void G_admin_cmdlist( gentity_t *ent ) +{ + int i; + char out[ MAX_STRING_CHARS ] = ""; + int len, outlen; + + outlen = 0; + + for( i = 0; i < adminNumCmds; i++ ) + { + if( !G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) + continue; + + len = strlen( g_admin_cmds[ i ].keyword ) + 1; + if( len + outlen >= sizeof( out ) - 1 ) + { + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); + outlen = 0; + } + + strcpy( out + outlen, va( " %s", g_admin_cmds[ i ].keyword ) ); + outlen += len; + } + + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); +} + +// match a certain flag within these flags +static qboolean admin_permission( char *flags, const char *flag, qboolean *perm ) +{ + char *token, *token_p = flags; + qboolean allflags = qfalse; + qboolean p = qfalse; + *perm = qfalse; + while( *( token = COM_Parse( &token_p ) ) ) + { + *perm = qtrue; + if( *token == '-' || *token == '+' ) + *perm = *token++ == '+'; + if( !strcmp( token, flag ) ) + return qtrue; + if( !strcmp( token, ADMF_ALLFLAGS ) ) + { + allflags = qtrue; + p = *perm; + } + } + if( allflags ) + *perm = p; + return allflags; +} + +g_admin_cmd_t *G_admin_cmd( const char *cmd ) +{ + return bsearch( cmd, g_admin_cmds, adminNumCmds, sizeof( g_admin_cmd_t ), + cmdcmp ); +} + +g_admin_level_t *G_admin_level( const int l ) +{ + g_admin_level_t *level; + + for( level = g_admin_levels; level; level = level->next ) + { + if( level->level == l ) + return level; + } + + return NULL; +} + +g_admin_admin_t *G_admin_admin( const char *guid ) +{ + g_admin_admin_t *admin; + + for( admin = g_admin_admins; admin; admin = admin->next ) + { + if( !Q_stricmp( admin->guid, guid ) ) + return admin; + } + + return NULL; +} + +g_admin_command_t *G_admin_command( const char *cmd ) +{ + g_admin_command_t *c; + + for( c = g_admin_commands; c; c = c->next ) + { + if( !Q_stricmp( c->command, cmd ) ) + return c; + } + + return NULL; +} + +qboolean G_admin_permission( gentity_t *ent, const char *flag ) +{ + qboolean perm; + g_admin_admin_t *a; + g_admin_level_t *l; + + // console always wins + if( !ent ) + return qtrue; + + if( ( a = ent->client->pers.admin ) ) + { + if( admin_permission( a->flags, flag, &perm ) ) + return perm; + + l = G_admin_level( a->level ); + } + else + l = G_admin_level( 0 ); + + if( l ) + return admin_permission( l->flags, flag, &perm ) && perm; + + return qfalse; +} + +qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ) +{ + int i; + gclient_t *client; + char testName[ MAX_NAME_LENGTH ] = {""}; + char name2[ MAX_NAME_LENGTH ] = {""}; + g_admin_admin_t *admin; + int alphaCount = 0; + + G_SanitiseString( name, name2, sizeof( name2 ) ); + + if( !strcmp( name2, "unnamedplayer" ) ) + return qtrue; + + if( !strcmp( name2, "console" ) ) + { + if( err && len > 0 ) + Q_strncpyz( err, "The name 'console' is not allowed.", len ); + return qfalse; + } + + G_DecolorString( name, testName, sizeof( testName ) ); + if( isdigit( testName[ 0 ] ) ) + { + if( err && len > 0 ) + Q_strncpyz( err, "Names cannot begin with numbers", len ); + return qfalse; + } + + for( i = 0; testName[ i ]; i++) + { + if( isalpha( testName[ i ] ) ) + alphaCount++; + } + + if( alphaCount == 0 ) + { + if( err && len > 0 ) + Q_strncpyz( err, "Names must contain letters", len ); + return qfalse; + } + + for( i = 0; i < level.maxclients; i++ ) + { + client = &level.clients[ i ]; + if( client->pers.connected == CON_DISCONNECTED ) + continue; + + // can rename ones self to the same name using different colors + if( i == ( ent - g_entities ) ) + continue; + + G_SanitiseString( client->pers.netname, testName, sizeof( testName ) ); + if( !strcmp( name2, testName ) ) + { + if( err && len > 0 ) + Com_sprintf( err, len, "The name '%s^7' is already in use", name ); + return qfalse; + } + } + + for( admin = g_admin_admins; admin; admin = admin->next ) + { + if( admin->level < 1 ) + continue; + G_SanitiseString( admin->name, testName, sizeof( testName ) ); + if( !strcmp( name2, testName ) && ent->client->pers.admin != admin ) + { + if( err && len > 0 ) + Com_sprintf( err, len, "The name '%s^7' belongs to an admin, " + "please use another name", name ); + return qfalse; + } + } + return qtrue; +} + +static qboolean admin_higher_admin( g_admin_admin_t *a, g_admin_admin_t *b ) +{ + qboolean perm; + + if( !b ) + return qtrue; + + if( admin_permission( b->flags, ADMF_IMMUTABLE, &perm ) ) + return !perm; + + return b->level <= ( a ? a->level : 0 ); +} + +static qboolean admin_higher_guid( char *admin_guid, char *victim_guid ) +{ + return admin_higher_admin( G_admin_admin( admin_guid ), + G_admin_admin( victim_guid ) ); +} + +static qboolean admin_higher( gentity_t *admin, gentity_t *victim ) +{ + + // console always wins + if( !admin ) + return qtrue; + + return admin_higher_admin( admin->client->pers.admin, + victim->client->pers.admin ); +} + +static void admin_writeconfig_string( char *s, fileHandle_t f ) +{ + if( s[ 0 ] ) + trap_FS_Write( s, strlen( s ), f ); + trap_FS_Write( "\n", 1, f ); +} + +static void admin_writeconfig_int( int v, fileHandle_t f ) +{ + char buf[ 32 ]; + + Com_sprintf( buf, sizeof( buf ), "%d\n", v ); + trap_FS_Write( buf, strlen( buf ), f ); +} + +static void admin_writeconfig( void ) +{ + fileHandle_t f; + int t; + g_admin_admin_t *a; + g_admin_level_t *l; + g_admin_ban_t *b; + g_admin_command_t *c; + + if( !g_admin.string[ 0 ] ) + { + G_Printf( S_COLOR_YELLOW "WARNING: g_admin is not set. " + " configuration will not be saved to a file.\n" ); + return; + } + t = trap_RealTime( NULL ); + if( trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE ) < 0 ) + { + G_Printf( "admin_writeconfig: could not open g_admin file \"%s\"\n", + g_admin.string ); + return; + } + for( l = g_admin_levels; l; l = l->next ) + { + trap_FS_Write( "[level]\n", 8, f ); + trap_FS_Write( "level = ", 10, f ); + admin_writeconfig_int( l->level, f ); + trap_FS_Write( "name = ", 10, f ); + admin_writeconfig_string( l->name, f ); + trap_FS_Write( "flags = ", 10, f ); + admin_writeconfig_string( l->flags, f ); + trap_FS_Write( "\n", 1, f ); + } + for( a = g_admin_admins; a; a = a->next ) + { + // don't write level 0 users + if( a->level == 0 ) + continue; + + trap_FS_Write( "[admin]\n", 8, f ); + trap_FS_Write( "name = ", 10, f ); + admin_writeconfig_string( a->name, f ); + trap_FS_Write( "guid = ", 10, f ); + admin_writeconfig_string( a->guid, f ); + trap_FS_Write( "level = ", 10, f ); + admin_writeconfig_int( a->level, f ); + trap_FS_Write( "flags = ", 10, f ); + admin_writeconfig_string( a->flags, f ); + trap_FS_Write( "\n", 1, f ); + } + for( b = g_admin_bans; b; b = b->next ) + { + // don't write expired bans + // if expires is 0, then it's a perm ban + if( b->expires != 0 && b->expires <= t ) + continue; + + trap_FS_Write( "[ban]\n", 6, f ); + trap_FS_Write( "name = ", 10, f ); + admin_writeconfig_string( b->name, f ); + trap_FS_Write( "guid = ", 10, f ); + admin_writeconfig_string( b->guid, f ); + trap_FS_Write( "ip = ", 10, f ); + admin_writeconfig_string( b->ip.str, f ); + trap_FS_Write( "reason = ", 10, f ); + admin_writeconfig_string( b->reason, f ); + trap_FS_Write( "made = ", 10, f ); + admin_writeconfig_string( b->made, f ); + trap_FS_Write( "expires = ", 10, f ); + admin_writeconfig_int( b->expires, f ); + trap_FS_Write( "banner = ", 10, f ); + admin_writeconfig_string( b->banner, f ); + trap_FS_Write( "\n", 1, f ); + } + for( c = g_admin_commands; c; c = c->next ) + { + trap_FS_Write( "[command]\n", 10, f ); + trap_FS_Write( "command = ", 10, f ); + admin_writeconfig_string( c->command, f ); + trap_FS_Write( "exec = ", 10, f ); + admin_writeconfig_string( c->exec, f ); + trap_FS_Write( "desc = ", 10, f ); + admin_writeconfig_string( c->desc, f ); + trap_FS_Write( "flag = ", 10, f ); + admin_writeconfig_string( c->flag, f ); + trap_FS_Write( "\n", 1, f ); + } + trap_FS_FCloseFile( f ); +} + +static void admin_readconfig_string( char **cnf, char *s, int size ) +{ + char *t; + + //COM_MatchToken(cnf, "="); + s[ 0 ] = '\0'; + t = COM_ParseExt( cnf, qfalse ); + if( strcmp( t, "=" ) ) + { + COM_ParseWarning( "expected '=' before \"%s\"", t ); + Q_strncpyz( s, t, size ); + } + while( 1 ) + { + t = COM_ParseExt( cnf, qfalse ); + if( !*t ) + break; + if( strlen( t ) + strlen( s ) >= size ) + break; + if( *s ) + Q_strcat( s, size, " " ); + Q_strcat( s, size, t ); + } +} + +static void admin_readconfig_int( char **cnf, int *v ) +{ + char *t; + + //COM_MatchToken(cnf, "="); + t = COM_ParseExt( cnf, qfalse ); + if( !strcmp( t, "=" ) ) + { + t = COM_ParseExt( cnf, qfalse ); + } + else + { + COM_ParseWarning( "expected '=' before \"%s\"", t ); + } + *v = atoi( t ); +} + +// if we can't parse any levels from readconfig, set up default +// ones to make new installs easier for admins +static void admin_default_levels( void ) +{ + g_admin_level_t *l; + int level = 0; + + l = g_admin_levels = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^4Unknown Player", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time", + sizeof( l->flags ) ); + + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^5Server Regular", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time", + sizeof( l->flags ) ); + + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^6Team Manager", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time putteam spec999", + sizeof( l->flags ) ); + + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^2Junior Admin", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time putteam spec999 kick mute ADMINCHAT", + sizeof( l->flags ) ); + + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^3Senior Admin", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "listplayers admintest adminhelp time putteam spec999 kick mute showbans ban " + "namelog ADMINCHAT", + sizeof( l->flags ) ); + + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + l->level = level++; + Q_strncpyz( l->name, "^1Server Operator", sizeof( l->name ) ); + Q_strncpyz( l->flags, + "ALLFLAGS -IMMUTABLE -INCOGNITO", + sizeof( l->flags ) ); + admin_level_maxname = 15; +} + +void G_admin_authlog( gentity_t *ent ) +{ + char aflags[ MAX_ADMIN_FLAGS * 2 ]; + g_admin_level_t *level; + int levelNum = 0; + + if( !ent ) + return; + + if( ent->client->pers.admin ) + levelNum = ent->client->pers.admin->level; + + level = G_admin_level( levelNum ); + + Com_sprintf( aflags, sizeof( aflags ), "%s %s", + ent->client->pers.admin->flags, + ( level ) ? level->flags : "" ); + + G_LogPrintf( "AdminAuth: %i \"%s" S_COLOR_WHITE "\" \"%s" S_COLOR_WHITE + "\" [%d] (%s): %s\n", + ent - g_entities, ent->client->pers.netname, + ent->client->pers.admin->name, ent->client->pers.admin->level, + ent->client->pers.guid, aflags ); +} + +static char adminLog[ MAX_STRING_CHARS ]; +static int adminLogLen; +static void admin_log_start( gentity_t *admin, const char *cmd ) +{ + const char *name = admin ? admin->client->pers.netname : "console"; + + adminLogLen = Q_snprintf( adminLog, sizeof( adminLog ), + "%d \"%s" S_COLOR_WHITE "\" \"%s" S_COLOR_WHITE "\" [%d] (%s): %s", + admin ? admin->s.clientNum : -1, + name, + admin && admin->client->pers.admin ? admin->client->pers.admin->name : name, + admin && admin->client->pers.admin ? admin->client->pers.admin->level : 0, + admin ? admin->client->pers.guid : "", + cmd ); +} + +static void admin_log( const char *str ) +{ + if( adminLog[ 0 ] ) + adminLogLen += Q_snprintf( adminLog + adminLogLen, + sizeof( adminLog ) - adminLogLen, ": %s", str ); +} + +static void admin_log_abort( void ) +{ + adminLog[ 0 ] = '\0'; + adminLogLen = 0; +} + +static void admin_log_end( const qboolean ok ) +{ + if( adminLog[ 0 ] ) + G_LogPrintf( "AdminExec: %s: %s\n", ok ? "ok" : "fail", adminLog ); + admin_log_abort( ); +} + +struct llist +{ + struct llist *next; +}; +static int admin_search( gentity_t *ent, + const char *cmd, + const char *noun, + qboolean ( *match )( void *, const void * ), + void ( *out )( void *, char * ), + const void *list, + const void *arg, /* this will be used as char* later */ + int start, + const int offset, + const int limit ) +{ + int i; + int count = 0; + int found = 0; + int total; + int next = 0, end = 0; + char str[ MAX_STRING_CHARS ]; + struct llist *l = (struct llist *)list; + + for( total = 0; l; total++, l = l->next ) ; + if( start < 0 ) + start += total; + else + start -= offset; + if( start < 0 || start > total ) + start = 0; + + ADMBP_begin(); + for( i = 0, l = (struct llist *)list; l; i++, l = l->next ) + { + if( match( l, arg ) ) + { + if( i >= start && ( limit < 1 || count < limit ) ) + { + out( l, str ); + ADMBP( va( "%-3d %s\n", i + offset, str ) ); + count++; + end = i; + } + else if( count == limit ) + { + if( next == 0 ) + next = i; + } + + found++; + } + } + + if( limit > 0 ) + { + ADMBP( va( "^3%s: ^7showing %d of %d %s %d-%d%s%s.", + cmd, count, found, noun, start + offset, end + offset, + *(char *)arg ? " matching " : "", (char *)arg ) ); + if( next ) + ADMBP( va( " use '%s%s%s %d' to see more", cmd, + *(char *)arg ? " " : "", + (char *)arg, + next + offset ) ); + } + ADMBP( "\n" ); + ADMBP_end(); + return next + offset; +} + +static qboolean admin_match( void *admin, const void *match ) +{ + char n1[ MAX_NAME_LENGTH ], n2[ MAX_NAME_LENGTH ]; + G_SanitiseString( (char *)match, n2, sizeof( n2 ) ); + if( !n2[ 0 ] ) + return qtrue; + G_SanitiseString( ( (g_admin_admin_t *)admin )->name, n1, sizeof( n1 ) ); + return strstr( n1, n2 ) ? qtrue : qfalse; +} +static void admin_out( void *admin, char *str ) +{ + g_admin_admin_t *a = (g_admin_admin_t *)admin; + g_admin_level_t *l = G_admin_level( a->level ); + int lncol = 0, i; + for( i = 0; l && l->name[ i ]; i++ ) + { + if( Q_IsColorString( l->name + i ) ) + lncol += 2; + } + Com_sprintf( str, MAX_STRING_CHARS, "%-6d %*s^7 %s", + a->level, admin_level_maxname + lncol - 1, l ? l->name : "(null)", + a->name ); +} +static int admin_listadmins( gentity_t *ent, int start, char *search ) +{ + return admin_search( ent, "listadmins", "admins", admin_match, admin_out, + g_admin_admins, search, start, MAX_CLIENTS, MAX_ADMIN_LISTITEMS ); +} + +#define MAX_DURATION_LENGTH 13 +void G_admin_duration( int secs, char *duration, int dursize ) +{ + // sizeof("12.5 minutes") == 13 + if( secs > ( 60 * 60 * 24 * 365 * 50 ) || secs < 0 ) + Q_strncpyz( duration, "PERMANENT", dursize ); + else if( secs >= ( 60 * 60 * 24 * 365 ) ) + Com_sprintf( duration, dursize, "%1.1f years", + ( secs / ( 60 * 60 * 24 * 365.0f ) ) ); + else if( secs >= ( 60 * 60 * 24 * 90 ) ) + Com_sprintf( duration, dursize, "%1.1f weeks", + ( secs / ( 60 * 60 * 24 * 7.0f ) ) ); + else if( secs >= ( 60 * 60 * 24 ) ) + Com_sprintf( duration, dursize, "%1.1f days", + ( secs / ( 60 * 60 * 24.0f ) ) ); + else if( secs >= ( 60 * 60 ) ) + Com_sprintf( duration, dursize, "%1.1f hours", + ( secs / ( 60 * 60.0f ) ) ); + else if( secs >= 60 ) + Com_sprintf( duration, dursize, "%1.1f minutes", + ( secs / 60.0f ) ); + else + Com_sprintf( duration, dursize, "%i seconds", secs ); +} + +static void G_admin_ban_message( + gentity_t *ent, + g_admin_ban_t *ban, + char *creason, + int clen, + char *areason, + int alen ) +{ + if( creason ) + { + char duration[ MAX_DURATION_LENGTH ]; + G_admin_duration( ban->expires - trap_RealTime( NULL ), duration, + sizeof( duration ) ); + // part of this might get cut off on the connect screen + Com_sprintf( creason, clen, + "You have been banned by %s" S_COLOR_WHITE " duration: %s" + " reason: %s", + ban->banner, + duration, + ban->reason ); + } + + if( areason && ent ) + { + // we just want the ban number + int n = 1; + g_admin_ban_t *b = g_admin_bans; + for( ; b && b != ban; b = b->next, n++ ) + ; + Com_sprintf( areason, alen, + S_COLOR_YELLOW "Banned player %s" S_COLOR_YELLOW + " tried to connect from %s (ban #%d)", + ent->client->pers.netname[ 0 ] ? ent->client->pers.netname : ban->name, + ent->client->pers.ip.str, + n ); + } +} + +static qboolean G_admin_ban_matches( g_admin_ban_t *ban, gentity_t *ent ) +{ + return !Q_stricmp( ban->guid, ent->client->pers.guid ) || + ( !G_admin_permission( ent, ADMF_IMMUNITY ) && + G_AddressCompare( &ban->ip, &ent->client->pers.ip ) ); +} + +static g_admin_ban_t *G_admin_match_ban( gentity_t *ent ) +{ + int t; + g_admin_ban_t *ban; + + t = trap_RealTime( NULL ); + if( ent->client->pers.localClient ) + return NULL; + + for( ban = g_admin_bans; ban; ban = ban->next ) + { + // 0 is for perm ban + if( ban->expires != 0 && ban->expires <= t ) + continue; + + if( G_admin_ban_matches( ban, ent ) ) + return ban; + } + + return NULL; +} + +qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen ) +{ + g_admin_ban_t *ban; + char warningMessage[ MAX_STRING_CHARS ]; + + if( ent->client->pers.localClient ) + return qfalse; + + if( ( ban = G_admin_match_ban( ent ) ) ) + { + G_admin_ban_message( ent, ban, reason, rlen, + warningMessage, sizeof( warningMessage ) ); + + // don't spam admins + if( ban->warnCount++ < 5 ) + G_AdminMessage( NULL, warningMessage ); + // and don't fill the console + else if( ban->warnCount < 10 ) + trap_Print( va( "%s%s\n", warningMessage, + ban->warnCount + 1 == 10 ? + S_COLOR_WHITE " - future messages for this ban will be suppressed" : + "" ) ); + return qtrue; + } + + return qfalse; +} + +qboolean G_admin_cmd_check( gentity_t *ent ) +{ + char command[ MAX_ADMIN_CMD_LEN ]; + g_admin_cmd_t *admincmd; + g_admin_command_t *c; + qboolean success; + + command[ 0 ] = '\0'; + trap_Argv( 0, command, sizeof( command ) ); + if( !command[ 0 ] ) + return qfalse; + + Q_strlwr( command ); + admin_log_start( ent, command ); + + if( ( c = G_admin_command( command ) ) ) + { + admin_log( ConcatArgsPrintable( 1 ) ); + if( ( success = G_admin_permission( ent, c->flag ) ) ) + { + if( G_FloodLimited( ent ) ) + return qtrue; + trap_SendConsoleCommand( EXEC_APPEND, c->exec ); + } + else + { + ADMP( va( "^3%s: ^7permission denied\n", c->command ) ); + } + admin_log_end( success ); + return qtrue; + } + + if( ( admincmd = G_admin_cmd( command ) ) ) + { + if( ( success = G_admin_permission( ent, admincmd->flag ) ) ) + { + if( G_FloodLimited( ent ) ) + return qtrue; + if( admincmd->silent ) + admin_log_abort( ); + if( !( success = admincmd->handler( ent ) ) ) + admin_log( ConcatArgsPrintable( 1 ) ); + } + else + { + ADMP( va( "^3%s: ^7permission denied\n", admincmd->keyword ) ); + admin_log( ConcatArgsPrintable( 1 ) ); + } + admin_log_end( success ); + return qtrue; + } + return qfalse; +} + +static void llsort( struct llist **head, int compar( const void *, const void * ) ) +{ + struct llist *a, *b, *t, *l; + int i, c = 1, ns, as, bs; + + if( !*head ) + return; + + do + { + a = *head, l = *head = NULL; + for( ns = 0; a; ns++, a = b ) + { + b = a; + for( i = as = 0; i < c; i++ ) + { + as++; + if( !( b = b->next ) ) + break; + } + for( bs = c; ( b && bs ) || as; l = t ) + { + if( as && ( !bs || !b || compar( a, b ) <= 0 ) ) + t = a, a = a->next, as--; + else + t = b, b = b->next, bs--; + if( l ) + l->next = t; + else + *head = t; + } + } + l->next = NULL; + c *= 2; + } while( ns > 1 ); +} + +static int cmplevel( const void *a, const void *b ) +{ + return ((g_admin_level_t *)b)->level - ((g_admin_level_t *)a)->level; +} + +qboolean G_admin_readconfig( gentity_t *ent ) +{ + g_admin_level_t *l = NULL; + g_admin_admin_t *a = NULL; + g_admin_ban_t *b = NULL; + g_admin_command_t *c = NULL; + int lc = 0, ac = 0, bc = 0, cc = 0; + fileHandle_t f; + int len; + char *cnf, *cnf2; + char *t; + qboolean level_open, admin_open, ban_open, command_open; + int i; + char ip[ 44 ]; + + G_admin_cleanup(); + + if( !g_admin.string[ 0 ] ) + { + ADMP( "^3readconfig: g_admin is not set, not loading configuration " + "from a file\n" ); + return qfalse; + } + + len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ ); + if( len < 0 ) + { + G_Printf( "^3readconfig: ^7could not open admin config file %s\n", + g_admin.string ); + admin_default_levels(); + return qfalse; + } + cnf = BG_Alloc( len + 1 ); + cnf2 = cnf; + trap_FS_Read( cnf, len, f ); + *( cnf + len ) = '\0'; + trap_FS_FCloseFile( f ); + + admin_level_maxname = 0; + + level_open = admin_open = ban_open = command_open = qfalse; + COM_BeginParseSession( g_admin.string ); + while( 1 ) + { + t = COM_Parse( &cnf ); + if( !*t ) + break; + + if( !Q_stricmp( t, "[level]" ) ) + { + if( l ) + l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); + else + l = g_admin_levels = BG_Alloc( sizeof( g_admin_level_t ) ); + level_open = qtrue; + admin_open = ban_open = command_open = qfalse; + lc++; + } + else if( !Q_stricmp( t, "[admin]" ) ) + { + if( a ) + a = a->next = BG_Alloc( sizeof( g_admin_admin_t ) ); + else + a = g_admin_admins = BG_Alloc( sizeof( g_admin_admin_t ) ); + admin_open = qtrue; + level_open = ban_open = command_open = qfalse; + ac++; + } + else if( !Q_stricmp( t, "[ban]" ) ) + { + if( b ) + b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) ); + else + b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) ); + ban_open = qtrue; + level_open = admin_open = command_open = qfalse; + bc++; + } + else if( !Q_stricmp( t, "[command]" ) ) + { + if( c ) + c = c->next = BG_Alloc( sizeof( g_admin_command_t ) ); + else + c = g_admin_commands = BG_Alloc( sizeof( g_admin_command_t ) ); + command_open = qtrue; + level_open = admin_open = ban_open = qfalse; + cc++; + } + else if( level_open ) + { + if( !Q_stricmp( t, "level" ) ) + { + admin_readconfig_int( &cnf, &l->level ); + } + else if( !Q_stricmp( t, "name" ) ) + { + admin_readconfig_string( &cnf, l->name, sizeof( l->name ) ); + // max printable name length for formatting + len = Q_PrintStrlen( l->name ); + if( len > admin_level_maxname ) + admin_level_maxname = len; + } + else if( !Q_stricmp( t, "flags" ) ) + { + admin_readconfig_string( &cnf, l->flags, sizeof( l->flags ) ); + } + else + { + COM_ParseError( "[level] unrecognized token \"%s\"", t ); + } + } + else if( admin_open ) + { + if( !Q_stricmp( t, "name" ) ) + { + admin_readconfig_string( &cnf, a->name, sizeof( a->name ) ); + } + else if( !Q_stricmp( t, "guid" ) ) + { + admin_readconfig_string( &cnf, a->guid, sizeof( a->guid ) ); + } + else if( !Q_stricmp( t, "level" ) ) + { + admin_readconfig_int( &cnf, &a->level ); + } + else if( !Q_stricmp( t, "flags" ) ) + { + admin_readconfig_string( &cnf, a->flags, sizeof( a->flags ) ); + } + else + { + COM_ParseError( "[admin] unrecognized token \"%s\"", t ); + } + + } + else if( ban_open ) + { + if( !Q_stricmp( t, "name" ) ) + { + admin_readconfig_string( &cnf, b->name, sizeof( b->name ) ); + } + else if( !Q_stricmp( t, "guid" ) ) + { + admin_readconfig_string( &cnf, b->guid, sizeof( b->guid ) ); + } + else if( !Q_stricmp( t, "ip" ) ) + { + admin_readconfig_string( &cnf, ip, sizeof( ip ) ); + G_AddressParse( ip, &b->ip ); + } + else if( !Q_stricmp( t, "reason" ) ) + { + admin_readconfig_string( &cnf, b->reason, sizeof( b->reason ) ); + } + else if( !Q_stricmp( t, "made" ) ) + { + admin_readconfig_string( &cnf, b->made, sizeof( b->made ) ); + } + else if( !Q_stricmp( t, "expires" ) ) + { + admin_readconfig_int( &cnf, &b->expires ); + } + else if( !Q_stricmp( t, "banner" ) ) + { + admin_readconfig_string( &cnf, b->banner, sizeof( b->banner ) ); + } + else + { + COM_ParseError( "[ban] unrecognized token \"%s\"", t ); + } + } + else if( command_open ) + { + if( !Q_stricmp( t, "command" ) ) + { + admin_readconfig_string( &cnf, c->command, sizeof( c->command ) ); + } + else if( !Q_stricmp( t, "exec" ) ) + { + admin_readconfig_string( &cnf, c->exec, sizeof( c->exec ) ); + } + else if( !Q_stricmp( t, "desc" ) ) + { + admin_readconfig_string( &cnf, c->desc, sizeof( c->desc ) ); + } + else if( !Q_stricmp( t, "flag" ) ) + { + admin_readconfig_string( &cnf, c->flag, sizeof( c->flag ) ); + } + else + { + COM_ParseError( "[command] unrecognized token \"%s\"", t ); + } + } + else + { + COM_ParseError( "unexpected token \"%s\"", t ); + } + } + BG_Free( cnf2 ); + ADMP( va( "^3readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n", + lc, ac, bc, cc ) ); + if( lc == 0 ) + admin_default_levels(); + else + { + llsort( (struct llist **)&g_admin_levels, cmplevel ); + llsort( (struct llist **)&g_admin_admins, cmplevel ); + } + + // restore admin mapping + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected != CON_DISCONNECTED ) + { + level.clients[ i ].pers.admin = + G_admin_admin( level.clients[ i ].pers.guid ); + if( level.clients[ i ].pers.admin ) + G_admin_authlog( &g_entities[ i ] ); + G_admin_cmdlist( &g_entities[ i ] ); + } + } + + return qtrue; +} + +qboolean G_admin_time( gentity_t *ent ) +{ + qtime_t qt; + + trap_RealTime( &qt ); + ADMP( va( "^3time: ^7local time is %02i:%02i:%02i\n", + qt.tm_hour, qt.tm_min, qt.tm_sec ) ); + return qtrue; +} + +// this should be in one of the headers, but it is only used here for now +namelog_t *G_NamelogFromString( gentity_t *ent, char *s ); + +/* +for consistency, we should be able to target a disconnected player with setlevel +but we can't use namelog and remain consistent, so the solution would be to make +everyone a real level 0 admin so they can be targeted until the next level +but that seems kind of stupid +*/ +qboolean G_admin_setlevel( gentity_t *ent ) +{ + char name[ MAX_NAME_LENGTH ] = {""}; + char lstr[ 12 ]; // 11 is max strlen() for 32-bit (signed) int + char testname[ MAX_NAME_LENGTH ] = {""}; + int i; + gentity_t *vic = NULL; + g_admin_admin_t *a = NULL; + g_admin_level_t *l = NULL; + int na; + + if( trap_Argc() < 3 ) + { + ADMP( "^3setlevel: ^7usage: setlevel [name|slot#] [level]\n" ); + return qfalse; + } + + trap_Argv( 1, testname, sizeof( testname ) ); + trap_Argv( 2, lstr, sizeof( lstr ) ); + + if( !( l = G_admin_level( atoi( lstr ) ) ) ) + { + ADMP( "^3setlevel: ^7level is not defined\n" ); + return qfalse; + } + + if( ent && l->level > + ( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ) ) + { + ADMP( "^3setlevel: ^7you may not use setlevel to set a level higher " + "than your current level\n" ); + return qfalse; + } + + for( na = 0, a = g_admin_admins; a; na++, a = a->next ); + + for( i = 0; testname[ i ] && isdigit( testname[ i ] ); i++ ); + if( !testname[ i ] ) + { + int id = atoi( testname ); + if( id < MAX_CLIENTS ) + { + vic = &g_entities[ id ]; + if( !vic || !vic->client || vic->client->pers.connected == CON_DISCONNECTED ) + { + ADMP( va( "^3setlevel: ^7no player connected in slot %d\n", id ) ); + return qfalse; + } + } + else if( id < na + MAX_CLIENTS ) + for( i = 0, a = g_admin_admins; i < id - MAX_CLIENTS; i++, a = a->next ); + else + { + ADMP( va( "^3setlevel: ^7%s not in range 1-%d\n", + testname, na + MAX_CLIENTS - 1 ) ); + return qfalse; + } + } + else + G_SanitiseString( testname, name, sizeof( name ) ); + + if( vic ) + a = vic->client->pers.admin; + else if( !a ) + { + g_admin_admin_t *wa; + int matches = 0; + + for( wa = g_admin_admins; wa && matches < 2; wa = wa->next ) + { + G_SanitiseString( wa->name, testname, sizeof( testname ) ); + if( strstr( testname, name ) ) + { + a = wa; + matches++; + } + } + + for( i = 0; i < level.maxclients && matches < 2; i++ ) + { + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + + if( matches && level.clients[ i ].pers.admin && + level.clients[ i ].pers.admin == a ) + { + vic = &g_entities[ i ]; + continue; + } + + G_SanitiseString( level.clients[ i ].pers.netname, testname, + sizeof( testname ) ); + if( strstr( testname, name ) ) + { + vic = &g_entities[ i ]; + a = vic->client->pers.admin; + matches++; + } + } + + if( matches == 0 ) + { + ADMP( "^3setlevel:^7 no match. use listplayers or listadmins to " + "find an appropriate number to use instead of name.\n" ); + return qfalse; + } + if( matches > 1 ) + { + ADMP( "^3setlevel:^7 more than one match. Use the admin number " + "instead:\n" ); + admin_listadmins( ent, 0, name ); + return qfalse; + } + } + + if( ent && !admin_higher_admin( ent->client->pers.admin, a ) ) + { + ADMP( "^3setlevel: ^7sorry, but your intended victim has a higher" + " admin level than you\n" ); + return qfalse; + } + + if( !a && vic ) + { + for( a = g_admin_admins; a && a->next; a = a->next ); + if( a ) + a = a->next = BG_Alloc( sizeof( g_admin_admin_t ) ); + else + a = g_admin_admins = BG_Alloc( sizeof( g_admin_admin_t ) ); + vic->client->pers.admin = a; + Q_strncpyz( a->guid, vic->client->pers.guid, sizeof( a->guid ) ); + } + + a->level = l->level; + if( vic ) + Q_strncpyz( a->name, vic->client->pers.netname, sizeof( a->name ) ); + + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", a->level, a->guid, + a->name ) ); + + AP( va( + "print \"^3setlevel: ^7%s^7 was given level %d admin rights by %s\n\"", + a->name, a->level, ( ent ) ? ent->client->pers.netname : "console" ) ); + + admin_writeconfig(); + if( vic ) + { + G_admin_authlog( vic ); + G_admin_cmdlist( vic ); + } + return qtrue; +} + +static void admin_create_ban( gentity_t *ent, + char *netname, + char *guid, + addr_t *ip, + int seconds, + char *reason ) +{ + g_admin_ban_t *b = NULL; + qtime_t qt; + int t; + int i; + char *name; + char disconnect[ MAX_STRING_CHARS ]; + + t = trap_RealTime( &qt ); + + for( b = g_admin_bans; b; b = b->next ) + { + if( !b->next ) + break; + } + + if( b ) + { + if( !b->next ) + b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) ); + } + else + b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) ); + + Q_strncpyz( b->name, netname, sizeof( b->name ) ); + Q_strncpyz( b->guid, guid, sizeof( b->guid ) ); + memcpy( &b->ip, ip, sizeof( b->ip ) ); + + Com_sprintf( b->made, sizeof( b->made ), "%02i/%02i/%02i %02i:%02i:%02i", + qt.tm_mon + 1, qt.tm_mday, qt.tm_year % 100, + qt.tm_hour, qt.tm_min, qt.tm_sec ); + + if( ent && ent->client->pers.admin ) + name = ent->client->pers.admin->name; + else if( ent ) + name = ent->client->pers.netname; + else + name = "console"; + + Q_strncpyz( b->banner, name, sizeof( b->banner ) ); + if( !seconds ) + b->expires = 0; + else + b->expires = t + seconds; + if( !*reason ) + Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) ); + else + Q_strncpyz( b->reason, reason, sizeof( b->reason ) ); + + G_admin_ban_message( NULL, b, disconnect, sizeof( disconnect ), NULL, 0 ); + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + if( G_admin_ban_matches( b, &g_entities[ i ] ) ) + { + trap_SendServerCommand( i, va( "disconnect \"%s\"", disconnect ) ); + + trap_DropClient( i, va( "has been kicked by %s^7. reason: %s", + b->banner, b->reason ) ); + } + } +} + +int G_admin_parse_time( const char *time ) +{ + int seconds = 0, num = 0; + if( !*time ) + return -1; + while( *time ) + { + if( !isdigit( *time ) ) + return -1; + while( isdigit( *time ) ) + num = num * 10 + *time++ - '0'; + + if( !*time ) + break; + switch( *time++ ) + { + case 'w': num *= 7; + case 'd': num *= 24; + case 'h': num *= 60; + case 'm': num *= 60; + case 's': break; + default: return -1; + } + seconds += num; + num = 0; + } + if( num ) + seconds += num; + return seconds; +} + +qboolean G_admin_kick( gentity_t *ent ) +{ + int pid; + char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; + int minargc; + gentity_t *vic; + + minargc = 3; + if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) + minargc = 2; + + if( trap_Argc() < minargc ) + { + ADMP( "^3kick: ^7usage: kick [name] [reason]\n" ); + return qfalse; + } + trap_Argv( 1, name, sizeof( name ) ); + reason = ConcatArgs( 2 ); + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) + { + ADMP( va( "^3kick: ^7%s", err ) ); + return qfalse; + } + vic = &g_entities[ pid ]; + if( !admin_higher( ent, vic ) ) + { + ADMP( "^3kick: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + if( vic->client->pers.localClient ) + { + ADMP( "^3kick: ^7disconnecting the host would end the game\n" ); + return qfalse; + } + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\"", + pid, + vic->client->pers.guid, + vic->client->pers.netname, + reason ) ); + admin_create_ban( ent, + vic->client->pers.netname, + vic->client->pers.guid, + &vic->client->pers.ip, + MAX( 1, G_admin_parse_time( g_adminTempBan.string ) ), + ( *reason ) ? reason : "kicked by admin" ); + admin_writeconfig(); + + return qtrue; +} + +qboolean G_admin_ban( gentity_t *ent ) +{ + int seconds; + char search[ MAX_NAME_LENGTH ]; + char secs[ MAX_TOKEN_CHARS ]; + char *reason; + char duration[ MAX_DURATION_LENGTH ]; + int i; + addr_t ip; + qboolean ipmatch = qfalse; + namelog_t *match = NULL; + + if( trap_Argc() < 2 ) + { + ADMP( "^3ban: ^7usage: ban [name|slot|IP(/mask)] [duration] [reason]\n" ); + return qfalse; + } + trap_Argv( 1, search, sizeof( search ) ); + trap_Argv( 2, secs, sizeof( secs ) ); + + seconds = G_admin_parse_time( secs ); + if( seconds <= 0 ) + { + seconds = 0; + reason = ConcatArgs( 2 ); + } + else + { + reason = ConcatArgs( 3 ); + } + if( !*reason && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) + { + ADMP( "^3ban: ^7you must specify a reason\n" ); + return qfalse; + } + if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) + { + int maximum = MAX( 1, G_admin_parse_time( g_adminMaxBan.string ) ); + if( seconds == 0 || seconds > maximum ) + { + ADMP( "^3ban: ^7you may not issue permanent bans\n" ); + seconds = maximum; + } + } + + if( G_AddressParse( search, &ip ) ) + { + int max = ip.type == IPv4 ? 32 : 128; + int min = ent ? max / 2 : 1; + + if( ip.mask < min || ip.mask > max ) + { + ADMP( va( "^3ban: ^7invalid netmask (%d is not one of %d-%d)\n", + ip.mask, min, max ) ); + return qfalse; + } + ipmatch = qtrue; + + for( match = level.namelogs; match; match = match->next ) + { + // skip players in the namelog who have already been banned + if( match->banned ) + continue; + + for( i = 0; i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ]; i++ ) + { + if( G_AddressCompare( &ip, &match->ip[ i ] ) ) + break; + } + if( i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ] ) + break; + } + + if( !match ) + { + ADMP( "^3ban: ^7no player found by that IP address\n" ); + return qfalse; + } + } + else if( !( match = G_NamelogFromString( ent, search ) ) || match->banned ) + { + ADMP( "^3ban: ^7no match\n" ); + return qfalse; + } + + if( ent && !admin_higher_guid( ent->client->pers.guid, match->guid ) ) + { + + ADMP( "^3ban: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + if( match->slot > -1 && level.clients[ match->slot ].pers.localClient ) + { + ADMP( "^3ban: ^7disconnecting the host would end the game\n" ); + return qfalse; + } + + G_admin_duration( ( seconds ) ? seconds : -1, + duration, sizeof( duration ) ); + + AP( va( "print \"^3ban:^7 %s^7 has been banned by %s^7 " + "duration: %s, reason: %s\n\"", + match->name[ match->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console", + duration, + ( *reason ) ? reason : "banned by admin" ) ); + + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\"", + seconds, match->guid, match->name[ match->nameOffset ], reason ) ); + if( ipmatch ) + { + admin_create_ban( ent, + match->slot == -1 ? + match->name[ match->nameOffset ] : + level.clients[ match->slot ].pers.netname, + match->guid, + &ip, + seconds, reason ); + admin_log( va( "[%s]", ip.str ) ); + } + else + { + // ban all IP addresses used by this player + for( i = 0; i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ]; i++ ) + { + admin_create_ban( ent, + match->slot == -1 ? + match->name[ match->nameOffset ] : + level.clients[ match->slot ].pers.netname, + match->guid, + &match->ip[ i ], + seconds, reason ); + admin_log( va( "[%s]", match->ip[ i ].str ) ); + } + } + + match->banned = qtrue; + + if( !g_admin.string[ 0 ] ) + ADMP( "^3ban: ^7WARNING g_admin not set, not saving ban to a file\n" ); + else + admin_writeconfig(); + + return qtrue; +} + +qboolean G_admin_unban( gentity_t *ent ) +{ + int bnum; + int time = trap_RealTime( NULL ); + char bs[ 5 ]; + int i; + g_admin_ban_t *ban, *p; + + if( trap_Argc() < 2 ) + { + ADMP( "^3unban: ^7usage: unban [ban#]\n" ); + return qfalse; + } + trap_Argv( 1, bs, sizeof( bs ) ); + bnum = atoi( bs ); + for( ban = p = g_admin_bans, i = 1; ban && i < bnum; + p = ban, ban = ban->next, i++ ); + if( i != bnum || !ban ) + { + ADMP( "^3unban: ^7invalid ban#\n" ); + return qfalse; + } + if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && + ( ban->expires == 0 || ( ban->expires - time > MAX( 1, + G_admin_parse_time( g_adminMaxBan.string ) ) ) ) ) + { + ADMP( "^3unban: ^7you cannot remove permanent bans\n" ); + return qfalse; + } + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\": [%s]", + ban->expires ? ban->expires - time : 0, ban->guid, ban->name, ban->reason, + ban->ip.str ) ); + AP( va( "print \"^3unban: ^7ban #%d for %s^7 has been removed by %s\n\"", + bnum, + ban->name, + ( ent ) ? ent->client->pers.netname : "console" ) ); + if( p == ban ) + g_admin_bans = ban->next; + else + p->next = ban->next; + BG_Free( ban ); + admin_writeconfig(); + return qtrue; +} + +qboolean G_admin_adjustban( gentity_t *ent ) +{ + int bnum; + int length, maximum; + int expires; + int time = trap_RealTime( NULL ); + char duration[ MAX_DURATION_LENGTH ] = {""}; + char *reason; + char bs[ 5 ]; + char secs[ MAX_TOKEN_CHARS ]; + char mode = '\0'; + g_admin_ban_t *ban; + int mask = 0; + int i; + int skiparg = 0; + + if( trap_Argc() < 3 ) + { + ADMP( "^3adjustban: ^7usage: adjustban [ban#] [/mask] [duration] [reason]" + "\n" ); + return qfalse; + } + trap_Argv( 1, bs, sizeof( bs ) ); + bnum = atoi( bs ); + for( ban = g_admin_bans, i = 1; ban && i < bnum; ban = ban->next, i++ ); + if( i != bnum || !ban ) + { + ADMP( "^3adjustban: ^7invalid ban#\n" ); + return qfalse; + } + maximum = MAX( 1, G_admin_parse_time( g_adminMaxBan.string ) ); + if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && + ( ban->expires == 0 || ban->expires - time > maximum ) ) + { + ADMP( "^3adjustban: ^7you cannot modify permanent bans\n" ); + return qfalse; + } + trap_Argv( 2, secs, sizeof( secs ) ); + if( secs[ 0 ] == '/' ) + { + int max = ban->ip.type == IPv6 ? 128 : 32; + int min = ent ? max / 2 : 1; + mask = atoi( secs + 1 ); + if( mask < min || mask > max ) + { + ADMP( va( "^3adjustban: ^7invalid netmask (%d is not one of %d-%d)\n", + mask, min, max ) ); + return qfalse; + } + trap_Argv( 3 + skiparg++, secs, sizeof( secs ) ); + } + if( secs[ 0 ] == '+' || secs[ 0 ] == '-' ) + mode = secs[ 0 ]; + length = G_admin_parse_time( &secs[ mode ? 1 : 0 ] ); + if( length < 0 ) + skiparg--; + else + { + if( length ) + { + if( ban->expires == 0 && mode ) + { + ADMP( "^3adjustban: ^7new duration must be explicit\n" ); + return qfalse; + } + if( mode == '+' ) + expires = ban->expires + length; + else if( mode == '-' ) + expires = ban->expires - length; + else + expires = time + length; + if( expires <= time ) + { + ADMP( "^3adjustban: ^7ban duration must be positive\n" ); + return qfalse; + } + } + else + length = expires = 0; + if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && + ( length == 0 || length > maximum ) ) + { + ADMP( "^3adjustban: ^7you may not issue permanent bans\n" ); + expires = time + maximum; + } + + ban->expires = expires; + G_admin_duration( ( expires ) ? expires - time : -1, duration, + sizeof( duration ) ); + } + if( mask ) + { + char *p = strchr( ban->ip.str, '/' ); + if( !p ) + p = ban->ip.str + strlen( ban->ip.str ); + if( mask == ( ban->ip.type == IPv6 ? 128 : 32 ) ) + *p = '\0'; + else + Com_sprintf( p, sizeof( ban->ip.str ) - ( p - ban->ip.str ), "/%d", mask ); + ban->ip.mask = mask; + } + reason = ConcatArgs( 3 + skiparg ); + if( *reason ) + Q_strncpyz( ban->reason, reason, sizeof( ban->reason ) ); + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\": [%s]", + ban->expires ? ban->expires - time : 0, ban->guid, ban->name, ban->reason, + ban->ip.str ) ); + AP( va( "print \"^3adjustban: ^7ban #%d for %s^7 has been updated by %s^7 " + "%s%s%s%s%s%s\n\"", + bnum, + ban->name, + ( ent ) ? ent->client->pers.netname : "console", + ( mask ) ? + va( "netmask: /%d%s", mask, + ( length >= 0 || *reason ) ? ", " : "" ) : "", + ( length >= 0 ) ? "duration: " : "", + duration, + ( length >= 0 && *reason ) ? ", " : "", + ( *reason ) ? "reason: " : "", + reason ) ); + if( ent ) + Q_strncpyz( ban->banner, ent->client->pers.netname, sizeof( ban->banner ) ); + admin_writeconfig(); + return qtrue; +} + +qboolean G_admin_putteam( gentity_t *ent ) +{ + int pid; + char name[ MAX_NAME_LENGTH ], team[ sizeof( "spectators" ) ], + err[ MAX_STRING_CHARS ]; + gentity_t *vic; + team_t teamnum = TEAM_NONE; + + trap_Argv( 1, name, sizeof( name ) ); + trap_Argv( 2, team, sizeof( team ) ); + if( trap_Argc() < 3 ) + { + ADMP( "^3putteam: ^7usage: putteam [name] [h|a|s]\n" ); + return qfalse; + } + + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) + { + ADMP( va( "^3putteam: ^7%s", err ) ); + return qfalse; + } + vic = &g_entities[ pid ]; + if( !admin_higher( ent, vic ) ) + { + ADMP( "^3putteam: ^7sorry, but your intended victim has a higher " + " admin level than you\n" ); + return qfalse; + } + teamnum = G_TeamFromString( team ); + if( teamnum == NUM_TEAMS ) + { + ADMP( va( "^3putteam: ^7unknown team %s\n", team ) ); + return qfalse; + } + if( vic->client->pers.teamSelection == teamnum ) + return qfalse; + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", pid, vic->client->pers.guid, + vic->client->pers.netname ) ); + G_ChangeTeam( vic, teamnum ); + + AP( va( "print \"^3putteam: ^7%s^7 put %s^7 on to the %s team\n\"", + ( ent ) ? ent->client->pers.netname : "console", + vic->client->pers.netname, BG_TeamName( teamnum ) ) ); + return qtrue; +} + +qboolean G_admin_changemap( gentity_t *ent ) +{ + char map[ MAX_QPATH ]; + char layout[ MAX_QPATH ] = { "" }; + + if( trap_Argc( ) < 2 ) + { + ADMP( "^3changemap: ^7usage: changemap [map] (layout)\n" ); + return qfalse; + } + + trap_Argv( 1, map, sizeof( map ) ); + + if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) ) + { + ADMP( va( "^3changemap: ^7invalid map name '%s'\n", map ) ); + return qfalse; + } + + if( trap_Argc( ) > 2 ) + { + trap_Argv( 2, layout, sizeof( layout ) ); + if( !Q_stricmp( layout, "*BUILTIN*" ) || + trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), + NULL, FS_READ ) > 0 ) + { + trap_Cvar_Set( "g_layouts", layout ); + } + else + { + ADMP( va( "^3changemap: ^7invalid layout name '%s'\n", layout ) ); + return qfalse; + } + } + admin_log( map ); + admin_log( layout ); + + trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) ); + level.restarted = qtrue; + AP( va( "print \"^3changemap: ^7map '%s' started by %s^7 %s\n\"", map, + ( ent ) ? ent->client->pers.netname : "console", + ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); + return qtrue; +} + +qboolean G_admin_mute( gentity_t *ent ) +{ + char name[ MAX_NAME_LENGTH ]; + char command[ MAX_ADMIN_CMD_LEN ]; + namelog_t *vic; + + trap_Argv( 0, command, sizeof( command ) ); + if( trap_Argc() < 2 ) + { + ADMP( va( "^3%s: ^7usage: %s [name|slot#]\n", command, command ) ); + return qfalse; + } + trap_Argv( 1, name, sizeof( name ) ); + if( !( vic = G_NamelogFromString( ent, name ) ) ) + { + ADMP( va( "^3%s: ^7no match\n", command ) ); + return qfalse; + } + if( ent && !admin_higher_admin( ent->client->pers.admin, + G_admin_admin( vic->guid ) ) ) + { + ADMP( va( "^3%s: ^7sorry, but your intended victim has a higher admin" + " level than you\n", command ) ); + return qfalse; + } + if( vic->muted ) + { + if( !Q_stricmp( command, "mute" ) ) + { + ADMP( "^3mute: ^7player is already muted\n" ); + return qfalse; + } + vic->muted = qfalse; + if( vic->slot > -1 ) + CPx( vic->slot, "cp \"^1You have been unmuted\"" ); + AP( va( "print \"^3unmute: ^7%s^7 has been unmuted by %s\n\"", + vic->name[ vic->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + else + { + if( !Q_stricmp( command, "unmute" ) ) + { + ADMP( "^3unmute: ^7player is not currently muted\n" ); + return qfalse; + } + vic->muted = qtrue; + if( vic->slot > -1 ) + CPx( vic->slot, "cp \"^1You've been muted\"" ); + AP( va( "print \"^3mute: ^7%s^7 has been muted by ^7%s\n\"", + vic->name[ vic->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", vic->slot, vic->guid, + vic->name[ vic->nameOffset ] ) ); + return qtrue; +} + +qboolean G_admin_denybuild( gentity_t *ent ) +{ + char name[ MAX_NAME_LENGTH ]; + char command[ MAX_ADMIN_CMD_LEN ]; + namelog_t *vic; + + trap_Argv( 0, command, sizeof( command ) ); + if( trap_Argc() < 2 ) + { + ADMP( va( "^3%s: ^7usage: %s [name|slot#]\n", command, command ) ); + return qfalse; + } + trap_Argv( 1, name, sizeof( name ) ); + if( !( vic = G_NamelogFromString( ent, name ) ) ) + { + ADMP( va( "^3%s: ^7no match\n", command ) ); + return qfalse; + } + if( ent && !admin_higher_admin( ent->client->pers.admin, + G_admin_admin( vic->guid ) ) ) + { + ADMP( va( "^3%s: ^7sorry, but your intended victim has a higher admin" + " level than you\n", command ) ); + return qfalse; + } + if( vic->denyBuild ) + { + if( !Q_stricmp( command, "denybuild" ) ) + { + ADMP( "^3denybuild: ^7player already has no building rights\n" ); + return qfalse; + } + vic->denyBuild = qfalse; + if( vic->slot > -1 ) + CPx( vic->slot, "cp \"^1You've regained your building rights\"" ); + AP( va( + "print \"^3allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"", + vic->name[ vic->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + else + { + if( !Q_stricmp( command, "allowbuild" ) ) + { + ADMP( "^3allowbuild: ^7player already has building rights\n" ); + return qfalse; + } + vic->denyBuild = qtrue; + if( vic->slot > -1 ) + { + level.clients[ vic->slot ].ps.stats[ STAT_BUILDABLE ] = BA_NONE; + CPx( vic->slot, "cp \"^1You've lost your building rights\"" ); + } + AP( va( + "print \"^3denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"", + vic->name[ vic->nameOffset ], + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", vic->slot, vic->guid, + vic->name[ vic->nameOffset ] ) ); + return qtrue; +} + +qboolean G_admin_listadmins( gentity_t *ent ) +{ + int i; + char search[ MAX_NAME_LENGTH ] = {""}; + char s[ MAX_NAME_LENGTH ] = {""}; + int start = MAX_CLIENTS; + + if( trap_Argc() == 3 ) + { + trap_Argv( 2, s, sizeof( s ) ); + start = atoi( s ); + } + if( trap_Argc() > 1 ) + { + trap_Argv( 1, s, sizeof( s ) ); + i = 0; + if( trap_Argc() == 2 ) + { + i = s[ 0 ] == '-'; + for( ; isdigit( s[ i ] ); i++ ); + } + if( i && !s[ i ] ) + start = atoi( s ); + else + G_SanitiseString( s, search, sizeof( search ) ); + } + + admin_listadmins( ent, start, search ); + return qtrue; +} + +qboolean G_admin_listlayouts( gentity_t *ent ) +{ + char list[ MAX_CVAR_VALUE_STRING ]; + char map[ MAX_QPATH ]; + int count = 0; + char *s; + char layout[ MAX_QPATH ] = { "" }; + int i = 0; + + if( trap_Argc( ) == 2 ) + trap_Argv( 1, map, sizeof( map ) ); + else + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + + count = G_LayoutList( map, list, sizeof( list ) ); + ADMBP_begin( ); + ADMBP( va( "^3listlayouts:^7 %d layouts found for '%s':\n", count, map ) ); + s = &list[ 0 ]; + while( *s ) + { + if( *s == ' ' ) + { + ADMBP( va ( " %s\n", layout ) ); + layout[ 0 ] = '\0'; + i = 0; + } + else if( i < sizeof( layout ) - 2 ) + { + layout[ i++ ] = *s; + layout[ i ] = '\0'; + } + s++; + } + if( layout[ 0 ] ) + ADMBP( va ( " %s\n", layout ) ); + ADMBP_end( ); + return qtrue; +} + +qboolean G_admin_listplayers( gentity_t *ent ) +{ + int i, j; + gclient_t *p; + char c, t; // color and team letter + char *registeredname; + char lname[ MAX_NAME_LENGTH ]; + char muted, denied; + int colorlen; + char namecleaned[ MAX_NAME_LENGTH ]; + char name2cleaned[ MAX_NAME_LENGTH ]; + g_admin_level_t *l; + g_admin_level_t *d = G_admin_level( 0 ); + qboolean hint; + qboolean canset = G_admin_permission( ent, "setlevel" ); + + ADMBP_begin(); + ADMBP( va( "^3listplayers: ^7%d players connected:\n", + level.numConnectedClients ) ); + for( i = 0; i < level.maxclients; i++ ) + { + p = &level.clients[ i ]; + if( p->pers.connected == CON_DISCONNECTED ) + continue; + if( p->pers.connected == CON_CONNECTING ) + { + t = 'C'; + c = COLOR_YELLOW; + } + else + { + t = toupper( *( BG_TeamName( p->pers.teamSelection ) ) ); + if( p->pers.teamSelection == TEAM_HUMANS ) + c = COLOR_CYAN; + else if( p->pers.teamSelection == TEAM_ALIENS ) + c = COLOR_RED; + else + c = COLOR_WHITE; + } + + muted = p->pers.namelog->muted ? 'M' : ' '; + denied = p->pers.namelog->denyBuild ? 'B' : ' '; + + l = d; + registeredname = NULL; + hint = canset; + if( p->pers.admin ) + { + if( hint ) + hint = admin_higher( ent, &g_entities[ i ] ); + if( hint || !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) ) + { + l = G_admin_level( p->pers.admin->level ); + G_SanitiseString( p->pers.netname, namecleaned, + sizeof( namecleaned ) ); + G_SanitiseString( p->pers.admin->name, + name2cleaned, sizeof( name2cleaned ) ); + if( Q_stricmp( namecleaned, name2cleaned ) ) + registeredname = p->pers.admin->name; + } + } + + if( l ) + Q_strncpyz( lname, l->name, sizeof( lname ) ); + + for( colorlen = j = 0; lname[ j ]; j++ ) + { + if( Q_IsColorString( &lname[ j ] ) ) + colorlen += 2; + } + + ADMBP( va( "%2i ^%c%c^7 %-2i^2%c^7 %*s^7 ^1%c%c^7 %s^7 %s%s%s\n", + i, + c, + t, + l ? l->level : 0, + hint ? '*' : ' ', + admin_level_maxname + colorlen, + lname, + muted, + denied, + p->pers.netname, + ( registeredname ) ? "(a.k.a. " : "", + ( registeredname ) ? registeredname : "", + ( registeredname ) ? S_COLOR_WHITE ")" : "" ) ); + + } + ADMBP_end(); + return qtrue; +} + +static qboolean ban_matchip( void *ban, const void *ip ) +{ + return G_AddressCompare( &((g_admin_ban_t *)ban)->ip, (addr_t *)ip ) || + G_AddressCompare( (addr_t *)ip, &((g_admin_ban_t *)ban)->ip ); +} +static qboolean ban_matchname( void *ban, const void *name ) +{ + char match[ MAX_NAME_LENGTH ]; + + G_SanitiseString( ( (g_admin_ban_t *)ban )->name, match, sizeof( match ) ); + return strstr( match, (const char *)name ) != NULL; +} +static void ban_out( void *ban, char *str ) +{ + int i; + int colorlen1 = 0; + char duration[ MAX_DURATION_LENGTH ]; + char *d_color = S_COLOR_WHITE; + char date[ 11 ]; + g_admin_ban_t *b = ( g_admin_ban_t * )ban; + int t = trap_RealTime( NULL ); + char *made = b->made; + + for( i = 0; b->name[ i ]; i++ ) + { + if( Q_IsColorString( &b->name[ i ] ) ) + colorlen1 += 2; + } + + // only print out the the date part of made + date[ 0 ] = '\0'; + for( i = 0; *made && *made != ' ' && i < sizeof( date ) - 1; i++ ) + date[ i ] = *made++; + date[ i ] = 0; + + if( !b->expires || b->expires - t > 0 ) + G_admin_duration( b->expires ? b->expires - t : - 1, + duration, sizeof( duration ) ); + else + { + Q_strncpyz( duration, "expired", sizeof( duration ) ); + d_color = S_COLOR_CYAN; + } + + Com_sprintf( str, MAX_STRING_CHARS, "%-*s %s%-15s " S_COLOR_WHITE "%-8s %s" + "\n \\__ %s%-*s " S_COLOR_WHITE "%s", + MAX_NAME_LENGTH + colorlen1 - 1, b->name, + ( strchr( b->ip.str, '/' ) ) ? S_COLOR_RED : S_COLOR_WHITE, + b->ip.str, + date, + b->banner, + d_color, + MAX_DURATION_LENGTH - 1, + duration, + b->reason ); +} +qboolean G_admin_showbans( gentity_t *ent ) +{ + int i; + int start = 1; + char filter[ MAX_NAME_LENGTH ] = {""}; + char name_match[ MAX_NAME_LENGTH ] = {""}; + qboolean ipmatch = qfalse; + addr_t ip; + + if( trap_Argc() == 3 ) + { + trap_Argv( 2, filter, sizeof( filter ) ); + start = atoi( filter ); + } + if( trap_Argc() > 1 ) + { + trap_Argv( 1, filter, sizeof( filter ) ); + i = 0; + if( trap_Argc() == 2 ) + for( i = filter[ 0 ] == '-'; isdigit( filter[ i ] ); i++ ); + if( !filter[ i ] ) + start = atoi( filter ); + else if( !( ipmatch = G_AddressParse( filter, &ip ) ) ) + G_SanitiseString( filter, name_match, sizeof( name_match ) ); + } + + admin_search( ent, "showbans", "bans", + ipmatch ? ban_matchip : ban_matchname, + ban_out, g_admin_bans, + ipmatch ? (void * )&ip : (void *)name_match, + start, 1, MAX_ADMIN_SHOWBANS ); + return qtrue; +} + +qboolean G_admin_adminhelp( gentity_t *ent ) +{ + g_admin_command_t *c; + if( trap_Argc() < 2 ) + { + int i; + int count = 0; + + ADMBP_begin(); + for( i = 0; i < adminNumCmds; i++ ) + { + if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) + { + ADMBP( va( "^3%-12s", g_admin_cmds[ i ].keyword ) ); + count++; + // show 6 commands per line + if( count % 6 == 0 ) + ADMBP( "\n" ); + } + } + for( c = g_admin_commands; c; c = c->next ) + { + if( !G_admin_permission( ent, c->flag ) ) + continue; + ADMBP( va( "^3%-12s", c->command ) ); + count++; + // show 6 commands per line + if( count % 6 == 0 ) + ADMBP( "\n" ); + } + if( count % 6 ) + ADMBP( "\n" ); + ADMBP( va( "^3adminhelp: ^7%i available commands\n", count ) ); + ADMBP( "run adminhelp [^3command^7] for adminhelp with a specific command.\n" ); + ADMBP_end(); + + return qtrue; + } + else + { + //!adminhelp param + char param[ MAX_ADMIN_CMD_LEN ]; + g_admin_cmd_t *admincmd; + qboolean denied = qfalse; + + trap_Argv( 1, param, sizeof( param ) ); + ADMBP_begin(); + if( ( c = G_admin_command( param ) ) ) + { + if( G_admin_permission( ent, c->flag ) ) + { + ADMBP( va( "^3adminhelp: ^7help for '%s':\n", c->command ) ); + ADMBP( va( " ^3Description: ^7%s\n", c->desc ) ); + ADMBP( va( " ^3Syntax: ^7%s\n", c->command ) ); + ADMBP( va( " ^3Flag: ^7'%s'\n", c->flag ) ); + ADMBP_end( ); + return qtrue; + } + denied = qtrue; + } + if( ( admincmd = G_admin_cmd( param ) ) ) + { + if( G_admin_permission( ent, admincmd->flag ) ) + { + ADMBP( va( "^3adminhelp: ^7help for '%s':\n", admincmd->keyword ) ); + ADMBP( va( " ^3Description: ^7%s\n", admincmd->function ) ); + ADMBP( va( " ^3Syntax: ^7%s %s\n", admincmd->keyword, + admincmd->syntax ) ); + ADMBP( va( " ^3Flag: ^7'%s'\n", admincmd->flag ) ); + ADMBP_end(); + return qtrue; + } + denied = qtrue; + } + ADMBP( va( "^3adminhelp: ^7%s '%s'\n", + denied ? "you do not have permission to use" : "no help found for", + param ) ); + ADMBP_end( ); + return qfalse; + } +} + +qboolean G_admin_admintest( gentity_t *ent ) +{ + g_admin_level_t *l; + + if( !ent ) + { + ADMP( "^3admintest: ^7you are on the console.\n" ); + return qtrue; + } + + l = G_admin_level( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ); + + AP( va( "print \"^3admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"", + ent->client->pers.netname, + l ? l->level : 0, + l ? "(" : "", + l ? l->name : "", + l ? ")" : "" ) ); + return qtrue; +} + +qboolean G_admin_allready( gentity_t *ent ) +{ + int i = 0; + gclient_t *cl; + + if( !level.intermissiontime ) + { + ADMP( "^3allready: ^7this command is only valid during intermission\n" ); + return qfalse; + } + + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + if( cl->pers.connected != CON_CONNECTED ) + continue; + + if( cl->pers.teamSelection == TEAM_NONE ) + continue; + + cl->readyToExit = qtrue; + } + AP( va( "print \"^3allready:^7 %s^7 says everyone is READY now\n\"", + ( ent ) ? ent->client->pers.netname : "console" ) ); + return qtrue; +} + +qboolean G_admin_endvote( gentity_t *ent ) +{ + char teamName[ sizeof( "spectators" ) ] = {"s"}; + char command[ MAX_ADMIN_CMD_LEN ]; + team_t team; + qboolean cancel; + char *msg; + + trap_Argv( 0, command, sizeof( command ) ); + cancel = !Q_stricmp( command, "cancelvote" ); + if( trap_Argc() == 2 ) + trap_Argv( 1, teamName, sizeof( teamName ) ); + team = G_TeamFromString( teamName ); + if( team == NUM_TEAMS ) + { + ADMP( va( "^3%s: ^7invalid team '%s'\n", command, teamName ) ); + return qfalse; + } + msg = va( "print \"^3%s: ^7%s^7 decided that everyone voted %s\n\"", + command, ( ent ) ? ent->client->pers.netname : "console", + cancel ? "No" : "Yes" ); + if( !level.voteTime[ team ] ) + { + ADMP( va( "^3%s: ^7no vote in progress\n", command ) ); + return qfalse; + } + admin_log( BG_TeamName( team ) ); + level.voteNo[ team ] = cancel ? level.numVotingClients[ team ] : 0; + level.voteYes[ team ] = cancel ? 0 : level.numVotingClients[ team ]; + G_CheckVote( team ); + if( team == TEAM_NONE ) + AP( msg ); + else + G_TeamCommand( team, msg ); + return qtrue; +} + +qboolean G_admin_spec999( gentity_t *ent ) +{ + int i; + gentity_t *vic; + + for( i = 0; i < level.maxclients; i++ ) + { + vic = &g_entities[ i ]; + if( !vic->client ) + continue; + if( vic->client->pers.connected != CON_CONNECTED ) + continue; + if( vic->client->pers.teamSelection == TEAM_NONE ) + continue; + if( vic->client->ps.ping == 999 ) + { + G_ChangeTeam( vic, TEAM_NONE ); + AP( va( "print \"^3spec999: ^7%s^7 moved %s^7 to spectators\n\"", + ( ent ) ? ent->client->pers.netname : "console", + vic->client->pers.netname ) ); + } + } + return qtrue; +} + +qboolean G_admin_rename( gentity_t *ent ) +{ + int pid; + char name[ MAX_NAME_LENGTH ]; + char newname[ MAX_NAME_LENGTH ]; + char err[ MAX_STRING_CHARS ]; + char userinfo[ MAX_INFO_STRING ]; + gentity_t *victim = NULL; + + if( trap_Argc() < 3 ) + { + ADMP( "^3rename: ^7usage: rename [name] [newname]\n" ); + return qfalse; + } + trap_Argv( 1, name, sizeof( name ) ); + Q_strncpyz( newname, ConcatArgs( 2 ), sizeof( newname ) ); + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) + { + ADMP( va( "^3rename: ^7%s", err ) ); + return qfalse; + } + victim = &g_entities[ pid ]; + if( !admin_higher( ent, victim ) ) + { + ADMP( "^3rename: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + if( !G_admin_name_check( victim, newname, err, sizeof( err ) ) ) + { + ADMP( va( "^3rename: ^7%s\n", err ) ); + return qfalse; + } + if( victim->client->pers.connected != CON_CONNECTED ) + { + ADMP( "^3rename: ^7sorry, but your intended victim is still connecting\n" ); + return qfalse; + } + admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", pid, + victim->client->pers.guid, victim->client->pers.netname ) ); + admin_log( newname ); + trap_GetUserinfo( pid, userinfo, sizeof( userinfo ) ); + AP( va( "print \"^3rename: ^7%s^7 has been renamed to %s^7 by %s\n\"", + victim->client->pers.netname, + newname, + ( ent ) ? ent->client->pers.netname : "console" ) ); + Info_SetValueForKey( userinfo, "name", newname ); + trap_SetUserinfo( pid, userinfo ); + ClientUserinfoChanged( pid, qtrue ); + return qtrue; +} + +qboolean G_admin_restart( gentity_t *ent ) +{ + char layout[ MAX_CVAR_VALUE_STRING ] = { "" }; + char teampref[ MAX_STRING_CHARS ] = { "" }; + int i; + gclient_t *cl; + + if( trap_Argc( ) > 1 ) + { + char map[ MAX_QPATH ]; + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + trap_Argv( 1, layout, sizeof( layout ) ); + + // Figure out which argument is which + if( Q_stricmp( layout, "keepteams" ) && + Q_stricmp( layout, "keepteamslock" ) && + Q_stricmp( layout, "switchteams" ) && + Q_stricmp( layout, "switchteamslock" ) ) + { + if( !Q_stricmp( layout, "*BUILTIN*" ) || + trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), + NULL, FS_READ ) > 0 ) + { + trap_Cvar_Set( "g_layouts", layout ); + } + else + { + ADMP( va( "^3restart: ^7layout '%s' does not exist\n", layout ) ); + return qfalse; + } + } + else + { + layout[ 0 ] = '\0'; + trap_Argv( 1, teampref, sizeof( teampref ) ); + } + } + + if( trap_Argc( ) > 2 ) + trap_Argv( 2, teampref, sizeof( teampref ) ); + + admin_log( layout ); + admin_log( teampref ); + + if( !Q_stricmpn( teampref, "keepteams", 9 ) ) + { + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + if( cl->pers.connected != CON_CONNECTED ) + continue; + + if( cl->pers.teamSelection == TEAM_NONE ) + continue; + + cl->sess.restartTeam = cl->pers.teamSelection; + } + } + else if( !Q_stricmpn( teampref, "switchteams", 11 ) ) + { + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + + if( cl->pers.connected != CON_CONNECTED ) + continue; + + if( cl->pers.teamSelection == TEAM_HUMANS ) + cl->sess.restartTeam = TEAM_ALIENS; + else if(cl->pers.teamSelection == TEAM_ALIENS ) + cl->sess.restartTeam = TEAM_HUMANS; + } + } + + if( !Q_stricmp( teampref, "switchteamslock" ) || + !Q_stricmp( teampref, "keepteamslock" ) ) + trap_Cvar_Set( "g_lockTeamsAtStart", "1" ); + + trap_SendConsoleCommand( EXEC_APPEND, "map_restart" ); + + AP( va( "print \"^3restart: ^7map restarted by %s %s %s\n\"", + ( ent ) ? ent->client->pers.netname : "console", + ( layout[ 0 ] ) ? va( "^7(forcing layout '%s^7')", layout ) : "", + ( teampref[ 0 ] ) ? va( "^7(with teams option: '%s^7')", teampref ) : "" ) ); + return qtrue; +} + +qboolean G_admin_nextmap( gentity_t *ent ) +{ + AP( va( "print \"^3nextmap: ^7%s^7 decided to load the next map\n\"", + ( ent ) ? ent->client->pers.netname : "console" ) ); + level.lastWin = TEAM_NONE; + trap_SetConfigstring( CS_WINNER, "Evacuation" ); + LogExit( va( "nextmap was run by %s", + ( ent ) ? ent->client->pers.netname : "console" ) ); + return qtrue; +} + +static qboolean namelog_matchip( void *namelog, const void *ip ) +{ + int i; + namelog_t *n = (namelog_t *)namelog; + for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ ) + { + if( G_AddressCompare( &n->ip[ i ], (addr_t *)ip ) || + G_AddressCompare( (addr_t *)ip, &n->ip[ i ] ) ) + return qtrue; + } + return qfalse; +} +static qboolean namelog_matchname( void *namelog, const void *name ) +{ + char match[ MAX_NAME_LENGTH ]; + int i; + namelog_t *n = (namelog_t *)namelog; + for( i = 0; i < MAX_NAMELOG_NAMES && n->name[ i ][ 0 ]; i++ ) + { + G_SanitiseString( n->name[ i ], match, sizeof( match ) ); + if( strstr( match, (const char *)name ) ) + return qtrue; + } + return qfalse; +} +static void namelog_out( void *namelog, char *str ) +{ + namelog_t *n = (namelog_t *)namelog; + char *p = str; + int l, l2 = MAX_STRING_CHARS, i; + const char *scolor; + + if( n->slot > -1 ) + { + scolor = S_COLOR_YELLOW; + l = Q_snprintf( p, l2, "%s%-2d", scolor, n->slot ); + p += l; + l2 -= l; + } + else + { + *p++ = '-'; + *p++ = ' '; + *p = '\0'; + l2 -= 2; + scolor = S_COLOR_WHITE; + } + + for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ ) + { + l = Q_snprintf( p, l2, " %s", n->ip[ i ].str ); + p += l; + l2 -= l; + } + + for( i = 0; i < MAX_NAMELOG_NAMES && n->name[ i ][ 0 ]; i++ ) + { + l = Q_snprintf( p, l2, " '" S_COLOR_WHITE "%s%s'%s", n->name[ i ], scolor, + i == n->nameOffset ? "*" : "" ); + p += l; + l2 -= l; + } +} +qboolean G_admin_namelog( gentity_t *ent ) +{ + char search[ MAX_NAME_LENGTH ] = {""}; + char s2[ MAX_NAME_LENGTH ] = {""}; + addr_t ip; + qboolean ipmatch = qfalse; + int start = MAX_CLIENTS, i; + + if( trap_Argc() == 3 ) + { + trap_Argv( 2, search, sizeof( search ) ); + start = atoi( search ); + } + if( trap_Argc() > 1 ) + { + trap_Argv( 1, search, sizeof( search ) ); + i = 0; + if( trap_Argc() == 2 ) + for( i = search[ 0 ] == '-'; isdigit( search[ i ] ); i++ ); + if( !search[ i ] ) + start = atoi( search ); + else if( !( ipmatch = G_AddressParse( search, &ip ) ) ) + G_SanitiseString( search, s2, sizeof( s2 ) ); + } + + admin_search( ent, "namelog", "recent players", + ipmatch ? namelog_matchip : namelog_matchname, namelog_out, level.namelogs, + ipmatch ? (void *)&ip : s2, start, MAX_CLIENTS, MAX_ADMIN_LISTITEMS ); + return qtrue; +} + +/* +================== +G_NamelogFromString + +This is similar to G_ClientNumberFromString but for namelog instead +Returns NULL if not exactly 1 match +================== +*/ +namelog_t *G_NamelogFromString( gentity_t *ent, char *s ) +{ + namelog_t *p, *m = NULL; + int i, found = 0; + char n2[ MAX_NAME_LENGTH ] = {""}; + char s2[ MAX_NAME_LENGTH ] = {""}; + + if( !s[ 0 ] ) + return NULL; + + // if a number is provided, it is a clientnum or namelog id + for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); + if( !s[ i ] ) + { + i = atoi( s ); + + if( i >= 0 && i < level.maxclients ) + { + if( level.clients[ i ].pers.connected != CON_DISCONNECTED ) + return level.clients[ i ].pers.namelog; + } + else if( i >= MAX_CLIENTS ) + { + for( p = level.namelogs; p; p = p->next ) + { + if( p->id == i ) + break; + } + if( p ) + return p; + } + + return NULL; + } + + // check for a name match + G_SanitiseString( s, s2, sizeof( s2 ) ); + + for( p = level.namelogs; p; p = p->next ) + { + for( i = 0; i < MAX_NAMELOG_NAMES && p->name[ i ][ 0 ]; i++ ) + { + G_SanitiseString( p->name[ i ], n2, sizeof( n2 ) ); + + // if this is an exact match to a current player + if( i == p->nameOffset && p->slot > -1 && !strcmp( s2, n2 ) ) + return p; + + if( strstr( n2, s2 ) ) + m = p; + } + + if( m == p ) + found++; + } + + if( found == 1 ) + return m; + + if( found > 1 ) + admin_search( ent, "namelog", "recent players", namelog_matchname, + namelog_out, level.namelogs, s2, 0, MAX_CLIENTS, -1 ); + + return NULL; +} + +qboolean G_admin_lock( gentity_t *ent ) +{ + char command[ MAX_ADMIN_CMD_LEN ]; + char teamName[ sizeof( "aliens" ) ]; + team_t team; + qboolean lock, fail = qfalse; + + trap_Argv( 0, command, sizeof( command ) ); + if( trap_Argc() < 2 ) + { + ADMP( va( "^3%s: ^7usage: %s [a|h]\n", command, command ) ); + return qfalse; + } + lock = !Q_stricmp( command, "lock" ); + trap_Argv( 1, teamName, sizeof( teamName ) ); + team = G_TeamFromString( teamName ); + + if( team == TEAM_ALIENS ) + { + if( level.alienTeamLocked == lock ) + fail = qtrue; + else + level.alienTeamLocked = lock; + } + else if( team == TEAM_HUMANS ) + { + if( level.humanTeamLocked == lock ) + fail = qtrue; + else + level.humanTeamLocked = lock; + } + else + { + ADMP( va( "^3%s: ^7invalid team: '%s'\n", command, teamName ) ); + return qfalse; + } + + if( fail ) + { + ADMP( va( "^3%s: ^7the %s team is %s locked\n", + command, BG_TeamName( team ), lock ? "already" : "not currently" ) ); + + return qfalse; + } + + admin_log( BG_TeamName( team ) ); + AP( va( "print \"^3%s: ^7the %s team has been %slocked by %s\n\"", + command, BG_TeamName( team ), lock ? "" : "un", + ent ? ent->client->pers.netname : "console" ) ); + + return qtrue; +} + +qboolean G_admin_builder( gentity_t *ent ) +{ + vec3_t forward, right, up; + vec3_t start, end, dist; + trace_t tr; + gentity_t *traceEnt; + buildLog_t *log; + int i; + + if( !ent ) + { + ADMP( "^3builder: ^7console can't aim.\n" ); + return qfalse; + } + + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + if( ent->client->pers.teamSelection != TEAM_NONE && + ent->client->sess.spectatorState == SPECTATOR_NOT ) + CalcMuzzlePoint( ent, forward, right, up, start ); + else + VectorCopy( ent->client->ps.origin, start ); + VectorMA( start, 1000, forward, end ); + + trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) ) + { + if( !G_admin_permission( ent, "buildlog" ) && + ent->client->pers.teamSelection != TEAM_NONE && + ent->client->pers.teamSelection != traceEnt->buildableTeam ) + { + ADMP( "^3builder: ^7structure not owned by your team\n" ); + return qfalse; + } + + for( i = 0 ; i < level.numBuildLogs; i++ ) + { + log = &level.buildLog[ ( level.buildId - i - 1 ) % MAX_BUILDLOG ]; + if( log->fate != BF_CONSTRUCT || traceEnt->s.modelindex != log->modelindex ) + continue; + + VectorSubtract( traceEnt->s.pos.trBase, log->origin, dist ); + if( VectorLengthSquared( dist ) < 2.0f ) + { + char logid[ 20 ] = {""}; + + if( G_admin_permission( ent, "buildlog" ) ) + Com_sprintf( logid, sizeof( logid ), ", buildlog #%d", + MAX_CLIENTS + level.buildId - i - 1 ); + ADMP( va( "^3builder: ^7%s built by %s^7%s\n", + BG_Buildable( log->modelindex, NULL )->humanName, + log->actor ? + log->actor->name[ log->actor->nameOffset ] : + "", + logid ) ); + break; + } + } + if( i == level.numBuildLogs ) + ADMP( va( "^3builder: ^7%s not in build log, possibly a layout item\n", + BG_Buildable( traceEnt->s.modelindex, NULL )->humanName ) ); + } + else + ADMP( "^3builder: ^7no structure found under crosshair\n" ); + + return qtrue; +} + +qboolean G_admin_pause( gentity_t *ent ) +{ + if( !level.pausedTime ) + { + AP( va( "print \"^3pause: ^7%s^7 paused the game.\n\"", + ( ent ) ? ent->client->pers.netname : "console" ) ); + level.pausedTime = 1; + trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" ); + } + else + { + // Prevent accidental pause->unpause race conditions by two admins + if( level.pausedTime < 1000 ) + { + ADMP( "^3pause: ^7Unpausing so soon assumed accidental and ignored.\n" ); + return qfalse; + } + + AP( va( "print \"^3pause: ^7%s^7 unpaused the game (Paused for %d sec) \n\"", + ( ent ) ? ent->client->pers.netname : "console", + (int) ( (float) level.pausedTime / 1000.0f ) ) ); + trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); + + level.pausedTime = 0; + } + + return qtrue; +} + +static char *fates[] = +{ + "^2built^7", + "^3deconstructed^7", + "^7replaced^7", + "^3destroyed^7", + "^1TEAMKILLED^7", + "^7unpowered^7", + "removed" +}; +qboolean G_admin_buildlog( gentity_t *ent ) +{ + char search[ MAX_NAME_LENGTH ] = {""}; + char s[ MAX_NAME_LENGTH ] = {""}; + char n[ MAX_NAME_LENGTH ]; + char stamp[ 8 ]; + int id = -1; + int printed = 0; + int time; + int start = MAX_CLIENTS + level.buildId - level.numBuildLogs; + int i = 0, j; + buildLog_t *log; + + if( !level.buildId ) + { + ADMP( "^3buildlog: ^7log is empty\n" ); + return qtrue; + } + + if( trap_Argc() == 3 ) + { + trap_Argv( 2, search, sizeof( search ) ); + start = atoi( search ); + } + if( trap_Argc() > 1 ) + { + trap_Argv( 1, search, sizeof( search ) ); + for( i = search[ 0 ] == '-'; isdigit( search[ i ] ); i++ ); + if( i && !search[ i ] ) + { + id = atoi( search ); + if( trap_Argc() == 2 && ( id < 0 || id >= MAX_CLIENTS ) ) + { + start = id; + id = -1; + } + else if( id < 0 || id >= MAX_CLIENTS || + level.clients[ id ].pers.connected != CON_CONNECTED ) + { + ADMP( "^3buildlog: ^7invalid client id\n" ); + return qfalse; + } + } + else + G_SanitiseString( search, s, sizeof( s ) ); + } + else + start = MAX( -MAX_ADMIN_LISTITEMS, -level.buildId ); + + if( start < 0 ) + start = MAX( level.buildId - level.numBuildLogs, start + level.buildId ); + else + start -= MAX_CLIENTS; + if( start < level.buildId - level.numBuildLogs || start >= level.buildId ) + { + ADMP( "^3buildlog: ^7invalid build ID\n" ); + return qfalse; + } + + if( ent && ent->client->pers.teamSelection != TEAM_NONE ) + trap_SendServerCommand( -1, + va( "print \"^3buildlog: ^7%s^7 requested a log of recent building activity\n\"", + ent->client->pers.netname ) ); + + ADMBP_begin(); + for( i = start; i < level.buildId && printed < MAX_ADMIN_LISTITEMS; i++ ) + { + log = &level.buildLog[ i % MAX_BUILDLOG ]; + if( id >= 0 && id < MAX_CLIENTS ) + { + if( log->actor != level.clients[ id ].pers.namelog ) + continue; + } + else if( s[ 0 ] ) + { + if( !log->actor ) + continue; + for( j = 0; j < MAX_NAMELOG_NAMES && log->actor->name[ j ][ 0 ]; j++ ) + { + G_SanitiseString( log->actor->name[ j ], n, sizeof( n ) ); + if( strstr( n, s ) ) + break; + } + if( j >= MAX_NAMELOG_NAMES || !log->actor->name[ j ][ 0 ] ) + continue; + } + printed++; + time = ( log->time - level.startTime ) / 1000; + Com_sprintf( stamp, sizeof( stamp ), "%3d:%02d", time / 60, time % 60 ); + ADMBP( va( "^2%c^7%-3d %s ^7%s^7 %s%s%s\n", + log->actor && log->fate != BF_REPLACE && log->fate != BF_UNPOWER ? + '*' : ' ', + i + MAX_CLIENTS, + log->actor && ( log->fate == BF_REPLACE || log->fate == BF_UNPOWER ) ? + " \\_" : stamp, + BG_Buildable( log->modelindex, NULL )->humanName, + fates[ log->fate ], + log->actor ? " by " : "", + log->actor ? + log->actor->name[ log->actor->nameOffset ] : + "" ) ); + } + ADMBP( va( "^3buildlog: ^7showing %d build logs %d - %d of %d - %d. %s\n", + printed, start + MAX_CLIENTS, i + MAX_CLIENTS - 1, + level.buildId + MAX_CLIENTS - level.numBuildLogs, + level.buildId + MAX_CLIENTS - 1, + i < level.buildId ? va( "run 'buildlog %s%s%d' to see more", + search, search[ 0 ] ? " " : "", i + MAX_CLIENTS ) : "" ) ); + ADMBP_end(); + return qtrue; +} + +qboolean G_admin_revert( gentity_t *ent ) +{ + char arg[ MAX_TOKEN_CHARS ]; + char time[ MAX_DURATION_LENGTH ]; + int id; + buildLog_t *log; + + if( trap_Argc() != 2 ) + { + ADMP( "^3revert: ^7usage: revert [id]\n" ); + return qfalse; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + id = atoi( arg ) - MAX_CLIENTS; + if( id < level.buildId - level.numBuildLogs || id >= level.buildId ) + { + ADMP( "^3revert: ^7invalid id\n" ); + return qfalse; + } + + log = &level.buildLog[ id % MAX_BUILDLOG ]; + if( !log->actor || log->fate == BF_REPLACE || log->fate == BF_UNPOWER ) + { + // fixme: then why list them with an id # in build log ? - rez + ADMP( "^3revert: ^7you can only revert direct player actions, " + "indicated by ^2* ^7in buildlog\n" ); + return qfalse; + } + + G_admin_duration( ( level.time - log->time ) / 1000, time, + sizeof( time ) ); + admin_log( arg ); + AP( va( "print \"^3revert: ^7%s^7 reverted %d %s over the past %s\n\"", + ent ? ent->client->pers.netname : "console", + level.buildId - id, + ( level.buildId - id ) > 1 ? "changes" : "change", + time ) ); + G_BuildLogRevert( id ); + return qtrue; +} + +/* +================ + G_admin_print + + This function facilitates the ADMP define. ADMP() is similar to CP except + that it prints the message to the server console if ent is not defined. +================ +*/ +void G_admin_print( gentity_t *ent, char *m ) +{ + if( ent ) + trap_SendServerCommand( ent - level.gentities, va( "print \"%s\"", m ) ); + else + { + char m2[ MAX_STRING_CHARS ]; + if( !trap_Cvar_VariableIntegerValue( "com_ansiColor" ) ) + { + G_DecolorString( m, m2, sizeof( m2 ) ); + trap_Print( m2 ); + } + else + trap_Print( m ); + } +} + +void G_admin_buffer_begin( void ) +{ + g_bfb[ 0 ] = '\0'; +} + +void G_admin_buffer_end( gentity_t *ent ) +{ + ADMP( g_bfb ); +} + +void G_admin_buffer_print( gentity_t *ent, char *m ) +{ + // 1022 - strlen("print 64 \"\"") - 1 + if( strlen( m ) + strlen( g_bfb ) >= 1009 ) + { + ADMP( g_bfb ); + g_bfb[ 0 ] = '\0'; + } + Q_strcat( g_bfb, sizeof( g_bfb ), m ); +} + + +void G_admin_cleanup( void ) +{ + g_admin_level_t *l; + g_admin_admin_t *a; + g_admin_ban_t *b; + g_admin_command_t *c; + void *n; + + for( l = g_admin_levels; l; l = n ) + { + n = l->next; + BG_Free( l ); + } + g_admin_levels = NULL; + for( a = g_admin_admins; a; a = n ) + { + n = a->next; + BG_Free( a ); + } + g_admin_admins = NULL; + for( b = g_admin_bans; b; b = n ) + { + n = b->next; + BG_Free( b ); + } + g_admin_bans = NULL; + for( c = g_admin_commands; c; c = n ) + { + n = c->next; + BG_Free( c ); + } + g_admin_commands = NULL; + BG_DefragmentMemory( ); +} diff --git a/src/game/g_admin.h b/src/game/g_admin.h new file mode 100644 index 0000000..8024fc8 --- /dev/null +++ b/src/game/g_admin.h @@ -0,0 +1,197 @@ +/* +=========================================================================== +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef _G_ADMIN_H +#define _G_ADMIN_H + +#define AP(x) trap_SendServerCommand(-1, x) +#define CP(x) trap_SendServerCommand(ent-g_entities, x) +#define CPx(x, y) trap_SendServerCommand(x, y) +#define ADMP(x) G_admin_print(ent, x) +#define ADMBP(x) G_admin_buffer_print(ent, x) +#define ADMBP_begin() G_admin_buffer_begin() +#define ADMBP_end() G_admin_buffer_end(ent) + +#define MAX_ADMIN_FLAG_LEN 20 +#define MAX_ADMIN_FLAGS 1024 +#define MAX_ADMIN_CMD_LEN 20 +#define MAX_ADMIN_BAN_REASON 50 + +/* + * IMMUNITY - cannot be vote kicked, vote muted + * NOCENSORFLOOD - cannot be censored or flood protected + * SPECALLCHAT - can see team chat as a spectator + * FORCETEAMCHANGE - can switch teams any time, regardless of balance + * UNACCOUNTABLE - does not need to specify a reason for a kick/ban + * NOVOTELIMIT - can call a vote at any time (regardless of a vote being + * disabled or voting limitations) + * CANPERMBAN - does not need to specify a duration for a ban + * ACTIVITY - inactivity rules do not apply to them + * IMMUTABLE - admin commands cannot be used on them + * INCOGNITO - does not show up as an admin in !listplayers + * ALLFLAGS - all flags (including command flags) apply to this player + * ADMINCHAT - receieves and can send /a admin messages + * ALWAYSFRIEND - always marked as a friendly entity during buildgames + */ +#define ADMF_IMMUNITY "IMMUNITY" +#define ADMF_NOCENSORFLOOD "NOCENSORFLOOD" +#define ADMF_SPEC_ALLCHAT "SPECALLCHAT" +#define ADMF_FORCETEAMCHANGE "FORCETEAMCHANGE" +#define ADMF_UNACCOUNTABLE "UNACCOUNTABLE" +#define ADMF_NO_VOTE_LIMIT "NOVOTELIMIT" +#define ADMF_CAN_PERM_BAN "CANPERMBAN" +#define ADMF_ACTIVITY "ACTIVITY" + +#define ADMF_IMMUTABLE "IMMUTABLE" +#define ADMF_INCOGNITO "INCOGNITO" +#define ADMF_ALLFLAGS "ALLFLAGS" +#define ADMF_ADMINCHAT "ADMINCHAT" + +#define ADMF_ALWAYSFRIEND "ALWAYSFRIEND" + +#define MAX_ADMIN_LISTITEMS 20 +#define MAX_ADMIN_SHOWBANS 10 + +// important note: QVM does not seem to allow a single char to be a +// member of a struct at init time. flag has been converted to char* +typedef struct +{ + char *keyword; + qboolean ( * handler ) ( gentity_t *ent ); + qboolean silent; + char *flag; + char *function; // used for !help + char *syntax; // used for !help +} +g_admin_cmd_t; + +typedef struct g_admin_level +{ + struct g_admin_level *next; + int level; + char name[ MAX_NAME_LENGTH ]; + char flags[ MAX_ADMIN_FLAGS ]; +} +g_admin_level_t; + +typedef struct g_admin_admin +{ + struct g_admin_admin *next; + int level; + char guid[ 33 ]; + char name[ MAX_NAME_LENGTH ]; + char flags[ MAX_ADMIN_FLAGS ]; +} +g_admin_admin_t; + +#define ADDRLEN 16 +/* +addr_ts are passed as "arg" to admin_search for IP address matching +admin_search prints (char *)arg, so the stringified address needs to be first +*/ +typedef struct +{ + char str[ 44 ]; + enum + { + IPv4, + IPv6 + } type; + byte addr[ ADDRLEN ]; + int mask; +} addr_t; + +typedef struct g_admin_ban +{ + struct g_admin_ban *next; + char name[ MAX_NAME_LENGTH ]; + char guid[ 33 ]; + addr_t ip; + char reason[ MAX_ADMIN_BAN_REASON ]; + char made[ 18 ]; // big enough for strftime() %c + int expires; + char banner[ MAX_NAME_LENGTH ]; + int warnCount; +} +g_admin_ban_t; + +typedef struct g_admin_command +{ + struct g_admin_command *next; + char command[ MAX_ADMIN_CMD_LEN ]; + char exec[ MAX_QPATH ]; + char desc[ 50 ]; + char flag[ MAX_ADMIN_FLAG_LEN ]; +} +g_admin_command_t; + +void G_admin_register_cmds( void ); +void G_admin_unregister_cmds( void ); +void G_admin_cmdlist( gentity_t *ent ); + +qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen ); +qboolean G_admin_cmd_check( gentity_t *ent ); +qboolean G_admin_readconfig( gentity_t *ent ); +qboolean G_admin_permission( gentity_t *ent, const char *flag ); +qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ); +g_admin_admin_t *G_admin_admin( const char *guid ); +void G_admin_authlog( gentity_t *ent ); + +// admin command functions +qboolean G_admin_time( gentity_t *ent ); +qboolean G_admin_setlevel( gentity_t *ent ); +qboolean G_admin_kick( gentity_t *ent ); +qboolean G_admin_adjustban( gentity_t *ent ); +qboolean G_admin_ban( gentity_t *ent ); +qboolean G_admin_unban( gentity_t *ent ); +qboolean G_admin_putteam( gentity_t *ent ); +qboolean G_admin_listadmins( gentity_t *ent ); +qboolean G_admin_listlayouts( gentity_t *ent ); +qboolean G_admin_listplayers( gentity_t *ent ); +qboolean G_admin_changemap( gentity_t *ent ); +qboolean G_admin_mute( gentity_t *ent ); +qboolean G_admin_denybuild( gentity_t *ent ); +qboolean G_admin_showbans( gentity_t *ent ); +qboolean G_admin_adminhelp( gentity_t *ent ); +qboolean G_admin_admintest( gentity_t *ent ); +qboolean G_admin_allready( gentity_t *ent ); +qboolean G_admin_endvote( gentity_t *ent ); +qboolean G_admin_spec999( gentity_t *ent ); +qboolean G_admin_rename( gentity_t *ent ); +qboolean G_admin_restart( gentity_t *ent ); +qboolean G_admin_nextmap( gentity_t *ent ); +qboolean G_admin_namelog( gentity_t *ent ); +qboolean G_admin_lock( gentity_t *ent ); +qboolean G_admin_pause( gentity_t *ent ); +qboolean G_admin_builder( gentity_t *ent ); +qboolean G_admin_buildlog( gentity_t *ent ); +qboolean G_admin_revert( gentity_t *ent ); + +void G_admin_print( gentity_t *ent, char *m ); +void G_admin_buffer_print( gentity_t *ent, char *m ); +void G_admin_buffer_begin( void ); +void G_admin_buffer_end( gentity_t *ent ); + +void G_admin_duration( int secs, char *duration, int dursize ); +void G_admin_cleanup( void ); + +#endif /* ifndef _G_ADMIN_H */ diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c new file mode 100644 index 0000000..ae3a97d --- /dev/null +++ b/src/game/g_buildable.c @@ -0,0 +1,4548 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +/* +================ +G_SetBuildableAnim + +Triggers an animation client side +================ +*/ +void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ) +{ + int localAnim = anim | ( ent->s.legsAnim & ANIM_TOGGLEBIT ); + + if( force ) + localAnim |= ANIM_FORCEBIT; + + // don't flip the togglebit more than once per frame + if( ent->animTime != level.time ) + { + ent->animTime = level.time; + localAnim ^= ANIM_TOGGLEBIT; + } + + ent->s.legsAnim = localAnim; +} + +/* +================ +G_SetIdleBuildableAnim + +Set the animation to use whilst no other animations are running +================ +*/ +void G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ) +{ + ent->s.torsoAnim = anim; +} + +/* +=============== +G_CheckSpawnPoint + +Check if a spawn at a specified point is valid +=============== +*/ +gentity_t *G_CheckSpawnPoint( int spawnNum, const vec3_t origin, + const vec3_t normal, buildable_t spawn, vec3_t spawnOrigin ) +{ + float displacement; + vec3_t mins, maxs; + vec3_t cmins, cmaxs; + vec3_t localOrigin; + trace_t tr; + + BG_BuildableBoundingBox( spawn, mins, maxs ); + + if( spawn == BA_A_SPAWN ) + { + VectorSet( cmins, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX ); + VectorSet( cmaxs, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX, MAX_ALIEN_BBOX ); + + displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3; + VectorMA( origin, displacement, normal, localOrigin ); + } + else if( spawn == BA_H_SPAWN ) + { + BG_ClassBoundingBox( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL ); + + VectorCopy( origin, localOrigin ); + localOrigin[ 2 ] += maxs[ 2 ] + fabs( cmins[ 2 ] ) + 1.0f; + } + else + return NULL; + + trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); + + if( tr.entityNum != ENTITYNUM_NONE ) + return &g_entities[ tr.entityNum ]; + + trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID ); + + if( tr.entityNum != ENTITYNUM_NONE ) + return &g_entities[ tr.entityNum ]; + + if( spawnOrigin != NULL ) + VectorCopy( localOrigin, spawnOrigin ); + + return NULL; +} + +#define POWER_REFRESH_TIME 2000 + +/* +================ +G_FindPower + +attempt to find power for self, return qtrue if successful +================ +*/ +qboolean G_FindPower( gentity_t *self, qboolean searchUnspawned ) +{ + int i, j; + gentity_t *ent, *ent2; + gentity_t *closestPower = NULL; + int distance = 0; + int minDistance = REPEATER_BASESIZE + 1; + vec3_t temp_v; + + if( self->buildableTeam != TEAM_HUMANS ) + return qfalse; + + // Reactor is always powered + if( self->s.modelindex == BA_H_REACTOR ) + { + self->parentNode = self; + + return qtrue; + } + + // Handle repeaters + if( self->s.modelindex == BA_H_REPEATER ) + { + self->parentNode = G_Reactor( ); + + return self->parentNode != NULL; + } + + // Iterate through entities + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + // If entity is a power item calculate the distance to it + if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && + ( searchUnspawned || ent->spawned ) && ent->powered && ent->health > 0 ) + { + VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + distance = VectorLength( temp_v ); + + // Always prefer a reactor if there is one in range + if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) + { + // Only power as much BP as the reactor can hold + if( self->s.modelindex != BA_NONE ) + { + int buildPoints = g_humanBuildPoints.integer; + + // Scan the buildables in the reactor zone + for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ ) + { + gentity_t *powerEntity; + + if( ent2->s.eType != ET_BUILDABLE ) + continue; + + if( ent2 == self ) + continue; + + powerEntity = ent2->parentNode; + + if( powerEntity && powerEntity->s.modelindex == BA_H_REACTOR && ( powerEntity == ent ) ) + { + buildPoints -= BG_Buildable( ent2->s.modelindex, ent2->cuboidSize )->buildPoints; + } + } + + buildPoints -= level.humanBuildPointQueue; + + buildPoints -= BG_Buildable( self->s.modelindex, self->cuboidSize )->buildPoints; + + if( buildPoints >= 0 ) + { + self->parentNode = ent; + return qtrue; + } + else + { + // a buildable can still be built if it shares BP from two zones + + // TODO: handle combined power zones here + } + } + + // Dummy buildables don't need to look for zones + else + { + self->parentNode = ent; + return qtrue; + } + } + else if( distance < minDistance ) + { + // It's a repeater, so check that enough BP will be available to power + // the buildable but only if self is a real buildable + + if( self->s.modelindex != BA_NONE ) + { + int buildPoints = g_humanRepeaterBuildPoints.integer; + + // Scan the buildables in the repeater zone + for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ ) + { + gentity_t *powerEntity; + + if( ent2->s.eType != ET_BUILDABLE ) + continue; + + if( ent2 == self ) + continue; + + powerEntity = ent2->parentNode; + + if( powerEntity && powerEntity->s.modelindex == BA_H_REPEATER && ( powerEntity == ent ) ) + { + buildPoints -= BG_Buildable( ent2->s.modelindex, ent->cuboidSize )->buildPoints; + } + } + + if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) + buildPoints -= level.buildPointZones[ ent->buildPointZone ].queuedBuildPoints; + + buildPoints -= BG_Buildable( self->s.modelindex, self->cuboidSize )->buildPoints; + + if( buildPoints >= 0 ) + { + closestPower = ent; + minDistance = distance; + } + else + { + // a buildable can still be built if it shares BP from two zones + + // TODO: handle combined power zones here + } + } + else + { + // Dummy buildables don't need to look for zones + closestPower = ent; + minDistance = distance; + } + } + } + } + + self->parentNode = closestPower; + return self->parentNode != NULL; +} + +/* +================ +G_PowerEntityForPoint + +Simple wrapper to G_FindPower to find the entity providing +power for the specified point +================ +*/ +gentity_t *G_PowerEntityForPoint( const vec3_t origin ) +{ + gentity_t dummy; + + dummy.parentNode = NULL; + dummy.buildableTeam = TEAM_HUMANS; + dummy.s.modelindex = BA_NONE; + VectorCopy( origin, dummy.s.origin ); + + if( G_FindPower( &dummy, qfalse ) ) + return dummy.parentNode; + else + return NULL; +} + +/* +================ +G_PowerEntityForEntity + +Simple wrapper to G_FindPower to find the entity providing +power for the specified entity +================ +*/ +gentity_t *G_PowerEntityForEntity( gentity_t *ent ) +{ + if( G_FindPower( ent, qfalse ) ) + return ent->parentNode; + return NULL; +} + +/* +================ +G_IsPowered + +Check if a location has power, returning the entity type +that is providing it +================ +*/ +buildable_t G_IsPowered( vec3_t origin ) +{ + gentity_t *ent = G_PowerEntityForPoint( origin ); + + if( ent ) + return ent->s.modelindex; + else + return BA_NONE; +} + + +/* +================== +G_GetBuildPoints + +Get the number of build points from a position +================== +*/ +int G_GetBuildPoints( const vec3_t pos, team_t team ) +{ + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + return 0; + } + else if( team == TEAM_ALIENS ) + { + return level.alienBuildPoints; + } + else if( team == TEAM_HUMANS ) + { + gentity_t *powerPoint = G_PowerEntityForPoint( pos ); + + if( powerPoint && powerPoint->s.modelindex == BA_H_REACTOR ) + return level.humanBuildPoints; + + if( powerPoint && powerPoint->s.modelindex == BA_H_REPEATER && + powerPoint->usesBuildPointZone && level.buildPointZones[ powerPoint->buildPointZone ].active ) + { + return level.buildPointZones[ powerPoint->buildPointZone ].totalBuildPoints - + level.buildPointZones[ powerPoint->buildPointZone ].queuedBuildPoints; + } + + // Return the BP of the main zone by default + return level.humanBuildPoints; + } + + return 0; +} + +/* +================== +G_GetMarkedBuildPoints + +Get the number of marked build points from a position +================== +*/ +int G_GetMarkedBuildPoints( const vec3_t pos, team_t team ) +{ + gentity_t *ent; + int i; + int sum = 0; + + if( G_TimeTilSuddenDeath( ) <= 0 ) + return 0; + + if( !g_markDeconstruct.integer ) + return 0; + + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( team == TEAM_HUMANS && + ent->s.modelindex != BA_H_REACTOR && + ent->s.modelindex != BA_H_REPEATER && + ent->parentNode != G_PowerEntityForPoint( pos ) ) + continue; + + if( !ent->inuse ) + continue; + + if( ent->health <= 0 ) + continue; + + if( ent->buildableTeam != team ) + continue; + + if( ent->deconstruct ) + sum += BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; + } + + return sum; +} + +/* +================== +G_InPowerZone + +See if a buildable is inside of another power zone. +Return pointer to provider if so. +It's different from G_FindPower because FindPower for +providers will find themselves. +(This doesn't check if power zones overlap) +================== +*/ +gentity_t *G_InPowerZone( gentity_t *self ) +{ + int i; + gentity_t *ent; + int distance; + vec3_t temp_v; + + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent == self ) + continue; + + if( !ent->spawned ) + continue; + + if( ent->health <= 0 ) + continue; + + // if entity is a power item calculate the distance to it + if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && + ent->spawned && ent->powered ) + { + VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + distance = VectorLength( temp_v ); + + if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) + return ent; + else if( ent->s.modelindex == BA_H_REPEATER && distance <= REPEATER_BASESIZE ) + return ent; + } + } + + return NULL; +} + +/* +================ +G_FindDCC + +attempt to find a controlling DCC for self, return number found +================ +*/ +int G_FindDCC( gentity_t *self ) +{ + int i; + gentity_t *ent; + int distance = 0; + vec3_t temp_v; + int foundDCC = 0; + + if( self->buildableTeam != TEAM_HUMANS ) + return 0; + + //iterate through entities + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + //if entity is a dcc calculate the distance to it + if( ent->s.modelindex == BA_H_DCC && ent->spawned ) + { + VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + distance = VectorLength( temp_v ); + if( distance < DC_RANGE && ent->powered ) + { + foundDCC++; + } + } + } + + return foundDCC; +} + +/* +================ +G_IsDCCBuilt + +See if any powered DCC exists +================ +*/ +qboolean G_IsDCCBuilt( void ) +{ + int i; + gentity_t *ent; + + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->s.modelindex != BA_H_DCC ) + continue; + + if( !ent->spawned ) + continue; + + if( ent->health <= 0 ) + continue; + + return qtrue; + } + + return qfalse; +} + +/* +================ +G_Reactor +G_Overmind + +Since there's only one of these and we quite often want to find them, cache the +results, but check them for validity each time + +The code here will break if more than one reactor or overmind is allowed, even +if one of them is dead/unspawned +================ +*/ +static gentity_t *G_FindBuildable( buildable_t buildable ); + +gentity_t *G_Reactor( void ) +{ + static gentity_t *rc; + + // If cache becomes invalid renew it + if( !rc || rc->s.eType != ET_BUILDABLE || rc->s.modelindex != BA_H_REACTOR ) + rc = G_FindBuildable( BA_H_REACTOR ); + + // If we found it and it's alive, return it + if( rc && rc->spawned && rc->health > 0 ) + return rc; + + return NULL; +} + +gentity_t *G_Overmind( void ) +{ + static gentity_t *om; + + // If cache becomes invalid renew it + if( !om || om->s.eType != ET_BUILDABLE || om->s.modelindex != BA_A_OVERMIND ) + om = G_FindBuildable( BA_A_OVERMIND ); + + // If we found it and it's alive, return it + if( om && om->spawned && om->health > 0 ) + return om; + + return NULL; +} + +/* +================ +G_FindCreep + +attempt to find creep for self, return qtrue if successful +================ +*/ +qboolean G_FindCreep( gentity_t *self ) +{ + int i; + gentity_t *ent; + gentity_t *closestSpawn = NULL; + int distance = 0; + int minDistance = 10000; + vec3_t temp_v; + + //don't check for creep if flying through the air + if( self->s.groundEntityNum == -1 ) + return qtrue; + + //if self does not have a parentNode or it's parentNode is invalid find a new one + if( self->client || self->parentNode == NULL || !self->parentNode->inuse || + self->parentNode->health <= 0 ) + { + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ( ent->s.modelindex == BA_A_SPAWN || + ent->s.modelindex == BA_A_OVERMIND ) && + ent->spawned && ent->health > 0 ) + { + VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + distance = VectorLength( temp_v ); + if( distance < minDistance ) + { + closestSpawn = ent; + minDistance = distance; + } + } + } + + if( minDistance <= CREEP_BASESIZE ) + { + if( !self->client ) + self->parentNode = closestSpawn; + return qtrue; + } + else + return qfalse; + } + + if( self->client ) + return qfalse; + + //if we haven't returned by now then we must already have a valid parent + return qtrue; +} + +/* +================ +G_IsCreepHere + +simple wrapper to G_FindCreep to check if a location has creep +================ +*/ +static qboolean G_IsCreepHere( vec3_t origin ) +{ + gentity_t dummy; + + memset( &dummy, 0, sizeof( gentity_t ) ); + + dummy.parentNode = NULL; + dummy.s.modelindex = BA_NONE; + VectorCopy( origin, dummy.s.origin ); + + return G_FindCreep( &dummy ); +} + +/* +================ +G_CreepSlow + +Set any nearby humans' SS_CREEPSLOWED flag +================ +*/ +static void G_CreepSlow( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *enemy; + buildable_t buildable = self->s.modelindex; + float creepSize = (float)BG_Buildable( buildable, self->cuboidSize )->creepSize; + + VectorSet( range, creepSize, creepSize, creepSize ); + + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + //find humans + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + enemy = &g_entities[ entityList[ i ] ]; + + if( enemy->flags & FL_NOTARGET ) + continue; + + if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED; + enemy->client->lastCreepSlowTime = level.time; + } + } +} + +/* +================ +nullDieFunction + +hack to prevent compilers complaining about function pointer -> NULL conversion +================ +*/ +static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ +} + +//================================================================================== + + + +/* +================ +AGeneric_CreepRecede + +Called when an alien buildable dies +================ +*/ +void AGeneric_CreepRecede( gentity_t *self ) +{ + //if the creep just died begin the recession + if( !( self->s.eFlags & EF_DEAD ) ) + { + self->s.eFlags |= EF_DEAD; + G_QueueBuildPoints( self ); + + G_RewardAttackers( self ); + + G_AddEvent( self, EV_BUILD_DESTROY, 0 ); + + if( self->spawned ) + self->s.time = -level.time; + else + self->s.time = -( level.time - + (int)( (float)CREEP_SCALEDOWN_TIME * + ( 1.0f - ( (float)( level.time - self->buildTime ) / + (float)BG_Buildable( self->s.modelindex, self->cuboidSize )->buildTime ) ) ) ); + } + + //creep is still receeding + if( ( self->timestamp + 10000 ) > level.time ) + self->nextthink = level.time + 500; + else //creep has died + G_FreeEntity( self ); +} + +/* +================ +AGeneric_Blast + +Called when an Alien buildable explodes after dead state +================ +*/ +void AGeneric_Blast( gentity_t *self ) +{ + vec3_t dir; + + VectorCopy( self->s.origin2, dir ); + + //do a bit of radius damage + G_SelectiveRadiusDamage( self->s.pos.trBase, g_entities + self->killedBy, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath, + TEAM_ALIENS ); + + //pretty events and item cleanup + self->s.eFlags |= EF_NODRAW; //don't draw the model once it's destroyed + G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); + self->timestamp = level.time; + self->think = AGeneric_CreepRecede; + self->nextthink = level.time + 500; + + self->r.contents = 0; //stop collisions... + trap_LinkEntity( self ); //...requires a relink +} + +/* +================ +AGeneric_Die + +Called when an Alien buildable is killed and enters a brief dead state prior to +exploding. +================ +*/ +void AGeneric_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + + self->die = nullDieFunction; + self->killedBy = attacker - g_entities; + self->think = AGeneric_Blast; + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + self->powered = qfalse; + + if( self->spawned ) + self->nextthink = level.time + 5000; + else + self->nextthink = level.time; //blast immediately + + G_LogDestruction( self, attacker, mod ); +} + +/* +================ +AGeneric_CreepCheck + +Tests for creep and kills the buildable if there is none +================ +*/ +void AGeneric_CreepCheck( gentity_t *self ) +{ + gentity_t *spawn; + + spawn = self->parentNode; + if( !G_FindCreep( self ) ) + { + if( spawn ) + G_Damage( self, NULL, g_entities + spawn->killedBy, NULL, NULL, + self->health, 0, MOD_NOCREEP ); + else + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP ); + return; + } + G_CreepSlow( self ); +} + +/* +================ +AGeneric_Think + +A generic think function for Alien buildables +================ +*/ +void AGeneric_Think( gentity_t *self ) +{ + self->powered = G_Overmind( ) != NULL; + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; + AGeneric_CreepCheck( self ); +} + +/* +================ +AGeneric_Pain + +A generic pain function for Alien buildables +================ +*/ +void AGeneric_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + if( self->health <= 0 ) + return; + + // Alien buildables only have the first pain animation defined + G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); +} + + + + +//================================================================================== + +/* +================ +ASpawn_Think + +think function for Alien Spawn +================ +*/ +void ASpawn_Think( gentity_t *self ) +{ + gentity_t *ent; + + if( self->spawned ) + { + //only suicide if at rest + if( self->s.groundEntityNum ) + { + if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, + self->s.origin2, BA_A_SPAWN, NULL ) ) != NULL ) + { + // If the thing blocking the spawn is a buildable, kill it. + // If it's part of the map, kill self. + if( ent->s.eType == ET_BUILDABLE ) + { + if( ent->builtBy >= 0 ) // don't queue the bp from this + G_Damage( ent, NULL, g_entities + ent->builtBy, NULL, NULL, 10000, 0, MOD_SUICIDE ); + else + G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + + G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); + } + else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) + { + G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + return; + } + + if( ent->s.eType == ET_CORPSE ) + G_FreeEntity( ent ); //quietly remove + } + } + } + + G_CreepSlow( self ); + + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; +} + + + + + +//================================================================================== + + + + + +#define OVERMIND_ATTACK_PERIOD 10000 +#define OVERMIND_DYING_PERIOD 5000 +#define OVERMIND_SPAWNS_PERIOD 30000 + +/* +================ +AOvermind_Think + +Think function for Alien Overmind +================ +*/ +void AOvermind_Think( gentity_t *self ) +{ + vec3_t range = { OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE }; + int i; + + if( self->spawned && ( self->health > 0 ) ) + { + //do some damage + if( G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, + self->splashRadius, self, MOD_OVERMIND, TEAM_ALIENS ) ) + { + self->timestamp = level.time; + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + } + + // just in case an egg finishes building after we tell overmind to stfu + if( level.numAlienSpawns > 0 ) + level.overmindMuted = qfalse; + + // shut up during intermission + if( level.intermissiontime ) + level.overmindMuted = qtrue; + + //low on spawns + if( !level.overmindMuted && level.numAlienSpawns <= 0 && + level.time > self->overmindSpawnsTimer ) + { + qboolean haveBuilder = qfalse; + gentity_t *builder; + + self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD; + G_BroadcastEvent( EV_OVERMIND_SPAWNS, 0 ); + + for( i = 0; i < level.numConnectedClients; i++ ) + { + builder = &g_entities[ level.sortedClients[ i ] ]; + if( builder->health > 0 && + ( builder->client->pers.classSelection == PCL_ALIEN_BUILDER0 || + builder->client->pers.classSelection == PCL_ALIEN_BUILDER0_UPG ) ) + { + haveBuilder = qtrue; + break; + } + } + // aliens now know they have no eggs, but they're screwed, so stfu + if( !haveBuilder || G_TimeTilSuddenDeath( ) <= 0 ) + level.overmindMuted = qtrue; + } + + //overmind dying + if( self->health < ( OVERMIND_HEALTH / 10.0f ) && level.time > self->overmindDyingTimer ) + { + self->overmindDyingTimer = level.time + OVERMIND_DYING_PERIOD; + G_BroadcastEvent( EV_OVERMIND_DYING, 0 ); + } + + //overmind under attack + if( self->health < self->lastHealth && level.time > self->overmindAttackTimer ) + { + self->overmindAttackTimer = level.time + OVERMIND_ATTACK_PERIOD; + G_BroadcastEvent( EV_OVERMIND_ATTACK, 0 ); + } + + self->lastHealth = self->health; + } + else + self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD; + + G_CreepSlow( self ); + + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; +} + + + + + +//================================================================================== + + + + + +/* +================ +ABarricade_Pain + +Barricade pain animation depends on shrunk state +================ +*/ +void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + if( self->health <= 0 ) + return; + + if( !self->shrunkTime ) + G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); + else + G_SetBuildableAnim( self, BANIM_PAIN2, qfalse ); +} + +/* +================ +ABarricade_Shrink + +Set shrink state for a barricade. When unshrinking, checks to make sure there +is enough room. +================ +*/ +void ABarricade_Shrink( gentity_t *self, qboolean shrink ) +{ + if ( !self->spawned || self->health <= 0 ) + shrink = qtrue; + if ( shrink && self->shrunkTime ) + { + int anim; + + // We need to make sure that the animation has been set to shrunk mode + // because we start out shrunk but with the construct animation when built + self->shrunkTime = level.time; + anim = self->s.torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); + if ( self->spawned && self->health > 0 && anim != BANIM_DESTROYED ) + { + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue ); + } + return; + } + + if ( !shrink && ( !self->shrunkTime || + level.time < self->shrunkTime + BARRICADE_SHRINKTIMEOUT ) ) + return; + + BG_BuildableBoundingBox( BA_A_BARRICADE, self->r.mins, self->r.maxs ); + + if ( shrink ) + { + self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); + self->shrunkTime = level.time; + + // shrink animation, the destroy animation is used + if ( self->spawned && self->health > 0 ) + { + G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + } + } + else + { + trace_t tr; + int anim; + + trap_Trace( &tr, self->s.origin, self->r.mins, self->r.maxs, + self->s.origin, self->s.number, MASK_PLAYERSOLID ); + if ( tr.startsolid || tr.fraction < 1.0f ) + { + self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); + return; + } + self->shrunkTime = 0; + + // unshrink animation, IDLE2 has been hijacked for this + anim = self->s.legsAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); + if ( self->spawned && self->health > 0 && + anim != BANIM_CONSTRUCT1 && anim != BANIM_CONSTRUCT2 ) + { + G_SetIdleBuildableAnim( self, BG_Buildable( BA_A_BARRICADE, NULL )->idleAnim ); + G_SetBuildableAnim( self, BANIM_ATTACK2, qtrue ); + } + } + + // a change in size requires a relink + if ( self->spawned ) + trap_LinkEntity( self ); +} + +/* +================ +ABarricade_Die + +Called when an alien barricade dies +================ +*/ +void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ + AGeneric_Die( self, inflictor, attacker, damage, mod ); + ABarricade_Shrink( self, qtrue ); +} + +/* +================ +ABarricade_Think + +Think function for Alien Barricade +================ +*/ +void ABarricade_Think( gentity_t *self ) +{ + AGeneric_Think( self ); + + // Shrink if unpowered + ABarricade_Shrink( self, !self->powered ); +} + +/* +================ +ABarricade_Touch + +Barricades shrink when they are come into contact with an Alien that can +pass through +================ +*/ + +void ABarricade_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + gclient_t *client = other->client; + int client_z, min_z; + + if( !client || client->pers.teamSelection != TEAM_ALIENS ) + return; + + // Client must be high enough to pass over. Note that STEPSIZE (18) is + // hardcoded here because we don't include bg_local.h! + client_z = other->s.origin[ 2 ] + other->r.mins[ 2 ]; + min_z = self->s.origin[ 2 ] - 18 + + (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); + if( client_z < min_z ) + return; + ABarricade_Shrink( self, qtrue ); +} + +//================================================================================== + + + + +/* +================ +AAcidTube_Think + +Think function for Alien Acid Tube +================ +*/ +void AAcidTube_Think( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE }; + vec3_t mins, maxs; + int i, num; + gentity_t *enemy; + + AGeneric_Think( self ); + + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + // attack nearby humans + if( self->spawned && self->health > 0 && self->powered ) + { + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + enemy = &g_entities[ entityList[ i ] ]; + + if( enemy->flags & FL_NOTARGET ) + continue; + + if( !G_Visible( self, enemy, CONTENTS_SOLID ) ) + continue; + + if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + // start the attack animation + if( level.time >= self->timestamp + ACIDTUBE_REPEAT_ANIM ) + { + self->timestamp = level.time; + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) ); + } + + G_SelectiveRadiusDamage( self->s.pos.trBase, self, ACIDTUBE_DAMAGE, + ACIDTUBE_RANGE, self, MOD_ATUBE, TEAM_ALIENS ); + self->nextthink = level.time + ACIDTUBE_REPEAT; + return; + } + } + } +} + + + + +//================================================================================== + +/* +================ +AHive_CheckTarget + +Returns true and fires the hive missile if the target is valid +================ +*/ +static qboolean AHive_CheckTarget( gentity_t *self, gentity_t *enemy ) +{ + trace_t trace; + vec3_t tip_origin, dirToTarget; + + // Check if this is a valid target + if( enemy->health <= 0 || !enemy->client || + enemy->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return qfalse; + + if( enemy->flags & FL_NOTARGET ) + return qfalse; + + // Check if the tip of the hive can see the target + VectorMA( self->s.pos.trBase, self->r.maxs[ 2 ], self->s.origin2, + tip_origin ); + if( Distance( tip_origin, enemy->s.origin ) > HIVE_SENSE_RANGE ) + return qfalse; + + trap_Trace( &trace, tip_origin, NULL, NULL, enemy->s.pos.trBase, + self->s.number, MASK_SHOT ); + if( trace.fraction < 1.0f && trace.entityNum != enemy->s.number ) + return qfalse; + + self->active = qtrue; + self->target_ent = enemy; + self->timestamp = level.time + HIVE_REPEAT; + + VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); + VectorNormalize( dirToTarget ); + vectoangles( dirToTarget, self->turretAim ); + + // Fire at target + FireWeapon( self ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + return qtrue; +} + +/* +================ +AHive_Think + +Think function for Alien Hive +================ +*/ +void AHive_Think( gentity_t *self ) +{ + int start; + + AGeneric_Think( self ); + + // Hive missile hasn't returned in HIVE_REPEAT seconds, forget about it + if( self->timestamp < level.time ) + self->active = qfalse; + + // Find a target to attack + if( self->spawned && !self->active && self->powered ) + { + int i, num, entityList[ MAX_GENTITIES ]; + vec3_t mins, maxs, + range = { HIVE_SENSE_RANGE, HIVE_SENSE_RANGE, HIVE_SENSE_RANGE }; + + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + if( num == 0 ) + return; + + start = rand( ) / ( RAND_MAX / num + 1 ); + for( i = start; i < num + start; i++ ) + { + if( AHive_CheckTarget( self, g_entities + entityList[ i % num ] ) ) + return; + } + } +} + +/* +================ +AHive_Pain + +pain function for Alien Hive +================ +*/ +void AHive_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + if( self->spawned && self->powered && !self->active ) + AHive_CheckTarget( self, attacker ); + + G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); +} + + +//================================================================================== + + +/* +================ +ABooster_Touch + +Called when an alien touches a booster +================ +*/ +void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + gclient_t *client = other->client; + + if( !self->spawned || !self->powered || self->health <= 0 ) + return; + + if( !client ) + return; + + if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + return; + + if( other->flags & FL_NOTARGET ) + return; // notarget cancels even beneficial effects? + + client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; + client->boostedTime = level.time; + + // give the rant bomb + if( client->lastRantBombTime + LEVEL4_BOMB_REGEN <= level.time ) + { + client->lastRantBombTime = level.time; + client->ps.ammo = 1; + } +} + + + + +//================================================================================== + +#define TRAPPER_ACCURACY 10 // lower is better + +/* +================ +ATrapper_FireOnEnemy + +Used by ATrapper_Think to fire at enemy +================ +*/ +void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range ) +{ + gentity_t *enemy = self->enemy; + vec3_t dirToTarget; + vec3_t halfAcceleration, thirdJerk; + float distanceToTarget = BG_Buildable( self->s.modelindex, NULL )->turretRange; + int lowMsec = 0; + int highMsec = (int)( ( + ( ( distanceToTarget * LOCKBLOB_SPEED ) + + ( distanceToTarget * BG_Class( enemy->client->ps.stats[ STAT_CLASS ] )->speed ) ) / + ( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f ); + + VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration ); + VectorScale( enemy->jerk, 1.0f / 3.0f, thirdJerk ); + + // highMsec and lowMsec can only move toward + // one another, so the loop must terminate + while( highMsec - lowMsec > TRAPPER_ACCURACY ) + { + int partitionMsec = ( highMsec + lowMsec ) / 2; + float time = (float)partitionMsec / 1000.0f; + float projectileDistance = LOCKBLOB_SPEED * time; + + VectorMA( enemy->s.pos.trBase, time, enemy->s.pos.trDelta, dirToTarget ); + VectorMA( dirToTarget, time * time, halfAcceleration, dirToTarget ); + VectorMA( dirToTarget, time * time * time, thirdJerk, dirToTarget ); + VectorSubtract( dirToTarget, self->s.pos.trBase, dirToTarget ); + distanceToTarget = VectorLength( dirToTarget ); + + if( projectileDistance < distanceToTarget ) + lowMsec = partitionMsec; + else if( projectileDistance > distanceToTarget ) + highMsec = partitionMsec; + else if( projectileDistance == distanceToTarget ) + break; // unlikely to happen + } + + VectorNormalize( dirToTarget ); + vectoangles( dirToTarget, self->turretAim ); + + //fire at target + FireWeapon( self ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + self->count = level.time + firespeed; +} + +/* +================ +ATrapper_CheckTarget + +Used by ATrapper_Think to check enemies for validity +================ +*/ +qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range ) +{ + vec3_t distance; + trace_t trace; + + if( !target ) // Do we have a target? + return qfalse; + if( !target->inuse ) // Does the target still exist? + return qfalse; + if( target == self ) // is the target us? + return qfalse; + if( !target->client ) // is the target a bot or player? + return qfalse; + if( target->flags & FL_NOTARGET ) // is the target cheating? + return qfalse; + if( target->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) // one of us? + return qfalse; + if( target->client->sess.spectatorState != SPECTATOR_NOT ) // is the target alive? + return qfalse; + if( target->health <= 0 ) // is the target still alive? + return qfalse; + if( target->client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) // locked? + return qfalse; + + VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, distance ); + if( VectorLength( distance ) > range ) // is the target within range? + return qfalse; + + //only allow a narrow field of "vision" + VectorNormalize( distance ); //is now direction of target + if( DotProduct( distance, self->s.origin2 ) < LOCKBLOB_DOT ) + return qfalse; + + trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT ); + if ( trace.contents & CONTENTS_SOLID ) // can we see the target? + return qfalse; + + return qtrue; +} + +/* +================ +ATrapper_FindEnemy + +Used by ATrapper_Think to locate enemy gentities +================ +*/ +void ATrapper_FindEnemy( gentity_t *ent, int range ) +{ + gentity_t *target; + int i; + int start; + + // iterate through entities + // note that if we exist then level.num_entities != 0 + start = rand( ) / ( RAND_MAX / level.num_entities + 1 ); + for( i = start; i < level.num_entities + start; i++ ) + { + target = g_entities + ( i % level.num_entities ); + //if target is not valid keep searching + if( !ATrapper_CheckTarget( ent, target, range ) ) + continue; + + //we found a target + ent->enemy = target; + return; + } + + //couldn't find a target + ent->enemy = NULL; +} + +/* +================ +ATrapper_Think + +think function for Alien Defense +================ +*/ +void ATrapper_Think( gentity_t *self ) +{ + int range = BG_Buildable( self->s.modelindex, NULL )->turretRange; + int firespeed = BG_Buildable( self->s.modelindex, NULL )->turretFireSpeed; + + AGeneric_Think( self ); + + if( self->spawned && self->powered ) + { + //if the current target is not valid find a new one + if( !ATrapper_CheckTarget( self, self->enemy, range ) ) + ATrapper_FindEnemy( self, range ); + + //if a new target cannot be found don't do anything + if( !self->enemy ) + return; + + //if we are pointing at our target and we can fire shoot it + if( self->count < level.time ) + ATrapper_FireOnEnemy( self, firespeed, range ); + } +} + + + + +//================================================================================== + + + + +/* +================ +G_SuicideIfNoPower + +Destroy human structures that have been unpowered too long +================ +*/ +static qboolean G_SuicideIfNoPower( gentity_t *self ) +{ + if( self->buildableTeam != TEAM_HUMANS ) + return qfalse; + + if( !self->powered ) + { + // if the power hasn't reached this buildable for some time, then destroy the buildable + if( self->count == 0 ) + self->count = level.time; + else if( ( level.time - self->count ) >= HUMAN_BUILDABLE_INACTIVE_TIME ) + { + if( self->parentNode ) + G_Damage( self, NULL, g_entities + self->parentNode->killedBy, + NULL, NULL, self->health, 0, MOD_NOCREEP ); + else + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP ); + return qtrue; + } + } + else + self->count = 0; + + return qfalse; +} + +/* +================ +G_IdlePowerState + +Set buildable idle animation to match power state +================ +*/ +static void G_IdlePowerState( gentity_t *self ) +{ + if( self->powered ) + { + if( self->s.torsoAnim == BANIM_IDLE3 ) + G_SetIdleBuildableAnim( self, BG_Buildable( self->s.modelindex, NULL )->idleAnim ); + } + else + { + if( self->s.torsoAnim != BANIM_IDLE3 ) + G_SetIdleBuildableAnim( self, BANIM_IDLE3 ); + } +} + + + + +//================================================================================== + + + + +/* +================ +HSpawn_Disappear + +Called when a human spawn is destroyed before it is spawned +think function +================ +*/ +void HSpawn_Disappear( gentity_t *self ) +{ + self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed + self->timestamp = level.time; + G_QueueBuildPoints( self ); + G_RewardAttackers( self ); + + G_FreeEntity( self ); +} + + +/* +================ +HSpawn_blast + +Called when a human spawn explodes +think function +================ +*/ +void HSpawn_Blast( gentity_t *self ) +{ + vec3_t dir; + + // we don't have a valid direction, so just point straight up + dir[ 0 ] = dir[ 1 ] = 0; + dir[ 2 ] = 1; + + self->timestamp = level.time; + + //do some radius damage + G_RadiusDamage( self->s.pos.trBase, g_entities + self->killedBy, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath ); + + // begin freeing build points + G_QueueBuildPoints( self ); + G_RewardAttackers( self ); + // turn into an explosion + self->s.eType = ET_EVENTS + EV_HUMAN_BUILDABLE_EXPLOSION; + self->freeAfterEvent = qtrue; + G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); +} + + +/* +================ +HSpawn_die + +Called when a human spawn dies +================ +*/ +void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + + self->die = nullDieFunction; + self->killedBy = attacker - g_entities; + self->powered = qfalse; //free up power + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + + if( self->spawned ) + { + self->think = HSpawn_Blast; + self->nextthink = level.time + HUMAN_DETONATION_DELAY; + } + else + { + self->think = HSpawn_Disappear; + self->nextthink = level.time; //blast immediately + } + + G_LogDestruction( self, attacker, mod ); +} + +/* +================ +HSpawn_Think + +Think for human spawn +================ +*/ +void HSpawn_Think( gentity_t *self ) +{ + gentity_t *ent; + + // set parentNode + self->powered = G_FindPower( self, qfalse ); + + if( G_SuicideIfNoPower( self ) ) + return; + + if( self->spawned ) + { + //only suicide if at rest + if( self->s.groundEntityNum ) + { + if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, + self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL ) + { + // If the thing blocking the spawn is a buildable, kill it. + // If it's part of the map, kill self. + if( ent->s.eType == ET_BUILDABLE ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); + G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); + } + else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) + { + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); + return; + } + + if( ent->s.eType == ET_CORPSE ) + G_FreeEntity( ent ); //quietly remove + } + } + } + + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; +} + + + + +//================================================================================== + + + + +/* +================ +HRepeater_Die + +Called when a repeater dies +================ +*/ +static void HRepeater_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + + self->die = nullDieFunction; + self->killedBy = attacker - g_entities; + self->powered = qfalse; //free up power + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + + if( self->spawned ) + { + self->think = HSpawn_Blast; + self->nextthink = level.time + HUMAN_DETONATION_DELAY; + } + else + { + self->think = HSpawn_Disappear; + self->nextthink = level.time; //blast immediately + } + + G_LogDestruction( self, attacker, mod ); + + if( self->usesBuildPointZone ) + { + buildPointZone_t *zone = &level.buildPointZones[self->buildPointZone]; + + zone->active = qfalse; + self->usesBuildPointZone = qfalse; + } +} + +/* +================ +HRepeater_Think + +Think for human power repeater +================ +*/ +void HRepeater_Think( gentity_t *self ) +{ + int i; + gentity_t *powerEnt; + buildPointZone_t *zone; + + self->powered = G_FindPower( self, qfalse ); + + powerEnt = G_InPowerZone( self ); + if( powerEnt != NULL ) + { + // If the repeater is inside of another power zone then suicide + // Attribute death to whoever built the reactor if that's a human, + // which will ensure that it does not queue the BP + if( powerEnt->builtBy >= 0 ) + G_Damage( self, NULL, g_entities + powerEnt->builtBy, NULL, NULL, self->health, 0, MOD_SUICIDE ); + else + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); + return; + } + + G_IdlePowerState( self ); + + // Initialise the zone once the repeater has spawned + if( self->spawned && ( !self->usesBuildPointZone || !level.buildPointZones[ self->buildPointZone ].active ) ) + { + // See if a free zone exists + for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) + { + zone = &level.buildPointZones[ i ]; + + if( !zone->active ) + { + // Initialise the BP queue with no BP queued + zone->queuedBuildPoints = 0; + zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; + zone->nextQueueTime = level.time; + zone->active = qtrue; + + self->buildPointZone = zone - level.buildPointZones; + self->usesBuildPointZone = qtrue; + + break; + } + } + } + + self->nextthink = level.time + POWER_REFRESH_TIME; +} + +/* +================ +HRepeater_Use + +Use for human power repeater +================ +*/ +void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->health <= 0 || !self->spawned ) + return; + + if( other && other->client ) + G_GiveClientMaxAmmo( other, qtrue ); +} + +/* +================ +HReactor_Think + +Think function for Human Reactor +================ +*/ +void HReactor_Think( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range = { REACTOR_ATTACK_RANGE, + REACTOR_ATTACK_RANGE, + REACTOR_ATTACK_RANGE }; + vec3_t dccrange = { REACTOR_ATTACK_DCC_RANGE, + REACTOR_ATTACK_DCC_RANGE, + REACTOR_ATTACK_DCC_RANGE }; + vec3_t mins, maxs; + int i, num; + gentity_t *enemy, *tent; + + if( self->dcc ) + { + VectorAdd( self->s.origin, dccrange, maxs ); + VectorSubtract( self->s.origin, dccrange, mins ); + } + else + { + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + } + + if( self->spawned && ( self->health > 0 ) ) + { + qboolean fired = qfalse; + + // Creates a tesla trail for every target + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + enemy = &g_entities[ entityList[ i ] ]; + if( !enemy->client || + enemy->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + continue; + if( enemy->flags & FL_NOTARGET ) + continue; + + tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); + tent->s.generic1 = self->s.number; //src + tent->s.clientNum = enemy->s.number; //dest + VectorCopy( self->s.pos.trBase, tent->s.origin2 ); + fired = qtrue; + } + + // Actual damage is done by radius + if( fired ) + { + self->timestamp = level.time; + if( self->dcc ) + G_SelectiveRadiusDamage( self->s.pos.trBase, self, + REACTOR_ATTACK_DCC_DAMAGE, + REACTOR_ATTACK_DCC_RANGE, self, + MOD_REACTOR, TEAM_HUMANS ); + else + G_SelectiveRadiusDamage( self->s.pos.trBase, self, + REACTOR_ATTACK_DAMAGE, + REACTOR_ATTACK_RANGE, self, + MOD_REACTOR, TEAM_HUMANS ); + } + } + + if( self->dcc ) + self->nextthink = level.time + REACTOR_ATTACK_DCC_REPEAT; + else + self->nextthink = level.time + REACTOR_ATTACK_REPEAT; +} + +//================================================================================== + + + +/* +================ +HArmoury_Activate + +Called when a human activates an Armoury +================ +*/ +void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->spawned ) + { + //only humans can activate this + if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return; + + //if this is powered then call the armoury menu + if( self->powered ) + G_TriggerMenu( activator->client->ps.clientNum, MN_H_ARMOURY ); + else + G_TriggerMenu( activator->client->ps.clientNum, MN_H_NOTPOWERED ); + } +} + +/* +================ +HArmoury_Think + +Think for armoury +================ +*/ +void HArmoury_Think( gentity_t *self ) +{ + //make sure we have power + self->nextthink = level.time + POWER_REFRESH_TIME; + + self->powered = G_FindPower( self, qfalse ); + + G_SuicideIfNoPower( self ); +} + + + + +//================================================================================== + + + + + +/* +================ +HDCC_Think + +Think for dcc +================ +*/ +void HDCC_Think( gentity_t *self ) +{ + //make sure we have power + self->nextthink = level.time + POWER_REFRESH_TIME; + + self->powered = G_FindPower( self, qfalse ); + + G_SuicideIfNoPower( self ); +} + + + + +//================================================================================== + + + + +/* +================ +HMedistat_Die + +Die function for Human Medistation +================ +*/ +void HMedistat_Die( gentity_t *self, gentity_t *inflictor, + gentity_t *attacker, int damage, int mod ) +{ + //clear target's healing flag + if( self->enemy && self->enemy->client ) + self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; + + HSpawn_Die( self, inflictor, attacker, damage, mod ); +} + +/* +================ +HMedistat_Think + +think function for Human Medistation +================ +*/ +void HMedistat_Think( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t mins, maxs; + int i, num; + gentity_t *player; + qboolean occupied = qfalse; + + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; + + self->powered = G_FindPower( self, qfalse ); + if( G_SuicideIfNoPower( self ) ) + return; + G_IdlePowerState( self ); + + //clear target's healing flag + if( self->enemy && self->enemy->client ) + self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; + + //make sure we have power + if( !self->powered ) + { + if( self->active ) + { + self->active = qfalse; + self->enemy = NULL; + } + + self->nextthink = level.time + POWER_REFRESH_TIME; + return; + } + + if( self->spawned ) + { + VectorAdd( self->s.origin, self->r.maxs, maxs ); + VectorAdd( self->s.origin, self->r.mins, mins ); + + mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ]; + maxs[ 2 ] += 60; //player height + + //if active use the healing idle + if( self->active ) + G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); + + //check if a previous occupier is still here + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + player = &g_entities[ entityList[ i ] ]; + + if( player->flags & FL_NOTARGET ) + continue; // notarget cancels even beneficial effects? + + //remove poison from everyone, not just the healed player + if( player->client && player->client->ps.stats[ STAT_STATE ] & SS_POISONED ) + player->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + + if( self->enemy == player && player->client && + ( (player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) && // DIFF NOTE: remove this change from diffs ASAP + player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && + PM_Live( player->client->ps.pm_type ) ) ) + { + occupied = qtrue; + player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; + } + } + + if( !occupied ) + { + self->enemy = NULL; + + //look for something to heal + for( i = 0; i < num; i++ ) + { + player = &g_entities[ entityList[ i ] ]; + + if( player->flags & FL_NOTARGET ) + continue; // notarget cancels even beneficial effects? + + if( player->client && player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + if( ( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] || + player->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) && + PM_Live( player->client->ps.pm_type ) ) + { + self->enemy = player; + + //start the heal anim + if( !self->active ) + { + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + self->active = qtrue; + player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; + } + } + else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) + BG_AddUpgradeToInventory( UP_MEDKIT, player->client->ps.stats ); + } + } + } + + //nothing left to heal so go back to idling + if( !self->enemy && self->active ) + { + G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); + + self->active = qfalse; + } + else if( self->enemy && self->enemy->client ) //heal! + { + if( self->enemy->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) + self->enemy->client->ps.stats[ STAT_STAMINA ] += STAMINA_MEDISTAT_RESTORE; + + if( self->enemy->client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) + self->enemy->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + + self->enemy->health++; + + //if they're completely healed, give them a medkit + if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] ) + { + self->enemy->health = self->enemy->client->ps.stats[ STAT_MAX_HEALTH ]; + if( !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) ) + BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats ); + } + } + } +} + + + + +//================================================================================== + + + + +/* +================ +HMGTurret_CheckTarget + +Used by HMGTurret_Think to check enemies for validity +================ +*/ +qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, + qboolean los_check ) +{ + trace_t tr; + vec3_t dir, end; + + if( !target || target->health <= 0 || !target->client || + target->client->pers.teamSelection != TEAM_ALIENS ) + return qfalse; + + if( target->flags & FL_NOTARGET ) + return qfalse; + + if( !los_check ) + return qtrue; + + // Accept target if we can line-trace to it + VectorSubtract( target->s.pos.trBase, self->s.pos.trBase, dir ); + VectorNormalize( dir ); + VectorMA( self->s.pos.trBase, MGTURRET_RANGE, dir, end ); + trap_Trace( &tr, self->s.pos.trBase, NULL, NULL, end, + self->s.number, MASK_SHOT ); + return tr.entityNum == target - g_entities; +} + + +/* +================ +HMGTurret_TrackEnemy + +Used by HMGTurret_Think to track enemy location +================ +*/ +qboolean HMGTurret_TrackEnemy( gentity_t *self ) +{ + vec3_t dirToTarget, dttAdjusted, angleToTarget, angularDiff, xNormal; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + float temp, rotAngle; + + VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); + VectorNormalize( dirToTarget ); + + CrossProduct( self->s.origin2, refNormal, xNormal ); + VectorNormalize( xNormal ); + rotAngle = RAD2DEG( acos( DotProduct( self->s.origin2, refNormal ) ) ); + RotatePointAroundVector( dttAdjusted, xNormal, dirToTarget, rotAngle ); + + vectoangles( dttAdjusted, angleToTarget ); + + angularDiff[ PITCH ] = AngleSubtract( self->s.angles2[ PITCH ], angleToTarget[ PITCH ] ); + angularDiff[ YAW ] = AngleSubtract( self->s.angles2[ YAW ], angleToTarget[ YAW ] ); + + //if not pointing at our target then move accordingly + if( angularDiff[ PITCH ] < 0 && angularDiff[ PITCH ] < (-MGTURRET_ANGULARSPEED) ) + self->s.angles2[ PITCH ] += MGTURRET_ANGULARSPEED; + else if( angularDiff[ PITCH ] > 0 && angularDiff[ PITCH ] > MGTURRET_ANGULARSPEED ) + self->s.angles2[ PITCH ] -= MGTURRET_ANGULARSPEED; + else + self->s.angles2[ PITCH ] = angleToTarget[ PITCH ]; + + //disallow vertical movement past a certain limit + temp = fabs( self->s.angles2[ PITCH ] ); + if( temp > 180 ) + temp -= 360; + + if( temp < -MGTURRET_VERTICALCAP ) + self->s.angles2[ PITCH ] = (-360) + MGTURRET_VERTICALCAP; + + //if not pointing at our target then move accordingly + if( angularDiff[ YAW ] < 0 && angularDiff[ YAW ] < ( -MGTURRET_ANGULARSPEED ) ) + self->s.angles2[ YAW ] += MGTURRET_ANGULARSPEED; + else if( angularDiff[ YAW ] > 0 && angularDiff[ YAW ] > MGTURRET_ANGULARSPEED ) + self->s.angles2[ YAW ] -= MGTURRET_ANGULARSPEED; + else + self->s.angles2[ YAW ] = angleToTarget[ YAW ]; + + AngleVectors( self->s.angles2, dttAdjusted, NULL, NULL ); + RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle ); + vectoangles( dirToTarget, self->turretAim ); + + //fire if target is within accuracy + return ( abs( angularDiff[ YAW ] ) - MGTURRET_ANGULARSPEED <= + MGTURRET_ACCURACY_TO_FIRE ) && + ( abs( angularDiff[ PITCH ] ) - MGTURRET_ANGULARSPEED <= + MGTURRET_ACCURACY_TO_FIRE ); +} + + +/* +================ +HMGTurret_FindEnemy + +Used by HMGTurret_Think to locate enemy gentities +================ +*/ +void HMGTurret_FindEnemy( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *target; + int start; + + if( self->enemy ) + self->enemy->targeted = NULL; + + self->enemy = NULL; + + // Look for targets in a box around the turret + VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE ); + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + if( num == 0 ) + return; + + start = rand( ) / ( RAND_MAX / num + 1 ); + for( i = start; i < num + start ; i++ ) + { + target = &g_entities[ entityList[ i % num ] ]; + if( !HMGTurret_CheckTarget( self, target, qtrue ) ) + continue; + + self->enemy = target; + self->enemy->targeted = self; + return; + } +} + +/* +================ +HMGTurret_State + +Raise or lower MG turret towards desired state +================ +*/ +enum { + MGT_STATE_INACTIVE, + MGT_STATE_DROP, + MGT_STATE_RISE, + MGT_STATE_ACTIVE +}; + +static qboolean HMGTurret_State( gentity_t *self, int state ) +{ + float angle; + + if( self->waterlevel == state ) + return qfalse; + + angle = AngleNormalize180( self->s.angles2[ PITCH ] ); + + if( state == MGT_STATE_INACTIVE ) + { + if( angle < MGTURRET_VERTICALCAP ) + { + if( self->waterlevel != MGT_STATE_DROP ) + { + self->speed = 0.25f; + self->waterlevel = MGT_STATE_DROP; + } + else + self->speed *= 1.25f; + + self->s.angles2[ PITCH ] = + MIN( MGTURRET_VERTICALCAP, angle + self->speed ); + return qtrue; + } + else + self->waterlevel = MGT_STATE_INACTIVE; + } + else if( state == MGT_STATE_ACTIVE ) + { + if( !self->enemy && angle > 0.0f ) + { + self->waterlevel = MGT_STATE_RISE; + self->s.angles2[ PITCH ] = + MAX( 0.0f, angle - MGTURRET_ANGULARSPEED * 0.5f ); + } + else + self->waterlevel = MGT_STATE_ACTIVE; + } + + return qfalse; +} + +/* +================ +HMGTurret_Think + +Think function for MG turret +================ +*/ +void HMGTurret_Think( gentity_t *self ) +{ + self->nextthink = level.time + + BG_Buildable( self->s.modelindex, NULL )->nextthink; + + // Turn off client side muzzle flashes + self->s.eFlags &= ~EF_FIRING; + + self->powered = G_FindPower( self, qfalse ); + if( G_SuicideIfNoPower( self ) ) + return; + G_IdlePowerState( self ); + + // If not powered or spawned don't do anything + if( !self->powered ) + { + // if power loss drop turret + if( self->spawned && + HMGTurret_State( self, MGT_STATE_INACTIVE ) ) + return; + + self->nextthink = level.time + POWER_REFRESH_TIME; + return; + } + if( !self->spawned ) + return; + + // If the current target is not valid find a new enemy + if( !HMGTurret_CheckTarget( self, self->enemy, qtrue ) ) + { + self->active = qfalse; + self->turretSpinupTime = -1; + HMGTurret_FindEnemy( self ); + } + // if newly powered raise turret + HMGTurret_State( self, MGT_STATE_ACTIVE ); + if( !self->enemy ) + return; + + // Track until we can hit the target + if( !HMGTurret_TrackEnemy( self ) ) + { + self->active = qfalse; + self->turretSpinupTime = -1; + return; + } + + // Update spin state + if( !self->active && self->timestamp < level.time ) + { + self->active = qtrue; + + self->turretSpinupTime = level.time + MGTURRET_SPINUP_TIME; + G_AddEvent( self, EV_MGTURRET_SPINUP, 0 ); + } + + // Not firing or haven't spun up yet + if( !self->active || self->turretSpinupTime > level.time ) + return; + + // Fire repeat delay + if( self->timestamp > level.time ) + return; + + FireWeapon( self ); + self->s.eFlags |= EF_FIRING; + self->timestamp = level.time + BG_Buildable( self->s.modelindex, NULL )->turretFireSpeed; + G_AddEvent( self, EV_FIRE_WEAPON, 0 ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); +} + + + + +//================================================================================== + + + + +/* +================ +HTeslaGen_Think + +Think function for Tesla Generator +================ +*/ +void HTeslaGen_Think( gentity_t *self ) +{ + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; + + self->powered = G_FindPower( self, qfalse ); + if( G_SuicideIfNoPower( self ) ) + return; + G_IdlePowerState( self ); + + //if not powered don't do anything and check again for power next think + if( !self->powered ) + { + self->s.eFlags &= ~EF_FIRING; + self->nextthink = level.time + POWER_REFRESH_TIME; + return; + } + + if( self->spawned && self->timestamp < level.time ) + { + vec3_t origin, range, mins, maxs; + int entityList[ MAX_GENTITIES ], i, num; + + // Communicates firing state to client + self->s.eFlags &= ~EF_FIRING; + + // Move the muzzle from the entity origin up a bit to fire over turrets + VectorMA( self->s.origin, self->r.maxs[ 2 ], self->s.origin2, origin ); + + VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE ); + VectorAdd( origin, range, maxs ); + VectorSubtract( origin, range, mins ); + + // Attack nearby Aliens + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + self->enemy = &g_entities[ entityList[ i ] ]; + + if( self->enemy->flags & FL_NOTARGET ) + continue; + + if( self->enemy->client && self->enemy->health > 0 && + self->enemy->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && + Distance( origin, self->enemy->s.pos.trBase ) <= TESLAGEN_RANGE ) + FireWeapon( self ); + } + self->enemy = NULL; + + if( self->s.eFlags & EF_FIRING ) + { + G_AddEvent( self, EV_FIRE_WEAPON, 0 ); + + //doesn't really need an anim + //G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + + self->timestamp = level.time + TESLAGEN_REPEAT; + } + } +} + +//================================================================================== +// CUBOID FUNCTIONS +// generic functions for all types of cuboids +//================================================================================== + +void Cuboid_Think(gentity_t *self) +{ +} + +//Cuboids need a new die function because of the cuboid explosion effects. +void Cuboid_Die(gentity_t *self,gentity_t *inflictor,gentity_t *attacker,int damage,int mod) +{ + vec3_t dir; + qboolean event=qfalse; + const cuboidAttributes_t *cuboid; + + cuboid=BG_CuboidAttributes(self->s.modelindex); + G_SetBuildableAnim(self,BANIM_DESTROY1,qtrue); // just for sound + self->die=nullDieFunction; + self->killedBy=attacker-g_entities; + self->powered=qfalse; + self->s.eFlags&=~EF_FIRING; + G_LogDestruction(self,attacker,mod); + dir[0]=dir[1]=0; + dir[2]=1; + self->timestamp = level.time; + G_QueueBuildPoints(self); + if(mod!=MOD_DECONSTRUCT&&self->spawned) + { + G_RewardAttackers(self); + G_RadiusDamage(self->s.pos.trBase,g_entities+self->killedBy,self->splashDamage,self->splashRadius,self,self->splashMethodOfDeath); + //NOTE: all cuboid info is already packed + self->s.eType=ET_EVENTS+EV_CUBOID_EXPLOSION; + self->freeAfterEvent = qtrue; + G_AddEvent(self,EV_HUMAN_BUILDABLE_EXPLOSION,DirToByte(dir)); + event=qtrue; + self->r.contents=0; + trap_LinkEntity(self); + } + else + { + self->s.eType=0; + G_FreeEntity(self); + } +} + + +//================================================================================== + + + + +/* +============ +G_QueueValue +============ +*/ + +static int G_QueueValue( gentity_t *self ) +{ + int i; + int damageTotal = 0; + int queuePoints; + double queueFraction = 0; + + for( i = 0; i < level.maxclients; i++ ) + { + gentity_t *player = g_entities + i; + + damageTotal += self->credits[ i ]; + + if( self->buildableTeam != player->client->pers.teamSelection ) + queueFraction += (double) self->credits[ i ]; + } + + if( damageTotal > 0 ) + queueFraction = queueFraction / (double) damageTotal; + else // all damage was done by nonclients, so queue everything + queueFraction = 1.0; + + queuePoints = (int) ( queueFraction * (double) BG_Buildable( self->s.modelindex, self->cuboidSize )->buildPoints ); + return queuePoints; +} + +/* +============ +G_QueueBuildPoints +============ +*/ +void G_QueueBuildPoints( gentity_t *self ) +{ + gentity_t *powerEntity; + int queuePoints; + + queuePoints = G_QueueValue( self ); + + if( !queuePoints ) + return; + + switch( self->buildableTeam ) + { + default: + case TEAM_NONE: + return; + + case TEAM_ALIENS: + if( !level.alienBuildPointQueue ) + level.alienNextQueueTime = level.time + g_alienBuildQueueTime.integer; + + level.alienBuildPointQueue += queuePoints; + break; + + case TEAM_HUMANS: + powerEntity = G_PowerEntityForEntity( self ); + + if( powerEntity ) + { + int nqt; + switch( powerEntity->s.modelindex ) + { + case BA_H_REACTOR: + nqt = G_NextQueueTime( level.humanBuildPointQueue, + g_humanBuildPoints.integer, + g_humanBuildQueueTime.integer ); + if( !level.humanBuildPointQueue || + level.time + nqt < level.humanNextQueueTime ) + level.humanNextQueueTime = level.time + nqt; + + level.humanBuildPointQueue += queuePoints; + break; + + case BA_H_REPEATER: + if( powerEntity->usesBuildPointZone && + level.buildPointZones[ powerEntity->buildPointZone ].active ) + { + buildPointZone_t *zone = &level.buildPointZones[ powerEntity->buildPointZone ]; + + nqt = G_NextQueueTime( zone->queuedBuildPoints, + zone->totalBuildPoints, + g_humanRepeaterBuildQueueTime.integer ); + + if( !zone->queuedBuildPoints || + level.time + nqt < zone->nextQueueTime ) + zone->nextQueueTime = level.time + nqt; + + zone->queuedBuildPoints += queuePoints; + } + break; + + default: + break; + } + } + } +} + +/* +============ +G_NextQueueTime +============ +*/ +int G_NextQueueTime( int queuedBP, int totalBP, int queueBaseRate ) +{ + float fractionQueued; + + if( totalBP == 0 ) + return 0; + + fractionQueued = queuedBP / (float)totalBP; + return ( 1.0f - fractionQueued ) * queueBaseRate; +} + +/* +============ +G_BuildableTouchTriggers + +Find all trigger entities that a buildable touches. +============ +*/ +void G_BuildableTouchTriggers( gentity_t *ent ) +{ + int i, num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + vec3_t bmins, bmaxs; + static vec3_t range = { 10, 10, 10 }; + + // dead buildables don't activate triggers! + if( ent->health <= 0 ) + return; + + BG_BuildableBoundingBox( ent->s.modelindex, bmins, bmaxs ); + + VectorAdd( ent->s.origin, bmins, mins ); + VectorAdd( ent->s.origin, bmaxs, maxs ); + + VectorSubtract( mins, range, mins ); + VectorAdd( maxs, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + VectorAdd( ent->s.origin, bmins, mins ); + VectorAdd( ent->s.origin, bmaxs, maxs ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if( !hit->touch ) + continue; + + if( !( hit->r.contents & CONTENTS_TRIGGER ) ) + continue; + + //ignore buildables not yet spawned + if( !ent->spawned ) + continue; + + if( !trap_EntityContact( mins, maxs, hit ) ) + continue; + + memset( &trace, 0, sizeof( trace ) ); + + if( hit->touch ) + hit->touch( hit, ent, &trace ); + } +} + + +/* +=============== +G_BuildableThink + +General think function for buildables +=============== +*/ +void G_BuildableThink( gentity_t *ent, int msec ) +{ + int maxHealth = BG_Buildable( ent->s.modelindex, ent->cuboidSize )->health; + int regenRate = BG_Buildable( ent->s.modelindex, ent->cuboidSize )->regenRate; + int buildTime = BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildTime; + int buildRate; + + //toggle spawned flag for buildables + if( !ent->spawned && ( ent->healthLeft<=0 || ent->health==maxHealth ) && !level.pausedTime ) + { + ent->spawned = qtrue; + if( ent->s.modelindex == BA_A_OVERMIND ) + { + G_TeamCommand( TEAM_ALIENS, "cp \"The Overmind has awakened!\"" ); + } + } + + // Timer actions + ent->time1000 += msec; + if( ent->time1000 >= 1000 ) + { + ent->time1000 -= 1000; + + if( !ent->spawned ) + { + buildRate=(int)(ceil((float)maxHealth/(float)buildTime*1e3f)); + ent->health=MIN(ent->health+buildRate,maxHealth); + ent->healthLeft=MAX(ent->healthLeft-buildRate,0); + } + else if( ent->health > 0 && ent->health < maxHealth ) + { + if( ent->buildableTeam == TEAM_ALIENS && regenRate && + ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) + { + ent->health += regenRate; + } + else if( ent->buildableTeam == TEAM_HUMANS && ent->dcc && + ( ent->lastDamageTime + HUMAN_REGEN_DAMAGE_TIME ) < level.time ) + { + ent->health += DC_HEALRATE * ent->dcc; + } + } + + if( ent->health >= maxHealth ) + { + int i; + ent->health = maxHealth; + for( i = 0; i < MAX_CLIENTS; i++ ) + ent->credits[ i ] = 0; + } + } + + if( ent->clientSpawnTime > 0 ) + ent->clientSpawnTime -= msec; + + if( ent->clientSpawnTime < 0 ) + ent->clientSpawnTime = 0; + + ent->dcc = ( ent->buildableTeam != TEAM_HUMANS ) ? 0 : G_FindDCC( ent ); + + // Set health + ent->s.generic1 = MIN(MAX(ent->health,0),999); + + // Set health for cuboids (BG_CuboidPackHealthSafe does nothing if not a cuboid) !@#CUBOID + BG_CuboidPackHealthSafe(ent->s.modelindex,&ent->s,ent->health); + + // Set flags + ent->s.eFlags &= ~( EF_B_POWERED | EF_B_SPAWNED | EF_B_MARKED ); + if( ent->powered ) + ent->s.eFlags |= EF_B_POWERED; + + if( ent->spawned ) + ent->s.eFlags |= EF_B_SPAWNED; + + if( ent->deconstruct ) + ent->s.eFlags |= EF_B_MARKED; + + // Check if this buildable is touching any triggers + G_BuildableTouchTriggers( ent ); + + // Fall back on normal physics routines + G_Physics( ent, msec ); +} + + +/* +=============== +G_BuildableRange + +Check whether a point is within some range of a type of buildable +=============== +*/ +qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *ent; + + VectorSet( range, r, r, r ); + VectorAdd( origin, range, maxs ); + VectorSubtract( origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + ent = &g_entities[ entityList[ i ] ]; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->buildableTeam == TEAM_HUMANS && !ent->powered ) + continue; + + if( ent->s.modelindex == buildable && ent->spawned ) + return qtrue; + } + + return qfalse; +} + +/* +================ +G_FindBuildable + +Finds a buildable of the specified type +================ +*/ +static gentity_t *G_FindBuildable( buildable_t buildable ) +{ + int i; + gentity_t *ent; + + for( i = MAX_CLIENTS, ent = g_entities + i; + i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->s.modelindex == buildable && !( ent->s.eFlags & EF_DEAD ) ) + return ent; + } + + return NULL; +} + +/* +=============== +G_BuildablesIntersect + +Test if two buildables intersect each other +=============== +*/ +static qboolean G_BuildablesIntersect( buildable_t a, vec3_t originA, vec3_t cuboidA, + buildable_t b, vec3_t originB, vec3_t cuboidB ) +{ + vec3_t minsA, maxsA; + vec3_t minsB, maxsB; + + if(BG_Buildable(a,NULL)->cuboid) + BG_CuboidBBox(cuboidA,minsA,maxsA); + else + BG_BuildableBoundingBox( a, minsA, maxsA ); + VectorAdd( minsA, originA, minsA ); + VectorAdd( maxsA, originA, maxsA ); + + if(BG_Buildable(b,NULL)->cuboid) + BG_CuboidBBox(cuboidB,minsB,maxsB); + else + BG_BuildableBoundingBox( b, minsB, maxsB ); + VectorAdd( minsB, originB, minsB ); + VectorAdd( maxsB, originB, maxsB ); + + return BoundsIntersect( minsA, maxsA, minsB, maxsB ); +} + +/* +=============== +G_CompareBuildablesForRemoval + +qsort comparison function for a buildable removal list +=============== +*/ +static buildable_t cmpBuildable; +static vec3_t cmpOrigin; +static vec3_t cmpCuboid; +static int G_CompareBuildablesForRemoval( const void *a, const void *b ) +{ + int precedence[ ] = + { + BA_NONE, + + BA_A_BARRICADE, + BA_A_ACIDTUBE, + BA_A_TRAPPER, + BA_A_HIVE, + BA_A_BOOSTER, + BA_A_SPAWN, + BA_A_CUBOID1, + BA_A_OVERMIND, + + BA_H_MGTURRET, + BA_H_TESLAGEN, + BA_H_DCC, + BA_H_MEDISTAT, + BA_H_ARMOURY, + BA_H_SPAWN, + BA_H_REPEATER, + BA_H_CUBOID1, + BA_H_CUBOID2, + BA_H_REACTOR + }; + + gentity_t *buildableA, *buildableB; + int i; + int aPrecedence = 0, bPrecedence = 0; + qboolean aMatches = qfalse, bMatches = qfalse; + + buildableA = *(gentity_t **)a; + buildableB = *(gentity_t **)b; + + // Prefer the one that collides with the thing we're building + aMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, cmpCuboid, + buildableA->s.modelindex, buildableA->s.origin, buildableA->cuboidSize ); + bMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, cmpCuboid, + buildableB->s.modelindex, buildableB->s.origin, buildableB->cuboidSize ); + if( aMatches && !bMatches ) + return -1; + else if( !aMatches && bMatches ) + return 1; + + // If the only spawn is marked, prefer it last + if( cmpBuildable == BA_A_SPAWN || cmpBuildable == BA_H_SPAWN ) + { + if( ( buildableA->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) || + ( buildableA->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) ) + return 1; + + if( ( buildableB->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) || + ( buildableB->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) ) + return -1; + } + + // If one matches the thing we're building, prefer it + aMatches = ( buildableA->s.modelindex == cmpBuildable ); + bMatches = ( buildableB->s.modelindex == cmpBuildable ); + if( aMatches && !bMatches ) + return -1; + else if( !aMatches && bMatches ) + return 1; + + // They're the same type + if( buildableA->s.modelindex == buildableB->s.modelindex ) + { + gentity_t *powerEntity = G_PowerEntityForPoint( cmpOrigin ); + + // Prefer the entity that is providing power for this point + aMatches = ( powerEntity == buildableA ); + bMatches = ( powerEntity == buildableB ); + if( aMatches && !bMatches ) + return -1; + else if( !aMatches && bMatches ) + return 1; + + // Pick the one marked earliest + return buildableA->deconstructTime - buildableB->deconstructTime; + } + + // Resort to preference list + for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ ) + { + if( buildableA->s.modelindex == precedence[ i ] ) + aPrecedence = i; + + if( buildableB->s.modelindex == precedence[ i ] ) + bPrecedence = i; + } + + return aPrecedence - bPrecedence; +} + +/* +=============== +G_ClearDeconMarks + +Remove decon mark from all buildables +=============== +*/ +void G_ClearDeconMarks( void ) +{ + int i; + gentity_t *ent; + + for( i = MAX_CLIENTS, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + + ent->deconstruct = qfalse; + } +} + +/* +=============== +G_FreeMarkedBuildables + +Free up build points for a team by deconstructing marked buildables +=============== +*/ +void G_FreeMarkedBuildables( gentity_t *deconner, char *readable, int rsize, + char *nums, int nsize ) +{ + int i; + int bNum; + int listItems = 0; + int totalListItems = 0; + gentity_t *ent; + int removalCounts[ BA_NUM_BUILDABLES ] = {0}; + + if( readable && rsize ) + readable[ 0 ] = '\0'; + if( nums && nsize ) + nums[ 0 ] = '\0'; + + if( !g_markDeconstruct.integer ) + return; // Not enabled, can't deconstruct anything + + for( i = 0; i < level.numBuildablesForRemoval; i++ ) + { + ent = level.markedBuildables[ i ]; + bNum = BG_Buildable( ent->s.modelindex, NULL )->number; + + if( removalCounts[ bNum ] == 0 ) + totalListItems++; + + G_Damage( ent, NULL, deconner, NULL, NULL, ent->health, 0, MOD_REPLACE ); + + removalCounts[ bNum ]++; + + if( nums ) + Q_strcat( nums, nsize, va( " %d", ent - g_entities ) ); + + G_FreeEntity( ent ); + } + + if( !readable ) + return; + + for( i = 0; i < BA_NUM_BUILDABLES; i++ ) + { + if( removalCounts[ i ] ) + { + if( listItems ) + { + if( listItems == ( totalListItems - 1 ) ) + Q_strcat( readable, rsize, va( "%s and ", + ( totalListItems > 2 ) ? "," : "" ) ); + else + Q_strcat( readable, rsize, ", " ); + } + Q_strcat( readable, rsize, va( "%s", BG_Buildable( i, NULL )->humanName ) ); + if( removalCounts[ i ] > 1 ) + Q_strcat( readable, rsize, va( " (%dx)", removalCounts[ i ] ) ); + listItems++; + } + } +} + +/* +=============== +G_SufficientBPAvailable + +Determine if enough build points can be released for the buildable +and list the buildables that must be destroyed if this is the case +=============== +*/ +static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, + vec3_t origin, + vec3_t cuboidSize ) +{ + int i; + int numBuildables = 0; + int numRequired = 0; + int pointsYielded = 0; + gentity_t *ent; + team_t team = BG_Buildable( buildable, NULL )->team; + int buildPoints = BG_Buildable( buildable, cuboidSize )->buildPoints; + int remainingBP, remainingSpawns; + qboolean collision = qfalse; + int collisionCount = 0; + qboolean repeaterInRange = qfalse; + int repeaterInRangeCount = 0; + itemBuildError_t bpError; + buildable_t spawn; + buildable_t core; + int spawnCount = 0; + qboolean changed = qtrue; + + level.numBuildablesForRemoval = 0; + + if( team == TEAM_ALIENS ) + { + remainingBP = G_GetBuildPoints( origin, team ); + remainingSpawns = level.numAlienSpawns; + bpError = IBE_NOALIENBP; + spawn = BA_A_SPAWN; + core = BA_A_OVERMIND; + } + else if( team == TEAM_HUMANS ) + { + if( buildable == BA_H_REACTOR || buildable == BA_H_REPEATER ) + remainingBP = level.humanBuildPoints; + else + remainingBP = G_GetBuildPoints( origin, team ); + + remainingSpawns = level.numHumanSpawns; + bpError = IBE_NOHUMANBP; + spawn = BA_H_SPAWN; + core = BA_H_REACTOR; + } + else + { + Com_Error( ERR_FATAL, "team is %d\n", team ); + return IBE_NONE; + } + + // Simple non-marking case + if( !g_markDeconstruct.integer ) + { + if( remainingBP - buildPoints < 0 ) + return bpError; + + // Check for buildable<->buildable collisions + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( G_BuildablesIntersect( buildable, origin, cuboidSize, ent->s.modelindex, ent->s.origin, ent->cuboidSize ) ) + return IBE_NOROOM; + + } + + return IBE_NONE; + } + + // Set buildPoints to the number extra that are required + buildPoints -= remainingBP; + + // Build a list of buildable entities + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + collision = G_BuildablesIntersect( buildable, origin, cuboidSize, ent->s.modelindex, ent->s.origin, ent->cuboidSize ); + + if( collision ) + { + // Don't allow replacements at all + if( g_markDeconstruct.integer == 1 ) + return IBE_NOROOM; + + // Only allow replacements of the same type + if( g_markDeconstruct.integer == 2 && ent->s.modelindex != buildable ) + return IBE_NOROOM; + + // Any other setting means anything goes + + collisionCount++; + } + + // Check if this is a repeater and it's in range + if( buildable == BA_H_REPEATER && + buildable == ent->s.modelindex && + Distance( ent->s.origin, origin ) < REPEATER_BASESIZE ) + { + repeaterInRange = qtrue; + repeaterInRangeCount++; + } + else + repeaterInRange = qfalse; + + // Don't allow marked buildables to be replaced in another zone, + // unless the marked buildable isn't in a zone (and thus unpowered) + if( team == TEAM_HUMANS && + buildable != BA_H_REACTOR && + buildable != BA_H_REPEATER && + ent->parentNode != G_PowerEntityForPoint( origin ) ) + continue; + + if( !ent->inuse ) + continue; + + if( ent->health <= 0 ) + continue; + + if( ent->buildableTeam != team ) + continue; + + // Explicitly disallow replacement of the core buildable with anything + // other than the core buildable + if( ent->s.modelindex == core && buildable != core ) + continue; + + // Don't allow a power source to be replaced by a dependant + if( team == TEAM_HUMANS && + G_PowerEntityForPoint( origin ) == ent && + buildable != BA_H_REPEATER && + buildable != core ) + continue; + + // Don't include unpowered buildables + if( !collision && !ent->powered ) + continue; + + if( ent->deconstruct ) + { + level.markedBuildables[ numBuildables++ ] = ent; + + // Buildables that are marked here will always end up at the front of the + // removal list, so just incrementing numBuildablesForRemoval is sufficient + if( collision || repeaterInRange ) + { + // Collided with something, so we definitely have to remove it or + // it's a repeater that intersects the new repeater's power area, + // so it must be removed + + if( collision ) + collisionCount--; + + if( repeaterInRange ) + repeaterInRangeCount--; + + if( ent->powered ) + pointsYielded += BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; + level.numBuildablesForRemoval++; + } + else if( BG_Buildable( ent->s.modelindex, NULL )->uniqueTest && + ent->s.modelindex == buildable ) + { + // If it's a unique buildable, it must be replaced by the same type + if( ent->powered ) + pointsYielded += BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; + level.numBuildablesForRemoval++; + } + } + } + + numRequired = level.numBuildablesForRemoval; + + // We still need build points, but have no candidates for removal + if( buildPoints > 0 && numBuildables == 0 ) + return bpError; + + // Collided with something we can't remove + if( collisionCount > 0 ) + return IBE_NOROOM; + + // There are one or more repeaters we can't remove + if( repeaterInRangeCount > 0 ) + return IBE_RPTPOWERHERE; + + // Sort the list + cmpBuildable = buildable; + VectorCopy( origin, cmpOrigin ); + VectorCopy( cuboidSize, cmpCuboid ); + qsort( level.markedBuildables, numBuildables, sizeof( level.markedBuildables[ 0 ] ), + G_CompareBuildablesForRemoval ); + + // Determine if there are enough markees to yield the required BP + for( ; pointsYielded < buildPoints && level.numBuildablesForRemoval < numBuildables; + level.numBuildablesForRemoval++ ) + { + ent = level.markedBuildables[ level.numBuildablesForRemoval ]; + if( ent->powered ) + pointsYielded += BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; + } + + // Do another pass to see if we can meet quota with fewer buildables + // than we have now due to mismatches between priority and BP amounts + // by repeatedly testing if we can chop off the first thing that isn't + // required by rules of collision/uniqueness, which are always at the head + while( changed && level.numBuildablesForRemoval > 1 && + level.numBuildablesForRemoval > numRequired ) + { + int pointsUnYielded = 0; + changed = qfalse; + ent = level.markedBuildables[ numRequired ]; + if( ent->powered ) + pointsUnYielded = BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; + + if( pointsYielded - pointsUnYielded >= buildPoints ) + { + pointsYielded -= pointsUnYielded; + memmove( &level.markedBuildables[ numRequired ], + &level.markedBuildables[ numRequired + 1 ], + ( level.numBuildablesForRemoval - numRequired ) + * sizeof( gentity_t * ) ); + level.numBuildablesForRemoval--; + changed = qtrue; + } + } + + for( i = 0; i < level.numBuildablesForRemoval; i++ ) + { + if( level.markedBuildables[ i ]->s.modelindex == spawn ) + spawnCount++; + } + + // Make sure we're not removing the last spawn + if( !g_cheats.integer && remainingSpawns > 0 && ( remainingSpawns - spawnCount ) < 1 ) + return IBE_LASTSPAWN; + + // Not enough points yielded + if( pointsYielded < buildPoints ) + return bpError; + else + return IBE_NONE; +} + +/* +================ +G_SetBuildableLinkState + +Links or unlinks all the buildable entities +================ +*/ +static void G_SetBuildableLinkState( qboolean link ) +{ + int i; + gentity_t *ent; + + for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( link ) + trap_LinkEntity( ent ); + else + trap_UnlinkEntity( ent ); + } +} + +static void G_SetBuildableMarkedLinkState( qboolean link ) +{ + int i; + gentity_t *ent; + + for( i = 0; i < level.numBuildablesForRemoval; i++ ) + { + ent = level.markedBuildables[ i ]; + if( link ) + trap_LinkEntity( ent ); + else + trap_UnlinkEntity( ent ); + } +} + + + +/* +================ +G_CanBuild + +Checks to see if a buildable can be built +================ +*/ +itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin, vec3_t normal, vec3_t cuboidSize ) +{ + vec3_t angles; + vec3_t entity_origin; + vec3_t mins, maxs; + trace_t tr1, tr2, tr3; + itemBuildError_t reason = IBE_NONE, tempReason; + gentity_t *tempent; + float minNormal; + qboolean invert; + int contents; + playerState_t *ps = &ent->client->ps; + + if( BG_Buildable(buildable,NULL)->cuboid ) + BG_CuboidBBox(cuboidSize,mins,maxs); + else + BG_BuildableBoundingBox( buildable, mins, maxs ); + + if(!BG_PositionBuildableRelativeToPlayer( ps, BG_Buildable(buildable,NULL)->cuboid, mins, maxs, trap_Trace, entity_origin, angles, &tr1 )) + return IBE_NOSURF; + trap_Trace( &tr2, entity_origin, mins, maxs, entity_origin, -1, MASK_PLAYERSOLID ); + trap_Trace( &tr3, ps->origin, NULL, NULL, entity_origin, ent->s.number, MASK_PLAYERSOLID ); + + VectorCopy( entity_origin, origin ); + + VectorCopy( tr1.plane.normal, normal ); + minNormal = BG_Buildable( buildable, NULL )->minNormal; + invert = BG_Buildable( buildable, NULL )->invertNormal; + + //can we build at this angle? + if(!BG_Buildable(buildable,NULL)->cuboid) + if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) ) + reason = IBE_NORMAL; + + //the only buildable one can build on is a cuboid + if( tr1.entityNum != ENTITYNUM_WORLD ) + if( g_entities[ tr1.entityNum ].s.eType == ET_BUILDABLE && + !BG_Buildable( g_entities[ tr1.entityNum ].s.modelindex, NULL )->cuboid ) + reason = IBE_NORMAL; + + contents = trap_PointContents( entity_origin, -1 ); + + if( ( tempReason = G_SufficientBPAvailable( buildable, origin, cuboidSize ) ) != IBE_NONE ) + reason = tempReason; + + if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + //alien criteria + + // Check there is an Overmind + if( buildable != BA_A_OVERMIND ) + { + if( !G_Overmind( ) ) + reason = IBE_NOOVERMIND; + } + + //check there is creep near by for building on + if( BG_Buildable( buildable, NULL )->creepTest ) + { + if( !G_IsCreepHere( entity_origin ) ) + reason = IBE_NOCREEP; + } + + // Check permission to build here + if( tr1.surfaceFlags & SURF_NOALIENBUILD || contents & CONTENTS_NOALIENBUILD ) + reason = IBE_PERMISSION; + } + else if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + //human criteria + + // Check for power + if( G_IsPowered( entity_origin ) == BA_NONE ) + { + //tell player to build a repeater to provide power + if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER ) + reason = IBE_NOPOWERHERE; + } + + //this buildable requires a DCC + if( BG_Buildable( buildable, NULL )->dccTest && !G_IsDCCBuilt( ) ) + reason = IBE_NODCC; + + //check that there is a parent reactor when building a repeater + if( buildable == BA_H_REPEATER ) + { + tempent = G_Reactor( ); + + if( tempent == NULL ) // No reactor + reason = IBE_RPTNOREAC; + else if( g_markDeconstruct.integer && G_IsPowered( entity_origin ) == BA_H_REACTOR ) + reason = IBE_RPTPOWERHERE; + else if( !g_markDeconstruct.integer && G_IsPowered( entity_origin ) ) + reason = IBE_RPTPOWERHERE; + } + + // Check permission to build here + if( tr1.surfaceFlags & SURF_NOHUMANBUILD || contents & CONTENTS_NOHUMANBUILD ) + reason = IBE_PERMISSION; + } + + // Check permission to build here + if( tr1.surfaceFlags & SURF_NOBUILD || contents & CONTENTS_NOBUILD ) + reason = IBE_PERMISSION; + + // Can we only have one of these? + if( BG_Buildable( buildable, NULL )->uniqueTest ) + { + tempent = G_FindBuildable( buildable ); + if( tempent && !tempent->deconstruct ) + { + switch( buildable ) + { + case BA_A_OVERMIND: + reason = IBE_ONEOVERMIND; + break; + + case BA_H_REACTOR: + reason = IBE_ONEREACTOR; + break; + + default: + Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable ); + break; + } + } + } + + //check there is enough room to spawn from (presuming this is a spawn) + if( reason == IBE_NONE ) + { + G_SetBuildableMarkedLinkState( qfalse ); + if( G_CheckSpawnPoint( ENTITYNUM_NONE, origin, normal, buildable, NULL ) != NULL ) + reason = IBE_NORMAL; + G_SetBuildableMarkedLinkState( qtrue ); + } + + //this item does not fit here + if( reason == IBE_NONE && ( tr2.startsolid || !COMPARE_FLOAT_EPSILON(tr3.fraction,1.0f) ) ) + reason = IBE_NOROOM; + if( reason != IBE_NONE ) + level.numBuildablesForRemoval = 0; + + if( g_buildableDensityLimit.integer > 0 ) + { + int i, count, numents, ents[ MAX_GENTITIES ]; + vec3_t rmins, rmaxs; + gentity_t *other; + + for( i = 0; i < 3; i++ ) + rmins[ i ] = origin[ i ] - g_buildableDensityLimitRange.value, + rmaxs[ i ] = origin[ i ] + g_buildableDensityLimitRange.value; + + numents = trap_EntitiesInBox( rmins, rmaxs, ents, MAX_GENTITIES ); + + for( count = 0, i = 0; i < numents ; i++ ) + { + other = &g_entities[ ents[ i ] ]; + + if( other->s.eType != ET_BUILDABLE ) + continue; + + if( other->buildableTeam != ent->client->ps.stats[ STAT_TEAM ] ) + continue; + + if( Distance( origin, other->s.origin ) > g_buildableDensityLimitRange.value ) + continue; + + if( !trap_InPVSIgnorePortals( origin, other->s.origin ) ) + continue; + + if( ++count > g_buildableDensityLimit.integer ) + { + reason = IBE_TOODENSE; + break; + } + } + } + + return reason; +} + +/* +================ +G_CuboidName + +For standard buildables the function returns humanName. +In case of cuboids, a "dynamic" one is generated. +================ +*/ + +const char *G_CuboidName(buildable_t buildable, const vec3_t cuboidSize, qboolean verbose) +{ + static char buffer[100]; + const buildableAttributes_t *battr; + + if((battr=BG_Buildable(buildable,cuboidSize))->cuboid) + { + if(verbose) + Com_sprintf(buffer,sizeof(buffer),"%s %fx%fx%f %ibp",battr->humanName,cuboidSize[0],cuboidSize[1],cuboidSize[2],battr->buildPoints); + else + Com_sprintf(buffer,sizeof(buffer),"%s %s (%i bp)",BG_CuboidAttributes(buildable)->icon,battr->humanName,battr->buildPoints); + return buffer; + } + else + return battr->humanName; +} + +/* +================ +G_Build + +Spawns a buildable +================ +*/ +static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, + const vec3_t origin, const vec3_t normal, const vec3_t angles, + const vec3_t cuboidSize ) +{ + gentity_t *built; + vec3_t localOrigin; + char readable[ MAX_STRING_CHARS ]; + char buildnums[ MAX_STRING_CHARS ]; + buildLog_t *log; + qboolean cuboid; + + cuboid=BG_Buildable(buildable,NULL)->cuboid; + + VectorCopy( origin, localOrigin ); + + if( builder->client ) + log = G_BuildLogNew( builder, BF_CONSTRUCT ); + else + log = NULL; + + // Free existing buildables + G_FreeMarkedBuildables( builder, readable, sizeof( readable ), + buildnums, sizeof( buildnums ) ); + + // Spawn the buildable + built = G_Spawn(); + built->s.eType = ET_BUILDABLE; + built->killedBy = ENTITYNUM_NONE; + built->classname = BG_Buildable( buildable, NULL )->entityName; + built->s.modelindex = buildable; + built->buildableTeam = built->s.modelindex2 = BG_Buildable( buildable, NULL )->team; + + // when building the initial layout, spawn the entity slightly off its + // target surface so that it can be "dropped" onto it + if( !builder->client ) + VectorMA( localOrigin, 1.0f, normal, localOrigin ); + + if(cuboid) + { + BG_CuboidBBox(cuboidSize,built->r.mins,built->r.maxs); + VectorCopy(cuboidSize,&built->cuboidSize); + } + else + BG_BuildableBoundingBox(buildable,built->r.mins,built->r.maxs); + + built->health = 1; + built->healthLeft = BG_Buildable( buildable, cuboidSize )->health-1; + built->s.generic1 = MIN(MAX(built->health,0),999); + built->splashDamage = BG_Buildable( buildable, cuboidSize )->splashDamage; + built->splashRadius = BG_Buildable( buildable, cuboidSize )->splashRadius; + built->splashMethodOfDeath = BG_Buildable( buildable, cuboidSize )->meansOfDeath; + + built->nextthink = BG_Buildable( buildable, NULL )->nextthink; + + built->takedamage = qtrue; + built->spawned = qfalse; + built->buildTime = built->s.time = level.time; + + // build instantly in cheat mode + if( builder->client && (g_cheats.integer || g_instantBuild.integer) ) + { + built->health = BG_Buildable( buildable, cuboidSize )->health; + built->buildTime = built->s.time = + level.time -BG_Buildable( buildable, cuboidSize )->buildTime; + } + + //things that vary for each buildable that aren't in the dbase + switch( buildable ) + { + case BA_A_SPAWN: + built->die = AGeneric_Die; + built->think = ASpawn_Think; + built->pain = AGeneric_Pain; + break; + + case BA_A_BARRICADE: + built->die = ABarricade_Die; + built->think = ABarricade_Think; + built->pain = ABarricade_Pain; + built->touch = ABarricade_Touch; + built->shrunkTime = 0; + ABarricade_Shrink( built, qtrue ); + break; + + case BA_A_BOOSTER: + built->die = AGeneric_Die; + built->think = AGeneric_Think; + built->pain = AGeneric_Pain; + built->touch = ABooster_Touch; + break; + + case BA_A_ACIDTUBE: + built->die = AGeneric_Die; + built->think = AAcidTube_Think; + built->pain = AGeneric_Pain; + break; + + case BA_A_HIVE: + built->die = AGeneric_Die; + built->think = AHive_Think; + built->pain = AHive_Pain; + break; + + case BA_A_TRAPPER: + built->die = AGeneric_Die; + built->think = ATrapper_Think; + built->pain = AGeneric_Pain; + break; + + case BA_A_OVERMIND: + built->die = AGeneric_Die; + built->think = AOvermind_Think; + built->pain = AGeneric_Pain; + break; + + case BA_H_SPAWN: + built->die = HSpawn_Die; + built->think = HSpawn_Think; + break; + + case BA_H_MGTURRET: + built->die = HSpawn_Die; + built->think = HMGTurret_Think; + break; + + case BA_H_TESLAGEN: + built->die = HSpawn_Die; + built->think = HTeslaGen_Think; + break; + + case BA_H_ARMOURY: + built->think = HArmoury_Think; + built->die = HSpawn_Die; + built->use = HArmoury_Activate; + break; + + case BA_H_DCC: + built->think = HDCC_Think; + built->die = HSpawn_Die; + break; + + case BA_H_MEDISTAT: + built->think = HMedistat_Think; + built->die = HMedistat_Die; + break; + + case BA_H_REACTOR: + built->think = HReactor_Think; + built->die = HSpawn_Die; + built->use = HRepeater_Use; + built->powered = built->active = qtrue; + break; + + case BA_H_REPEATER: + built->think = HRepeater_Think; + built->die = HRepeater_Die; + built->use = HRepeater_Use; + built->count = -1; + break; + + default: + //erk + break; + } + + if(buildable>=CUBOID_FIRST && buildable<=CUBOID_LAST) + { + built->think=Cuboid_Think; + built->die=Cuboid_Die; + } + + built->clipmask = MASK_PLAYERSOLID; + built->r.contents=CONTENTS_BODY; + built->s.number = built - g_entities; + built->enemy = NULL; + built->s.weapon = BG_Buildable( buildable, NULL )->turretProjType; + + if( builder->client ) + built->builtBy = builder->client->ps.clientNum; + else + built->builtBy = -1; + + G_SetOrigin( built, localOrigin ); + + // roughly nudge the buildable onto the surface D:< !@#CUBOID + VectorScale( normal, -512.0f, built->s.pos.trDelta ); + + if(BG_Buildable(buildable, NULL)->cuboid) + VectorCopy(cuboidSize,built->s.angles); + else + { + VectorCopy( angles, built->s.angles ); + built->s.angles[ PITCH ] = 0.0f; + } + + built->s.pos.trType = BG_Buildable( buildable, NULL )->traj; + built->s.pos.trTime = level.time; + built->physicsBounce = BG_Buildable( buildable, NULL )->bounce; + built->s.groundEntityNum = -1; + //turret angles + VectorCopy( builder->s.angles2, built->s.angles2 ); + built->s.angles2[ YAW ] = angles[ YAW ]; + built->s.angles2[ PITCH ] = MGTURRET_VERTICALCAP; + + if( BG_Buildable( buildable, NULL )->team == TEAM_ALIENS ) + { + built->powered = qtrue; + built->s.eFlags |= EF_B_POWERED; + } + else if( ( built->powered = G_FindPower( built, qfalse ) ) ) + built->s.eFlags |= EF_B_POWERED; + + built->s.eFlags &= ~EF_B_SPAWNED; + + VectorCopy( normal, built->s.origin2 ); + + G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 ); + + G_SetIdleBuildableAnim( built, BG_Buildable( buildable, NULL )->idleAnim ); + + if( built->builtBy >= 0 ) + G_SetBuildableAnim( built, BANIM_CONSTRUCT1, qtrue ); + + trap_LinkEntity( built ); + + if( builder && builder->client ) + { + G_TeamCommand( builder->client->ps.stats[ STAT_TEAM ], + va( "print \"%s ^2built^7 by %s%s%s\n\"", + G_CuboidName(built->s.modelindex,cuboidSize,qfalse), + builder->client->pers.netname, + ( readable[ 0 ] ) ? "^7, ^3replacing^7 " : "", + readable ) ); + G_LogPrintf( "Construct: %d %d %s%s: %s" S_COLOR_WHITE " is building " + "%s%s%s\n", + builder - g_entities, + built - g_entities, + BG_Buildable( built->s.modelindex, NULL )->name, + buildnums, + builder->client->pers.netname, + G_CuboidName(built->s.modelindex,built->cuboidSize,qtrue), + readable[ 0 ] ? ", replacing " : "", + readable ); + } + + if( log ) + G_BuildLogSet( log, built ); + + BG_CuboidPackHealthSafe(built->s.modelindex,&built->s,built->health); + + return built; +} + +/* +================= +G_BuildIfValid +================= +*/ +qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable, vec3_t cuboidSize ) +{ + float dist; + vec3_t origin, normal; + + dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; + + switch( G_CanBuild( ent, buildable, dist, origin, normal, cuboidSize ) ) + { + case IBE_NONE: + G_Build( ent, buildable, origin, normal, ent->s.apos.trBase, cuboidSize ); + return qtrue; + + case IBE_NOALIENBP: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOBP ); + return qfalse; + + case IBE_NOOVERMIND: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND ); + return qfalse; + + case IBE_NOCREEP: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP ); + return qfalse; + + case IBE_ONEOVERMIND: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_ONEOVERMIND ); + return qfalse; + + case IBE_NORMAL: + G_TriggerMenu( ent->client->ps.clientNum, MN_B_NORMAL ); + return qfalse; + + case IBE_PERMISSION: + G_TriggerMenu( ent->client->ps.clientNum, MN_B_NORMAL ); + return qfalse; + + case IBE_ONEREACTOR: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ONEREACTOR ); + return qfalse; + + case IBE_NOPOWERHERE: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWERHERE ); + return qfalse; + + case IBE_NOROOM: + G_TriggerMenu( ent->client->ps.clientNum, MN_B_NOROOM ); + return qfalse; + + case IBE_NOHUMANBP: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOBP); + return qfalse; + + case IBE_NODCC: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC ); + return qfalse; + + case IBE_RPTPOWERHERE: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTPOWERHERE ); + return qfalse; + + case IBE_LASTSPAWN: + G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN ); + return qfalse; + + case IBE_NOSURF: + G_TriggerMenu( ent->client->ps.clientNum, MN_B_NOSURF ); + return qfalse; + + case IBE_TOODENSE: + G_TriggerMenu( ent->client->ps.clientNum, MN_B_TOODENSE ); + return qfalse; + + default: + break; + } + + return qfalse; +} + +/* +================ +G_FinishSpawningBuildable + +Traces down to find where an item should rest, instead of letting them +free fall from their spawn points +================ +*/ +static gentity_t *G_FinishSpawningBuildable( gentity_t *ent, qboolean force ) +{ + trace_t tr; + vec3_t normal, dest; + gentity_t *built; + buildable_t buildable = ent->s.modelindex; + + if( ent->s.origin2[ 0 ] || ent->s.origin2[ 1 ] || ent->s.origin2[ 2 ] ) + VectorCopy( ent->s.origin2, normal ); + else if( BG_Buildable( buildable, NULL )->traj == TR_BUOYANCY ) + VectorSet( normal, 0.0f, 0.0f, -1.0f ); + else + VectorSet( normal, 0.0f, 0.0f, 1.0f ); + + built = G_Build( ent, buildable, ent->s.pos.trBase, normal, ent->s.angles, built->cuboidSize ); + + built->takedamage = qtrue; + built->spawned = qtrue; //map entities are already spawned + built->health = BG_Buildable( buildable, built->cuboidSize )->health; + built->s.eFlags |= EF_B_SPAWNED; + + // drop towards normal surface + VectorScale( built->s.origin2, -4096.0f, dest ); + VectorAdd( dest, built->s.origin, dest ); + + trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); + + if( tr.startsolid && !force ) + { + G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n", + built->classname, vtos( built->s.origin ) ); + G_FreeEntity( built ); + return NULL; + } + + //point items in the correct direction + VectorCopy( tr.plane.normal, built->s.origin2 ); + + // allow to ride movers + built->s.groundEntityNum = tr.entityNum; + + G_SetOrigin( built, tr.endpos ); + + trap_LinkEntity( built ); + return built; +} + +/* +============ +G_SpawnBuildableThink + +Complete spawning a buildable using it's placeholder +============ +*/ +static void G_SpawnBuildableThink( gentity_t *ent ) +{ + G_FinishSpawningBuildable( ent, qfalse ); + G_FreeEntity( ent ); +} + +/* +============ +G_SpawnBuildable + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void G_SpawnBuildable( gentity_t *ent, buildable_t buildable ) +{ + ent->s.modelindex = buildable; + + // some movers spawn on the second frame, so delay item + // spawns until the third frame so they can ride trains + ent->nextthink = level.time + FRAMETIME * 2; + ent->think = G_SpawnBuildableThink; +} + +/* +============ +G_LayoutSave +============ +*/ +void G_LayoutSave( char *name ) +{ + char map[ MAX_QPATH ]; + char fileName[ MAX_OSPATH ]; + fileHandle_t f; + int len; + int i; + gentity_t *ent; + char *s; + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + if( !map[ 0 ] ) + { + G_Printf( "LayoutSave( ): no map is loaded\n" ); + return; + } + Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, name ); + + len = trap_FS_FOpenFile( fileName, &f, FS_WRITE ); + if( len < 0 ) + { + G_Printf( "layoutsave: could not open %s\n", fileName ); + return; + } + + G_Printf( "layoutsave: saving layout to %s\n", fileName ); + + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + ent = &level.gentities[ i ]; + if( ent->s.eType != ET_BUILDABLE ) + continue; + + s = va( "%s %f %f %f %f %f %f %f %f %f %f %f %f\n", + BG_Buildable( ent->s.modelindex, NULL )->name, + ent->s.pos.trBase[ 0 ], + ent->s.pos.trBase[ 1 ], + ent->s.pos.trBase[ 2 ], + ent->s.angles[ 0 ], + ent->s.angles[ 1 ], + ent->s.angles[ 2 ], + ent->s.origin2[ 0 ], + ent->s.origin2[ 1 ], + ent->s.origin2[ 2 ], + ent->s.angles2[ 0 ], + ent->s.angles2[ 1 ], + ent->s.angles2[ 2 ] ); + trap_FS_Write( s, strlen( s ), f ); + } + trap_FS_FCloseFile( f ); +} + +/* +============ +G_LayoutList +============ +*/ +int G_LayoutList( const char *map, char *list, int len ) +{ + // up to 128 single character layout names could fit in layouts + char fileList[ ( MAX_CVAR_VALUE_STRING / 2 ) * 5 ] = {""}; + char layouts[ MAX_CVAR_VALUE_STRING ] = {""}; + int numFiles, i, fileLen = 0, listLen; + int count = 0; + char *filePtr; + + Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " ); + numFiles = trap_FS_GetFileList( va( "layouts/%s", map ), ".dat", + fileList, sizeof( fileList ) ); + filePtr = fileList; + for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + listLen = strlen( layouts ); + if( fileLen < 5 ) + continue; + + // list is full, stop trying to add to it + if( ( listLen + fileLen ) >= sizeof( layouts ) ) + break; + + Q_strcat( layouts, sizeof( layouts ), filePtr ); + listLen = strlen( layouts ); + + // strip extension and add space delimiter + layouts[ listLen - 4 ] = ' '; + layouts[ listLen - 3 ] = '\0'; + count++; + } + if( count != numFiles ) + { + G_Printf( S_COLOR_YELLOW "WARNING: layout list was truncated to %d " + "layouts, but %d layout files exist in layouts/%s/.\n", + count, numFiles, map ); + } + Q_strncpyz( list, layouts, len ); + return count + 1; +} + +/* +============ +G_LayoutSelect + +set level.layout based on g_layouts or g_layoutAuto +============ +*/ +void G_LayoutSelect( void ) +{ + char fileName[ MAX_OSPATH ]; + char layouts[ MAX_CVAR_VALUE_STRING ]; + char layouts2[ MAX_CVAR_VALUE_STRING ]; + char *l; + char map[ MAX_QPATH ]; + char *s; + int cnt = 0; + int layoutNum; + + Q_strncpyz( layouts, g_layouts.string, sizeof( layouts ) ); + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + + // one time use cvar + trap_Cvar_Set( "g_layouts", "" ); + + // pick an included layout at random if no list has been provided + if( !layouts[ 0 ] && g_layoutAuto.integer ) + { + G_LayoutList( map, layouts, sizeof( layouts ) ); + } + + if( !layouts[ 0 ] ) + return; + + Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) ); + l = &layouts2[ 0 ]; + layouts[ 0 ] = '\0'; + while( 1 ) + { + s = COM_ParseExt( &l, qfalse ); + if( !*s ) + break; + + if( !Q_stricmp( s, "*BUILTIN*" ) ) + { + Q_strcat( layouts, sizeof( layouts ), s ); + Q_strcat( layouts, sizeof( layouts ), " " ); + cnt++; + continue; + } + + Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, s ); + if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) > 0 ) + { + Q_strcat( layouts, sizeof( layouts ), s ); + Q_strcat( layouts, sizeof( layouts ), " " ); + cnt++; + } + else + G_Printf( S_COLOR_YELLOW "WARNING: layout \"%s\" does not exist\n", s ); + } + if( !cnt ) + { + G_Printf( S_COLOR_RED "ERROR: none of the specified layouts could be " + "found, using map default\n" ); + return; + } + layoutNum = rand( ) / ( RAND_MAX / cnt + 1 ) + 1; + cnt = 0; + + Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) ); + l = &layouts2[ 0 ]; + while( 1 ) + { + s = COM_ParseExt( &l, qfalse ); + if( !*s ) + break; + + Q_strncpyz( level.layout, s, sizeof( level.layout ) ); + cnt++; + if( cnt >= layoutNum ) + break; + } + G_Printf( "using layout \"%s\" from list (%s)\n", level.layout, layouts ); +} + +/* +============ +G_LayoutBuildItem +============ +*/ +void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, + vec3_t angles, vec3_t origin2, vec3_t angles2 ) +{ + gentity_t *builder; + + builder = G_Spawn( ); + builder->client = 0; + VectorCopy( origin, builder->s.pos.trBase ); + VectorCopy( angles, builder->s.angles ); + VectorCopy( angles, builder->cuboidSize ); + VectorCopy( origin2, builder->s.origin2 ); + VectorCopy( angles2, builder->s.angles2 ); + G_SpawnBuildable( builder, buildable ); +} + +/* +============ +G_LayoutLoad + +load the layout .dat file indicated by level.layout and spawn buildables +as if a builder was creating them +============ +*/ +void G_LayoutLoad( void ) +{ + fileHandle_t f; + int len; + char *layout, *layoutHead; + char map[ MAX_QPATH ]; + char buildName[ MAX_TOKEN_CHARS ]; + int buildable; + vec3_t origin = { 0.0f, 0.0f, 0.0f }; + vec3_t angles = { 0.0f, 0.0f, 0.0f }; + vec3_t origin2 = { 0.0f, 0.0f, 0.0f }; + vec3_t angles2 = { 0.0f, 0.0f, 0.0f }; + char line[ MAX_STRING_CHARS ]; + int i = 0; + + if( !level.layout[ 0 ] || !Q_stricmp( level.layout, "*BUILTIN*" ) ) + return; + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, level.layout ), + &f, FS_READ ); + if( len < 0 ) + { + G_Printf( "ERROR: layout %s could not be opened\n", level.layout ); + return; + } + layoutHead = layout = BG_Alloc( len + 1 ); + trap_FS_Read( layout, len, f ); + layout[ len ] = '\0'; + trap_FS_FCloseFile( f ); + while( *layout ) + { + if( i >= sizeof( line ) - 1 ) + { + G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n", + va( "layouts/%s/%s.dat", map, level.layout ), line ); + break; + } + line[ i++ ] = *layout; + line[ i ] = '\0'; + if( *layout == '\n' ) + { + i = 0; + sscanf( line, "%s %f %f %f %f %f %f %f %f %f %f %f %f\n", + buildName, + &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], + &angles[ 0 ], &angles[ 1 ], &angles[ 2 ], + &origin2[ 0 ], &origin2[ 1 ], &origin2[ 2 ], + &angles2[ 0 ], &angles2[ 1 ], &angles2[ 2 ] ); + + buildable = BG_BuildableByName( buildName )->number; + if( buildable <= BA_NONE || buildable >= BA_NUM_BUILDABLES ) + G_Printf( S_COLOR_YELLOW "WARNING: bad buildable name (%s) in layout." + " skipping\n", buildName ); + else + G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 ); + } + layout++; + } + BG_Free( layoutHead ); +} + +/* +============ +G_BaseSelfDestruct +============ +*/ +void G_BaseSelfDestruct( team_t team ) +{ + int i; + gentity_t *ent; + + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + ent = &level.gentities[ i ]; + if( ent->health <= 0 ) + continue; + if( ent->s.eType != ET_BUILDABLE ) + continue; + if( ent->buildableTeam != team ) + continue; + G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + } +} + +/* +============ +build log +============ +*/ +buildLog_t *G_BuildLogNew( gentity_t *actor, buildFate_t fate ) +{ + buildLog_t *log = &level.buildLog[ level.buildId++ % MAX_BUILDLOG ]; + + if( level.numBuildLogs < MAX_BUILDLOG ) + level.numBuildLogs++; + log->time = level.time; + log->fate = fate; + log->actor = actor && actor->client ? actor->client->pers.namelog : NULL; + return log; +} + +void G_BuildLogSet( buildLog_t *log, gentity_t *ent ) +{ + log->modelindex = ent->s.modelindex; + log->deconstruct = log->deconstruct; + log->deconstructTime = ent->deconstructTime; + VectorCopy( ent->s.pos.trBase, log->origin ); + VectorCopy( ent->s.angles, log->angles ); + VectorCopy( ent->s.origin2, log->origin2 ); + VectorCopy( ent->s.angles2, log->angles2 ); + log->powerSource = ent->parentNode ? ent->parentNode->s.modelindex : BA_NONE; + log->powerValue = G_QueueValue( ent ); +} + +void G_BuildLogAuto( gentity_t *actor, gentity_t *buildable, buildFate_t fate ) +{ + G_BuildLogSet( G_BuildLogNew( actor, fate ), buildable ); +} + +void G_BuildLogRevertThink( gentity_t *ent ) +{ + gentity_t *built; + vec3_t mins, maxs; + int blockers[ MAX_GENTITIES ]; + int num; + int victims = 0; + int i; + + if( ent->suicideTime > 0 ) + { + BG_BuildableBoundingBox( ent->s.modelindex, mins, maxs ); + VectorAdd( ent->s.pos.trBase, mins, mins ); + VectorAdd( ent->s.pos.trBase, maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + gentity_t *targ; + vec3_t push; + + targ = g_entities + blockers[ i ]; + if( targ->client ) + { + float val = ( targ->client->ps.eFlags & EF_WALLCLIMB) ? 300.0 : 150.0; + + VectorSet( push, crandom() * val, crandom() * val, random() * val ); + VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity ); + victims++; + } + } + + ent->suicideTime--; + + if( victims ) + { + // still a blocker + ent->nextthink = level.time + FRAMETIME; + return; + } + } + + built = G_FinishSpawningBuildable( ent, qtrue ); + if( ( built->deconstruct = ent->deconstruct ) ) + built->deconstructTime = ent->deconstructTime; + built->buildTime = built->s.time = 0; + G_KillBox( built ); + + G_LogPrintf( "revert: restore %d %s\n", + built - g_entities, BG_Buildable( built->s.modelindex, NULL )->name ); + + G_FreeEntity( ent ); +} + +void G_BuildLogRevert( int id ) +{ + buildLog_t *log; + gentity_t *ent; + int i; + vec3_t dist; + + level.numBuildablesForRemoval = 0; + + level.numBuildLogs -= level.buildId - id; + while( level.buildId > id ) + { + log = &level.buildLog[ --level.buildId % MAX_BUILDLOG ]; + if( log->fate == BF_CONSTRUCT ) + { + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + ent = &g_entities[ i ]; + if( ( ( ent->s.eType == ET_BUILDABLE && + ent->health > 0 ) || + ( ent->s.eType == ET_GENERAL && + ent->think == G_BuildLogRevertThink ) ) && + ent->s.modelindex == log->modelindex ) + { + VectorSubtract( ent->s.pos.trBase, log->origin, dist ); + if( VectorLengthSquared( dist ) <= 2.0f ) + { + if( ent->s.eType == ET_BUILDABLE ) + G_LogPrintf( "revert: remove %d %s\n", + ent - g_entities, BG_Buildable( ent->s.modelindex, NULL )->name ); + G_FreeEntity( ent ); + break; + } + } + } + } + else + { + gentity_t *builder = G_Spawn(); + + builder->client = NULL; + VectorCopy( log->origin, builder->s.pos.trBase ); + VectorCopy( log->angles, builder->s.angles ); + VectorCopy( log->origin2, builder->s.origin2 ); + VectorCopy( log->angles2, builder->s.angles2 ); + builder->s.modelindex = log->modelindex; + builder->deconstruct = log->deconstruct; + builder->deconstructTime = log->deconstructTime; + + builder->think = G_BuildLogRevertThink; + builder->nextthink = level.time + FRAMETIME; + + // Number of thinks before giving up and killing players in the way + builder->suicideTime = 30; + + if( log->fate == BF_DESTROY || log->fate == BF_TEAMKILL ) + { + int value = log->powerValue; + + if( BG_Buildable( log->modelindex, NULL )->team == TEAM_ALIENS ) + { + level.alienBuildPointQueue = + MAX( 0, level.alienBuildPointQueue - value ); + } + else + { + if( log->powerSource == BA_H_REACTOR ) + { + level.humanBuildPointQueue = + MAX( 0, level.humanBuildPointQueue - value ); + } + else if( log->powerSource == BA_H_REPEATER ) + { + gentity_t *source; + buildPointZone_t *zone; + + source = G_PowerEntityForPoint( log->origin ); + if( source && source->usesBuildPointZone ) + { + zone = &level.buildPointZones[ source->buildPointZone ]; + zone->queuedBuildPoints = + MAX( 0, zone->queuedBuildPoints - value ); + } + } + } + } + } + } +} + diff --git a/src/game/g_client.c b/src/game/g_client.c new file mode 100644 index 0000000..c8421f0 --- /dev/null +++ b/src/game/g_client.c @@ -0,0 +1,1800 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, -24}; +static vec3_t playerMaxs = {15, 15, 32}; + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_deathmatch( gentity_t *ent ) +{ + int i; + + G_SpawnInt( "nobots", "0", &i); + + if( i ) + ent->flags |= FL_NO_BOTS; + + G_SpawnInt( "nohumans", "0", &i ); + if( i ) + ent->flags |= FL_NO_HUMANS; +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +equivelant to info_player_deathmatch +*/ +void SP_info_player_start( gentity_t *ent ) +{ + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) +{ +} + +/*QUAKED info_alien_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_alien_intermission( gentity_t *ent ) +{ +} + +/*QUAKED info_human_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_human_intermission( gentity_t *ent ) +{ +} + +/* +=============== +G_AddCreditToClient +=============== +*/ +void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ) +{ + int capAmount; + + if( !client ) + return; + + if( cap && credit > 0 ) + { + capAmount = client->pers.teamSelection == TEAM_ALIENS ? + ALIEN_MAX_CREDITS : HUMAN_MAX_CREDITS; + if( client->pers.credit < capAmount ) + { + client->pers.credit += credit; + if( client->pers.credit > capAmount ) + client->pers.credit = capAmount; + } + } + else + client->pers.credit += credit; + + if( client->pers.credit < 0 ) + client->pers.credit = 0; + + // Copy to ps so the client can access it + client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; +} + + +/* +======================================================================= + + G_SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) +{ + int i, num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + //if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if( hit->client ) + return qtrue; + } + + return qfalse; +} + + +/* +=========== +G_SelectRandomFurthestSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +static gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[ 64 ]; + gentity_t *list_spot[ 64 ]; + int numSpots, rnd, i, j; + + numSpots = 0; + spot = NULL; + + while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) + { + if( SpotWouldTelefrag( spot ) ) + continue; + + VectorSubtract( spot->s.origin, avoidPoint, delta ); + dist = VectorLength( delta ); + + for( i = 0; i < numSpots; i++ ) + { + if( dist > list_dist[ i ] ) + { + if( numSpots >= 64 ) + numSpots = 64 - 1; + + for( j = numSpots; j > i; j-- ) + { + list_dist[ j ] = list_dist[ j - 1 ]; + list_spot[ j ] = list_spot[ j - 1 ]; + } + + list_dist[ i ] = dist; + list_spot[ i ] = spot; + numSpots++; + + if( numSpots > 64 ) + numSpots = 64; + + break; + } + } + + if( i >= numSpots && numSpots < 64 ) + { + list_dist[ numSpots ] = dist; + list_spot[ numSpots ] = spot; + numSpots++; + } + } + + if( !numSpots ) + { + spot = G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); + + if( !spot ) + G_Error( "Couldn't find a spawn point" ); + + VectorCopy( spot->s.origin, origin ); + origin[ 2 ] += 9; + VectorCopy( spot->s.angles, angles ); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random( ) * ( numSpots / 2 ); + + VectorCopy( list_spot[ rnd ]->s.origin, origin ); + origin[ 2 ] += 9; + VectorCopy( list_spot[ rnd ]->s.angles, angles ); + + return list_spot[ rnd ]; +} + + +/* +================ +G_SelectSpawnBuildable + +find the nearest buildable of the right type that is +spawned/healthy/unblocked etc. +================ +*/ +static gentity_t *G_SelectSpawnBuildable( vec3_t preference, buildable_t buildable ) +{ + gentity_t *search, *spot; + + search = spot = NULL; + + while( ( search = G_Find( search, FOFS( classname ), + BG_Buildable( buildable, NULL )->entityName ) ) != NULL ) + { + if( !search->spawned ) + continue; + + if( search->health <= 0 ) + continue; + + if( !search->s.groundEntityNum ) + continue; + + if( search->clientSpawnTime > 0 ) + continue; + + if( G_CheckSpawnPoint( search->s.number, search->s.origin, + search->s.origin2, buildable, NULL ) != NULL ) + continue; + + if( !spot || DistanceSquared( preference, search->s.origin ) < + DistanceSquared( preference, spot->s.origin ) ) + spot = search; + } + + return spot; +} + +/* +================ +G_SelectAlienImplantedSpawn + +find the nearest impregnated human for spawning +================ +*/ +static gentity_t *G_SelectAlienImplantedSpawn( vec3_t preference ) +{ + int i; + float r; + gentity_t *search, *spot = NULL; + + for( i = 0; i < level.num_entities; i++ ) + { + search = g_entities + i; + + if( !search->client ) + continue; + + if( search->client->sess.spectatorState != SPECTATOR_NOT ) + continue; + + if( search->client->pers.teamSelection != TEAM_HUMANS ) + continue; + + if( search->client->ps.stats[ STAT_HEALTH ] <= 0 ) + continue; + + if( !search->client->isImpregnated ) + continue; + + if( !search->client->isImplantMature ) + continue; + + if( !spot || DistanceSquared( preference, search->s.origin ) < + DistanceSquared( preference, spot->s.origin ) ) + spot = search; + } + + return spot; +} + +/* +=========== +G_SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +{ + return G_SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); +} + + +/* +=========== +G_SelectTremulousSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles ) +{ + gentity_t *spot = NULL; + + if( team == TEAM_ALIENS ) + { + if( level.numAlienImplantedSpawns > 0 ) + { + spot = G_SelectAlienImplantedSpawn( preference ); + + if( spot ) + { + //spawn from a human + VectorCopy( spot->s.pos.trBase, origin ); + VectorCopy( spot->s.apos.trBase, angles ); + return spot; + } + } + + + if( level.numAlienSpawns <= 0 ) + return NULL; + + spot = G_SelectSpawnBuildable( preference, BA_A_SPAWN ); + } + else if( team == TEAM_HUMANS ) + { + if( level.numHumanSpawns <= 0 ) + return NULL; + + spot = G_SelectSpawnBuildable( preference, BA_H_SPAWN ); + } + + //no available spots + if( !spot ) + return NULL; + + if( team == TEAM_ALIENS ) + G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_A_SPAWN, origin ); + else if( team == TEAM_HUMANS ) + G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_H_SPAWN, origin ); + + VectorCopy( spot->s.angles, angles ); + angles[ ROLL ] = 0; + + return spot; + +} + + +/* +=========== +G_SelectSpectatorSpawnPoint + +============ +*/ +static gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) +{ + FindIntermissionPoint( ); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + + +/* +=========== +G_SelectAlienLockSpawnPoint + +Try to find a spawn point for alien intermission otherwise +use normal intermission spawn. +============ +*/ +gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + + spot = NULL; + spot = G_Find( spot, FOFS( classname ), "info_alien_intermission" ); + + if( !spot ) + return G_SelectSpectatorSpawnPoint( origin, angles ); + + VectorCopy( spot->s.origin, origin ); + VectorCopy( spot->s.angles, angles ); + + return spot; +} + + +/* +=========== +G_SelectHumanLockSpawnPoint + +Try to find a spawn point for human intermission otherwise +use normal intermission spawn. +============ +*/ +gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + + spot = NULL; + spot = G_Find( spot, FOFS( classname ), "info_human_intermission" ); + + if( !spot ) + return G_SelectSpectatorSpawnPoint( origin, angles ); + + VectorCopy( spot->s.origin, origin ); + VectorCopy( spot->s.angles, angles ); + + return spot; +} + + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +static void BodySink( gentity_t *ent ) +{ + //run on first BodySink call + if( !ent->active ) + { + ent->active = qtrue; + + //sinking bodies can't be infested + ent->killedBy = ent->s.misc = MAX_CLIENTS; + ent->timestamp = level.time; + } + + if( level.time - ent->timestamp > 6500 ) + { + G_FreeEntity( ent ); + return; + } + + ent->nextthink = level.time + 100; + ent->s.pos.trBase[ 2 ] -= 1; +} + + +/* +============= +SpawnCorpse + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +static void SpawnCorpse( gentity_t *ent ) +{ + gentity_t *body; + int contents; + vec3_t origin, dest; + trace_t tr; + float vDiff; + + VectorCopy( ent->r.currentOrigin, origin ); + + trap_UnlinkEntity( ent ); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( origin, -1 ); + if( contents & CONTENTS_NODROP ) + return; + + body = G_Spawn( ); + + VectorCopy( ent->s.apos.trBase, body->s.angles ); + body->s.eFlags = EF_DEAD; + body->s.eType = ET_CORPSE; + body->s.number = body - g_entities; + body->timestamp = level.time; + body->s.event = 0; + body->r.contents = CONTENTS_CORPSE; + body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ]; + body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; + + if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + body->classname = "humanCorpse"; + else + body->classname = "alienCorpse"; + + body->s.misc = MAX_CLIENTS; + + body->think = BodySink; + body->nextthink = level.time + 20000; + + body->s.legsAnim = ent->s.legsAnim; + + if( !body->nonSegModel ) + { + switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) + { + case BOTH_DEATH1: + case BOTH_DEAD1: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; + break; + case BOTH_DEATH3: + case BOTH_DEAD3: + default: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; + break; + } + } + else + { + switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) + { + case NSPA_DEATH1: + case NSPA_DEAD1: + body->s.legsAnim = NSPA_DEAD1; + break; + case NSPA_DEATH2: + case NSPA_DEAD2: + body->s.legsAnim = NSPA_DEAD2; + break; + case NSPA_DEATH3: + case NSPA_DEAD3: + default: + body->s.legsAnim = NSPA_DEAD3; + break; + } + } + + body->takedamage = qfalse; + + body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; + ent->health = 0; + + //change body dimensions + BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); + vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; + + //drop down to match the *model* origins of ent and body + VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); + trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); + VectorCopy( tr.endpos, origin ); + + G_SetOrigin( body, origin ); + VectorCopy( origin, body->s.origin ); + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + + VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); + trap_LinkEntity( body ); +} + +//====================================================================== + + +/* +================== +G_SetClientViewAngle + +================== +*/ +void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ) +{ + int i; + + // set the delta angle + for( i = 0; i < 3; i++ ) + { + int cmdAngle; + + cmdAngle = ANGLE2SHORT( angle[ i ] ); + ent->client->ps.delta_angles[ i ] = cmdAngle - ent->client->pers.cmd.angles[ i ]; + } + + VectorCopy( angle, ent->s.angles ); + VectorCopy( ent->s.angles, ent->client->ps.viewangles ); +} + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) +{ + int i; + + SpawnCorpse( ent ); + + // Clients can't respawn - they must go through the class cmd + ent->client->pers.classSelection = PCL_NONE; + ClientSpawn( ent, NULL, NULL, NULL ); + + // stop any following clients that don't have sticky spec on + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && + level.clients[ i ].sess.spectatorClient == ent - g_entities ) + { + if( !( level.clients[ i ].pers.stickySpec ) ) + { + if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) + G_StopFollowing( &g_entities[ i ] ); + } + else + G_FollowLockView( &g_entities[ i ] ); + } + } +} + +static qboolean G_IsEmoticon( const char *s, qboolean *escaped ) +{ + int i, j; + const char *p = s; + char emoticon[ MAX_EMOTICON_NAME_LEN ] = {""}; + qboolean escape = qfalse; + + if( *p != '[' ) + return qfalse; + p++; + if( *p == '[' ) + { + escape = qtrue; + p++; + } + i = 0; + while( *p && i < ( MAX_EMOTICON_NAME_LEN - 1 ) ) + { + if( *p == ']' ) + { + for( j = 0; j < level.emoticonCount; j++ ) + { + if( !Q_stricmp( emoticon, level.emoticons[ j ].name ) ) + { + *escaped = escape; + return qtrue; + } + } + return qfalse; + } + emoticon[ i++ ] = *p; + emoticon[ i ] = '\0'; + p++; + } + return qfalse; +} + +/* +=========== +G_ClientCleanName +============ +*/ +static void G_ClientCleanName( const char *in, char *out, int outSize ) +{ + int len, colorlessLen; + char *p; + int spaces; + qboolean escaped; + qboolean invalid = qfalse; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + for( ; *in; in++ ) + { + // don't allow leading spaces + if( colorlessLen == 0 && *in == ' ' ) + continue; + + // don't allow nonprinting characters or (dead) console keys + if( *in < ' ' || *in > '}' || *in == '`' ) + continue; + + // check colors + if( Q_IsColorString( in ) ) + { + in++; + + // make sure room in dest for both chars + if( len > outSize - 2 ) + break; + + *out++ = Q_COLOR_ESCAPE; + + *out++ = *in; + + len += 2; + continue; + } + else if( !g_emoticonsAllowedInNames.integer && G_IsEmoticon( in, &escaped ) ) + { + // make sure room in dest for both chars + if( len > outSize - 2 ) + break; + + *out++ = '['; + *out++ = '['; + len += 2; + if( escaped ) + in++; + continue; + } + + // don't allow too many consecutive spaces + if( *in == ' ' ) + { + spaces++; + if( spaces > 3 ) + continue; + } + else + spaces = 0; + + if( len > outSize - 1 ) + break; + + *out++ = *in; + colorlessLen++; + len++; + } + + *out = 0; + + // don't allow names beginning with "[skipnotify]" because it messes up /ignore-related code + if( !Q_stricmpn( p, "[skipnotify]", 12 ) ) + invalid = qtrue; + + // don't allow comment-beginning strings because it messes up various parsers + if( strstr( p, "//" ) || strstr( p, "/*" ) ) + invalid = qtrue; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) + invalid = qtrue; + + // if something made the name bad, put them back to UnnamedPlayer + if( invalid ) + Q_strncpyz( p, "UnnamedPlayer", outSize ); +} + + +/* +====================== +G_NonSegModel + +Reads an animation.cfg to check for nonsegmentation +====================== +*/ +static qboolean G_NonSegModel( const char *filename ) +{ + char *text_p; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( !f ) + { + G_Printf( "File not found: %s\n", filename ); + return qfalse; + } + + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + G_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + //EOF + if( !token[ 0 ] ) + break; + + if( !Q_stricmp( token, "nonsegmented" ) ) + return qtrue; + } + + return qfalse; +} + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +char *ClientUserinfoChanged( int clientNum, qboolean forceName ) +{ + gentity_t *ent; + char *s; + char model[ MAX_QPATH ]; + char buffer[ MAX_QPATH ]; + char filename[ MAX_QPATH ]; + char oldname[ MAX_NAME_LENGTH ]; + char newname[ MAX_NAME_LENGTH ]; + char err[ MAX_STRING_CHARS ]; + qboolean revertName = qfalse; + gclient_t *client; + char userinfo[ MAX_INFO_STRING ]; + + ent = g_entities + clientNum; + client = ent->client; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if( !Info_Validate(userinfo) ) + { + trap_SendServerCommand( ent - g_entities, + "disconnect \"illegal or malformed userinfo\n\"" ); + trap_DropClient( ent - g_entities, + "dropped: illegal or malformed userinfo"); + return "Illegal or malformed userinfo"; + } + // If their userinfo overflowed, tremded is in the process of disconnecting them. + // If we send our own disconnect, it won't work, so just return to prevent crashes later + // in this function. This check must come after the Info_Validate call. + else if( !userinfo[ 0 ] ) + return "Empty (overflowed) userinfo"; + + // stickyspec toggle + s = Info_ValueForKey( userinfo, "cg_stickySpec" ); + client->pers.stickySpec = atoi( s ) != 0; + + // set name + Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey( userinfo, "name" ); + G_ClientCleanName( s, newname, sizeof( newname ) ); + + if( strcmp( oldname, newname ) ) + { + if( !forceName && client->pers.namelog->nameChangeTime && + level.time - client->pers.namelog->nameChangeTime <= + g_minNameChangePeriod.value * 1000 ) + { + trap_SendServerCommand( ent - g_entities, va( + "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", + g_minNameChangePeriod.integer ) ); + revertName = qtrue; + } + else if( !forceName && g_maxNameChanges.integer > 0 && + client->pers.namelog->nameChanges >= g_maxNameChanges.integer ) + { + trap_SendServerCommand( ent - g_entities, va( + "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", + g_maxNameChanges.integer ) ); + revertName = qtrue; + } + else if( !forceName && client->pers.namelog->muted ) + { + trap_SendServerCommand( ent - g_entities, + "print \"You cannot change your name while you are muted\n\"" ); + revertName = qtrue; + } + else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) + { + trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) ); + revertName = qtrue; + } + + if( revertName ) + { + Q_strncpyz( client->pers.netname, *oldname ? oldname : "UnnamedPlayer", + sizeof( client->pers.netname ) ); + Info_SetValueForKey( userinfo, "name", oldname ); + trap_SetUserinfo( clientNum, userinfo ); + } + else + { + G_CensorString( client->pers.netname, newname, + sizeof( client->pers.netname ), ent ); + if( !forceName && client->pers.connected == CON_CONNECTED ) + { + client->pers.namelog->nameChangeTime = level.time; + client->pers.namelog->nameChanges++; + } + if( *oldname ) + { + G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\" \"%c%s%c^7\"\n", + clientNum, client->pers.ip.str, client->pers.guid, + oldname, client->pers.netname, + DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); + } + } + G_namelog_update_name( client ); + } + + if( client->pers.classSelection == PCL_NONE ) + { + //This looks hacky and frankly it is. The clientInfo string needs to hold different + //model details to that of the spawning class or the info change will not be + //registered and an axis appears instead of the player model. There is zero chance + //the player can spawn with the battlesuit, hence this choice. + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( PCL_HUMAN_BSUIT )->modelName, + BG_ClassConfig( PCL_HUMAN_BSUIT )->skinName ); + } + else + { + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( client->pers.classSelection )->modelName, + BG_ClassConfig( client->pers.classSelection )->skinName ); + + //model segmentation + Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", + BG_ClassConfig( client->pers.classSelection )->modelName ); + + if( G_NonSegModel( filename ) ) + client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; + } + Q_strncpyz( model, buffer, sizeof( model ) ); + + // wallwalk follow + s = Info_ValueForKey( userinfo, "cg_wwFollow" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW; + + // wallwalk toggle + s = Info_ValueForKey( userinfo, "cg_wwToggle" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; + + // always sprint + s = Info_ValueForKey( userinfo, "cg_sprintToggle" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_SPRINTTOGGLE; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_SPRINTTOGGLE; + + // fly speed + s = Info_ValueForKey( userinfo, "cg_flySpeed" ); + + if( *s ) + client->pers.flySpeed = atoi( s ); + else + client->pers.flySpeed = BG_Class( PCL_NONE )->speed; + + // disable blueprint errors + s = Info_ValueForKey( userinfo, "cg_disableBlueprintErrors" ); + + if( atoi( s ) ) + client->pers.disableBlueprintErrors = qtrue; + else + client->pers.disableBlueprintErrors = qfalse; + + // teamInfo + s = Info_ValueForKey( userinfo, "teamoverlay" ); + + if( atoi( s ) != 0 ) + client->pers.teamInfo = qtrue; + else + client->pers.teamInfo = qfalse; + + s = Info_ValueForKey( userinfo, "cg_unlagged" ); + if( !s[0] || atoi( s ) != 0 ) + client->pers.useUnlagged = qtrue; + else + client->pers.useUnlagged = qfalse; + + Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), + sizeof( client->pers.voice ) ); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + + Com_sprintf( userinfo, sizeof( userinfo ), + "n\\%s\\t\\%i\\model\\%s\\ig\\%16s\\v\\%s", + client->pers.netname, client->pers.teamSelection, model, + Com_ClientListString( &client->sess.ignoreList ), + client->pers.voice ); + + trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); + + /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ + + return NULL; +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime ) +{ + char *value; + char *userInfoError; + gclient_t *client; + char userinfo[ MAX_INFO_STRING ]; + gentity_t *ent; + char reason[ MAX_STRING_CHARS ] = {""}; + int i; + + ent = &g_entities[ clientNum ]; + client = &level.clients[ clientNum ]; + + // ignore if client already connected + if( client->pers.connected != CON_DISCONNECTED ) + return NULL; + + ent->client = client; + memset( client, 0, sizeof( *client ) ); + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + value = Info_ValueForKey( userinfo, "cl_guid" ); + Q_strncpyz( client->pers.guid, value, sizeof( client->pers.guid ) ); + + value = Info_ValueForKey( userinfo, "ip" ); + // check for local client + if( !strcmp( value, "localhost" ) ) + client->pers.localClient = qtrue; + G_AddressParse( value, &client->pers.ip ); + + client->pers.admin = G_admin_admin( client->pers.guid ); + + // check for admin ban + if( G_admin_ban_check( ent, reason, sizeof( reason ) ) ) + { + return va( "%s", reason ); + } + + // check for a password + value = Info_ValueForKey( userinfo, "password" ); + + if( g_password.string[ 0 ] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value ) != 0 ) + return "Invalid password"; + + // add guid to session so we don't have to keep parsing userinfo everywhere + for( i = 0; i < sizeof( client->pers.guid ) - 1 && + isxdigit( client->pers.guid[ i ] ); i++ ); + + if( i < sizeof( client->pers.guid ) - 1 ) + return "Invalid GUID"; + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + + if( !Q_stricmp( client->pers.guid, level.clients[ i ].pers.guid ) ) + { + if( !G_ClientIsLagging( level.clients + i ) ) + { + trap_SendServerCommand( i, "cp \"Your GUID is not secure\"" ); + return "Duplicate GUID"; + } + trap_DropClient( i, "Ghost" ); + } + } + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if( firstTime || level.newSession ) + G_InitSessionData( client, userinfo ); + + G_ReadSessionData( client ); + + // get and distribute relevent paramters + G_namelog_connect( client ); + userInfoError = ClientUserinfoChanged( clientNum, qfalse ); + if( userInfoError != NULL ) + return userInfoError; + + G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%c%s%c^7\"\n", + clientNum, client->pers.ip.str, client->pers.guid, + client->pers.netname, + DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if( firstTime ) + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", + client->pers.netname ) ); + + if( client->pers.admin ) + G_admin_authlog( ent ); + + // count current clients and rank for scoreboard + CalculateRanks( ); + + + // if this is after !restart keepteams or !restart switchteams, apply said selection + if ( client->sess.restartTeam != TEAM_NONE ) + { + G_ChangeTeam( ent, client->sess.restartTeam ); + client->sess.restartTeam = TEAM_NONE; + } + + + return NULL; +} + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the level. This will happen every level load, +and on transition between teams, but doesn't happen on respawns +============ +*/ +void ClientBegin( int clientNum ) +{ + gentity_t *ent; + gclient_t *client; + int flags; + + ent = g_entities + clientNum; + + client = level.clients + clientNum; + + // ignore if client already entered the game + if( client->pers.connected != CON_CONNECTING ) + return; + + if( ent->r.linked ) + trap_UnlinkEntity( ent ); + + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + flags = client->ps.eFlags; + memset( &client->ps, 0, sizeof( client->ps ) ); + memset( &client->pmext, 0, sizeof( client->pmext ) ); + client->ps.eFlags = flags; + + // locate ent at a spawn point + ClientSpawn( ent, NULL, NULL, NULL ); + + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); + + G_namelog_restore( client ); + + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks( ); + + // send the client a list of commands that can be used + G_ListCommands( ent ); + + // reset cuboidSelection + client->cuboidSelection[ 0 ] = + client->cuboidSelection[ 1 ] = + client->cuboidSelection[ 2 ] = 32; +} + +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState +============ +*/ +void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) +{ + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[ MAX_PERSISTANT ]; + gentity_t *spawnPoint = NULL, *event; + int flags; + int savedPing; + int teamLocal; + int eventSequence; + char userinfo[ MAX_INFO_STRING ]; + vec3_t up = { 0.0f, 0.0f, 1.0f }, implant_dir, implant_angles, spawnPoint_velocity; + int maxAmmo, maxClips; + weapon_t weapon; + qboolean fromImplant = qfalse, hatchingFailed = qfalse; + + index = ent - g_entities; + client = ent->client; + + teamLocal = client->pers.teamSelection; + + //if client is dead and following teammate, stop following before spawning + if( client->sess.spectatorClient != -1 ) + { + client->sess.spectatorClient = -1; + client->sess.spectatorState = SPECTATOR_FREE; + } + + // only start client if chosen a class and joined a team + if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) + client->sess.spectatorState = SPECTATOR_FREE; + else if( client->pers.classSelection == PCL_NONE ) + client->sess.spectatorState = SPECTATOR_LOCKED; + + // if client is dead and following teammate, stop following before spawning + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + + if( origin != NULL ) + VectorCopy( origin, spawn_origin ); + + if( angles != NULL ) + VectorCopy( angles, spawn_angles ); + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if( client->sess.spectatorState != SPECTATOR_NOT ) + { + if( teamLocal == TEAM_NONE ) + spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); + else if( teamLocal == TEAM_ALIENS ) + spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( teamLocal == TEAM_HUMANS ) + spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + } + else + { + if( spawn == NULL ) + { + G_Error( "ClientSpawn: spawn is NULL\n" ); + return; + } + + spawnPoint = spawn; + + if( ent != spawn ) + { + if( !spawnPoint->client ) //might be a human + { + //start spawn animation on spawnPoint + G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); + + if( spawnPoint->buildableTeam == TEAM_ALIENS ) + spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; + else if( spawnPoint->buildableTeam == TEAM_HUMANS ) + spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; + } + else + { + qboolean crouch; + int i; + float zoffs = 0.0f; + trace_t tr; + vec3_t neworigin, mins, maxs; + + fromImplant = qtrue; //spawning from a human + + // move the origin a bit on the Z axis so the aliens jumps out of the chest, not knees + // also prevents grangers from getting stuck in ceilings and floors + crouch = spawnPoint->client->ps.pm_flags & PMF_DUCKED; + switch( client->pers.classSelection ) + { + case PCL_ALIEN_BUILDER0: + if( !crouch ) + zoffs = 19.0f; + else + zoffs = -4.0f; + break; + case PCL_ALIEN_BUILDER0_UPG: + if( !crouch ) + zoffs = 16.5f; + else + zoffs = -4.0f; + break; + case PCL_ALIEN_LEVEL0: + if( !crouch ) + zoffs = 15.0f; + else + zoffs = -9.1f; + break; + } + spawn_origin[ 2 ] += zoffs; + + // check if the spot would block + BG_ClassBoundingBox( client->pers.classSelection, mins, maxs, NULL, NULL, NULL ); + trap_Trace( &tr, spawn_origin, mins, maxs, spawn_origin, spawnPoint->s.number, MASK_PLAYERSOLID ); + + // try to unblock the player + if( tr.startsolid ) + { + Com_Printf("DEBUG: player is stuck!\n"); + for( i = 0; i < 16*2; i++ ) + { + float a, r; + + VectorCopy( spawn_origin, neworigin ); + + a = (float)i / 16.0f * 2.0f * M_PI; + #define fmod(a,n) ((a)-(n)*floor((a)/(n))) + r = ( i < 16 ? 5.5f : 11.0f ) * 1.0f / cos( fmod( a+0.25f*M_PI, 0.5f*M_PI ) - 0.25f*M_PI ); + neworigin[ 0 ] += cos( a ) * r; + neworigin[ 1 ] += sin( a ) * r; + + trap_Trace( &tr, neworigin, mins, maxs, neworigin, spawnPoint->s.number, MASK_PLAYERSOLID ); + + if( !tr.startsolid ) + { + Com_Printf("DEBUG: player position fixed at iteration %i\n",i); + VectorCopy( neworigin, spawn_origin ); + break; + } + } + } + + //reward the player that implanted this one + if( spawnPoint->client->impregnatedBy >= 0 ) + { + gentity_t *granger; + + granger = &g_entities[ spawnPoint->client->impregnatedBy ]; + G_AddCreditToClient( granger->client, ALIEN_IMPREGNATION_REWARD, qtrue ); + AddScore( granger, ALIEN_IMPREGNATION_REWARD_SCORE ); + } + + // kill the human, set up angles and velocity for the new alien + if( !BG_InventoryContainsUpgrade( UP_BATTLESUIT, spawnPoint->client->ps.stats ) //humans without battlesuits always die + || spawnPoint->client->ps.stats[ STAT_HEALTH ] < ALIEN_HATCHING_MAX_BATTLESUIT_HEALTH ) //battlesuits survive if high hp + { + //save viewangles and spawn velocity for velocity calculation + VectorCopy( spawnPoint->client->ps.viewangles, implant_angles ); + AngleVectors( implant_angles, implant_dir, NULL, NULL ); + VectorCopy( spawnPoint->client->ps.velocity, spawnPoint_velocity ); + + //fire a nice chest exploding effect + event = G_TempEntity( spawnPoint->s.pos.trBase, EV_ALIEN_HATCH ); + VectorCopy( implant_dir, event->s.angles ); + + //kill the player + G_Damage( spawnPoint, NULL, ent, NULL, NULL, spawnPoint->client->ps.stats[ STAT_HEALTH ], DAMAGE_NO_ARMOR, MOD_ALIEN_HATCH ); + } + else //human survives + { + //clear impregnation so the human won't explode again + spawnPoint->client->isImpregnated = qfalse; + spawnPoint->client->isImplantMature = qfalse; + + //make a sound + event = G_TempEntity( spawnPoint->s.pos.trBase, EV_ALIEN_HATCH_FAILURE ); + + //damage the human + G_Damage( spawnPoint, NULL, ent, NULL, NULL, ALIEN_FAILED_HATCH_DAMAGE, DAMAGE_NO_ARMOR, MOD_ALIEN_HATCH ); + + //kill the newly spawned alien + VectorCopy( spawnPoint->client->ps.viewangles, implant_angles ); + implant_dir[0] = 0.0f; + implant_dir[1] = 0.0f; + implant_dir[2] = 0.0f; + hatchingFailed = qtrue; + } + } + } + } + + // toggle the teleport bit so the client knows to not lerp + flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; + G_UnlaggedClear( ent ); + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; + + for( i = 0; i < MAX_PERSISTANT; i++ ) + persistant[ i ] = client->ps.persistant[ i ]; + + eventSequence = client->ps.eventSequence; + memset( client, 0, sizeof( *client ) ); + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; + client->lastkilled_client = -1; + + for( i = 0; i < MAX_PERSISTANT; i++ ) + client->ps.persistant[ i ] = persistant[ i ]; + + client->ps.eventSequence = eventSequence; + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[ PERS_SPAWN_COUNT ]++; + client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; + + client->airOutTime = level.time + 12000; + + trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); + client->ps.eFlags = flags; + + //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[ index ]; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "player"; + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + // calculate each client's acceleration + ent->evaluateAcceleration = qtrue; + + client->ps.stats[ STAT_MISC ] = 0; + client->buildTimer = 0; + + client->ps.eFlags = flags; + client->ps.clientNum = index; + + BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); + + if( client->sess.spectatorState == SPECTATOR_NOT ) + client->ps.stats[ STAT_MAX_HEALTH ] = + BG_Class( ent->client->pers.classSelection )->health; + else + client->ps.stats[ STAT_MAX_HEALTH ] = 100; + + // clear entity values + if( ent->client->pers.classSelection == PCL_HUMAN ) + { + BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); + weapon = client->pers.humanItemSelection; + } + else if( client->sess.spectatorState == SPECTATOR_NOT ) + weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; + else + weapon = WP_NONE; + + maxAmmo = BG_Weapon( weapon )->maxAmmo; + maxClips = BG_Weapon( weapon )->maxClips; + client->ps.stats[ STAT_WEAPON ] = weapon; + client->ps.ammo = maxAmmo; + client->ps.clips = maxClips; + + // We just spawned, not changing weapons + client->ps.persistant[ PERS_NEWWEAPON ] = 0; + + ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; + + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + ent->client->ps.stats[ STAT_STATE ] = 0; + VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); + + // health will count down towards max_health + ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; + + //if evolving scale health + if( ent == spawn ) + { + ent->health *= ent->client->pers.evolveHealthFraction; + client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; + } + + //clear the credits array + for( i = 0; i < MAX_CLIENTS; i++ ) + ent->credits[ i ] = 0; + + client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + + //never impregnated after respawning + client->isImpregnated = qfalse; + client->isImplantMature = qfalse; + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + +#define UP_VEL 150.0f +#define F_VEL 50.0f + + //give aliens some spawn velocity + if( client->sess.spectatorState == SPECTATOR_NOT && + client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + if( ent == spawn ) + { + //evolution particle system + G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); + } + else if( !fromImplant ) //regular egg + { + spawn_angles[ YAW ] += 180.0f; + AngleNormalize360( spawn_angles[ YAW ] ); + + if( spawnPoint->s.origin2[ 2 ] > 0.0f ) + { + vec3_t forward, dir; + + AngleVectors( spawn_angles, forward, NULL, NULL ); + VectorScale( forward, F_VEL, forward ); + VectorAdd( spawnPoint->s.origin2, forward, dir ); + VectorNormalize( dir ); + + VectorScale( dir, UP_VEL, client->ps.velocity ); + } + + G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); + } + else //implanted egg + { + VectorCopy( implant_angles, spawn_angles ); + VectorScale( implant_dir, ALIEN_HATCHING_VELOCITY, client->ps.velocity ); + VectorAdd( client->ps.velocity, spawnPoint_velocity, client->ps.velocity ); + } + } + else if( client->sess.spectatorState == SPECTATOR_NOT && + client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + spawn_angles[ YAW ] += 180.0f; + AngleNormalize360( spawn_angles[ YAW ] ); + } + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + G_SetClientViewAngle( ent, spawn_angles ); + + if( client->sess.spectatorState == SPECTATOR_NOT ) + { + trap_LinkEntity( ent ); + + // force the base weapon up + if( client->pers.teamSelection == TEAM_HUMANS ) + G_ForceWeaponChange( ent, weapon ); + + client->ps.weaponstate = WEAPON_READY; + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + ent->nextRegenTime = level.time; + + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + // set default animations + client->ps.torsoAnim = TORSO_STAND; + client->ps.legsAnim = LEGS_IDLE; + + if( level.intermissiontime ) + MoveClientToIntermission( ent ); + else + { + // fire the targets of the spawn point + if( !spawn ) + G_UseTargets( spawnPoint, ent ); + + // select the highest weapon number available, after any + // spawn given items have fired + client->ps.weapon = 1; + + for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) + { + if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) + { + client->ps.weapon = i; + break; + } + } + } + + client->lastRantBombTime = level.time; + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities ); + + // positively link the client, even if the command times are weird + if( client->sess.spectatorState == SPECTATOR_NOT ) + { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } + + // must do this here so the number of active clients is calculated + CalculateRanks( ); + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + + // kill him instantly after respawning if hatching failed + if( fromImplant && hatchingFailed ) + { + VectorCopy( spawnPoint->client->ps.velocity, client->ps.velocity ); + client->ps.stats[ STAT_HEALTH ] = ent->health = 0; + player_die( ent, NULL, spawnPoint, 0, MOD_ALIEN_HATCH_FAILED ); + } + +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) +{ + gclient_t *client; + gentity_t *ent; + gentity_t *tent; + int i; + + ent = g_entities + clientNum; + + if( !ent->client || ent->client->pers.connected == CON_DISCONNECTED ) + return; + + G_LeaveTeam( ent ); + G_namelog_disconnect( ent->client ); + G_Vote( ent, TEAM_NONE, qfalse ); + + // stop any following clients + for( i = 0; i < level.maxclients; i++ ) + { + client = &level.clients[ i ]; + + // remove any /ignore settings for this clientNum + Com_ClientListRemove( &client->sess.ignoreList, clientNum ); + + // clear impregnatedBy for everyone impregnated by this player + if( client->isImpregnated && client->impregnatedBy == clientNum ) + client->impregnatedBy = -2; + } + + // send effect if they were completely connected + if( ent->client->pers.connected == CON_CONNECTED && + ent->client->sess.spectatorState == SPECTATOR_NOT ) + { + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + } + + G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s^7\"\n", clientNum, + ent->client->pers.ip.str, ent->client->pers.guid, ent->client->pers.netname ); + + trap_UnlinkEntity( ent ); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_NOT; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks( ); +} + +/* +=========== +G_RelayCuboidToSpectators + +Called everytime a player changes his cuboid size. +A server command is issued to everyone spectating him +so that their clients can know the cuboid size as well. +============ +*/ +void G_RelayCuboidToSpectators(gentity_t *self) +{ +} + diff --git a/src/game/g_client.c.orig b/src/game/g_client.c.orig new file mode 100644 index 0000000..735c59d --- /dev/null +++ b/src/game/g_client.c.orig @@ -0,0 +1,1600 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, -24}; +static vec3_t playerMaxs = {15, 15, 32}; + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_deathmatch( gentity_t *ent ) +{ + int i; + + G_SpawnInt( "nobots", "0", &i); + + if( i ) + ent->flags |= FL_NO_BOTS; + + G_SpawnInt( "nohumans", "0", &i ); + if( i ) + ent->flags |= FL_NO_HUMANS; +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +equivelant to info_player_deathmatch +*/ +void SP_info_player_start( gentity_t *ent ) +{ + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) +{ +} + +/*QUAKED info_alien_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_alien_intermission( gentity_t *ent ) +{ +} + +/*QUAKED info_human_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_human_intermission( gentity_t *ent ) +{ +} + +/* +=============== +G_AddCreditToClient +=============== +*/ +void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ) +{ + int capAmount; + + if( !client ) + return; + + if( cap && credit > 0 ) + { + capAmount = client->pers.teamSelection == TEAM_ALIENS ? + ALIEN_MAX_CREDITS : HUMAN_MAX_CREDITS; + if( client->pers.credit < capAmount ) + { + client->pers.credit += credit; + if( client->pers.credit > capAmount ) + client->pers.credit = capAmount; + } + } + else + client->pers.credit += credit; + + if( client->pers.credit < 0 ) + client->pers.credit = 0; + + // Copy to ps so the client can access it + client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; +} + + +/* +======================================================================= + + G_SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) +{ + int i, num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + //if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if( hit->client ) + return qtrue; + } + + return qfalse; +} + + +/* +=========== +G_SelectRandomFurthestSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +static gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[ 64 ]; + gentity_t *list_spot[ 64 ]; + int numSpots, rnd, i, j; + + numSpots = 0; + spot = NULL; + + while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) + { + if( SpotWouldTelefrag( spot ) ) + continue; + + VectorSubtract( spot->s.origin, avoidPoint, delta ); + dist = VectorLength( delta ); + + for( i = 0; i < numSpots; i++ ) + { + if( dist > list_dist[ i ] ) + { + if( numSpots >= 64 ) + numSpots = 64 - 1; + + for( j = numSpots; j > i; j-- ) + { + list_dist[ j ] = list_dist[ j - 1 ]; + list_spot[ j ] = list_spot[ j - 1 ]; + } + + list_dist[ i ] = dist; + list_spot[ i ] = spot; + numSpots++; + + if( numSpots > 64 ) + numSpots = 64; + + break; + } + } + + if( i >= numSpots && numSpots < 64 ) + { + list_dist[ numSpots ] = dist; + list_spot[ numSpots ] = spot; + numSpots++; + } + } + + if( !numSpots ) + { + spot = G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); + + if( !spot ) + G_Error( "Couldn't find a spawn point" ); + + VectorCopy( spot->s.origin, origin ); + origin[ 2 ] += 9; + VectorCopy( spot->s.angles, angles ); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random( ) * ( numSpots / 2 ); + + VectorCopy( list_spot[ rnd ]->s.origin, origin ); + origin[ 2 ] += 9; + VectorCopy( list_spot[ rnd ]->s.angles, angles ); + + return list_spot[ rnd ]; +} + + +/* +================ +G_SelectSpawnBuildable + +find the nearest buildable of the right type that is +spawned/healthy/unblocked etc. +================ +*/ +static gentity_t *G_SelectSpawnBuildable( vec3_t preference, buildable_t buildable ) +{ + gentity_t *search, *spot; + + search = spot = NULL; + + while( ( search = G_Find( search, FOFS( classname ), + BG_Buildable( buildable, NULL )->entityName ) ) != NULL ) + { + if( !search->spawned ) + continue; + + if( search->health <= 0 ) + continue; + + if( !search->s.groundEntityNum ) + continue; + + if( search->clientSpawnTime > 0 ) + continue; + + if( G_CheckSpawnPoint( search->s.number, search->s.origin, + search->s.origin2, buildable, NULL ) != NULL ) + continue; + + if( !spot || DistanceSquared( preference, search->s.origin ) < + DistanceSquared( preference, spot->s.origin ) ) + spot = search; + } + + return spot; +} + +/* +=========== +G_SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +{ + return G_SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); +} + + +/* +=========== +G_SelectTremulousSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles ) +{ + gentity_t *spot = NULL; + + if( team == TEAM_ALIENS ) + { + if( level.numAlienSpawns <= 0 ) + return NULL; + + spot = G_SelectSpawnBuildable( preference, BA_A_SPAWN ); + } + else if( team == TEAM_HUMANS ) + { + if( level.numHumanSpawns <= 0 ) + return NULL; + + spot = G_SelectSpawnBuildable( preference, BA_H_SPAWN ); + } + + //no available spots + if( !spot ) + return NULL; + + if( team == TEAM_ALIENS ) + G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_A_SPAWN, origin ); + else if( team == TEAM_HUMANS ) + G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_H_SPAWN, origin ); + + VectorCopy( spot->s.angles, angles ); + angles[ ROLL ] = 0; + + return spot; + +} + + +/* +=========== +G_SelectSpectatorSpawnPoint + +============ +*/ +static gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) +{ + FindIntermissionPoint( ); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + + +/* +=========== +G_SelectAlienLockSpawnPoint + +Try to find a spawn point for alien intermission otherwise +use normal intermission spawn. +============ +*/ +gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + + spot = NULL; + spot = G_Find( spot, FOFS( classname ), "info_alien_intermission" ); + + if( !spot ) + return G_SelectSpectatorSpawnPoint( origin, angles ); + + VectorCopy( spot->s.origin, origin ); + VectorCopy( spot->s.angles, angles ); + + return spot; +} + + +/* +=========== +G_SelectHumanLockSpawnPoint + +Try to find a spawn point for human intermission otherwise +use normal intermission spawn. +============ +*/ +gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + + spot = NULL; + spot = G_Find( spot, FOFS( classname ), "info_human_intermission" ); + + if( !spot ) + return G_SelectSpectatorSpawnPoint( origin, angles ); + + VectorCopy( spot->s.origin, origin ); + VectorCopy( spot->s.angles, angles ); + + return spot; +} + + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +static void BodySink( gentity_t *ent ) +{ + //run on first BodySink call + if( !ent->active ) + { + ent->active = qtrue; + + //sinking bodies can't be infested + ent->killedBy = ent->s.misc = MAX_CLIENTS; + ent->timestamp = level.time; + } + + if( level.time - ent->timestamp > 6500 ) + { + G_FreeEntity( ent ); + return; + } + + ent->nextthink = level.time + 100; + ent->s.pos.trBase[ 2 ] -= 1; +} + + +/* +============= +SpawnCorpse + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +static void SpawnCorpse( gentity_t *ent ) +{ + gentity_t *body; + int contents; + vec3_t origin, dest; + trace_t tr; + float vDiff; + + VectorCopy( ent->r.currentOrigin, origin ); + + trap_UnlinkEntity( ent ); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( origin, -1 ); + if( contents & CONTENTS_NODROP ) + return; + + body = G_Spawn( ); + + VectorCopy( ent->s.apos.trBase, body->s.angles ); + body->s.eFlags = EF_DEAD; + body->s.eType = ET_CORPSE; + body->s.number = body - g_entities; + body->timestamp = level.time; + body->s.event = 0; + body->r.contents = CONTENTS_CORPSE; + body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ]; + body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; + + if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + body->classname = "humanCorpse"; + else + body->classname = "alienCorpse"; + + body->s.misc = MAX_CLIENTS; + + body->think = BodySink; + body->nextthink = level.time + 20000; + + body->s.legsAnim = ent->s.legsAnim; + + if( !body->nonSegModel ) + { + switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) + { + case BOTH_DEATH1: + case BOTH_DEAD1: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; + break; + case BOTH_DEATH3: + case BOTH_DEAD3: + default: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; + break; + } + } + else + { + switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) + { + case NSPA_DEATH1: + case NSPA_DEAD1: + body->s.legsAnim = NSPA_DEAD1; + break; + case NSPA_DEATH2: + case NSPA_DEAD2: + body->s.legsAnim = NSPA_DEAD2; + break; + case NSPA_DEATH3: + case NSPA_DEAD3: + default: + body->s.legsAnim = NSPA_DEAD3; + break; + } + } + + body->takedamage = qfalse; + + body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; + ent->health = 0; + + //change body dimensions + BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); + vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; + + //drop down to match the *model* origins of ent and body + VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); + trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); + VectorCopy( tr.endpos, origin ); + + G_SetOrigin( body, origin ); + VectorCopy( origin, body->s.origin ); + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + + VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); + trap_LinkEntity( body ); +} + +//====================================================================== + + +/* +================== +G_SetClientViewAngle + +================== +*/ +void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ) +{ + int i; + + // set the delta angle + for( i = 0; i < 3; i++ ) + { + int cmdAngle; + + cmdAngle = ANGLE2SHORT( angle[ i ] ); + ent->client->ps.delta_angles[ i ] = cmdAngle - ent->client->pers.cmd.angles[ i ]; + } + + VectorCopy( angle, ent->s.angles ); + VectorCopy( ent->s.angles, ent->client->ps.viewangles ); +} + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) +{ + int i; + + SpawnCorpse( ent ); + + // Clients can't respawn - they must go through the class cmd + ent->client->pers.classSelection = PCL_NONE; + ClientSpawn( ent, NULL, NULL, NULL ); + + // stop any following clients that don't have sticky spec on + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && + level.clients[ i ].sess.spectatorClient == ent - g_entities ) + { + if( !( level.clients[ i ].pers.stickySpec ) ) + { + if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) + G_StopFollowing( &g_entities[ i ] ); + } + else + G_FollowLockView( &g_entities[ i ] ); + } + } +} + +static qboolean G_IsEmoticon( const char *s, qboolean *escaped ) +{ + int i, j; + const char *p = s; + char emoticon[ MAX_EMOTICON_NAME_LEN ] = {""}; + qboolean escape = qfalse; + + if( *p != '[' ) + return qfalse; + p++; + if( *p == '[' ) + { + escape = qtrue; + p++; + } + i = 0; + while( *p && i < ( MAX_EMOTICON_NAME_LEN - 1 ) ) + { + if( *p == ']' ) + { + for( j = 0; j < level.emoticonCount; j++ ) + { + if( !Q_stricmp( emoticon, level.emoticons[ j ].name ) ) + { + *escaped = escape; + return qtrue; + } + } + return qfalse; + } + emoticon[ i++ ] = *p; + emoticon[ i ] = '\0'; + p++; + } + return qfalse; +} + +/* +=========== +G_ClientCleanName +============ +*/ +static void G_ClientCleanName( const char *in, char *out, int outSize ) +{ + int len, colorlessLen; + char *p; + int spaces; + qboolean escaped; + qboolean invalid = qfalse; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + for( ; *in; in++ ) + { + // don't allow leading spaces + if( colorlessLen == 0 && *in == ' ' ) + continue; + + // don't allow nonprinting characters or (dead) console keys + if( *in < ' ' || *in > '}' || *in == '`' ) + continue; + + // check colors + if( Q_IsColorString( in ) ) + { + in++; + + // make sure room in dest for both chars + if( len > outSize - 2 ) + break; + + *out++ = Q_COLOR_ESCAPE; + + *out++ = *in; + + len += 2; + continue; + } + else if( !g_emoticonsAllowedInNames.integer && G_IsEmoticon( in, &escaped ) ) + { + // make sure room in dest for both chars + if( len > outSize - 2 ) + break; + + *out++ = '['; + *out++ = '['; + len += 2; + if( escaped ) + in++; + continue; + } + + // don't allow too many consecutive spaces + if( *in == ' ' ) + { + spaces++; + if( spaces > 3 ) + continue; + } + else + spaces = 0; + + if( len > outSize - 1 ) + break; + + *out++ = *in; + colorlessLen++; + len++; + } + + *out = 0; + + // don't allow names beginning with "[skipnotify]" because it messes up /ignore-related code + if( !Q_stricmpn( p, "[skipnotify]", 12 ) ) + invalid = qtrue; + + // don't allow comment-beginning strings because it messes up various parsers + if( strstr( p, "//" ) || strstr( p, "/*" ) ) + invalid = qtrue; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) + invalid = qtrue; + + // if something made the name bad, put them back to UnnamedPlayer + if( invalid ) + Q_strncpyz( p, "UnnamedPlayer", outSize ); +} + + +/* +====================== +G_NonSegModel + +Reads an animation.cfg to check for nonsegmentation +====================== +*/ +static qboolean G_NonSegModel( const char *filename ) +{ + char *text_p; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( !f ) + { + G_Printf( "File not found: %s\n", filename ); + return qfalse; + } + + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + G_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + //EOF + if( !token[ 0 ] ) + break; + + if( !Q_stricmp( token, "nonsegmented" ) ) + return qtrue; + } + + return qfalse; +} + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +char *ClientUserinfoChanged( int clientNum, qboolean forceName ) +{ + gentity_t *ent; + char *s; + char model[ MAX_QPATH ]; + char buffer[ MAX_QPATH ]; + char filename[ MAX_QPATH ]; + char oldname[ MAX_NAME_LENGTH ]; + char newname[ MAX_NAME_LENGTH ]; + char err[ MAX_STRING_CHARS ]; + qboolean revertName = qfalse; + gclient_t *client; + char userinfo[ MAX_INFO_STRING ]; + + ent = g_entities + clientNum; + client = ent->client; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if( !Info_Validate(userinfo) ) + { + trap_SendServerCommand( ent - g_entities, + "disconnect \"illegal or malformed userinfo\n\"" ); + trap_DropClient( ent - g_entities, + "dropped: illegal or malformed userinfo"); + return "Illegal or malformed userinfo"; + } + // If their userinfo overflowed, tremded is in the process of disconnecting them. + // If we send our own disconnect, it won't work, so just return to prevent crashes later + // in this function. This check must come after the Info_Validate call. + else if( !userinfo[ 0 ] ) + return "Empty (overflowed) userinfo"; + + // stickyspec toggle + s = Info_ValueForKey( userinfo, "cg_stickySpec" ); + client->pers.stickySpec = atoi( s ) != 0; + + // set name + Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey( userinfo, "name" ); + G_ClientCleanName( s, newname, sizeof( newname ) ); + + if( strcmp( oldname, newname ) ) + { + if( !forceName && client->pers.namelog->nameChangeTime && + level.time - client->pers.namelog->nameChangeTime <= + g_minNameChangePeriod.value * 1000 ) + { + trap_SendServerCommand( ent - g_entities, va( + "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", + g_minNameChangePeriod.integer ) ); + revertName = qtrue; + } + else if( !forceName && g_maxNameChanges.integer > 0 && + client->pers.namelog->nameChanges >= g_maxNameChanges.integer ) + { + trap_SendServerCommand( ent - g_entities, va( + "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", + g_maxNameChanges.integer ) ); + revertName = qtrue; + } + else if( !forceName && client->pers.namelog->muted ) + { + trap_SendServerCommand( ent - g_entities, + "print \"You cannot change your name while you are muted\n\"" ); + revertName = qtrue; + } + else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) + { + trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) ); + revertName = qtrue; + } + + if( revertName ) + { + Q_strncpyz( client->pers.netname, *oldname ? oldname : "UnnamedPlayer", + sizeof( client->pers.netname ) ); + Info_SetValueForKey( userinfo, "name", oldname ); + trap_SetUserinfo( clientNum, userinfo ); + } + else + { + G_CensorString( client->pers.netname, newname, + sizeof( client->pers.netname ), ent ); + if( !forceName && client->pers.connected == CON_CONNECTED ) + { + client->pers.namelog->nameChangeTime = level.time; + client->pers.namelog->nameChanges++; + } + if( *oldname ) + { + G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\" \"%c%s%c^7\"\n", + clientNum, client->pers.ip.str, client->pers.guid, + oldname, client->pers.netname, + DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); + } + } + G_namelog_update_name( client ); + } + + if( client->pers.classSelection == PCL_NONE ) + { + //This looks hacky and frankly it is. The clientInfo string needs to hold different + //model details to that of the spawning class or the info change will not be + //registered and an axis appears instead of the player model. There is zero chance + //the player can spawn with the battlesuit, hence this choice. + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( PCL_HUMAN_BSUIT )->modelName, + BG_ClassConfig( PCL_HUMAN_BSUIT )->skinName ); + } + else + { + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( client->pers.classSelection )->modelName, + BG_ClassConfig( client->pers.classSelection )->skinName ); + + //model segmentation + Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", + BG_ClassConfig( client->pers.classSelection )->modelName ); + + if( G_NonSegModel( filename ) ) + client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; + } + Q_strncpyz( model, buffer, sizeof( model ) ); + + // wallwalk follow + s = Info_ValueForKey( userinfo, "cg_wwFollow" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW; + + // wallwalk toggle + s = Info_ValueForKey( userinfo, "cg_wwToggle" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; + + // always sprint + s = Info_ValueForKey( userinfo, "cg_sprintToggle" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_SPRINTTOGGLE; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_SPRINTTOGGLE; + + // fly speed + s = Info_ValueForKey( userinfo, "cg_flySpeed" ); + + if( *s ) + client->pers.flySpeed = atoi( s ); + else + client->pers.flySpeed = BG_Class( PCL_NONE )->speed; + + // disable blueprint errors + s = Info_ValueForKey( userinfo, "cg_disableBlueprintErrors" ); + + if( atoi( s ) ) + client->pers.disableBlueprintErrors = qtrue; + else + client->pers.disableBlueprintErrors = qfalse; + + // teamInfo + s = Info_ValueForKey( userinfo, "teamoverlay" ); + + if( atoi( s ) != 0 ) + client->pers.teamInfo = qtrue; + else + client->pers.teamInfo = qfalse; + + s = Info_ValueForKey( userinfo, "cg_unlagged" ); + if( !s[0] || atoi( s ) != 0 ) + client->pers.useUnlagged = qtrue; + else + client->pers.useUnlagged = qfalse; + + Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), + sizeof( client->pers.voice ) ); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + + Com_sprintf( userinfo, sizeof( userinfo ), + "n\\%s\\t\\%i\\model\\%s\\ig\\%16s\\v\\%s", + client->pers.netname, client->pers.teamSelection, model, + Com_ClientListString( &client->sess.ignoreList ), + client->pers.voice ); + + trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); + + /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ + + return NULL; +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime ) +{ + char *value; + char *userInfoError; + gclient_t *client; + char userinfo[ MAX_INFO_STRING ]; + gentity_t *ent; + char reason[ MAX_STRING_CHARS ] = {""}; + int i; + + ent = &g_entities[ clientNum ]; + client = &level.clients[ clientNum ]; + + // ignore if client already connected + if( client->pers.connected != CON_DISCONNECTED ) + return NULL; + + ent->client = client; + memset( client, 0, sizeof( *client ) ); + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + value = Info_ValueForKey( userinfo, "cl_guid" ); + Q_strncpyz( client->pers.guid, value, sizeof( client->pers.guid ) ); + + value = Info_ValueForKey( userinfo, "ip" ); + // check for local client + if( !strcmp( value, "localhost" ) ) + client->pers.localClient = qtrue; + G_AddressParse( value, &client->pers.ip ); + + client->pers.admin = G_admin_admin( client->pers.guid ); + + // check for admin ban + if( G_admin_ban_check( ent, reason, sizeof( reason ) ) ) + { + return va( "%s", reason ); + } + + // check for a password + value = Info_ValueForKey( userinfo, "password" ); + + if( g_password.string[ 0 ] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value ) != 0 ) + return "Invalid password"; + + // add guid to session so we don't have to keep parsing userinfo everywhere + for( i = 0; i < sizeof( client->pers.guid ) - 1 && + isxdigit( client->pers.guid[ i ] ); i++ ); + + if( i < sizeof( client->pers.guid ) - 1 ) + return "Invalid GUID"; + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + + if( !Q_stricmp( client->pers.guid, level.clients[ i ].pers.guid ) ) + { + if( !G_ClientIsLagging( level.clients + i ) ) + { + trap_SendServerCommand( i, "cp \"Your GUID is not secure\"" ); + return "Duplicate GUID"; + } + trap_DropClient( i, "Ghost" ); + } + } + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if( firstTime || level.newSession ) + G_InitSessionData( client, userinfo ); + + G_ReadSessionData( client ); + + // get and distribute relevent paramters + G_namelog_connect( client ); + userInfoError = ClientUserinfoChanged( clientNum, qfalse ); + if( userInfoError != NULL ) + return userInfoError; + + G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%c%s%c^7\"\n", + clientNum, client->pers.ip.str, client->pers.guid, + client->pers.netname, + DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if( firstTime ) + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", + client->pers.netname ) ); + + if( client->pers.admin ) + G_admin_authlog( ent ); + + // count current clients and rank for scoreboard + CalculateRanks( ); + + + // if this is after !restart keepteams or !restart switchteams, apply said selection + if ( client->sess.restartTeam != TEAM_NONE ) + { + G_ChangeTeam( ent, client->sess.restartTeam ); + client->sess.restartTeam = TEAM_NONE; + } + + + return NULL; +} + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the level. This will happen every level load, +and on transition between teams, but doesn't happen on respawns +============ +*/ +void ClientBegin( int clientNum ) +{ + gentity_t *ent; + gclient_t *client; + int flags; + + ent = g_entities + clientNum; + + client = level.clients + clientNum; + + // ignore if client already entered the game + if( client->pers.connected != CON_CONNECTING ) + return; + + if( ent->r.linked ) + trap_UnlinkEntity( ent ); + + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + flags = client->ps.eFlags; + memset( &client->ps, 0, sizeof( client->ps ) ); + memset( &client->pmext, 0, sizeof( client->pmext ) ); + client->ps.eFlags = flags; + + // locate ent at a spawn point + ClientSpawn( ent, NULL, NULL, NULL ); + + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); + + G_namelog_restore( client ); + + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks( ); + + // send the client a list of commands that can be used + G_ListCommands( ent ); + + // reset cuboidSelection + client->cuboidSelection[ 0 ] = + client->cuboidSelection[ 1 ] = + client->cuboidSelection[ 2 ] = 32; +} + +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState +============ +*/ +void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) +{ + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[ MAX_PERSISTANT ]; + gentity_t *spawnPoint = NULL; + int flags; + int savedPing; + int teamLocal; + int eventSequence; + char userinfo[ MAX_INFO_STRING ]; + vec3_t up = { 0.0f, 0.0f, 1.0f }; + int maxAmmo, maxClips; + weapon_t weapon; + + index = ent - g_entities; + client = ent->client; + + teamLocal = client->pers.teamSelection; + + //if client is dead and following teammate, stop following before spawning + if( client->sess.spectatorClient != -1 ) + { + client->sess.spectatorClient = -1; + client->sess.spectatorState = SPECTATOR_FREE; + } + + // only start client if chosen a class and joined a team + if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) + client->sess.spectatorState = SPECTATOR_FREE; + else if( client->pers.classSelection == PCL_NONE ) + client->sess.spectatorState = SPECTATOR_LOCKED; + + // if client is dead and following teammate, stop following before spawning + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + + if( origin != NULL ) + VectorCopy( origin, spawn_origin ); + + if( angles != NULL ) + VectorCopy( angles, spawn_angles ); + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if( client->sess.spectatorState != SPECTATOR_NOT ) + { + if( teamLocal == TEAM_NONE ) + spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); + else if( teamLocal == TEAM_ALIENS ) + spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( teamLocal == TEAM_HUMANS ) + spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + } + else + { + if( spawn == NULL ) + { + G_Error( "ClientSpawn: spawn is NULL\n" ); + return; + } + + spawnPoint = spawn; + + if( ent != spawn ) + { + //start spawn animation on spawnPoint + G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); + + if( spawnPoint->buildableTeam == TEAM_ALIENS ) + spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; + else if( spawnPoint->buildableTeam == TEAM_HUMANS ) + spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; + } + } + + // toggle the teleport bit so the client knows to not lerp + flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; + G_UnlaggedClear( ent ); + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; + + for( i = 0; i < MAX_PERSISTANT; i++ ) + persistant[ i ] = client->ps.persistant[ i ]; + + eventSequence = client->ps.eventSequence; + memset( client, 0, sizeof( *client ) ); + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; + client->lastkilled_client = -1; + + for( i = 0; i < MAX_PERSISTANT; i++ ) + client->ps.persistant[ i ] = persistant[ i ]; + + client->ps.eventSequence = eventSequence; + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[ PERS_SPAWN_COUNT ]++; + client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; + + client->airOutTime = level.time + 12000; + + trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); + client->ps.eFlags = flags; + + //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[ index ]; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "player"; + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + // calculate each client's acceleration + ent->evaluateAcceleration = qtrue; + + client->ps.stats[ STAT_MISC ] = 0; + client->buildTimer = 0; + + client->ps.eFlags = flags; + client->ps.clientNum = index; + + BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); + + if( client->sess.spectatorState == SPECTATOR_NOT ) + client->ps.stats[ STAT_MAX_HEALTH ] = + BG_Class( ent->client->pers.classSelection )->health; + else + client->ps.stats[ STAT_MAX_HEALTH ] = 100; + + // clear entity values + if( ent->client->pers.classSelection == PCL_HUMAN ) + { + BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); + weapon = client->pers.humanItemSelection; + } + else if( client->sess.spectatorState == SPECTATOR_NOT ) + weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; + else + weapon = WP_NONE; + + maxAmmo = BG_Weapon( weapon )->maxAmmo; + maxClips = BG_Weapon( weapon )->maxClips; + client->ps.stats[ STAT_WEAPON ] = weapon; + client->ps.ammo = maxAmmo; + client->ps.clips = maxClips; + + // We just spawned, not changing weapons + client->ps.persistant[ PERS_NEWWEAPON ] = 0; + + ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; + + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + ent->client->ps.stats[ STAT_STATE ] = 0; + VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); + + // health will count down towards max_health + ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; + + //if evolving scale health + if( ent == spawn ) + { + ent->health *= ent->client->pers.evolveHealthFraction; + client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; + } + + //clear the credits array + for( i = 0; i < MAX_CLIENTS; i++ ) + ent->credits[ i ] = 0; + + client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + +#define UP_VEL 150.0f +#define F_VEL 50.0f + + //give aliens some spawn velocity + if( client->sess.spectatorState == SPECTATOR_NOT && + client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + if( ent == spawn ) + { + //evolution particle system + G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); + } + else + { + spawn_angles[ YAW ] += 180.0f; + AngleNormalize360( spawn_angles[ YAW ] ); + + if( spawnPoint->s.origin2[ 2 ] > 0.0f ) + { + vec3_t forward, dir; + + AngleVectors( spawn_angles, forward, NULL, NULL ); + VectorScale( forward, F_VEL, forward ); + VectorAdd( spawnPoint->s.origin2, forward, dir ); + VectorNormalize( dir ); + + VectorScale( dir, UP_VEL, client->ps.velocity ); + } + + G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); + } + } + else if( client->sess.spectatorState == SPECTATOR_NOT && + client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + spawn_angles[ YAW ] += 180.0f; + AngleNormalize360( spawn_angles[ YAW ] ); + } + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + G_SetClientViewAngle( ent, spawn_angles ); + + if( client->sess.spectatorState == SPECTATOR_NOT ) + { + trap_LinkEntity( ent ); + + // force the base weapon up + if( client->pers.teamSelection == TEAM_HUMANS ) + G_ForceWeaponChange( ent, weapon ); + + client->ps.weaponstate = WEAPON_READY; + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + ent->nextRegenTime = level.time; + + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + // set default animations + client->ps.torsoAnim = TORSO_STAND; + client->ps.legsAnim = LEGS_IDLE; + + if( level.intermissiontime ) + MoveClientToIntermission( ent ); + else + { + // fire the targets of the spawn point + if( !spawn ) + G_UseTargets( spawnPoint, ent ); + + // select the highest weapon number available, after any + // spawn given items have fired + client->ps.weapon = 1; + + for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) + { + if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) + { + client->ps.weapon = i; + break; + } + } + } + + client->lastRantBombTime = level.time; + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities ); + + // positively link the client, even if the command times are weird + if( client->sess.spectatorState == SPECTATOR_NOT ) + { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } + + // must do this here so the number of active clients is calculated + CalculateRanks( ); + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) +{ + gentity_t *ent; + gentity_t *tent; + int i; + + ent = g_entities + clientNum; + + if( !ent->client || ent->client->pers.connected == CON_DISCONNECTED ) + return; + + G_LeaveTeam( ent ); + G_namelog_disconnect( ent->client ); + G_Vote( ent, TEAM_NONE, qfalse ); + + // stop any following clients + for( i = 0; i < level.maxclients; i++ ) + { + // remove any /ignore settings for this clientNum + Com_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum ); + } + + // send effect if they were completely connected + if( ent->client->pers.connected == CON_CONNECTED && + ent->client->sess.spectatorState == SPECTATOR_NOT ) + { + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + } + + G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s^7\"\n", clientNum, + ent->client->pers.ip.str, ent->client->pers.guid, ent->client->pers.netname ); + + trap_UnlinkEntity( ent ); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_NOT; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks( ); +} + +/* +=========== +G_RelayCuboidToSpectators + +Called everytime a player changes his cuboid size. +A server command is issued to everyone spectating him +so that their clients can know the cuboid size as well. +============ +*/ +void G_RelayCuboidToSpectators(gentity_t *self) +{ +} + diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c new file mode 100644 index 0000000..3406f4e --- /dev/null +++ b/src/game/g_cmds.c @@ -0,0 +1,3609 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +/* +================== +G_SanitiseString + +Remove color codes and non-alphanumeric characters from a string +================== +*/ +void G_SanitiseString( char *in, char *out, int len ) +{ + len--; + + while( *in && len > 0 ) + { + if( Q_IsColorString( in ) ) + { + in += 2; // skip color code + continue; + } + + if( isalnum( *in ) ) + { + *out++ = tolower( *in ); + len--; + } + in++; + } + *out = 0; +} + +/* +================== +G_ClientNumberFromString + +Returns a player number for either a number or name string +Returns -1 and optionally sets err if invalid or not exactly 1 match +err will have a trailing \n if set +================== +*/ +int G_ClientNumberFromString( char *s, char *err, int len ) +{ + gclient_t *cl; + int i, found = 0, m = -1; + char s2[ MAX_NAME_LENGTH ]; + char n2[ MAX_NAME_LENGTH ]; + char *p = err; + int l, l2 = len; + + if( !s[ 0 ] ) + { + if( p ) + Q_strncpyz( p, "no player name or slot # provided\n", len ); + + return -1; + } + + // numeric values are just slot numbers + for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); + if( !s[ i ] ) + { + i = atoi( s ); + + if( i < 0 || i >= level.maxclients ) + return -1; + + cl = &level.clients[ i ]; + + if( cl->pers.connected == CON_DISCONNECTED ) + { + if( p ) + Q_strncpyz( p, "no player connected in that slot #\n", len ); + + return -1; + } + + return i; + } + + G_SanitiseString( s, s2, sizeof( s2 ) ); + if( !s2[ 0 ] ) + { + if( p ) + Q_strncpyz( p, "no player name provided\n", len ); + + return -1; + } + + if( p ) + { + Q_strncpyz( p, "more than one player name matches. " + "be more specific or use the slot #:\n", l2 ); + l = strlen( p ); + p += l; + l2 -= l; + } + + // check for a name match + for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ ) + { + if( cl->pers.connected == CON_DISCONNECTED ) + continue; + + G_SanitiseString( cl->pers.netname, n2, sizeof( n2 ) ); + + if( !strcmp( n2, s2 ) ) + return i; + + if( strstr( n2, s2 ) ) + { + if( p ) + { + l = Q_snprintf( p, l2, "%-2d - %s^7\n", i, cl->pers.netname ); + p += l; + l2 -= l; + } + + found++; + m = i; + } + } + + if( found == 1 ) + return m; + + if( found == 0 && err ) + Q_strncpyz( err, "no connected player by that name or slot #\n", len ); + + return -1; +} + +/* +================== +G_ClientNumbersFromString + +Sets plist to an array of integers that represent client numbers that have +names that are a partial match for s. + +Returns number of matching clientids up to max. +================== +*/ +int G_ClientNumbersFromString( char *s, int *plist, int max ) +{ + gclient_t *p; + int i, found = 0; + char *endptr; + char n2[ MAX_NAME_LENGTH ] = {""}; + char s2[ MAX_NAME_LENGTH ] = {""}; + + if( max == 0 ) + return 0; + + if( !s[ 0 ] ) + return 0; + + // if a number is provided, it is a clientnum + i = strtol( s, &endptr, 10 ); + if( *endptr == '\0' ) + { + if( i >= 0 && i < level.maxclients ) + { + p = &level.clients[ i ]; + if( p->pers.connected != CON_DISCONNECTED ) + { + *plist = i; + return 1; + } + } + // we must assume that if only a number is provided, it is a clientNum + return 0; + } + + // now look for name matches + G_SanitiseString( s, s2, sizeof( s2 ) ); + if( !s2[ 0 ] ) + return 0; + for( i = 0; i < level.maxclients && found < max; i++ ) + { + p = &level.clients[ i ]; + if( p->pers.connected == CON_DISCONNECTED ) + { + continue; + } + G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) ); + if( strstr( n2, s2 ) ) + { + *plist++ = i; + found++; + } + } + return found; +} + +/* +================== +ScoreboardMessage + +================== +*/ +void ScoreboardMessage( gentity_t *ent ) +{ + char entry[ 1024 ]; + char string[ 1400 ]; + int stringlength; + int i, j; + gclient_t *cl; + int numSorted; + weapon_t weapon = WP_NONE; + upgrade_t upgrade = UP_NONE; + + // send the latest information on all clients + string[ 0 ] = 0; + stringlength = 0; + + numSorted = level.numConnectedClients; + + for( i = 0; i < numSorted; i++ ) + { + int ping; + + cl = &level.clients[ level.sortedClients[ i ] ]; + + if( cl->pers.connected == CON_CONNECTING ) + ping = -1; + else + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + + if( cl->sess.spectatorState == SPECTATOR_NOT && + ( ent->client->pers.teamSelection == TEAM_NONE || + cl->pers.teamSelection == ent->client->pers.teamSelection ) ) + { + weapon = cl->ps.weapon; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) + upgrade = UP_BATTLESUIT; + else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) + upgrade = UP_JETPACK; + else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) ) + upgrade = UP_BATTPACK; + else if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, cl->ps.stats ) ) + upgrade = UP_HELMET_MK1; + else if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, cl->ps.stats ) ) + upgrade = UP_HELMET_MK2; + else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) + upgrade = UP_LIGHTARMOUR; + else + upgrade = UP_NONE; + } + else + { + weapon = WP_NONE; + upgrade = UP_NONE; + } + + Com_sprintf( entry, sizeof( entry ), + " %d %d %d %d %d %d", level.sortedClients[ i ], cl->ps.persistant[ PERS_SCORE ], + ping, ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade ); + + j = strlen( entry ); + + if( stringlength + j >= 1024 ) + break; + + strcpy( string + stringlength, entry ); + stringlength += j; + } + + trap_SendServerCommand( ent-g_entities, va( "scores %i %i%s", + level.alienKills, level.humanKills, string ) ); +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs( int start ) +{ + int i, c, tlen; + static char line[ MAX_STRING_CHARS ]; + int len; + char arg[ MAX_STRING_CHARS ]; + + len = 0; + c = trap_Argc( ); + + for( i = start; i < c; i++ ) + { + trap_Argv( i, arg, sizeof( arg ) ); + tlen = strlen( arg ); + + if( len + tlen >= MAX_STRING_CHARS - 1 ) + break; + + memcpy( line + len, arg, tlen ); + len += tlen; + + if( len == MAX_STRING_CHARS - 1 ) + break; + + if( i != c - 1 ) + { + line[ len ] = ' '; + len++; + } + } + + line[ len ] = 0; + + return line; +} + +/* +================== +ConcatArgsPrintable +Duplicate of concatargs but enquotes things that need to be +Used to log command arguments in a way that preserves user intended tokenizing +================== +*/ +char *ConcatArgsPrintable( int start ) +{ + int i, c, tlen; + static char line[ MAX_STRING_CHARS ]; + int len; + char arg[ MAX_STRING_CHARS + 2 ]; + char *printArg; + + len = 0; + c = trap_Argc( ); + + for( i = start; i < c; i++ ) + { + printArg = arg; + trap_Argv( i, arg, sizeof( arg ) ); + if( strchr( arg, ' ' ) ) + printArg = va( "\"%s\"", arg ); + tlen = strlen( printArg ); + + if( len + tlen >= MAX_STRING_CHARS - 1 ) + break; + + memcpy( line + len, printArg, tlen ); + len += tlen; + + if( len == MAX_STRING_CHARS - 1 ) + break; + + if( i != c - 1 ) + { + line[ len ] = ' '; + len++; + } + } + + line[ len ] = 0; + + return line; +} + + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( gentity_t *ent ) +{ + char *name; + qboolean give_all = qfalse; + + if( trap_Argc( ) < 2 ) + { + ADMP( "usage: give [what]\n" ); + ADMP( "usage: valid choices are: all, health, funds [amount], stamina, " + "poison, gas, ammo\n" ); + return; + } + + name = ConcatArgs( 1 ); + if( Q_stricmp( name, "all" ) == 0 ) + give_all = qtrue; + + if( give_all || Q_stricmp( name, "health" ) == 0 ) + { + ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; + BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats ); + } + + if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) + { + float credits; + + if( give_all || trap_Argc( ) < 3 ) + credits = 30000.0f; + else + { + credits = atof( name + 6 ) * + ( ent->client->pers.teamSelection == + TEAM_ALIENS ? ALIEN_CREDITS_PER_KILL : 1.0f ); + + // clamp credits manually, as G_AddCreditToClient() expects a short int + if( credits > SHRT_MAX ) + credits = 30000.0f; + else if( credits < SHRT_MIN ) + credits = -30000.0f; + } + + G_AddCreditToClient( ent->client, (short)credits, qtrue ); + } + + if( give_all || Q_stricmp( name, "stamina" ) == 0 ) + ent->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + + if( Q_stricmp( name, "poison" ) == 0 ) + { + if( ent->client->pers.teamSelection == TEAM_HUMANS ) + { + ent->client->ps.stats[ STAT_STATE ] |= SS_POISONED; + ent->client->lastPoisonTime = level.time; + ent->client->lastPoisonClient = ent; + } + else + { + ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; + ent->client->boostedTime = level.time; + } + } + + if( Q_stricmp( name, "gas" ) == 0 ) + { + ent->client->ps.eFlags |= EF_POISONCLOUDED; + ent->client->lastPoisonCloudedTime = level.time; + trap_SendServerCommand( ent->client->ps.clientNum, + "poisoncloud" ); + } + + if( give_all || Q_stricmp( name, "ammo" ) == 0 ) + { + gclient_t *client = ent->client; + + if( client->ps.weapon != WP_ALEVEL3_UPG && + BG_Weapon( client->ps.weapon )->infiniteAmmo ) + return; + + client->ps.ammo = BG_Weapon( client->ps.weapon )->maxAmmo; + client->ps.clips = BG_Weapon( client->ps.weapon )->maxClips; + + if( BG_Weapon( client->ps.weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, client->ps.stats ) ) + client->ps.ammo = (int)( (float)client->ps.ammo * BATTPACK_MODIFIER ); + } + + if( give_all || Q_stricmp( name, "fuel" ) == 0 ) + { + if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) ) + ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( gentity_t *ent ) +{ + char *msg; + + ent->flags ^= FL_GODMODE; + + if( !( ent->flags & FL_GODMODE ) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( gentity_t *ent ) +{ + char *msg; + + ent->flags ^= FL_NOTARGET; + + if( !( ent->flags & FL_NOTARGET ) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( gentity_t *ent ) +{ + char *msg; + + if( ent->client->noclip ) + msg = "noclip OFF\n"; + else + msg = "noclip ON\n"; + + ent->client->noclip = !ent->client->noclip; + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_LevelShot_f( gentity_t *ent ) +{ + BeginIntermission( ); + trap_SendServerCommand( ent - g_entities, "clientLevelShot" ); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( gentity_t *ent ) +{ + if( g_cheats.integer ) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; + player_die( ent, ent, ent, 100000, MOD_SUICIDE ); + } + else + { + if( ent->suicideTime == 0 ) + { + trap_SendServerCommand( ent-g_entities, "print \"You will suicide in 20 seconds\n\"" ); + ent->suicideTime = level.time + 20000; + } + else if( ent->suicideTime > level.time ) + { + trap_SendServerCommand( ent-g_entities, "print \"Suicide cancelled\n\"" ); + ent->suicideTime = 0; + } + } +} + +/* +================= +Cmd_Impregnate_f + +Implants an egg in one's stomach (slowblob equivalent) +Used for debugging only (cheat-protected) +================= +*/ +void Cmd_Impregnate_f( gentity_t *ent ) +{ + if( !ent->client->isImpregnated ) + { + ent->client->isImpregnated = qtrue; + ent->client->impregnationTime = level.time; + + trap_SendServerCommand( ent-g_entities, + "print \"You have impregnated yourself with an alien egg\n\"" ); + } +} + +/* +================= +Cmd_Team_f +================= +*/ +void Cmd_Team_f( gentity_t *ent ) +{ + team_t team; + team_t oldteam = ent->client->pers.teamSelection; + char s[ MAX_TOKEN_CHARS ]; + qboolean force = G_admin_permission( ent, ADMF_FORCETEAMCHANGE ); + int aliens = level.numAlienClients; + int humans = level.numHumanClients; + + if( oldteam == TEAM_ALIENS ) + aliens--; + else if( oldteam == TEAM_HUMANS ) + humans--; + + // stop team join spam + if( ent->client->pers.teamChangeTime && + level.time - ent->client->pers.teamChangeTime < 1000 ) + return; + + // stop switching teams for gameplay exploit reasons by enforcing a long + // wait before they can come back + if( !force && !g_cheats.integer && ent->client->pers.aliveSeconds && + level.time - ent->client->pers.teamChangeTime < 30000 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"You must wait another %d seconds before changing teams again\n\"", + (int) ( ( 30000 - ( level.time - ent->client->pers.teamChangeTime ) ) / 1000.f ) ) ); + return; + } + + // disallow joining teams during warmup + if( g_doWarmup.integer && ( ( level.warmupTime - level.time ) / 1000 ) > 0 ) + { + G_TriggerMenu( ent - g_entities, MN_WARMUP ); + return; + } + + trap_Argv( 1, s, sizeof( s ) ); + + if( !s[ 0 ] ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"team: %s\n\"", + BG_TeamName( oldteam ) ) ); + return; + } + + if( !Q_stricmp( s, "auto" ) ) + { + if( level.humanTeamLocked && level.alienTeamLocked ) + team = TEAM_NONE; + else if( level.humanTeamLocked || humans > aliens ) + team = TEAM_ALIENS; + + else if( level.alienTeamLocked || aliens > humans ) + team = TEAM_HUMANS; + else + team = TEAM_ALIENS + rand( ) / ( RAND_MAX / 2 + 1 ); + } + else switch( G_TeamFromString( s ) ) + { + case TEAM_NONE: + team = TEAM_NONE; + break; + + case TEAM_ALIENS: + if( level.alienTeamLocked ) + { + G_TriggerMenu( ent - g_entities, MN_A_TEAMLOCKED ); + return; + } + else if( level.humanTeamLocked ) + force = qtrue; + + if( !force && g_teamForceBalance.integer && aliens > humans ) + { + G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); + return; + } + + team = TEAM_ALIENS; + break; + + case TEAM_HUMANS: + if( level.humanTeamLocked ) + { + G_TriggerMenu( ent - g_entities, MN_H_TEAMLOCKED ); + return; + } + else if( level.alienTeamLocked ) + force = qtrue; + + if( !force && g_teamForceBalance.integer && humans > aliens ) + { + G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); + return; + } + + team = TEAM_HUMANS; + break; + + default: + trap_SendServerCommand( ent-g_entities, + va( "print \"Unknown team: %s\n\"", s ) ); + return; + } + + // stop team join spam + if( oldteam == team ) + return; + + if( team != TEAM_NONE && g_maxGameClients.integer && + level.numPlayingClients >= g_maxGameClients.integer ) + { + G_TriggerMenu( ent - g_entities, MN_PLAYERLIMIT ); + return; + } + + // Apply the change + G_ChangeTeam( ent, team ); +} + +/* +================== +G_CensorString +================== +*/ +static char censors[ 20000 ]; +static int numcensors; + +void G_LoadCensors( void ) +{ + char *text_p, *token; + char text[ 20000 ]; + char *term; + int len; + fileHandle_t f; + + numcensors = 0; + + if( !g_censorship.string[ 0 ] ) + return; + + len = trap_FS_FOpenFile( g_censorship.string, &f, FS_READ ); + if( len < 0 ) + { + Com_Printf( S_COLOR_RED "ERROR: Censors file %s doesn't exist\n", + g_censorship.string ); + return; + } + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + Com_Printf( S_COLOR_RED "ERROR: Censors file %s is %s\n", + g_censorship.string, len == 0 ? "empty" : "too long" ); + return; + } + trap_FS_Read( text, len, f ); + trap_FS_FCloseFile( f ); + text[ len ] = 0; + + term = censors; + + text_p = text; + while( 1 ) + { + token = COM_Parse( &text_p ); + if( !*token || sizeof( censors ) - ( term - censors ) < 4 ) + break; + Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); + Q_strlwr( term ); + term += strlen( term ) + 1; + if( sizeof( censors ) - ( term - censors ) == 0 ) + break; + token = COM_ParseExt( &text_p, qfalse ); + Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); + term += strlen( term ) + 1; + numcensors++; + } + G_Printf( "Parsed %d string replacements\n", numcensors ); +} + +void G_CensorString( char *out, const char *in, int len, gentity_t *ent ) +{ + const char *s, *m; + int i; + + if( !numcensors || G_admin_permission( ent, ADMF_NOCENSORFLOOD) ) + { + Q_strncpyz( out, in, len ); + return; + } + + len--; + while( *in ) + { + if( Q_IsColorString( in ) ) + { + if( len < 2 ) + break; + *out++ = *in++; + *out++ = *in++; + len -= 2; + continue; + } + if( !isalnum( *in ) ) + { + if( len < 1 ) + break; + *out++ = *in++; + len--; + continue; + } + m = censors; + for( i = 0; i < numcensors; i++, m++ ) + { + s = in; + while( *s && *m ) + { + if( Q_IsColorString( s ) ) + { + s += 2; + continue; + } + if( !isalnum( *s ) ) + { + s++; + continue; + } + if( tolower( *s ) != *m ) + break; + s++; + m++; + } + // match + if( !*m ) + { + in = s; + m++; + while( *m ) + { + if( len < 1 ) + break; + *out++ = *m++; + len--; + } + break; + } + else + { + while( *m ) + m++; + m++; + while( *m ) + m++; + } + } + if( len < 1 ) + break; + // no match + if( i == numcensors ) + { + *out++ = *in++; + len--; + } + } + *out = 0; +} + +/* +================== +G_Say +================== +*/ +static qboolean G_SayTo( gentity_t *ent, gentity_t *other, saymode_t mode, const char *message ) +{ + if( !other ) + return qfalse; + + if( !other->inuse ) + return qfalse; + + if( !other->client ) + return qfalse; + + if( other->client->pers.connected != CON_CONNECTED ) + return qfalse; + + if( ( ent && !OnSameTeam( ent, other ) ) && + ( mode == SAY_TEAM || mode == SAY_AREA || mode == SAY_TPRIVMSG ) ) + { + if( other->client->pers.teamSelection != TEAM_NONE ) + return qfalse; + + // specs with ADMF_SPEC_ALLCHAT flag can see team chat + if( !G_admin_permission( other, ADMF_SPEC_ALLCHAT ) && mode != SAY_TPRIVMSG ) + return qfalse; + } + + trap_SendServerCommand( other-g_entities, va( "chat %d %d \"%s\"", + ent ? ent-g_entities : -1, + mode, + message ) ); + + return qtrue; +} + +void G_Say( gentity_t *ent, saymode_t mode, const char *chatText ) +{ + int j; + gentity_t *other; + // don't let text be too long for malicious reasons + char text[ MAX_SAY_TEXT ]; + + // check if blocked by g_specChat 0 + if( ( !g_specChat.integer ) && ( mode != SAY_TEAM ) && + ( ent ) && ( ent->client->pers.teamSelection == TEAM_NONE ) && + ( !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"say: Global chatting for " + "spectators has been disabled. You may only use team chat.\n\"" ); + mode = SAY_TEAM; + } + + switch( mode ) + { + case SAY_ALL: + G_LogPrintf( "Say: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_GREEN "%s\n", + ( ent ) ? ent - g_entities : -1, + ( ent ) ? ent->client->pers.netname : "console", chatText ); + break; + case SAY_TEAM: + // console say_team is handled in g_svscmds, not here + if( !ent || !ent->client ) + Com_Error( ERR_FATAL, "SAY_TEAM by non-client entity\n" ); + G_LogPrintf( "SayTeam: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_CYAN "%s\n", + ent - g_entities, ent->client->pers.netname, chatText ); + break; + case SAY_RAW: + if( ent ) + Com_Error( ERR_FATAL, "SAY_RAW by client entity\n" ); + G_LogPrintf( "Chat: -1 \"console\": %s\n", chatText ); + default: + break; + } + + G_CensorString( text, chatText, sizeof( text ), ent ); + + // send it to all the apropriate clients + for( j = 0; j < level.maxclients; j++ ) + { + other = &g_entities[ j ]; + G_SayTo( ent, other, mode, text ); + } +} + +/* +================== +Cmd_SayArea_f +================== +*/ +static void Cmd_SayArea_f( gentity_t *ent ) +{ + int entityList[ MAX_GENTITIES ]; + int num, i; + vec3_t range = { 1000.0f, 1000.0f, 1000.0f }; + vec3_t mins, maxs; + char *msg; + + if( trap_Argc( ) < 2 ) + { + ADMP( "usage: say_area [message]\n" ); + return; + } + + msg = ConcatArgs( 1 ); + + for(i = 0; i < 3; i++ ) + range[ i ] = g_sayAreaRange.value; + + G_LogPrintf( "SayArea: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_BLUE "%s\n", + ent - g_entities, ent->client->pers.netname, msg ); + + VectorAdd( ent->s.origin, range, maxs ); + VectorSubtract( ent->s.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_AREA, msg ); + + //Send to ADMF_SPEC_ALLCHAT candidates + for( i = 0; i < level.maxclients; i++ ) + { + if( g_entities[ i ].client->pers.teamSelection == TEAM_NONE && + G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) + { + G_SayTo( ent, &g_entities[ i ], SAY_AREA, msg ); + } + } +} + + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( gentity_t *ent ) +{ + char *p; + char cmd[ MAX_TOKEN_CHARS ]; + saymode_t mode = SAY_ALL; + + if( trap_Argc( ) < 2 ) + return; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "say_team" ) == 0 ) + mode = SAY_TEAM; + + p = ConcatArgs( 1 ); + + G_Say( ent, mode, p ); +} + +/* +================== +Cmd_VSay_f +================== +*/ +void Cmd_VSay_f( gentity_t *ent ) +{ + char arg[MAX_TOKEN_CHARS]; + char text[ MAX_TOKEN_CHARS ]; + voiceChannel_t vchan; + voice_t *voice; + voiceCmd_t *cmd; + voiceTrack_t *track; + int cmdNum = 0; + int trackNum = 0; + char voiceName[ MAX_VOICE_NAME_LEN ] = {"default"}; + char voiceCmd[ MAX_VOICE_CMD_LEN ] = {""}; + char vsay[ 12 ] = {""}; + weapon_t weapon; + + if( !ent || !ent->client ) + Com_Error( ERR_FATAL, "Cmd_VSay_f() called by non-client entity\n" ); + + trap_Argv( 0, arg, sizeof( arg ) ); + if( trap_Argc( ) < 2 ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"usage: %s command [text] \n\"", arg ) ); + return; + } + if( !level.voices ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice system is not installed on this server\n\"", arg ) ); + return; + } + if( !g_voiceChats.integer ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice system administratively disabled on this server\n\"", + arg ) ); + return; + } + if( !Q_stricmp( arg, "vsay" ) ) + vchan = VOICE_CHAN_ALL; + else if( !Q_stricmp( arg, "vsay_team" ) ) + vchan = VOICE_CHAN_TEAM; + else if( !Q_stricmp( arg, "vsay_local" ) ) + vchan = VOICE_CHAN_LOCAL; + else + return; + Q_strncpyz( vsay, arg, sizeof( vsay ) ); + + if( ent->client->pers.voice[ 0 ] ) + Q_strncpyz( voiceName, ent->client->pers.voice, sizeof( voiceName ) ); + voice = BG_VoiceByName( level.voices, voiceName ); + if( !voice ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice '%s' not found\n\"", vsay, voiceName ) ); + return; + } + + trap_Argv( 1, voiceCmd, sizeof( voiceCmd ) ) ; + cmd = BG_VoiceCmdFind( voice->cmds, voiceCmd, &cmdNum ); + if( !cmd ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: command '%s' not found in voice '%s'\n\"", + vsay, voiceCmd, voiceName ) ); + return; + } + + // filter non-spec humans by their primary weapon as well + weapon = WP_NONE; + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + { + weapon = BG_PrimaryWeapon( ent->client->ps.stats ); + } + + track = BG_VoiceTrackFind( cmd->tracks, ent->client->pers.teamSelection, + ent->client->pers.classSelection, weapon, (int)ent->client->voiceEnthusiasm, + &trackNum ); + if( !track ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: no available track for command '%s', team %d, " + "class %d, weapon %d, and enthusiasm %d in voice '%s'\n\"", + vsay, voiceCmd, ent->client->pers.teamSelection, + ent->client->pers.classSelection, weapon, + (int)ent->client->voiceEnthusiasm, voiceName ) ); + return; + } + + if( !Q_stricmp( ent->client->lastVoiceCmd, cmd->cmd ) ) + ent->client->voiceEnthusiasm++; + + Q_strncpyz( ent->client->lastVoiceCmd, cmd->cmd, + sizeof( ent->client->lastVoiceCmd ) ); + + // optional user supplied text + trap_Argv( 2, arg, sizeof( arg ) ); + G_CensorString( text, arg, sizeof( text ), ent ); + + switch( vchan ) + { + case VOICE_CHAN_ALL: + case VOICE_CHAN_LOCAL: + trap_SendServerCommand( -1, va( + "voice %d %d %d %d \"%s\"\n", + ent-g_entities, vchan, cmdNum, trackNum, text ) ); + break; + case VOICE_CHAN_TEAM: + G_TeamCommand( ent->client->pers.teamSelection, va( + "voice %d %d %d %d \"%s\"\n", + ent-g_entities, vchan, cmdNum, trackNum, text ) ); + break; + default: + break; + } +} + +/* +================== +Cmd_Where_f +================== +*/ +void Cmd_Where_f( gentity_t *ent ) +{ + if( !ent->client ) + return; + trap_SendServerCommand( ent - g_entities, + va( "print \"origin: %f %f %f\n\"", + ent->s.origin[ 0 ], ent->s.origin[ 1 ], + ent->s.origin[ 2 ] ) ); +} + +/* +================== +Cmd_CallVote_f +================== +*/ +void Cmd_CallVote_f( gentity_t *ent ) +{ + char cmd[ MAX_TOKEN_CHARS ], + vote[ MAX_TOKEN_CHARS ], + arg[ MAX_TOKEN_CHARS ]; + char name[ MAX_NAME_LENGTH ] = ""; + char caller[ MAX_NAME_LENGTH ] = ""; + char reason[ MAX_TOKEN_CHARS ]; + char *creason; + int clientNum = -1; + int id = -1; + team_t team; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + trap_Argv( 1, vote, sizeof( vote ) ); + trap_Argv( 2, arg, sizeof( arg ) ); + creason = ConcatArgs( 3 ); + G_DecolorString( creason, reason, sizeof( reason ) ); + + if( !Q_stricmp( cmd, "callteamvote" ) ) + team = ent->client->pers.teamSelection; + else + team = TEAM_NONE; + + if( !g_allowVote.integer ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: voting not allowed here\n\"", cmd ) ); + return; + } + + if( level.voteTime[ team ] ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: a vote is already in progress\n\"", cmd ) ); + return; + } + + if( level.voteExecuteTime[ team ] ) + G_ExecuteVote( team ); + + level.voteDelay[ team ] = 0; + level.voteThreshold[ team ] = 50; + + if( g_voteLimit.integer > 0 && + ent->client->pers.namelog->voteCount >= g_voteLimit.integer && + !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: you have already called the maximum number of votes (%d)\n\"", + cmd, g_voteLimit.integer ) ); + return; + } + + // kick, mute, unmute, denybuild, allowbuild + if( !Q_stricmp( vote, "kick" ) || + !Q_stricmp( vote, "mute" ) || !Q_stricmp( vote, "unmute" ) || + !Q_stricmp( vote, "denybuild" ) || !Q_stricmp( vote, "allowbuild" ) ) + { + char err[ MAX_STRING_CHARS ]; + + if( !arg[ 0 ] ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: no target\n\"", cmd ) ); + return; + } + + // with a little extra work only players from the right team are considered + clientNum = G_ClientNumberFromString( arg, err, sizeof( err ) ); + + if( clientNum == -1 ) + { + ADMP( va( "%s: %s", cmd, err ) ); + return; + } + + G_DecolorString( level.clients[ clientNum ].pers.netname, name, sizeof( name ) ); + id = level.clients[ clientNum ].pers.namelog->id; + + if( !Q_stricmp( vote, "kick" ) || !Q_stricmp( vote, "mute" ) || + !Q_stricmp( vote, "denybuild" ) ) + { + if( G_admin_permission( g_entities + clientNum, ADMF_IMMUNITY ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: admin is immune\n\"", cmd ) ); + + G_AdminMessage( NULL, va( S_COLOR_WHITE "%s" S_COLOR_YELLOW " attempted %s %s" + " on immune admin " S_COLOR_WHITE "%s" S_COLOR_YELLOW + " for: %s", + ent->client->pers.netname, cmd, vote, + g_entities[ clientNum ].client->pers.netname, + reason[ 0 ] ? reason : "no reason" ) ); + return; + } + + if( team != TEAM_NONE && + ( ent->client->pers.teamSelection != + level.clients[ clientNum ].pers.teamSelection ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player is not on your team\n\"", cmd ) ); + return; + } + + if( !reason[ 0 ] && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: You must provide a reason\n\"", cmd ) ); + return; + } + } + } + + if( !Q_stricmp( vote, "kick" ) ) + { + if( level.clients[ clientNum ].pers.localClient ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: admin is immune\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "ban %s \"1s%s\" vote kick (%s)", level.clients[ clientNum ].pers.ip.str, + g_adminTempBan.string, reason ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), "Kick player '%s'", name ); + if( reason[ 0 ] ) + { + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); + } + } + else if( team == TEAM_NONE ) + { + if( !Q_stricmp( vote, "mute" ) ) + { + if( level.clients[ clientNum ].pers.namelog->muted ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player is already muted\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "mute %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Mute player '%s'", name ); + if( reason[ 0 ] ) + { + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); + } + } + else if( !Q_stricmp( vote, "unmute" ) ) + { + if( !level.clients[ clientNum ].pers.namelog->muted ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player is not currently muted\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "unmute %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Unmute player '%s'", name ); + } + else if( !Q_stricmp( vote, "map_restart" ) ) + { + strcpy( level.voteString[ team ], vote ); + strcpy( level.voteDisplayString[ team ], "Restart current map" ); + // map_restart comes with a default delay + } + else if( !Q_stricmp( vote, "map" ) ) + { + if( !G_MapExists( arg ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", + cmd, arg ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString ), + "%s \"%s\"", vote, arg ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Change to map '%s'", arg ); + level.voteDelay[ team ] = 3000; + } + else if( !Q_stricmp( vote, "nextmap" ) ) + { + if( G_MapExists( g_nextMap.string ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: the next map is already set to '%s'\n\"", + cmd, g_nextMap.string ) ); + return; + } + + if( !G_MapExists( arg ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", + cmd, arg ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "set g_nextMap \"%s\"", arg ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Set the next map to '%s'", arg ); + } + else if( !Q_stricmp( vote, "draw" ) ) + { + strcpy( level.voteString[ team ], "evacuation" ); + strcpy( level.voteDisplayString[ team ], "End match in a draw" ); + level.voteDelay[ team ] = 3000; + } + else if( !Q_stricmp( vote, "sudden_death" ) ) + { + if(!g_suddenDeathVotePercent.integer) + { + trap_SendServerCommand( ent-g_entities, + "print \"Sudden Death votes have been disabled\n\"" ); + return; + } + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"callvote: Sudden Death has already begun\n\"") ); + return; + } + if( level.suddenDeathBeginTime > 0 && + G_TimeTilSuddenDeath() <= g_suddenDeathVoteDelay.integer * 1000 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"callvote: Sudden Death is imminent\n\"") ); + return; + } + level.voteThreshold[ team ] = g_suddenDeathVotePercent.integer; + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "suddendeath %d", g_suddenDeathVoteDelay.integer ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Begin sudden death in %d seconds", + g_suddenDeathVoteDelay.integer ); + } + else + { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " + "map, nextmap, map_restart, draw, sudden_death, kick, mute and unmute\n" ); + return; + } + } + else if( !Q_stricmp( vote, "denybuild" ) ) + { + if( level.clients[ clientNum ].pers.namelog->denyBuild ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player already lost building rights\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "denybuild %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Take away building rights from '%s'", name ); + if( reason[ 0 ] ) + { + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); + } + } + else if( !Q_stricmp( vote, "allowbuild" ) ) + { + if( !level.clients[ clientNum ].pers.namelog->denyBuild ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player already has building rights\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "allowbuild %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Allow '%s' to build", name ); + } + else if( !Q_stricmp( vote, "admitdefeat" ) ) + { + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "admitdefeat %d", team ); + strcpy( level.voteDisplayString[ team ], "Admit Defeat" ); + level.voteDelay[ team ] = 3000; + } + else + { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); + trap_SendServerCommand( ent-g_entities, + "print \"Valid team vote commands are: " + "kick, denybuild, allowbuild and admitdefeat\n\"" ); + return; + } + + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": %s\n", + team == TEAM_NONE ? "CallVote" : "CallTeamVote", + ent - g_entities, ent->client->pers.netname, level.voteString[ team ] ); + + if( team == TEAM_NONE ) + { + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE + " called a vote: %s\n\"", ent->client->pers.netname, + level.voteDisplayString[ team ] ) ); + } + else + { + int i; + + for( i = 0 ; i < level.maxclients ; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + { + if( level.clients[ i ].pers.teamSelection == team || + ( level.clients[ i ].pers.teamSelection == TEAM_NONE && + G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) + { + trap_SendServerCommand( i, va( "print \"%s" S_COLOR_WHITE + " called a team vote: %s\n\"", ent->client->pers.netname, + level.voteDisplayString[ team ] ) ); + } + else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) + { + trap_SendServerCommand( i, va( "chat -1 %d \"" S_COLOR_YELLOW "%s" + S_COLOR_YELLOW " called a team vote (%ss): %s\"", SAY_ADMINS, + ent->client->pers.netname, BG_TeamName( team ), + level.voteDisplayString[ team ] ) ); + } + } + } + } + + G_DecolorString( ent->client->pers.netname, caller, sizeof( caller ) ); + + level.voteTime[ team ] = level.time; + trap_SetConfigstring( CS_VOTE_TIME + team, + va( "%d", level.voteTime[ team ] ) ); + trap_SetConfigstring( CS_VOTE_STRING + team, + level.voteDisplayString[ team ] ); + trap_SetConfigstring( CS_VOTE_CALLER + team, + caller ); + + ent->client->pers.namelog->voteCount++; + ent->client->pers.vote |= 1 << team; + G_Vote( ent, team, qtrue ); +} + +/* +================== +Cmd_Vote_f +================== +*/ +void Cmd_Vote_f( gentity_t *ent ) +{ + char cmd[ MAX_TOKEN_CHARS ], vote[ MAX_TOKEN_CHARS ]; + team_t team = ent->client->pers.teamSelection; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "teamvote" ) ) + team = TEAM_NONE; + + if( !level.voteTime[ team ] ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: no vote in progress\n\"", cmd ) ); + return; + } + + if( ent->client->pers.voted & ( 1 << team ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: vote already cast\n\"", cmd ) ); + return; + } + + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: vote cast\n\"", cmd ) ); + + trap_Argv( 1, vote, sizeof( vote ) ); + if( vote[ 0 ] == 'y' ) + ent->client->pers.vote |= 1 << team; + else + ent->client->pers.vote &= ~( 1 << team ); + G_Vote( ent, team, qtrue ); +} + + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( gentity_t *ent ) +{ + vec3_t origin, angles; + char buffer[ MAX_TOKEN_CHARS ]; + int i; + + if( trap_Argc( ) != 5 ) + { + trap_SendServerCommand( ent-g_entities, "print \"usage: setviewpos x y z yaw\n\"" ); + return; + } + + VectorClear( angles ); + + for( i = 0; i < 3; i++ ) + { + trap_Argv( i + 1, buffer, sizeof( buffer ) ); + origin[ i ] = atof( buffer ); + } + + trap_Argv( 4, buffer, sizeof( buffer ) ); + angles[ YAW ] = atof( buffer ); + + TeleportPlayer( ent, origin, angles ); +} + +#define AS_OVER_RT3 ((ALIENSENSE_RANGE*0.5f)/M_ROOT3) + +static qboolean G_RoomForClassChange( gentity_t *ent, class_t class, + vec3_t newOrigin ) +{ + vec3_t fromMins, fromMaxs; + vec3_t toMins, toMaxs; + vec3_t temp; + trace_t tr; + float nudgeHeight; + float maxHorizGrowth; + class_t oldClass = ent->client->ps.stats[ STAT_CLASS ]; + + BG_ClassBoundingBox( oldClass, fromMins, fromMaxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, toMins, toMaxs, NULL, NULL, NULL ); + + VectorCopy( ent->s.origin, newOrigin ); + + // find max x/y diff + maxHorizGrowth = toMaxs[ 0 ] - fromMaxs[ 0 ]; + if( toMaxs[ 1 ] - fromMaxs[ 1 ] > maxHorizGrowth ) + maxHorizGrowth = toMaxs[ 1 ] - fromMaxs[ 1 ]; + if( toMins[ 0 ] - fromMins[ 0 ] > -maxHorizGrowth ) + maxHorizGrowth = -( toMins[ 0 ] - fromMins[ 0 ] ); + if( toMins[ 1 ] - fromMins[ 1 ] > -maxHorizGrowth ) + maxHorizGrowth = -( toMins[ 1 ] - fromMins[ 1 ] ); + + if( maxHorizGrowth > 0.0f ) + { + // test by moving the player up the max required on a 60 degree slope + nudgeHeight = maxHorizGrowth * 2.0f; + } + else + { + // player is shrinking, so there's no need to nudge them upwards + nudgeHeight = 0.0f; + } + + // find what the new origin would be on a level surface + newOrigin[ 2 ] -= toMins[ 2 ] - fromMins[ 2 ]; + + //compute a place up in the air to start the real trace + VectorCopy( newOrigin, temp ); + temp[ 2 ] += nudgeHeight; + trap_Trace( &tr, newOrigin, toMins, toMaxs, temp, ent->s.number, MASK_PLAYERSOLID ); + + //trace down to the ground so that we can evolve on slopes + VectorCopy( newOrigin, temp ); + temp[ 2 ] += ( nudgeHeight * tr.fraction ); + trap_Trace( &tr, temp, toMins, toMaxs, newOrigin, ent->s.number, MASK_PLAYERSOLID ); + VectorCopy( tr.endpos, newOrigin ); + + //make REALLY sure + trap_Trace( &tr, newOrigin, toMins, toMaxs, newOrigin, + ent->s.number, MASK_PLAYERSOLID ); + + //check there is room to evolve + return ( !tr.startsolid && tr.fraction == 1.0f ); +} + +/* +================= +Cmd_Class_f +================= +*/ +void Cmd_Class_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + int clientNum; + int i; + vec3_t infestOrigin; + class_t currentClass = ent->client->pers.classSelection; + class_t newClass; + int entityList[ MAX_GENTITIES ]; + vec3_t range = { AS_OVER_RT3, AS_OVER_RT3, AS_OVER_RT3 }; + vec3_t mins, maxs; + int num; + gentity_t *other; + int oldBoostTime = -1; + vec3_t oldVel; + + clientNum = ent->client - level.clients; + trap_Argv( 1, s, sizeof( s ) ); + newClass = BG_ClassByName( s )->number; + + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) + { + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + { + if( newClass != PCL_ALIEN_BUILDER0 && + newClass != PCL_ALIEN_BUILDER0_UPG && + newClass != PCL_ALIEN_LEVEL0 ) + { + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTSPAWN, newClass ); + return; + } + + if( !BG_ClassIsAllowed( newClass ) ) + { + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTALLOWED, newClass ); + return; + } + + if( !BG_ClassAllowedInStage( newClass, g_alienStage.integer ) ) + { + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTATSTAGE, newClass ); + return; + } + + // spawn from an egg + if( G_PushSpawnQueue( &level.alienSpawnQueue, clientNum ) ) + { + ent->client->pers.classSelection = newClass; + ent->client->ps.stats[ STAT_CLASS ] = newClass; + } + } + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + { + //set the item to spawn with + if( !Q_stricmp( s, BG_Weapon( WP_MACHINEGUN )->name ) && + BG_WeaponIsAllowed( WP_MACHINEGUN ) ) + { + ent->client->pers.humanItemSelection = WP_MACHINEGUN; + } + else if( !Q_stricmp( s, BG_Weapon( WP_HBUILD )->name ) && + BG_WeaponIsAllowed( WP_HBUILD ) ) + { + ent->client->pers.humanItemSelection = WP_HBUILD; + } + else + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNSPAWNITEM ); + return; + } + // spawn from a telenode + if( G_PushSpawnQueue( &level.humanSpawnQueue, clientNum ) ) + { + ent->client->pers.classSelection = PCL_HUMAN; + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; + } + } + return; + } + + if( ent->health <= 0 ) + return; + + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + { + if( newClass == PCL_NONE ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_A_UNKNOWNCLASS ); + return; + } + + //if we are not currently spectating, we are attempting evolution + if( ent->client->pers.classSelection != PCL_NONE ) + { + int cost; + + //check that we have an overmind + if( !G_Overmind( ) ) + { + G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); + return; + } + + //check there are no humans nearby + VectorAdd( ent->client->ps.origin, range, maxs ); + VectorSubtract( ent->client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + other = &g_entities[ entityList[ i ] ]; + + if( ( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || + ( other->s.eType == ET_BUILDABLE && other->buildableTeam == TEAM_HUMANS && + other->powered ) ) + { + trace_t tr; + + trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, other->s.origin, ent->client->ps.clientNum, MASK_SOLID ); + + if( tr.fraction > 0.99f ) + { + G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); + return; + } + } + } + + //check that we are not wallwalking + if( ent->client->ps.eFlags & EF_WALLCLIMB ) + { + G_TriggerMenu( clientNum, MN_A_EVOLVEWALLWALK ); + return; + } + + //guard against selling the HBUILD weapons exploit + if( ent->client->sess.spectatorState == SPECTATOR_NOT && + ( currentClass == PCL_ALIEN_BUILDER0 || + currentClass == PCL_ALIEN_BUILDER0_UPG ) && + ent->client->buildTimer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_A_EVOLVEBUILDTIMER ); + return; + } + + cost = BG_ClassCanEvolveFromTo( currentClass, newClass, + ent->client->pers.credit, + g_alienStage.integer, 0 ); + + if( G_RoomForClassChange( ent, newClass, infestOrigin ) ) + { + if( cost >= 0 ) + { + + ent->client->pers.evolveHealthFraction = (float)ent->client->ps.stats[ STAT_HEALTH ] / + (float)BG_Class( currentClass )->health; + + if( ent->client->pers.evolveHealthFraction < 0.0f ) + ent->client->pers.evolveHealthFraction = 0.0f; + else if( ent->client->pers.evolveHealthFraction > 1.0f ) + ent->client->pers.evolveHealthFraction = 1.0f; + + //remove credit + G_AddCreditToClient( ent->client, -cost, qtrue ); + ent->client->pers.classSelection = newClass; + ClientUserinfoChanged( clientNum, qfalse ); + VectorCopy( infestOrigin, ent->s.pos.trBase ); + VectorCopy( ent->client->ps.velocity, oldVel ); + + if( ent->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + oldBoostTime = ent->client->boostedTime; + + ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase ); + + VectorCopy( oldVel, ent->client->ps.velocity ); + if( oldBoostTime > 0 ) + { + ent->client->boostedTime = oldBoostTime; + ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; + } + } + else + G_TriggerMenuArgs( clientNum, MN_A_CANTEVOLVE, newClass ); + } + else + G_TriggerMenu( clientNum, MN_A_NOEROOM ); + } + } + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + G_TriggerMenu( clientNum, MN_H_DEADTOCLASS ); +} + + +/* +================= +Cmd_Destroy_f +================= +*/ +void Cmd_Destroy_f( gentity_t *ent ) +{ + vec3_t viewOrigin, forward, end; + trace_t tr; + gentity_t *traceEnt; + char cmd[ 12 ]; + qboolean deconstruct = qtrue; + qboolean lastSpawn = qfalse; + + if( ent->client->pers.namelog->denyBuild ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED ); + return; + } + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "destroy" ) == 0 ) + deconstruct = qfalse; + + BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); + AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( viewOrigin, 100, forward, end ); + + trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + + if( tr.fraction < 1.0f && + ( traceEnt->s.eType == ET_BUILDABLE ) && + ( traceEnt->buildableTeam == ent->client->pers.teamSelection ) && + ( ( ent->client->ps.weapon >= WP_ABUILD ) && + ( ent->client->ps.weapon <= WP_HBUILD ) ) ) + { + // Always let the builder prevent the explosion + if( traceEnt->health <= 0 ) + { + G_QueueBuildPoints( traceEnt ); + G_RewardAttackers( traceEnt ); + G_FreeEntity( traceEnt ); + return; + } + + // Cancel deconstruction (unmark) + if( deconstruct && g_markDeconstruct.integer && traceEnt->deconstruct ) + { + traceEnt->deconstruct = qfalse; + return; + } + + // Prevent destruction of the last spawn + if( ent->client->pers.teamSelection == TEAM_ALIENS && + traceEnt->s.modelindex == BA_A_SPAWN ) + { + if( level.numAlienSpawns <= 1 ) + lastSpawn = qtrue; + } + else if( ent->client->pers.teamSelection == TEAM_HUMANS && + traceEnt->s.modelindex == BA_H_SPAWN ) + { + if( level.numHumanSpawns <= 1 ) + lastSpawn = qtrue; + } + + if( lastSpawn && !g_cheats.integer && + !g_markDeconstruct.integer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN ); + return; + } + + // Don't allow destruction of buildables that cannot be rebuilt + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); + return; + } + + if( !g_markDeconstruct.integer || + ( ent->client->pers.teamSelection == TEAM_HUMANS && + !G_FindPower( traceEnt, qtrue ) ) ) + { + if( ent->client->buildTimer ) + { + G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); + return; + } + } + + if( traceEnt->health > 0 ) + { + if( !deconstruct ) + { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + traceEnt->health, 0, MOD_SUICIDE ); + } + else if( g_markDeconstruct.integer && + ( ent->client->pers.teamSelection != TEAM_HUMANS || + G_FindPower( traceEnt , qtrue ) || lastSpawn ) ) + { + traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction + traceEnt->deconstructTime = level.time; + } + else + { + if( !g_cheats.integer && !g_instantBuild.integer ) // add a bit to the build timer + { + ent->client->buildTimer += + BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->buildTime / 4; + G_RecalcBuildTimer(ent->client); + } + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + traceEnt->health, 0, MOD_DECONSTRUCT ); + G_FreeEntity( traceEnt ); + } + } + } +} + +/* +================= +Cmd_ActivateItem_f + +Activate an item +================= +*/ +void Cmd_ActivateItem_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + int upgrade, weapon; + + trap_Argv( 1, s, sizeof( s ) ); + + // "weapon" aliased to whatever weapon you have + if( !Q_stricmp( "weapon", s ) ) + { + if( ent->client->ps.weapon == WP_BLASTER && + BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + G_ForceWeaponChange( ent, WP_NONE ); + return; + } + + upgrade = BG_UpgradeByName( s )->number; + weapon = BG_WeaponByName( s )->number; + + if( upgrade != UP_NONE && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + BG_ActivateUpgrade( upgrade, ent->client->ps.stats ); + else if( weapon != WP_NONE && + BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) + { + if( ent->client->ps.weapon != weapon && + BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + G_ForceWeaponChange( ent, weapon ); + } + else + trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); +} + + +/* +================= +Cmd_DeActivateItem_f + +Deactivate an item +================= +*/ +void Cmd_DeActivateItem_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + upgrade_t upgrade; + + trap_Argv( 1, s, sizeof( s ) ); + upgrade = BG_UpgradeByName( s )->number; + + if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + { + BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); + if( upgrade == UP_JETPACK ) + BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &ent->client->ps ); + } + else + trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); +} + + +/* +================= +Cmd_ToggleItem_f +================= +*/ +void Cmd_ToggleItem_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + weapon_t weapon; + upgrade_t upgrade; + + trap_Argv( 1, s, sizeof( s ) ); + upgrade = BG_UpgradeByName( s )->number; + weapon = BG_WeaponByName( s )->number; + + if( weapon != WP_NONE ) + { + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + + //special case to allow switching between + //the blaster and the primary weapon + if( ent->client->ps.weapon != WP_BLASTER ) + weapon = WP_BLASTER; + else + weapon = WP_NONE; + + G_ForceWeaponChange( ent, weapon ); + } + else if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + { + if( BG_UpgradeIsActive( upgrade, ent->client->ps.stats ) ) + { + BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); + if( upgrade == UP_JETPACK ) + BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &ent->client->ps ); + } + else + BG_ActivateUpgrade( upgrade, ent->client->ps.stats ); + } + else + trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); +} + +/* +================= +Cmd_Buy_f +================= +*/ +void Cmd_Buy_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + weapon_t weapon; + upgrade_t upgrade; + qboolean energyOnly, sellHelmet = qfalse; + + trap_Argv( 1, s, sizeof( s ) ); + + weapon = BG_WeaponByName( s )->number; + upgrade = BG_UpgradeByName( s )->number; + + if( upgrade == UP_NONE && !Q_stricmp(s, "helmet") ) + { + if( g_humanStage.integer < S2 ) + upgrade = UP_HELMET_MK1; + else + upgrade = UP_HELMET_MK2; + sellHelmet = qtrue; + } + + // Only give energy from reactors or repeaters + if( G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + energyOnly = qfalse; + else if( upgrade == UP_AMMO && + BG_Weapon( ent->client->ps.stats[ STAT_WEAPON ] )->usesEnergy && + ( G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) || + G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) ) ) + energyOnly = qtrue; + else + { + if( upgrade == UP_AMMO && + BG_Weapon( ent->client->ps.weapon )->usesEnergy ) + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOENERGYAMMOHERE ); + else + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); + return; + } + + if( weapon != WP_NONE ) + { + //already got this? + if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD ); + return; + } + + // Only humans can buy stuff + if( BG_Weapon( weapon )->team != TEAM_HUMANS ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" ); + return; + } + + //are we /allowed/ to buy this? + if( !BG_Weapon( weapon )->purchasable ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); + return; + } + + //are we /allowed/ to buy this? + if( !BG_WeaponAllowedInStage( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); + return; + } + + //can afford this? + if( BG_Weapon( weapon )->price > (short)ent->client->pers.credit ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); + return; + } + + //have space to carry this? + if( BG_Weapon( weapon )->slots & BG_SlotsForInventory( ent->client->ps.stats ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); + return; + } + + // In some instances, weapons can't be changed + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + + ent->client->ps.stats[ STAT_WEAPON ] = weapon; + ent->client->ps.ammo = BG_Weapon( weapon )->maxAmmo; + ent->client->ps.clips = BG_Weapon( weapon )->maxClips; + + if( BG_Weapon( weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) + ent->client->ps.ammo *= BATTPACK_MODIFIER; + + G_ForceWeaponChange( ent, weapon ); + + //set build delay/pounce etc to 0 + ent->client->ps.stats[ STAT_MISC ] = 0; + ent->client->buildTimer = 0; + + //subtract from funds + G_AddCreditToClient( ent->client, -(short)BG_Weapon( weapon )->price, qfalse ); + } + else if( upgrade != UP_NONE ) + { + //already got this? + if( !sellHelmet && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD ); + return; + } + + //can afford this? + if( BG_Upgrade( upgrade )->price > (short)ent->client->pers.credit ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); + return; + } + + //have space to carry this? + if( !sellHelmet && BG_Upgrade( upgrade )->slots & BG_SlotsForInventory( ent->client->ps.stats ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); + return; + } + + // Only humans can buy stuff + if( BG_Upgrade( upgrade )->team != TEAM_HUMANS ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" ); + return; + } + + //are we /allowed/ to buy this? + if( !BG_Upgrade( upgrade )->purchasable ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); + return; + } + + //are we /allowed/ to buy this? + if( !BG_UpgradeAllowedInStage( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); + return; + } + + if( upgrade == UP_AMMO ) + { + G_GiveClientMaxAmmo( ent, energyOnly ); + if( !energyOnly && BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) && + ent->client->ps.stats[ STAT_FUEL ] < JETPACK_FUEL_FULL ) + { + G_AddEvent( ent, EV_JETPACK_REFUEL, 0) ; + ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; + } + } + else + { + if( upgrade == UP_BATTLESUIT ) + { + vec3_t newOrigin; + + if( !G_RoomForClassChange( ent, PCL_HUMAN_BSUIT, newOrigin ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITON ); + return; + } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN_BSUIT; + ent->client->pers.classSelection = PCL_HUMAN_BSUIT; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + } + else if( upgrade == UP_JETPACK ) + ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; + + if( sellHelmet ) + { + BG_RemoveUpgradeFromInventory( UP_HELMET_MK1, ent->client->ps.stats ); + BG_RemoveUpgradeFromInventory( UP_HELMET_MK2, ent->client->ps.stats ); + } + + //add to inventory + BG_AddUpgradeToInventory( upgrade, ent->client->ps.stats ); + } + + if( upgrade == UP_BATTPACK ) + G_GiveClientMaxAmmo( ent, qtrue ); + + //subtract from funds + G_AddCreditToClient( ent->client, -(short)BG_Upgrade( upgrade )->price, qfalse ); + } + else + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); + + //update ClientInfo + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); +} + + +/* +================= +Cmd_Sell_f +================= +*/ +void Cmd_Sell_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + int i; + weapon_t weapon; + upgrade_t upgrade; + + trap_Argv( 1, s, sizeof( s ) ); + + //no armoury nearby + if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); + return; + } + + if( !Q_stricmpn( s, "weapon", 6 ) ) + weapon = ent->client->ps.stats[ STAT_WEAPON ]; + else + weapon = BG_WeaponByName( s )->number; + + upgrade = BG_UpgradeByName( s )->number; + + if( !upgrade && !Q_stricmp( s, "helmet" ) ) + { + if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, ent->client->ps.stats ) ) + upgrade = UP_HELMET_MK1; + else if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, ent->client->ps.stats ) ) + upgrade = UP_HELMET_MK2; + } + + if( weapon != WP_NONE ) + { + weapon_t selected = BG_GetPlayerWeapon( &ent->client->ps ); + + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + + //are we /allowed/ to sell this? + if( !BG_Weapon( weapon )->purchasable ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't sell this weapon\n\"" ); + return; + } + + //remove weapon if carried + if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) + { + //guard against selling the HBUILD weapons exploit + if( weapon == WP_HBUILD && ent->client->buildTimer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); + return; + } + + ent->client->ps.stats[ STAT_WEAPON ] = WP_NONE; + // Cancel ghost buildables + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + + //add to funds + G_AddCreditToClient( ent->client, (short)BG_Weapon( weapon )->price, qfalse ); + } + + //if we have this weapon selected, force a new selection + if( weapon == selected ) + G_ForceWeaponChange( ent, WP_NONE ); + } + else if( upgrade != UP_NONE ) + { + //are we /allowed/ to sell this? + if( !BG_Upgrade( upgrade )->purchasable ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't sell this item\n\"" ); + return; + } + //remove upgrade if carried + if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + { + // shouldn't really need to test for this, but just to be safe + if( upgrade == UP_BATTLESUIT ) + { + vec3_t newOrigin; + + if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); + return; + } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; + ent->client->pers.classSelection = PCL_HUMAN; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + } + + //add to inventory + BG_RemoveUpgradeFromInventory( upgrade, ent->client->ps.stats ); + + if( upgrade == UP_BATTPACK ) + G_GiveClientMaxAmmo( ent, qtrue ); + + //add to funds + G_AddCreditToClient( ent->client, (short)BG_Upgrade( upgrade )->price, qfalse ); + } + } + else if( !Q_stricmp( s, "weapons" ) ) + { + weapon_t selected = BG_GetPlayerWeapon( &ent->client->ps ); + + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + //guard against selling the HBUILD weapons exploit + if( i == WP_HBUILD && ent->client->buildTimer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); + continue; + } + + if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && + BG_Weapon( i )->purchasable ) + { + ent->client->ps.stats[ STAT_WEAPON ] = WP_NONE; + + //add to funds + G_AddCreditToClient( ent->client, (short)BG_Weapon( i )->price, qfalse ); + } + + //if we have this weapon selected, force a new selection + if( i == selected ) + G_ForceWeaponChange( ent, WP_NONE ); + } + } + else if( !Q_stricmp( s, "upgrades" ) ) + { + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + //remove upgrade if carried + if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) && + BG_Upgrade( i )->purchasable ) + { + + // shouldn't really need to test for this, but just to be safe + if( i == UP_BATTLESUIT ) + { + vec3_t newOrigin; + + if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); + continue; + } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; + ent->client->pers.classSelection = PCL_HUMAN; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + } + + BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats ); + + if( i == UP_BATTPACK ) + G_GiveClientMaxAmmo( ent, qtrue ); + + //add to funds + G_AddCreditToClient( ent->client, (short)BG_Upgrade( i )->price, qfalse ); + } + } + } + else + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); + + //update ClientInfo + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); +} + +/* +================= +Cmd_CheckCuboidSize + +Check if the specified dimensions are valid. +================= +*/ +qboolean Cmd_CheckCuboidSize(vec3_t dims) +{ + if(g_cuboidSizeLimit.integer) + if(dims[0]>g_cuboidSizeLimit.integer||dims[1]>g_cuboidSizeLimit.integer||dims[2]>g_cuboidSizeLimit.integer) + return qfalse; + if(dims[0]*dims[1]*dims[2]client->cuboidSelection); + trap_SendServerCommand(ent->client-level.clients,va("cb3 %i\n",echo)); + G_RelayCuboidToSpectators(ent->client); + } + else + { + if(Cmd_CheckCuboidSize(ent->client->cuboidSelection)) + trap_SendServerCommand(ent->client-level.clients,va("cb3 %i %f %f %f\n",echo,ent->client->cuboidSelection[0],ent->client->cuboidSelection[1],ent->client->cuboidSelection[2])); + else + trap_SendServerCommand(ent->client-level.clients,va("cb3 %i %f %f %f\n",echo,g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer)); + } +} + + +/* +================= +Cmd_Build_f +================= +*/ +void Cmd_Build_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + buildable_t buildable; + float dist; + vec3_t origin, normal; + team_t team; + char buf[128]; + vec3_t dims; + + if( ent->client->pers.namelog->denyBuild ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED ); + return; + } + + if( ent->client->pers.teamSelection == level.surrenderTeam ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SURRENDER ); + return; + } + + trap_Argv( 1, s, sizeof( s ) ); + + buildable = BG_BuildableByName( s )->number; + + /* To allow players build cuboids with completely arbitrary + * dimensions (the current resizing method doesn't provide + * much precision) the build command was extended with the + * following syntax: + * build [building] [X] [Y] [Z] + * where X, Y and Z are respectively the cuboid's length + * on the X, Y and Z axis. + */ + if( BG_Buildable(buildable,NULL)->cuboid ) + { + if( trap_Argc() >= 5 ) + { + trap_Argv(2,s,sizeof(s)); + dims[0]=MAX(1,atof(s)); + trap_Argv(3,s,sizeof(s)); + dims[1]=MAX(1,atof(s)); + trap_Argv(4,s,sizeof(s)); + dims[2]=MAX(1,atof(s)); + if(!Cmd_CheckCuboidSize(dims)) + { + Com_sprintf(buf,sizeof(buf),"print \"^1error: invalid cuboid size (min volume: %i, max size: %s)\n\"", + CUBOID_MINVOLUME,(g_cuboidSizeLimit.integer?va("%ix%ix%i",g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer, g_cuboidSizeLimit.integer):"no limit")); + trap_SendServerCommand(ent->client-level.clients,buf); + return; + } + VectorCopy(dims,ent->client->cuboidSelection); + } + // client is building a cuboid for the first time so reset the selection to default + if(!Cmd_CheckCuboidSize(ent->client->cuboidSelection)) + { + ent->client->cuboidSelection[0]=32; + ent->client->cuboidSelection[1]=32; + ent->client->cuboidSelection[2]=32; + trap_SendServerCommand(ent->client-level.clients,"cb2 32 32 32"); + G_RelayCuboidToSpectators(ent->client); + } + + if(!BG_CuboidAllowed((team==TEAM_ALIENS?g_alienStage.integer:g_humanStage.integer))) + { + if(BG_CuboidMode()==1) + G_TriggerMenu(ent->client->ps.clientNum,MN_B_CUBOID_MODE1); + else + G_TriggerMenu(ent->client->ps.clientNum,MN_B_CUBOID_MODE2); + return; + } + } + + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); + return; + } + + team = ent->client->ps.stats[ STAT_TEAM ]; + + if( buildable != BA_NONE && + ( ( 1 << ent->client->ps.weapon ) & BG_Buildable( buildable, NULL )->buildWeapon ) && + BG_BuildableIsAllowed( buildable ) && + ( ( team == TEAM_ALIENS && BG_BuildableAllowedInStage( buildable, g_alienStage.integer ) ) || + ( team == TEAM_HUMANS && BG_BuildableAllowedInStage( buildable, g_humanStage.integer ) ) ) ) + { + dynMenu_t err; + dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; + + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + + //these are the errors displayed when the builder first selects something to use + switch( G_CanBuild( ent, buildable, dist, origin, normal, ent->client->cuboidSelection ) ) + { + // can place right away, set the blueprint and the valid togglebit + case IBE_NONE: + case IBE_TNODEWARN: + case IBE_RPTNOREAC: + case IBE_RPTPOWERHERE: + case IBE_SPWNWARN: + err = MN_NONE; + // we OR-in the selected builable later + ent->client->ps.stats[ STAT_BUILDABLE ] = SB_VALID_TOGGLEBIT; + break; + + // can't place yet but maybe soon: start with valid togglebit off + case IBE_NORMAL: + case IBE_NOCREEP: + case IBE_NOROOM: + case IBE_NOOVERMIND: + case IBE_NOPOWERHERE: + case IBE_NOSURF: + err = MN_NONE; + break; + + // more serious errors just pop a menu + case IBE_NOALIENBP: + err = MN_A_NOBP; + break; + + case IBE_ONEOVERMIND: + err = MN_A_ONEOVERMIND; + break; + + case IBE_ONEREACTOR: + err = MN_H_ONEREACTOR; + break; + + case IBE_NOHUMANBP: + err = MN_H_NOBP; + break; + + case IBE_NODCC: + err = MN_H_NODCC; + break; + + case IBE_PERMISSION: + err = MN_B_CANNOT; + break; + + case IBE_LASTSPAWN: + err = MN_B_LASTSPAWN; + break; + + case IBE_TOODENSE: + err = MN_B_TOODENSE; + break; + + default: + err = -1; // stop uninitialised warning + break; + } + + if( ( err == MN_A_NOBP || err == MN_H_NOBP ) && BG_Buildable(buildable,NULL)->cuboid ) + { + err = MN_NONE; + ent->client->ps.stats[ STAT_BUILDABLE ] = SB_VALID_TOGGLEBIT; + } + + if( err == MN_NONE || ent->client->pers.disableBlueprintErrors ) + { + trap_SendServerCommand(ent->client-level.clients,va("cb2 %f %f %f\n", + ent->client->cuboidSelection[0], + ent->client->cuboidSelection[1], + ent->client->cuboidSelection[2])); + G_RelayCuboidToSpectators(ent->client); + ent->client->ps.stats[ STAT_BUILDABLE ] |= buildable; + } + else + G_TriggerMenu( ent->client->ps.clientNum, err ); + } + else + G_TriggerMenu( ent->client->ps.clientNum, MN_B_CANNOT ); +} + +/* +================= +Cmd_Reload_f +================= +*/ +void Cmd_Reload_f( gentity_t *ent ) +{ + playerState_t *ps = &ent->client->ps; + int ammo; + + // weapon doesn't ever need reloading + if( BG_Weapon( ps->weapon )->infiniteAmmo ) + return; + + if( ps->clips <= 0 ) + return; + + if( BG_Weapon( ps->weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, ps->stats ) ) + ammo = BG_Weapon( ps->weapon )->maxAmmo * BATTPACK_MODIFIER; + else + ammo = BG_Weapon( ps->weapon )->maxAmmo; + + // don't reload when full + if( ps->ammo >= ammo ) + return; + + // the animation, ammo refilling etc. is handled by PM_Weapon + if( ent->client->ps.weaponstate != WEAPON_RELOADING ) + ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD; +} + +/* +================= +G_StopFromFollowing + +stops any other clients from following this one +called when a player leaves a team or dies +================= +*/ +void G_StopFromFollowing( gentity_t *ent ) +{ + int i; + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && + level.clients[ i ].sess.spectatorClient == ent->client->ps.clientNum ) + { + if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) + G_StopFollowing( &g_entities[ i ] ); + } + } +} + +/* +================= +G_StopFollowing + +If the client being followed leaves the game, or you just want to drop +to free floating spectator mode +================= +*/ +void G_StopFollowing( gentity_t *ent ) +{ + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; + + if( ent->client->pers.teamSelection == TEAM_NONE ) + { + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FREE; + } + else + { + vec3_t spawn_origin, spawn_angles; + + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_LOCKED; + + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, ent->client->ps.origin ); + G_SetClientViewAngle( ent, spawn_angles ); + } + ent->client->sess.spectatorClient = -1; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + ent->client->ps.stats[ STAT_STATE ] = 0; + ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; + ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); + ent->client->ps.viewangles[ PITCH ] = 0.0f; + ent->client->ps.clientNum = ent - g_entities; + ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit; + + CalculateRanks( ); +} + +/* +================= +G_FollowLockView + +Client is still following a player, but that player has gone to spectator +mode and cannot be followed for the moment +================= +*/ +void G_FollowLockView( gentity_t *ent ) +{ + vec3_t spawn_origin, spawn_angles; + int clientNum; + + clientNum = ent->client->sess.spectatorClient; + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FOLLOW; + ent->client->ps.clientNum = clientNum; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; + ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; + ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + ent->client->ps.viewangles[ PITCH ] = 0.0f; + + // Put the view at the team spectator lock position + if( level.clients[ clientNum ].pers.teamSelection == TEAM_ALIENS ) + G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( level.clients[ clientNum ].pers.teamSelection == TEAM_HUMANS ) + G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, ent->client->ps.origin ); + G_SetClientViewAngle( ent, spawn_angles ); +} + +/* +================= +G_FollowNewClient + +This was a really nice, elegant function. Then I fucked it up. +================= +*/ +qboolean G_FollowNewClient( gentity_t *ent, int dir ) +{ + int clientnum = ent->client->sess.spectatorClient; + int original = clientnum; + qboolean selectAny = qfalse; + + if( dir > 1 ) + dir = 1; + else if( dir < -1 ) + dir = -1; + else if( dir == 0 ) + return qtrue; + + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + return qfalse; + + // select any if no target exists + if( clientnum < 0 || clientnum >= level.maxclients ) + { + clientnum = original = 0; + selectAny = qtrue; + } + + do + { + clientnum += dir; + + if( clientnum >= level.maxclients ) + clientnum = 0; + + if( clientnum < 0 ) + clientnum = level.maxclients - 1; + + // can't follow self + if( &g_entities[ clientnum ] == ent ) + continue; + + // avoid selecting existing follow target + if( clientnum == original && !selectAny ) + continue; //effectively break; + + // can only follow connected clients + if( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) + continue; + + // can't follow a spectator + if( level.clients[ clientnum ].pers.teamSelection == TEAM_NONE ) + continue; + + // if stickyspec is disabled, can't follow someone in queue either + if( !ent->client->pers.stickySpec && + level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) + continue; + + // can only follow teammates when dead and on a team + if( ent->client->pers.teamSelection != TEAM_NONE && + ( level.clients[ clientnum ].pers.teamSelection != + ent->client->pers.teamSelection ) ) + continue; + + // this is good, we can use it + ent->client->sess.spectatorClient = clientnum; + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + + // if this client is in the spawn queue, we need to do something special + if( level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) + G_FollowLockView( ent ); + + return qtrue; + + } while( clientnum != original ); + + return qfalse; +} + +/* +================= +G_ToggleFollow +================= +*/ +void G_ToggleFollow( gentity_t *ent ) +{ + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + else + G_FollowNewClient( ent, 1 ); +} + +/* +================= +Cmd_Follow_f +================= +*/ +void Cmd_Follow_f( gentity_t *ent ) +{ + int i; + char arg[ MAX_NAME_LENGTH ]; + + // won't work unless spectating + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + return; + + if( trap_Argc( ) != 2 ) + { + G_ToggleFollow( ent ); + } + else + { + char err[ MAX_STRING_CHARS ]; + trap_Argv( 1, arg, sizeof( arg ) ); + + i = G_ClientNumberFromString( arg, err, sizeof( err ) ); + + if( i == -1 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"follow: %s\"", err ) ); + return; + } + + // can't follow self + if( &level.clients[ i ] == ent->client ) + return; + + // can't follow another spectator if sticky spec is off + if( !ent->client->pers.stickySpec && + level.clients[ i ].sess.spectatorState != SPECTATOR_NOT ) + return; + + // if not on team spectator, you can only follow teammates + if( ent->client->pers.teamSelection != TEAM_NONE && + ( level.clients[ i ].pers.teamSelection != + ent->client->pers.teamSelection ) ) + return; + + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + ent->client->sess.spectatorClient = i; + } +} + +/* +================= +Cmd_FollowCycle_f +================= +*/ +void Cmd_FollowCycle_f( gentity_t *ent ) +{ + char args[ 11 ]; + int dir = 1; + + trap_Argv( 0, args, sizeof( args ) ); + if( Q_stricmp( args, "followprev" ) == 0 ) + dir = -1; + + // won't work unless spectating + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + return; + + G_FollowNewClient( ent, dir ); +} + +static void Cmd_Ignore_f( gentity_t *ent ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ]; + char cmd[ 9 ]; + int matches = 0; + int i; + qboolean ignore = qfalse; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "ignore" ) == 0 ) + ignore = qtrue; + + if( trap_Argc() < 2 ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "usage: %s [clientNum | partial name match]\n\"", cmd ) ); + return; + } + + Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); + matches = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); + if( matches < 1 ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "%s: no clients match the name '%s'\n\"", cmd, name ) ); + return; + } + + for( i = 0; i < matches; i++ ) + { + if( ignore ) + { + if( !Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) ) + { + Com_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] ); + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "ignore: added %s^7 to your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + else + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "ignore: %s^7 is already on your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + } + else + { + if( Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) ) + { + Com_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] ); + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "unignore: removed %s^7 from your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + else + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "unignore: %s^7 is not on your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + } + } +} + +/* +================= +Cmd_ListMaps_f + +List all maps on the server +================= +*/ + +static int SortMaps( const void *a, const void *b ) +{ + return strcmp( *(char **)a, *(char **)b ); +} + +#define MAX_MAPLIST_MAPS 256 +#define MAX_MAPLIST_ROWS 9 +void Cmd_ListMaps_f( gentity_t *ent ) +{ + char search[ 16 ] = {""}; + char fileList[ 4096 ] = {""}; + char *fileSort[ MAX_MAPLIST_MAPS ]; + char *filePtr, *p; + int numFiles; + int fileLen = 0; + int shown = 0; + int count = 0; + int page = 0; + int pages; + int row, rows; + int start, i, j; + + if( trap_Argc( ) > 1 ) + { + trap_Argv( 1, search, sizeof( search ) ); + for( p = search; ( *p ) && isdigit( *p ); p++ ); + if( !( *p ) ) + { + page = atoi( search ); + search[ 0 ] = '\0'; + } + else if( trap_Argc( ) > 2 ) + { + char lp[ 8 ]; + trap_Argv( 2, lp, sizeof( lp ) ); + page = atoi( lp ); + } + + if( page > 0 ) + page--; + else if( page < 0 ) + page = 0; + } + + numFiles = trap_FS_GetFileList( "maps/", ".bsp", + fileList, sizeof( fileList ) ); + filePtr = fileList; + for( i = 0; i < numFiles && count < MAX_MAPLIST_MAPS; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + if ( fileLen < 5 ) + continue; + + filePtr[ fileLen - 4 ] = '\0'; + + if( search[ 0 ] && !strstr( filePtr, search ) ) + continue; + + fileSort[ count ] = filePtr; + count++; + } + qsort( fileSort, count, sizeof( fileSort[ 0 ] ), SortMaps ); + + rows = ( count + 2 ) / 3; + pages = MAX( 1, ( rows + MAX_MAPLIST_ROWS - 1 ) / MAX_MAPLIST_ROWS ); + if( page >= pages ) + page = pages - 1; + + start = page * MAX_MAPLIST_ROWS * 3; + if( count < start + ( 3 * MAX_MAPLIST_ROWS ) ) + rows = ( count - start + 2 ) / 3; + else + rows = MAX_MAPLIST_ROWS; + + ADMBP_begin( ); + for( row = 0; row < rows; row++ ) + { + for( i = start + row, j = 0; i < count && j < 3; i += rows, j++ ) + { + ADMBP( va( "^7 %-20s", fileSort[ i ] ) ); + shown++; + } + ADMBP( "\n" ); + } + if ( search[ 0 ] ) + ADMBP( va( "^3listmaps: ^7found %d maps matching '%s^7'", count, search ) ); + else + ADMBP( va( "^3listmaps: ^7listing %d of %d maps", shown, count ) ); + if( pages > 1 ) + ADMBP( va( ", page %d of %d", page + 1, pages ) ); + if( page + 1 < pages ) + ADMBP( va( ", use 'listmaps %s%s%d' to see more", + search, ( search[ 0 ] ) ? " ": "", page + 2 ) ); + ADMBP( ".\n" ); + ADMBP_end( ); +} + +/* +================= +Cmd_Test_f +================= +*/ +void Cmd_Test_f( gentity_t *humanPlayer ) +{ +} + +/* +================= +Cmd_Damage_f + +Deals damage to you (for testing), arguments: [damage] [dx] [dy] [dz] +The dx/dy arguments describe the damage point's offset from the entity origin +================= +*/ +void Cmd_Damage_f( gentity_t *ent ) +{ + vec3_t point; + char arg[ 16 ]; + float dx = 0.0f, dy = 0.0f, dz = 100.0f; + int damage = 100; + qboolean nonloc = qtrue; + + if( trap_Argc() > 1 ) + { + trap_Argv( 1, arg, sizeof( arg ) ); + damage = atoi( arg ); + } + if( trap_Argc() > 4 ) + { + trap_Argv( 2, arg, sizeof( arg ) ); + dx = atof( arg ); + trap_Argv( 3, arg, sizeof( arg ) ); + dy = atof( arg ); + trap_Argv( 4, arg, sizeof( arg ) ); + dz = atof( arg ); + nonloc = qfalse; + } + VectorCopy( ent->s.origin, point ); + point[ 0 ] += dx; + point[ 1 ] += dy; + point[ 2 ] += dz; + G_Damage( ent, NULL, NULL, NULL, point, damage, + ( nonloc ? DAMAGE_NO_LOCDAMAGE : 0 ), MOD_TARGET_LASER ); +} + +/* +================== +G_FloodLimited + +Determine whether a user is flood limited, and adjust their flood demerits +Print them a warning message if they are over the limit +Return is time in msec until the user can speak again +================== +*/ +int G_FloodLimited( gentity_t *ent ) +{ + int deltatime, ms; + + if( g_floodMinTime.integer <= 0 ) + return 0; + + // handles !ent + if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) + return 0; + + deltatime = level.time - ent->client->pers.floodTime; + + ent->client->pers.floodDemerits += g_floodMinTime.integer - deltatime; + if( ent->client->pers.floodDemerits < 0 ) + ent->client->pers.floodDemerits = 0; + ent->client->pers.floodTime = level.time; + + ms = ent->client->pers.floodDemerits - g_floodMaxDemerits.integer; + if( ms <= 0 ) + return 0; + trap_SendServerCommand( ent - g_entities, va( "print \"You are flooding: " + "please wait %d second%s before trying again\n", + ( ms + 999 ) / 1000, ( ms > 1000 ) ? "s" : "" ) ); + return ms; +} + +commands_t cmds[ ] = { + { "a", CMD_MESSAGE|CMD_INTERMISSION, Cmd_AdminMessage_f }, + { "build", CMD_TEAM|CMD_LIVING, Cmd_Build_f }, + { "buy", CMD_HUMAN|CMD_LIVING, Cmd_Buy_f }, + { "callteamvote", CMD_MESSAGE|CMD_TEAM, Cmd_CallVote_f }, + { "callvote", CMD_MESSAGE, Cmd_CallVote_f }, + { "cb", 0, Cmd_Cb_f }, //NOTE: it's a command used only by cgame + { "class", CMD_TEAM, Cmd_Class_f }, + { "damage", CMD_CHEAT|CMD_LIVING, Cmd_Damage_f }, + { "deconstruct", CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, + { "destroy", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, + { "follow", CMD_SPEC, Cmd_Follow_f }, + { "follownext", CMD_SPEC, Cmd_FollowCycle_f }, + { "followprev", CMD_SPEC, Cmd_FollowCycle_f }, + { "give", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Give_f }, + { "god", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_God_f }, + { "ignore", 0, Cmd_Ignore_f }, + { "impregnate", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Impregnate_f }, + { "itemact", CMD_HUMAN|CMD_LIVING, Cmd_ActivateItem_f }, + { "itemdeact", CMD_HUMAN|CMD_LIVING, Cmd_DeActivateItem_f }, + { "itemtoggle", CMD_HUMAN|CMD_LIVING, Cmd_ToggleItem_f }, + { "kill", CMD_TEAM|CMD_LIVING, Cmd_Kill_f }, + { "levelshot", CMD_CHEAT, Cmd_LevelShot_f }, + { "listmaps", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListMaps_f }, + { "m", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, + { "mt", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, + { "noclip", CMD_CHEAT_TEAM, Cmd_Noclip_f }, + { "notarget", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Notarget_f }, + { "reload", CMD_HUMAN|CMD_LIVING, Cmd_Reload_f }, + { "say", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, + { "say_area", CMD_MESSAGE|CMD_TEAM|CMD_LIVING, Cmd_SayArea_f }, + { "say_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, + { "score", CMD_INTERMISSION, ScoreboardMessage }, + { "sell", CMD_HUMAN|CMD_LIVING, Cmd_Sell_f }, + { "setviewpos", CMD_CHEAT_TEAM, Cmd_SetViewpos_f }, + { "team", 0, Cmd_Team_f }, + { "teamvote", CMD_TEAM, Cmd_Vote_f }, + { "test", CMD_CHEAT, Cmd_Test_f }, + { "unignore", 0, Cmd_Ignore_f }, + { "vote", 0, Cmd_Vote_f }, + { "vsay", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "vsay_local", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "vsay_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "where", 0, Cmd_Where_f } +}; +static size_t numCmds = sizeof( cmds ) / sizeof( cmds[ 0 ] ); + +/* +================= +ClientCommand +================= +*/ +void ClientCommand( int clientNum ) +{ + gentity_t *ent; + char cmd[ MAX_TOKEN_CHARS ]; + commands_t *command; + + ent = g_entities + clientNum; + if( !ent->client || ent->client->pers.connected != CON_CONNECTED ) + return; // not fully in game yet + + trap_Argv( 0, cmd, sizeof( cmd ) ); + + command = bsearch( cmd, cmds, numCmds, sizeof( cmds[ 0 ] ), cmdcmp ); + + if( !command ) + { + if( !G_admin_cmd_check( ent ) ) + trap_SendServerCommand( clientNum, + va( "print \"Unknown command %s\n\"", cmd ) ); + return; + } + + // do tests here to reduce the amount of repeated code + + if( !( command->cmdFlags & CMD_INTERMISSION ) && + ( level.intermissiontime || level.pausedTime ) ) + return; + + if( command->cmdFlags & CMD_CHEAT && !g_cheats.integer ) + { + G_TriggerMenu( clientNum, MN_CMD_CHEAT ); + return; + } + + if( command->cmdFlags & CMD_MESSAGE && ( ent->client->pers.namelog->muted || + G_FloodLimited( ent ) ) ) + return; + + if( command->cmdFlags & CMD_TEAM && + ent->client->pers.teamSelection == TEAM_NONE ) + { + G_TriggerMenu( clientNum, MN_CMD_TEAM ); + return; + } + + if( command->cmdFlags & CMD_CHEAT_TEAM && !g_cheats.integer && + ent->client->pers.teamSelection != TEAM_NONE ) + { + G_TriggerMenu( clientNum, MN_CMD_CHEAT_TEAM ); + return; + } + + if( command->cmdFlags & CMD_SPEC && + ent->client->sess.spectatorState == SPECTATOR_NOT ) + { + G_TriggerMenu( clientNum, MN_CMD_SPEC ); + return; + } + + if( command->cmdFlags & CMD_ALIEN && + ent->client->pers.teamSelection != TEAM_ALIENS ) + { + G_TriggerMenu( clientNum, MN_CMD_ALIEN ); + return; + } + + if( command->cmdFlags & CMD_HUMAN && + ent->client->pers.teamSelection != TEAM_HUMANS ) + { + G_TriggerMenu( clientNum, MN_CMD_HUMAN ); + return; + } + + if( command->cmdFlags & CMD_LIVING && + ( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || + ent->client->sess.spectatorState != SPECTATOR_NOT ) ) + { + G_TriggerMenu( clientNum, MN_CMD_LIVING ); + return; + } + + command->cmdHandler( ent ); +} + +void G_ListCommands( gentity_t *ent ) +{ + int i; + char out[ MAX_STRING_CHARS ] = ""; + int len, outlen; + + outlen = 0; + + for( i = 0; i < numCmds; i++ ) + { + len = strlen( cmds[ i ].cmdName ) + 1; + if( len + outlen >= sizeof( out ) - 1 ) + { + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); + outlen = 0; + } + + strcpy( out + outlen, va( " %s", cmds[ i ].cmdName ) ); + outlen += len; + } + + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); + G_admin_cmdlist( ent ); +} + +void G_DecolorString( char *in, char *out, int len ) +{ + qboolean decolor = qtrue; + + len--; + + while( *in && len > 0 ) { + if( *in == DECOLOR_OFF || *in == DECOLOR_ON ) + { + decolor = ( *in == DECOLOR_ON ); + in++; + continue; + } + if( Q_IsColorString( in ) && decolor ) { + in += 2; + continue; + } + *out++ = *in++; + len--; + } + *out = '\0'; +} + +void G_UnEscapeString( char *in, char *out, int len ) +{ + len--; + + while( *in && len > 0 ) + { + if( *in >= ' ' || *in == '\n' ) + { + *out++ = *in; + len--; + } + in++; + } + *out = '\0'; +} + +void Cmd_PrivateMessage_f( gentity_t *ent ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ]; + char cmd[ 12 ]; + char text[ MAX_STRING_CHARS ]; + char *msg; + char color; + int i, pcount; + int count = 0; + qboolean teamonly = qfalse; + char recipients[ MAX_STRING_CHARS ] = ""; + + if( !g_privateMessages.integer && ent ) + { + ADMP( "Sorry, but private messages have been disabled\n" ); + return; + } + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( trap_Argc( ) < 3 ) + { + ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) ); + return; + } + + if( !Q_stricmp( cmd, "mt" ) ) + teamonly = qtrue; + + trap_Argv( 1, name, sizeof( name ) ); + msg = ConcatArgs( 2 ); + pcount = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); + + G_CensorString( text, msg, sizeof( text ), ent ); + + // send the message + for( i = 0; i < pcount; i++ ) + { + if( G_SayTo( ent, &g_entities[ pids[ i ] ], + teamonly ? SAY_TPRIVMSG : SAY_PRIVMSG, text ) ) + { + count++; + Q_strcat( recipients, sizeof( recipients ), va( "%s" S_COLOR_WHITE ", ", + level.clients[ pids[ i ] ].pers.netname ) ); + } + } + + // report the results + color = teamonly ? COLOR_CYAN : COLOR_YELLOW; + + if( !count ) + ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n", + name ) ); + else + { + ADMP( va( "^%cPrivate message: ^7%s\n", color, text ) ); + // remove trailing ", " + recipients[ strlen( recipients ) - 2 ] = '\0'; + ADMP( va( "^%csent to %i player%s: " S_COLOR_WHITE "%s\n", color, count, + count == 1 ? "" : "s", recipients ) ); + + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\" \"%s\": ^%c%s\n", + ( teamonly ) ? "TPrivMsg" : "PrivMsg", + ( ent ) ? ent - g_entities : -1, + ( ent ) ? ent->client->pers.netname : "console", + name, color, msg ); + } +} + +/* +================= +Cmd_AdminMessage_f + +Send a message to all active admins +================= +*/ +void Cmd_AdminMessage_f( gentity_t *ent ) +{ + // Check permissions and add the appropriate user [prefix] + if( !G_admin_permission( ent, ADMF_ADMINCHAT ) ) + { + if( !g_publicAdminMessages.integer ) + { + ADMP( "Sorry, but use of /a by non-admins has been disabled.\n" ); + return; + } + else + { + ADMP( "Your message has been sent to any available admins " + "and to the server logs.\n" ); + } + } + + if( trap_Argc( ) < 2 ) + { + ADMP( "usage: a [message]\n" ); + return; + } + + G_AdminMessage( ent, ConcatArgs( 1 ) ); +} + diff --git a/src/game/g_cmds.c.orig b/src/game/g_cmds.c.orig new file mode 100644 index 0000000..5a30f7b --- /dev/null +++ b/src/game/g_cmds.c.orig @@ -0,0 +1,3588 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +/* +================== +G_SanitiseString + +Remove color codes and non-alphanumeric characters from a string +================== +*/ +void G_SanitiseString( char *in, char *out, int len ) +{ + len--; + + while( *in && len > 0 ) + { + if( Q_IsColorString( in ) ) + { + in += 2; // skip color code + continue; + } + + if( isalnum( *in ) ) + { + *out++ = tolower( *in ); + len--; + } + in++; + } + *out = 0; +} + +/* +================== +G_ClientNumberFromString + +Returns a player number for either a number or name string +Returns -1 and optionally sets err if invalid or not exactly 1 match +err will have a trailing \n if set +================== +*/ +int G_ClientNumberFromString( char *s, char *err, int len ) +{ + gclient_t *cl; + int i, found = 0, m = -1; + char s2[ MAX_NAME_LENGTH ]; + char n2[ MAX_NAME_LENGTH ]; + char *p = err; + int l, l2 = len; + + if( !s[ 0 ] ) + { + if( p ) + Q_strncpyz( p, "no player name or slot # provided\n", len ); + + return -1; + } + + // numeric values are just slot numbers + for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); + if( !s[ i ] ) + { + i = atoi( s ); + + if( i < 0 || i >= level.maxclients ) + return -1; + + cl = &level.clients[ i ]; + + if( cl->pers.connected == CON_DISCONNECTED ) + { + if( p ) + Q_strncpyz( p, "no player connected in that slot #\n", len ); + + return -1; + } + + return i; + } + + G_SanitiseString( s, s2, sizeof( s2 ) ); + if( !s2[ 0 ] ) + { + if( p ) + Q_strncpyz( p, "no player name provided\n", len ); + + return -1; + } + + if( p ) + { + Q_strncpyz( p, "more than one player name matches. " + "be more specific or use the slot #:\n", l2 ); + l = strlen( p ); + p += l; + l2 -= l; + } + + // check for a name match + for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ ) + { + if( cl->pers.connected == CON_DISCONNECTED ) + continue; + + G_SanitiseString( cl->pers.netname, n2, sizeof( n2 ) ); + + if( !strcmp( n2, s2 ) ) + return i; + + if( strstr( n2, s2 ) ) + { + if( p ) + { + l = Q_snprintf( p, l2, "%-2d - %s^7\n", i, cl->pers.netname ); + p += l; + l2 -= l; + } + + found++; + m = i; + } + } + + if( found == 1 ) + return m; + + if( found == 0 && err ) + Q_strncpyz( err, "no connected player by that name or slot #\n", len ); + + return -1; +} + +/* +================== +G_ClientNumbersFromString + +Sets plist to an array of integers that represent client numbers that have +names that are a partial match for s. + +Returns number of matching clientids up to max. +================== +*/ +int G_ClientNumbersFromString( char *s, int *plist, int max ) +{ + gclient_t *p; + int i, found = 0; + char *endptr; + char n2[ MAX_NAME_LENGTH ] = {""}; + char s2[ MAX_NAME_LENGTH ] = {""}; + + if( max == 0 ) + return 0; + + if( !s[ 0 ] ) + return 0; + + // if a number is provided, it is a clientnum + i = strtol( s, &endptr, 10 ); + if( *endptr == '\0' ) + { + if( i >= 0 && i < level.maxclients ) + { + p = &level.clients[ i ]; + if( p->pers.connected != CON_DISCONNECTED ) + { + *plist = i; + return 1; + } + } + // we must assume that if only a number is provided, it is a clientNum + return 0; + } + + // now look for name matches + G_SanitiseString( s, s2, sizeof( s2 ) ); + if( !s2[ 0 ] ) + return 0; + for( i = 0; i < level.maxclients && found < max; i++ ) + { + p = &level.clients[ i ]; + if( p->pers.connected == CON_DISCONNECTED ) + { + continue; + } + G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) ); + if( strstr( n2, s2 ) ) + { + *plist++ = i; + found++; + } + } + return found; +} + +/* +================== +ScoreboardMessage + +================== +*/ +void ScoreboardMessage( gentity_t *ent ) +{ + char entry[ 1024 ]; + char string[ 1400 ]; + int stringlength; + int i, j; + gclient_t *cl; + int numSorted; + weapon_t weapon = WP_NONE; + upgrade_t upgrade = UP_NONE; + + // send the latest information on all clients + string[ 0 ] = 0; + stringlength = 0; + + numSorted = level.numConnectedClients; + + for( i = 0; i < numSorted; i++ ) + { + int ping; + + cl = &level.clients[ level.sortedClients[ i ] ]; + + if( cl->pers.connected == CON_CONNECTING ) + ping = -1; + else + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + + if( cl->sess.spectatorState == SPECTATOR_NOT && + ( ent->client->pers.teamSelection == TEAM_NONE || + cl->pers.teamSelection == ent->client->pers.teamSelection ) ) + { + weapon = cl->ps.weapon; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) + upgrade = UP_BATTLESUIT; + else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) + upgrade = UP_JETPACK; + else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) ) + upgrade = UP_BATTPACK; + else if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, cl->ps.stats ) ) + upgrade = UP_HELMET_MK1; + else if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, cl->ps.stats ) ) + upgrade = UP_HELMET_MK2; + else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) + upgrade = UP_LIGHTARMOUR; + else + upgrade = UP_NONE; + } + else + { + weapon = WP_NONE; + upgrade = UP_NONE; + } + + Com_sprintf( entry, sizeof( entry ), + " %d %d %d %d %d %d", level.sortedClients[ i ], cl->ps.persistant[ PERS_SCORE ], + ping, ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade ); + + j = strlen( entry ); + + if( stringlength + j >= 1024 ) + break; + + strcpy( string + stringlength, entry ); + stringlength += j; + } + + trap_SendServerCommand( ent-g_entities, va( "scores %i %i%s", + level.alienKills, level.humanKills, string ) ); +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs( int start ) +{ + int i, c, tlen; + static char line[ MAX_STRING_CHARS ]; + int len; + char arg[ MAX_STRING_CHARS ]; + + len = 0; + c = trap_Argc( ); + + for( i = start; i < c; i++ ) + { + trap_Argv( i, arg, sizeof( arg ) ); + tlen = strlen( arg ); + + if( len + tlen >= MAX_STRING_CHARS - 1 ) + break; + + memcpy( line + len, arg, tlen ); + len += tlen; + + if( len == MAX_STRING_CHARS - 1 ) + break; + + if( i != c - 1 ) + { + line[ len ] = ' '; + len++; + } + } + + line[ len ] = 0; + + return line; +} + +/* +================== +ConcatArgsPrintable +Duplicate of concatargs but enquotes things that need to be +Used to log command arguments in a way that preserves user intended tokenizing +================== +*/ +char *ConcatArgsPrintable( int start ) +{ + int i, c, tlen; + static char line[ MAX_STRING_CHARS ]; + int len; + char arg[ MAX_STRING_CHARS + 2 ]; + char *printArg; + + len = 0; + c = trap_Argc( ); + + for( i = start; i < c; i++ ) + { + printArg = arg; + trap_Argv( i, arg, sizeof( arg ) ); + if( strchr( arg, ' ' ) ) + printArg = va( "\"%s\"", arg ); + tlen = strlen( printArg ); + + if( len + tlen >= MAX_STRING_CHARS - 1 ) + break; + + memcpy( line + len, printArg, tlen ); + len += tlen; + + if( len == MAX_STRING_CHARS - 1 ) + break; + + if( i != c - 1 ) + { + line[ len ] = ' '; + len++; + } + } + + line[ len ] = 0; + + return line; +} + + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( gentity_t *ent ) +{ + char *name; + qboolean give_all = qfalse; + + if( trap_Argc( ) < 2 ) + { + ADMP( "usage: give [what]\n" ); + ADMP( "usage: valid choices are: all, health, funds [amount], stamina, " + "poison, gas, ammo\n" ); + return; + } + + name = ConcatArgs( 1 ); + if( Q_stricmp( name, "all" ) == 0 ) + give_all = qtrue; + + if( give_all || Q_stricmp( name, "health" ) == 0 ) + { + ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; + BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats ); + } + + if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) + { + float credits; + + if( give_all || trap_Argc( ) < 3 ) + credits = 30000.0f; + else + { + credits = atof( name + 6 ) * + ( ent->client->pers.teamSelection == + TEAM_ALIENS ? ALIEN_CREDITS_PER_KILL : 1.0f ); + + // clamp credits manually, as G_AddCreditToClient() expects a short int + if( credits > SHRT_MAX ) + credits = 30000.0f; + else if( credits < SHRT_MIN ) + credits = -30000.0f; + } + + G_AddCreditToClient( ent->client, (short)credits, qtrue ); + } + + if( give_all || Q_stricmp( name, "stamina" ) == 0 ) + ent->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + + if( Q_stricmp( name, "poison" ) == 0 ) + { + if( ent->client->pers.teamSelection == TEAM_HUMANS ) + { + ent->client->ps.stats[ STAT_STATE ] |= SS_POISONED; + ent->client->lastPoisonTime = level.time; + ent->client->lastPoisonClient = ent; + } + else + { + ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; + ent->client->boostedTime = level.time; + } + } + + if( Q_stricmp( name, "gas" ) == 0 ) + { + ent->client->ps.eFlags |= EF_POISONCLOUDED; + ent->client->lastPoisonCloudedTime = level.time; + trap_SendServerCommand( ent->client->ps.clientNum, + "poisoncloud" ); + } + + if( give_all || Q_stricmp( name, "ammo" ) == 0 ) + { + gclient_t *client = ent->client; + + if( client->ps.weapon != WP_ALEVEL3_UPG && + BG_Weapon( client->ps.weapon )->infiniteAmmo ) + return; + + client->ps.ammo = BG_Weapon( client->ps.weapon )->maxAmmo; + client->ps.clips = BG_Weapon( client->ps.weapon )->maxClips; + + if( BG_Weapon( client->ps.weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, client->ps.stats ) ) + client->ps.ammo = (int)( (float)client->ps.ammo * BATTPACK_MODIFIER ); + } + + if( give_all || Q_stricmp( name, "fuel" ) == 0 ) + { + if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) ) + ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( gentity_t *ent ) +{ + char *msg; + + ent->flags ^= FL_GODMODE; + + if( !( ent->flags & FL_GODMODE ) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( gentity_t *ent ) +{ + char *msg; + + ent->flags ^= FL_NOTARGET; + + if( !( ent->flags & FL_NOTARGET ) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( gentity_t *ent ) +{ + char *msg; + + if( ent->client->noclip ) + msg = "noclip OFF\n"; + else + msg = "noclip ON\n"; + + ent->client->noclip = !ent->client->noclip; + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_LevelShot_f( gentity_t *ent ) +{ + BeginIntermission( ); + trap_SendServerCommand( ent - g_entities, "clientLevelShot" ); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( gentity_t *ent ) +{ + if( g_cheats.integer ) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; + player_die( ent, ent, ent, 100000, MOD_SUICIDE ); + } + else + { + if( ent->suicideTime == 0 ) + { + trap_SendServerCommand( ent-g_entities, "print \"You will suicide in 20 seconds\n\"" ); + ent->suicideTime = level.time + 20000; + } + else if( ent->suicideTime > level.time ) + { + trap_SendServerCommand( ent-g_entities, "print \"Suicide cancelled\n\"" ); + ent->suicideTime = 0; + } + } +} + +/* +================= +Cmd_Team_f +================= +*/ +void Cmd_Team_f( gentity_t *ent ) +{ + team_t team; + team_t oldteam = ent->client->pers.teamSelection; + char s[ MAX_TOKEN_CHARS ]; + qboolean force = G_admin_permission( ent, ADMF_FORCETEAMCHANGE ); + int aliens = level.numAlienClients; + int humans = level.numHumanClients; + + if( oldteam == TEAM_ALIENS ) + aliens--; + else if( oldteam == TEAM_HUMANS ) + humans--; + + // stop team join spam + if( ent->client->pers.teamChangeTime && + level.time - ent->client->pers.teamChangeTime < 1000 ) + return; + + // stop switching teams for gameplay exploit reasons by enforcing a long + // wait before they can come back + if( !force && !g_cheats.integer && ent->client->pers.aliveSeconds && + level.time - ent->client->pers.teamChangeTime < 30000 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"You must wait another %d seconds before changing teams again\n\"", + (int) ( ( 30000 - ( level.time - ent->client->pers.teamChangeTime ) ) / 1000.f ) ) ); + return; + } + + // disallow joining teams during warmup + if( g_doWarmup.integer && ( ( level.warmupTime - level.time ) / 1000 ) > 0 ) + { + G_TriggerMenu( ent - g_entities, MN_WARMUP ); + return; + } + + trap_Argv( 1, s, sizeof( s ) ); + + if( !s[ 0 ] ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"team: %s\n\"", + BG_TeamName( oldteam ) ) ); + return; + } + + if( !Q_stricmp( s, "auto" ) ) + { + if( level.humanTeamLocked && level.alienTeamLocked ) + team = TEAM_NONE; + else if( level.humanTeamLocked || humans > aliens ) + team = TEAM_ALIENS; + + else if( level.alienTeamLocked || aliens > humans ) + team = TEAM_HUMANS; + else + team = TEAM_ALIENS + rand( ) / ( RAND_MAX / 2 + 1 ); + } + else switch( G_TeamFromString( s ) ) + { + case TEAM_NONE: + team = TEAM_NONE; + break; + + case TEAM_ALIENS: + if( level.alienTeamLocked ) + { + G_TriggerMenu( ent - g_entities, MN_A_TEAMLOCKED ); + return; + } + else if( level.humanTeamLocked ) + force = qtrue; + + if( !force && g_teamForceBalance.integer && aliens > humans ) + { + G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); + return; + } + + team = TEAM_ALIENS; + break; + + case TEAM_HUMANS: + if( level.humanTeamLocked ) + { + G_TriggerMenu( ent - g_entities, MN_H_TEAMLOCKED ); + return; + } + else if( level.alienTeamLocked ) + force = qtrue; + + if( !force && g_teamForceBalance.integer && humans > aliens ) + { + G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); + return; + } + + team = TEAM_HUMANS; + break; + + default: + trap_SendServerCommand( ent-g_entities, + va( "print \"Unknown team: %s\n\"", s ) ); + return; + } + + // stop team join spam + if( oldteam == team ) + return; + + if( team != TEAM_NONE && g_maxGameClients.integer && + level.numPlayingClients >= g_maxGameClients.integer ) + { + G_TriggerMenu( ent - g_entities, MN_PLAYERLIMIT ); + return; + } + + // Apply the change + G_ChangeTeam( ent, team ); +} + +/* +================== +G_CensorString +================== +*/ +static char censors[ 20000 ]; +static int numcensors; + +void G_LoadCensors( void ) +{ + char *text_p, *token; + char text[ 20000 ]; + char *term; + int len; + fileHandle_t f; + + numcensors = 0; + + if( !g_censorship.string[ 0 ] ) + return; + + len = trap_FS_FOpenFile( g_censorship.string, &f, FS_READ ); + if( len < 0 ) + { + Com_Printf( S_COLOR_RED "ERROR: Censors file %s doesn't exist\n", + g_censorship.string ); + return; + } + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + Com_Printf( S_COLOR_RED "ERROR: Censors file %s is %s\n", + g_censorship.string, len == 0 ? "empty" : "too long" ); + return; + } + trap_FS_Read( text, len, f ); + trap_FS_FCloseFile( f ); + text[ len ] = 0; + + term = censors; + + text_p = text; + while( 1 ) + { + token = COM_Parse( &text_p ); + if( !*token || sizeof( censors ) - ( term - censors ) < 4 ) + break; + Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); + Q_strlwr( term ); + term += strlen( term ) + 1; + if( sizeof( censors ) - ( term - censors ) == 0 ) + break; + token = COM_ParseExt( &text_p, qfalse ); + Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); + term += strlen( term ) + 1; + numcensors++; + } + G_Printf( "Parsed %d string replacements\n", numcensors ); +} + +void G_CensorString( char *out, const char *in, int len, gentity_t *ent ) +{ + const char *s, *m; + int i; + + if( !numcensors || G_admin_permission( ent, ADMF_NOCENSORFLOOD) ) + { + Q_strncpyz( out, in, len ); + return; + } + + len--; + while( *in ) + { + if( Q_IsColorString( in ) ) + { + if( len < 2 ) + break; + *out++ = *in++; + *out++ = *in++; + len -= 2; + continue; + } + if( !isalnum( *in ) ) + { + if( len < 1 ) + break; + *out++ = *in++; + len--; + continue; + } + m = censors; + for( i = 0; i < numcensors; i++, m++ ) + { + s = in; + while( *s && *m ) + { + if( Q_IsColorString( s ) ) + { + s += 2; + continue; + } + if( !isalnum( *s ) ) + { + s++; + continue; + } + if( tolower( *s ) != *m ) + break; + s++; + m++; + } + // match + if( !*m ) + { + in = s; + m++; + while( *m ) + { + if( len < 1 ) + break; + *out++ = *m++; + len--; + } + break; + } + else + { + while( *m ) + m++; + m++; + while( *m ) + m++; + } + } + if( len < 1 ) + break; + // no match + if( i == numcensors ) + { + *out++ = *in++; + len--; + } + } + *out = 0; +} + +/* +================== +G_Say +================== +*/ +static qboolean G_SayTo( gentity_t *ent, gentity_t *other, saymode_t mode, const char *message ) +{ + if( !other ) + return qfalse; + + if( !other->inuse ) + return qfalse; + + if( !other->client ) + return qfalse; + + if( other->client->pers.connected != CON_CONNECTED ) + return qfalse; + + if( ( ent && !OnSameTeam( ent, other ) ) && + ( mode == SAY_TEAM || mode == SAY_AREA || mode == SAY_TPRIVMSG ) ) + { + if( other->client->pers.teamSelection != TEAM_NONE ) + return qfalse; + + // specs with ADMF_SPEC_ALLCHAT flag can see team chat + if( !G_admin_permission( other, ADMF_SPEC_ALLCHAT ) && mode != SAY_TPRIVMSG ) + return qfalse; + } + + trap_SendServerCommand( other-g_entities, va( "chat %d %d \"%s\"", + ent ? ent-g_entities : -1, + mode, + message ) ); + + return qtrue; +} + +void G_Say( gentity_t *ent, saymode_t mode, const char *chatText ) +{ + int j; + gentity_t *other; + // don't let text be too long for malicious reasons + char text[ MAX_SAY_TEXT ]; + + // check if blocked by g_specChat 0 + if( ( !g_specChat.integer ) && ( mode != SAY_TEAM ) && + ( ent ) && ( ent->client->pers.teamSelection == TEAM_NONE ) && + ( !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"say: Global chatting for " + "spectators has been disabled. You may only use team chat.\n\"" ); + mode = SAY_TEAM; + } + + switch( mode ) + { + case SAY_ALL: + G_LogPrintf( "Say: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_GREEN "%s\n", + ( ent ) ? ent - g_entities : -1, + ( ent ) ? ent->client->pers.netname : "console", chatText ); + break; + case SAY_TEAM: + // console say_team is handled in g_svscmds, not here + if( !ent || !ent->client ) + Com_Error( ERR_FATAL, "SAY_TEAM by non-client entity\n" ); + G_LogPrintf( "SayTeam: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_CYAN "%s\n", + ent - g_entities, ent->client->pers.netname, chatText ); + break; + case SAY_RAW: + if( ent ) + Com_Error( ERR_FATAL, "SAY_RAW by client entity\n" ); + G_LogPrintf( "Chat: -1 \"console\": %s\n", chatText ); + default: + break; + } + + G_CensorString( text, chatText, sizeof( text ), ent ); + + // send it to all the apropriate clients + for( j = 0; j < level.maxclients; j++ ) + { + other = &g_entities[ j ]; + G_SayTo( ent, other, mode, text ); + } +} + +/* +================== +Cmd_SayArea_f +================== +*/ +static void Cmd_SayArea_f( gentity_t *ent ) +{ + int entityList[ MAX_GENTITIES ]; + int num, i; + vec3_t range = { 1000.0f, 1000.0f, 1000.0f }; + vec3_t mins, maxs; + char *msg; + + if( trap_Argc( ) < 2 ) + { + ADMP( "usage: say_area [message]\n" ); + return; + } + + msg = ConcatArgs( 1 ); + + for(i = 0; i < 3; i++ ) + range[ i ] = g_sayAreaRange.value; + + G_LogPrintf( "SayArea: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_BLUE "%s\n", + ent - g_entities, ent->client->pers.netname, msg ); + + VectorAdd( ent->s.origin, range, maxs ); + VectorSubtract( ent->s.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_AREA, msg ); + + //Send to ADMF_SPEC_ALLCHAT candidates + for( i = 0; i < level.maxclients; i++ ) + { + if( g_entities[ i ].client->pers.teamSelection == TEAM_NONE && + G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) + { + G_SayTo( ent, &g_entities[ i ], SAY_AREA, msg ); + } + } +} + + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( gentity_t *ent ) +{ + char *p; + char cmd[ MAX_TOKEN_CHARS ]; + saymode_t mode = SAY_ALL; + + if( trap_Argc( ) < 2 ) + return; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "say_team" ) == 0 ) + mode = SAY_TEAM; + + p = ConcatArgs( 1 ); + + G_Say( ent, mode, p ); +} + +/* +================== +Cmd_VSay_f +================== +*/ +void Cmd_VSay_f( gentity_t *ent ) +{ + char arg[MAX_TOKEN_CHARS]; + char text[ MAX_TOKEN_CHARS ]; + voiceChannel_t vchan; + voice_t *voice; + voiceCmd_t *cmd; + voiceTrack_t *track; + int cmdNum = 0; + int trackNum = 0; + char voiceName[ MAX_VOICE_NAME_LEN ] = {"default"}; + char voiceCmd[ MAX_VOICE_CMD_LEN ] = {""}; + char vsay[ 12 ] = {""}; + weapon_t weapon; + + if( !ent || !ent->client ) + Com_Error( ERR_FATAL, "Cmd_VSay_f() called by non-client entity\n" ); + + trap_Argv( 0, arg, sizeof( arg ) ); + if( trap_Argc( ) < 2 ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"usage: %s command [text] \n\"", arg ) ); + return; + } + if( !level.voices ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice system is not installed on this server\n\"", arg ) ); + return; + } + if( !g_voiceChats.integer ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice system administratively disabled on this server\n\"", + arg ) ); + return; + } + if( !Q_stricmp( arg, "vsay" ) ) + vchan = VOICE_CHAN_ALL; + else if( !Q_stricmp( arg, "vsay_team" ) ) + vchan = VOICE_CHAN_TEAM; + else if( !Q_stricmp( arg, "vsay_local" ) ) + vchan = VOICE_CHAN_LOCAL; + else + return; + Q_strncpyz( vsay, arg, sizeof( vsay ) ); + + if( ent->client->pers.voice[ 0 ] ) + Q_strncpyz( voiceName, ent->client->pers.voice, sizeof( voiceName ) ); + voice = BG_VoiceByName( level.voices, voiceName ); + if( !voice ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice '%s' not found\n\"", vsay, voiceName ) ); + return; + } + + trap_Argv( 1, voiceCmd, sizeof( voiceCmd ) ) ; + cmd = BG_VoiceCmdFind( voice->cmds, voiceCmd, &cmdNum ); + if( !cmd ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: command '%s' not found in voice '%s'\n\"", + vsay, voiceCmd, voiceName ) ); + return; + } + + // filter non-spec humans by their primary weapon as well + weapon = WP_NONE; + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + { + weapon = BG_PrimaryWeapon( ent->client->ps.stats ); + } + + track = BG_VoiceTrackFind( cmd->tracks, ent->client->pers.teamSelection, + ent->client->pers.classSelection, weapon, (int)ent->client->voiceEnthusiasm, + &trackNum ); + if( !track ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: no available track for command '%s', team %d, " + "class %d, weapon %d, and enthusiasm %d in voice '%s'\n\"", + vsay, voiceCmd, ent->client->pers.teamSelection, + ent->client->pers.classSelection, weapon, + (int)ent->client->voiceEnthusiasm, voiceName ) ); + return; + } + + if( !Q_stricmp( ent->client->lastVoiceCmd, cmd->cmd ) ) + ent->client->voiceEnthusiasm++; + + Q_strncpyz( ent->client->lastVoiceCmd, cmd->cmd, + sizeof( ent->client->lastVoiceCmd ) ); + + // optional user supplied text + trap_Argv( 2, arg, sizeof( arg ) ); + G_CensorString( text, arg, sizeof( text ), ent ); + + switch( vchan ) + { + case VOICE_CHAN_ALL: + case VOICE_CHAN_LOCAL: + trap_SendServerCommand( -1, va( + "voice %d %d %d %d \"%s\"\n", + ent-g_entities, vchan, cmdNum, trackNum, text ) ); + break; + case VOICE_CHAN_TEAM: + G_TeamCommand( ent->client->pers.teamSelection, va( + "voice %d %d %d %d \"%s\"\n", + ent-g_entities, vchan, cmdNum, trackNum, text ) ); + break; + default: + break; + } +} + +/* +================== +Cmd_Where_f +================== +*/ +void Cmd_Where_f( gentity_t *ent ) +{ + if( !ent->client ) + return; + trap_SendServerCommand( ent - g_entities, + va( "print \"origin: %f %f %f\n\"", + ent->s.origin[ 0 ], ent->s.origin[ 1 ], + ent->s.origin[ 2 ] ) ); +} + +/* +================== +Cmd_CallVote_f +================== +*/ +void Cmd_CallVote_f( gentity_t *ent ) +{ + char cmd[ MAX_TOKEN_CHARS ], + vote[ MAX_TOKEN_CHARS ], + arg[ MAX_TOKEN_CHARS ]; + char name[ MAX_NAME_LENGTH ] = ""; + char caller[ MAX_NAME_LENGTH ] = ""; + char reason[ MAX_TOKEN_CHARS ]; + char *creason; + int clientNum = -1; + int id = -1; + team_t team; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + trap_Argv( 1, vote, sizeof( vote ) ); + trap_Argv( 2, arg, sizeof( arg ) ); + creason = ConcatArgs( 3 ); + G_DecolorString( creason, reason, sizeof( reason ) ); + + if( !Q_stricmp( cmd, "callteamvote" ) ) + team = ent->client->pers.teamSelection; + else + team = TEAM_NONE; + + if( !g_allowVote.integer ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: voting not allowed here\n\"", cmd ) ); + return; + } + + if( level.voteTime[ team ] ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: a vote is already in progress\n\"", cmd ) ); + return; + } + + if( level.voteExecuteTime[ team ] ) + G_ExecuteVote( team ); + + level.voteDelay[ team ] = 0; + level.voteThreshold[ team ] = 50; + + if( g_voteLimit.integer > 0 && + ent->client->pers.namelog->voteCount >= g_voteLimit.integer && + !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: you have already called the maximum number of votes (%d)\n\"", + cmd, g_voteLimit.integer ) ); + return; + } + + // kick, mute, unmute, denybuild, allowbuild + if( !Q_stricmp( vote, "kick" ) || + !Q_stricmp( vote, "mute" ) || !Q_stricmp( vote, "unmute" ) || + !Q_stricmp( vote, "denybuild" ) || !Q_stricmp( vote, "allowbuild" ) ) + { + char err[ MAX_STRING_CHARS ]; + + if( !arg[ 0 ] ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: no target\n\"", cmd ) ); + return; + } + + // with a little extra work only players from the right team are considered + clientNum = G_ClientNumberFromString( arg, err, sizeof( err ) ); + + if( clientNum == -1 ) + { + ADMP( va( "%s: %s", cmd, err ) ); + return; + } + + G_DecolorString( level.clients[ clientNum ].pers.netname, name, sizeof( name ) ); + id = level.clients[ clientNum ].pers.namelog->id; + + if( !Q_stricmp( vote, "kick" ) || !Q_stricmp( vote, "mute" ) || + !Q_stricmp( vote, "denybuild" ) ) + { + if( G_admin_permission( g_entities + clientNum, ADMF_IMMUNITY ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: admin is immune\n\"", cmd ) ); + + G_AdminMessage( NULL, va( S_COLOR_WHITE "%s" S_COLOR_YELLOW " attempted %s %s" + " on immune admin " S_COLOR_WHITE "%s" S_COLOR_YELLOW + " for: %s", + ent->client->pers.netname, cmd, vote, + g_entities[ clientNum ].client->pers.netname, + reason[ 0 ] ? reason : "no reason" ) ); + return; + } + + if( team != TEAM_NONE && + ( ent->client->pers.teamSelection != + level.clients[ clientNum ].pers.teamSelection ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player is not on your team\n\"", cmd ) ); + return; + } + + if( !reason[ 0 ] && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: You must provide a reason\n\"", cmd ) ); + return; + } + } + } + + if( !Q_stricmp( vote, "kick" ) ) + { + if( level.clients[ clientNum ].pers.localClient ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: admin is immune\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "ban %s \"1s%s\" vote kick (%s)", level.clients[ clientNum ].pers.ip.str, + g_adminTempBan.string, reason ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), "Kick player '%s'", name ); + if( reason[ 0 ] ) + { + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); + } + } + else if( team == TEAM_NONE ) + { + if( !Q_stricmp( vote, "mute" ) ) + { + if( level.clients[ clientNum ].pers.namelog->muted ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player is already muted\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "mute %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Mute player '%s'", name ); + if( reason[ 0 ] ) + { + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); + } + } + else if( !Q_stricmp( vote, "unmute" ) ) + { + if( !level.clients[ clientNum ].pers.namelog->muted ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player is not currently muted\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "unmute %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Unmute player '%s'", name ); + } + else if( !Q_stricmp( vote, "map_restart" ) ) + { + strcpy( level.voteString[ team ], vote ); + strcpy( level.voteDisplayString[ team ], "Restart current map" ); + // map_restart comes with a default delay + } + else if( !Q_stricmp( vote, "map" ) ) + { + if( !G_MapExists( arg ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", + cmd, arg ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString ), + "%s \"%s\"", vote, arg ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Change to map '%s'", arg ); + level.voteDelay[ team ] = 3000; + } + else if( !Q_stricmp( vote, "nextmap" ) ) + { + if( G_MapExists( g_nextMap.string ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: the next map is already set to '%s'\n\"", + cmd, g_nextMap.string ) ); + return; + } + + if( !G_MapExists( arg ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", + cmd, arg ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "set g_nextMap \"%s\"", arg ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Set the next map to '%s'", arg ); + } + else if( !Q_stricmp( vote, "draw" ) ) + { + strcpy( level.voteString[ team ], "evacuation" ); + strcpy( level.voteDisplayString[ team ], "End match in a draw" ); + level.voteDelay[ team ] = 3000; + } + else if( !Q_stricmp( vote, "sudden_death" ) ) + { + if(!g_suddenDeathVotePercent.integer) + { + trap_SendServerCommand( ent-g_entities, + "print \"Sudden Death votes have been disabled\n\"" ); + return; + } + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"callvote: Sudden Death has already begun\n\"") ); + return; + } + if( level.suddenDeathBeginTime > 0 && + G_TimeTilSuddenDeath() <= g_suddenDeathVoteDelay.integer * 1000 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"callvote: Sudden Death is imminent\n\"") ); + return; + } + level.voteThreshold[ team ] = g_suddenDeathVotePercent.integer; + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "suddendeath %d", g_suddenDeathVoteDelay.integer ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Begin sudden death in %d seconds", + g_suddenDeathVoteDelay.integer ); + } + else + { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " + "map, nextmap, map_restart, draw, sudden_death, kick, mute and unmute\n" ); + return; + } + } + else if( !Q_stricmp( vote, "denybuild" ) ) + { + if( level.clients[ clientNum ].pers.namelog->denyBuild ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player already lost building rights\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "denybuild %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Take away building rights from '%s'", name ); + if( reason[ 0 ] ) + { + Q_strcat( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); + } + } + else if( !Q_stricmp( vote, "allowbuild" ) ) + { + if( !level.clients[ clientNum ].pers.namelog->denyBuild ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: player already has building rights\n\"", cmd ) ); + return; + } + + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "allowbuild %d", id ); + Com_sprintf( level.voteDisplayString[ team ], + sizeof( level.voteDisplayString[ team ] ), + "Allow '%s' to build", name ); + } + else if( !Q_stricmp( vote, "admitdefeat" ) ) + { + Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), + "admitdefeat %d", team ); + strcpy( level.voteDisplayString[ team ], "Admit Defeat" ); + level.voteDelay[ team ] = 3000; + } + else + { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); + trap_SendServerCommand( ent-g_entities, + "print \"Valid team vote commands are: " + "kick, denybuild, allowbuild and admitdefeat\n\"" ); + return; + } + + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": %s\n", + team == TEAM_NONE ? "CallVote" : "CallTeamVote", + ent - g_entities, ent->client->pers.netname, level.voteString[ team ] ); + + if( team == TEAM_NONE ) + { + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE + " called a vote: %s\n\"", ent->client->pers.netname, + level.voteDisplayString[ team ] ) ); + } + else + { + int i; + + for( i = 0 ; i < level.maxclients ; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + { + if( level.clients[ i ].pers.teamSelection == team || + ( level.clients[ i ].pers.teamSelection == TEAM_NONE && + G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) + { + trap_SendServerCommand( i, va( "print \"%s" S_COLOR_WHITE + " called a team vote: %s\n\"", ent->client->pers.netname, + level.voteDisplayString[ team ] ) ); + } + else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) + { + trap_SendServerCommand( i, va( "chat -1 %d \"" S_COLOR_YELLOW "%s" + S_COLOR_YELLOW " called a team vote (%ss): %s\"", SAY_ADMINS, + ent->client->pers.netname, BG_TeamName( team ), + level.voteDisplayString[ team ] ) ); + } + } + } + } + + G_DecolorString( ent->client->pers.netname, caller, sizeof( caller ) ); + + level.voteTime[ team ] = level.time; + trap_SetConfigstring( CS_VOTE_TIME + team, + va( "%d", level.voteTime[ team ] ) ); + trap_SetConfigstring( CS_VOTE_STRING + team, + level.voteDisplayString[ team ] ); + trap_SetConfigstring( CS_VOTE_CALLER + team, + caller ); + + ent->client->pers.namelog->voteCount++; + ent->client->pers.vote |= 1 << team; + G_Vote( ent, team, qtrue ); +} + +/* +================== +Cmd_Vote_f +================== +*/ +void Cmd_Vote_f( gentity_t *ent ) +{ + char cmd[ MAX_TOKEN_CHARS ], vote[ MAX_TOKEN_CHARS ]; + team_t team = ent->client->pers.teamSelection; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "teamvote" ) ) + team = TEAM_NONE; + + if( !level.voteTime[ team ] ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: no vote in progress\n\"", cmd ) ); + return; + } + + if( ent->client->pers.voted & ( 1 << team ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: vote already cast\n\"", cmd ) ); + return; + } + + trap_SendServerCommand( ent-g_entities, + va( "print \"%s: vote cast\n\"", cmd ) ); + + trap_Argv( 1, vote, sizeof( vote ) ); + if( vote[ 0 ] == 'y' ) + ent->client->pers.vote |= 1 << team; + else + ent->client->pers.vote &= ~( 1 << team ); + G_Vote( ent, team, qtrue ); +} + + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( gentity_t *ent ) +{ + vec3_t origin, angles; + char buffer[ MAX_TOKEN_CHARS ]; + int i; + + if( trap_Argc( ) != 5 ) + { + trap_SendServerCommand( ent-g_entities, "print \"usage: setviewpos x y z yaw\n\"" ); + return; + } + + VectorClear( angles ); + + for( i = 0; i < 3; i++ ) + { + trap_Argv( i + 1, buffer, sizeof( buffer ) ); + origin[ i ] = atof( buffer ); + } + + trap_Argv( 4, buffer, sizeof( buffer ) ); + angles[ YAW ] = atof( buffer ); + + TeleportPlayer( ent, origin, angles ); +} + +#define AS_OVER_RT3 ((ALIENSENSE_RANGE*0.5f)/M_ROOT3) + +static qboolean G_RoomForClassChange( gentity_t *ent, class_t class, + vec3_t newOrigin ) +{ + vec3_t fromMins, fromMaxs; + vec3_t toMins, toMaxs; + vec3_t temp; + trace_t tr; + float nudgeHeight; + float maxHorizGrowth; + class_t oldClass = ent->client->ps.stats[ STAT_CLASS ]; + + BG_ClassBoundingBox( oldClass, fromMins, fromMaxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( class, toMins, toMaxs, NULL, NULL, NULL ); + + VectorCopy( ent->s.origin, newOrigin ); + + // find max x/y diff + maxHorizGrowth = toMaxs[ 0 ] - fromMaxs[ 0 ]; + if( toMaxs[ 1 ] - fromMaxs[ 1 ] > maxHorizGrowth ) + maxHorizGrowth = toMaxs[ 1 ] - fromMaxs[ 1 ]; + if( toMins[ 0 ] - fromMins[ 0 ] > -maxHorizGrowth ) + maxHorizGrowth = -( toMins[ 0 ] - fromMins[ 0 ] ); + if( toMins[ 1 ] - fromMins[ 1 ] > -maxHorizGrowth ) + maxHorizGrowth = -( toMins[ 1 ] - fromMins[ 1 ] ); + + if( maxHorizGrowth > 0.0f ) + { + // test by moving the player up the max required on a 60 degree slope + nudgeHeight = maxHorizGrowth * 2.0f; + } + else + { + // player is shrinking, so there's no need to nudge them upwards + nudgeHeight = 0.0f; + } + + // find what the new origin would be on a level surface + newOrigin[ 2 ] -= toMins[ 2 ] - fromMins[ 2 ]; + + //compute a place up in the air to start the real trace + VectorCopy( newOrigin, temp ); + temp[ 2 ] += nudgeHeight; + trap_Trace( &tr, newOrigin, toMins, toMaxs, temp, ent->s.number, MASK_PLAYERSOLID ); + + //trace down to the ground so that we can evolve on slopes + VectorCopy( newOrigin, temp ); + temp[ 2 ] += ( nudgeHeight * tr.fraction ); + trap_Trace( &tr, temp, toMins, toMaxs, newOrigin, ent->s.number, MASK_PLAYERSOLID ); + VectorCopy( tr.endpos, newOrigin ); + + //make REALLY sure + trap_Trace( &tr, newOrigin, toMins, toMaxs, newOrigin, + ent->s.number, MASK_PLAYERSOLID ); + + //check there is room to evolve + return ( !tr.startsolid && tr.fraction == 1.0f ); +} + +/* +================= +Cmd_Class_f +================= +*/ +void Cmd_Class_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + int clientNum; + int i; + vec3_t infestOrigin; + class_t currentClass = ent->client->pers.classSelection; + class_t newClass; + int entityList[ MAX_GENTITIES ]; + vec3_t range = { AS_OVER_RT3, AS_OVER_RT3, AS_OVER_RT3 }; + vec3_t mins, maxs; + int num; + gentity_t *other; + int oldBoostTime = -1; + vec3_t oldVel; + + clientNum = ent->client - level.clients; + trap_Argv( 1, s, sizeof( s ) ); + newClass = BG_ClassByName( s )->number; + + if( ent->client->sess.spectatorState != SPECTATOR_NOT ) + { + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + { + if( newClass != PCL_ALIEN_BUILDER0 && + newClass != PCL_ALIEN_BUILDER0_UPG && + newClass != PCL_ALIEN_LEVEL0 ) + { + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTSPAWN, newClass ); + return; + } + + if( !BG_ClassIsAllowed( newClass ) ) + { + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTALLOWED, newClass ); + return; + } + + if( !BG_ClassAllowedInStage( newClass, g_alienStage.integer ) ) + { + G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTATSTAGE, newClass ); + return; + } + + // spawn from an egg + if( G_PushSpawnQueue( &level.alienSpawnQueue, clientNum ) ) + { + ent->client->pers.classSelection = newClass; + ent->client->ps.stats[ STAT_CLASS ] = newClass; + } + } + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + { + //set the item to spawn with + if( !Q_stricmp( s, BG_Weapon( WP_MACHINEGUN )->name ) && + BG_WeaponIsAllowed( WP_MACHINEGUN ) ) + { + ent->client->pers.humanItemSelection = WP_MACHINEGUN; + } + else if( !Q_stricmp( s, BG_Weapon( WP_HBUILD )->name ) && + BG_WeaponIsAllowed( WP_HBUILD ) ) + { + ent->client->pers.humanItemSelection = WP_HBUILD; + } + else + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNSPAWNITEM ); + return; + } + // spawn from a telenode + if( G_PushSpawnQueue( &level.humanSpawnQueue, clientNum ) ) + { + ent->client->pers.classSelection = PCL_HUMAN; + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; + } + } + return; + } + + if( ent->health <= 0 ) + return; + + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + { + if( newClass == PCL_NONE ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_A_UNKNOWNCLASS ); + return; + } + + //if we are not currently spectating, we are attempting evolution + if( ent->client->pers.classSelection != PCL_NONE ) + { + int cost; + + //check that we have an overmind + if( !G_Overmind( ) ) + { + G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); + return; + } + + //check there are no humans nearby + VectorAdd( ent->client->ps.origin, range, maxs ); + VectorSubtract( ent->client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + other = &g_entities[ entityList[ i ] ]; + + if( ( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || + ( other->s.eType == ET_BUILDABLE && other->buildableTeam == TEAM_HUMANS && + other->powered ) ) + { + trace_t tr; + + trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, other->s.origin, ent->client->ps.clientNum, MASK_SOLID ); + + if( tr.fraction > 0.99f ) + { + G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); + return; + } + } + } + + //check that we are not wallwalking + if( ent->client->ps.eFlags & EF_WALLCLIMB ) + { + G_TriggerMenu( clientNum, MN_A_EVOLVEWALLWALK ); + return; + } + + //guard against selling the HBUILD weapons exploit + if( ent->client->sess.spectatorState == SPECTATOR_NOT && + ( currentClass == PCL_ALIEN_BUILDER0 || + currentClass == PCL_ALIEN_BUILDER0_UPG ) && + ent->client->buildTimer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_A_EVOLVEBUILDTIMER ); + return; + } + + cost = BG_ClassCanEvolveFromTo( currentClass, newClass, + ent->client->pers.credit, + g_alienStage.integer, 0 ); + + if( G_RoomForClassChange( ent, newClass, infestOrigin ) ) + { + if( cost >= 0 ) + { + + ent->client->pers.evolveHealthFraction = (float)ent->client->ps.stats[ STAT_HEALTH ] / + (float)BG_Class( currentClass )->health; + + if( ent->client->pers.evolveHealthFraction < 0.0f ) + ent->client->pers.evolveHealthFraction = 0.0f; + else if( ent->client->pers.evolveHealthFraction > 1.0f ) + ent->client->pers.evolveHealthFraction = 1.0f; + + //remove credit + G_AddCreditToClient( ent->client, -cost, qtrue ); + ent->client->pers.classSelection = newClass; + ClientUserinfoChanged( clientNum, qfalse ); + VectorCopy( infestOrigin, ent->s.pos.trBase ); + VectorCopy( ent->client->ps.velocity, oldVel ); + + if( ent->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + oldBoostTime = ent->client->boostedTime; + + ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase ); + + VectorCopy( oldVel, ent->client->ps.velocity ); + if( oldBoostTime > 0 ) + { + ent->client->boostedTime = oldBoostTime; + ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; + } + } + else + G_TriggerMenuArgs( clientNum, MN_A_CANTEVOLVE, newClass ); + } + else + G_TriggerMenu( clientNum, MN_A_NOEROOM ); + } + } + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + G_TriggerMenu( clientNum, MN_H_DEADTOCLASS ); +} + + +/* +================= +Cmd_Destroy_f +================= +*/ +void Cmd_Destroy_f( gentity_t *ent ) +{ + vec3_t viewOrigin, forward, end; + trace_t tr; + gentity_t *traceEnt; + char cmd[ 12 ]; + qboolean deconstruct = qtrue; + qboolean lastSpawn = qfalse; + + if( ent->client->pers.namelog->denyBuild ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED ); + return; + } + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "destroy" ) == 0 ) + deconstruct = qfalse; + + BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); + AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( viewOrigin, 100, forward, end ); + + trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + + if( tr.fraction < 1.0f && + ( traceEnt->s.eType == ET_BUILDABLE ) && + ( traceEnt->buildableTeam == ent->client->pers.teamSelection ) && + ( ( ent->client->ps.weapon >= WP_ABUILD ) && + ( ent->client->ps.weapon <= WP_HBUILD ) ) ) + { + // Always let the builder prevent the explosion + if( traceEnt->health <= 0 ) + { + G_QueueBuildPoints( traceEnt ); + G_RewardAttackers( traceEnt ); + G_FreeEntity( traceEnt ); + return; + } + + // Cancel deconstruction (unmark) + if( deconstruct && g_markDeconstruct.integer && traceEnt->deconstruct ) + { + traceEnt->deconstruct = qfalse; + return; + } + + // Prevent destruction of the last spawn + if( ent->client->pers.teamSelection == TEAM_ALIENS && + traceEnt->s.modelindex == BA_A_SPAWN ) + { + if( level.numAlienSpawns <= 1 ) + lastSpawn = qtrue; + } + else if( ent->client->pers.teamSelection == TEAM_HUMANS && + traceEnt->s.modelindex == BA_H_SPAWN ) + { + if( level.numHumanSpawns <= 1 ) + lastSpawn = qtrue; + } + + if( lastSpawn && !g_cheats.integer && + !g_markDeconstruct.integer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN ); + return; + } + + // Don't allow destruction of buildables that cannot be rebuilt + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); + return; + } + + if( !g_markDeconstruct.integer || + ( ent->client->pers.teamSelection == TEAM_HUMANS && + !G_FindPower( traceEnt, qtrue ) ) ) + { + if( ent->client->buildTimer ) + { + G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); + return; + } + } + + if( traceEnt->health > 0 ) + { + if( !deconstruct ) + { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + traceEnt->health, 0, MOD_SUICIDE ); + } + else if( g_markDeconstruct.integer && + ( ent->client->pers.teamSelection != TEAM_HUMANS || + G_FindPower( traceEnt , qtrue ) || lastSpawn ) ) + { + traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction + traceEnt->deconstructTime = level.time; + } + else + { + if( !g_cheats.integer && !g_instantBuild.integer ) // add a bit to the build timer + { + ent->client->buildTimer += + BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->buildTime / 4; + G_RecalcBuildTimer(ent->client); + } + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + traceEnt->health, 0, MOD_DECONSTRUCT ); + G_FreeEntity( traceEnt ); + } + } + } +} + +/* +================= +Cmd_ActivateItem_f + +Activate an item +================= +*/ +void Cmd_ActivateItem_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + int upgrade, weapon; + + trap_Argv( 1, s, sizeof( s ) ); + + // "weapon" aliased to whatever weapon you have + if( !Q_stricmp( "weapon", s ) ) + { + if( ent->client->ps.weapon == WP_BLASTER && + BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + G_ForceWeaponChange( ent, WP_NONE ); + return; + } + + upgrade = BG_UpgradeByName( s )->number; + weapon = BG_WeaponByName( s )->number; + + if( upgrade != UP_NONE && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + BG_ActivateUpgrade( upgrade, ent->client->ps.stats ); + else if( weapon != WP_NONE && + BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) + { + if( ent->client->ps.weapon != weapon && + BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + G_ForceWeaponChange( ent, weapon ); + } + else + trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); +} + + +/* +================= +Cmd_DeActivateItem_f + +Deactivate an item +================= +*/ +void Cmd_DeActivateItem_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + upgrade_t upgrade; + + trap_Argv( 1, s, sizeof( s ) ); + upgrade = BG_UpgradeByName( s )->number; + + if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + { + BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); + if( upgrade == UP_JETPACK ) + BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &ent->client->ps ); + } + else + trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); +} + + +/* +================= +Cmd_ToggleItem_f +================= +*/ +void Cmd_ToggleItem_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + weapon_t weapon; + upgrade_t upgrade; + + trap_Argv( 1, s, sizeof( s ) ); + upgrade = BG_UpgradeByName( s )->number; + weapon = BG_WeaponByName( s )->number; + + if( weapon != WP_NONE ) + { + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + + //special case to allow switching between + //the blaster and the primary weapon + if( ent->client->ps.weapon != WP_BLASTER ) + weapon = WP_BLASTER; + else + weapon = WP_NONE; + + G_ForceWeaponChange( ent, weapon ); + } + else if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + { + if( BG_UpgradeIsActive( upgrade, ent->client->ps.stats ) ) + { + BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); + if( upgrade == UP_JETPACK ) + BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &ent->client->ps ); + } + else + BG_ActivateUpgrade( upgrade, ent->client->ps.stats ); + } + else + trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); +} + +/* +================= +Cmd_Buy_f +================= +*/ +void Cmd_Buy_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + weapon_t weapon; + upgrade_t upgrade; + qboolean energyOnly, sellHelmet = qfalse; + + trap_Argv( 1, s, sizeof( s ) ); + + weapon = BG_WeaponByName( s )->number; + upgrade = BG_UpgradeByName( s )->number; + + if( upgrade == UP_NONE && !Q_stricmp(s, "helmet") ) + { + if( g_humanStage.integer < S2 ) + upgrade = UP_HELMET_MK1; + else + upgrade = UP_HELMET_MK2; + sellHelmet = qtrue; + } + + // Only give energy from reactors or repeaters + if( G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + energyOnly = qfalse; + else if( upgrade == UP_AMMO && + BG_Weapon( ent->client->ps.stats[ STAT_WEAPON ] )->usesEnergy && + ( G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) || + G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) ) ) + energyOnly = qtrue; + else + { + if( upgrade == UP_AMMO && + BG_Weapon( ent->client->ps.weapon )->usesEnergy ) + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOENERGYAMMOHERE ); + else + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); + return; + } + + if( weapon != WP_NONE ) + { + //already got this? + if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD ); + return; + } + + // Only humans can buy stuff + if( BG_Weapon( weapon )->team != TEAM_HUMANS ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" ); + return; + } + + //are we /allowed/ to buy this? + if( !BG_Weapon( weapon )->purchasable ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); + return; + } + + //are we /allowed/ to buy this? + if( !BG_WeaponAllowedInStage( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); + return; + } + + //can afford this? + if( BG_Weapon( weapon )->price > (short)ent->client->pers.credit ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); + return; + } + + //have space to carry this? + if( BG_Weapon( weapon )->slots & BG_SlotsForInventory( ent->client->ps.stats ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); + return; + } + + // In some instances, weapons can't be changed + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + + ent->client->ps.stats[ STAT_WEAPON ] = weapon; + ent->client->ps.ammo = BG_Weapon( weapon )->maxAmmo; + ent->client->ps.clips = BG_Weapon( weapon )->maxClips; + + if( BG_Weapon( weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) + ent->client->ps.ammo *= BATTPACK_MODIFIER; + + G_ForceWeaponChange( ent, weapon ); + + //set build delay/pounce etc to 0 + ent->client->ps.stats[ STAT_MISC ] = 0; + ent->client->buildTimer = 0; + + //subtract from funds + G_AddCreditToClient( ent->client, -(short)BG_Weapon( weapon )->price, qfalse ); + } + else if( upgrade != UP_NONE ) + { + //already got this? + if( !sellHelmet && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD ); + return; + } + + //can afford this? + if( BG_Upgrade( upgrade )->price > (short)ent->client->pers.credit ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); + return; + } + + //have space to carry this? + if( !sellHelmet && BG_Upgrade( upgrade )->slots & BG_SlotsForInventory( ent->client->ps.stats ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); + return; + } + + // Only humans can buy stuff + if( BG_Upgrade( upgrade )->team != TEAM_HUMANS ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" ); + return; + } + + //are we /allowed/ to buy this? + if( !BG_Upgrade( upgrade )->purchasable ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); + return; + } + + //are we /allowed/ to buy this? + if( !BG_UpgradeAllowedInStage( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); + return; + } + + if( upgrade == UP_AMMO ) + { + G_GiveClientMaxAmmo( ent, energyOnly ); + if( !energyOnly && BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) && + ent->client->ps.stats[ STAT_FUEL ] < JETPACK_FUEL_FULL ) + { + G_AddEvent( ent, EV_JETPACK_REFUEL, 0) ; + ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; + } + } + else + { + if( upgrade == UP_BATTLESUIT ) + { + vec3_t newOrigin; + + if( !G_RoomForClassChange( ent, PCL_HUMAN_BSUIT, newOrigin ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITON ); + return; + } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN_BSUIT; + ent->client->pers.classSelection = PCL_HUMAN_BSUIT; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + } + else if( upgrade == UP_JETPACK ) + ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; + + if( sellHelmet ) + { + BG_RemoveUpgradeFromInventory( UP_HELMET_MK1, ent->client->ps.stats ); + BG_RemoveUpgradeFromInventory( UP_HELMET_MK2, ent->client->ps.stats ); + } + + //add to inventory + BG_AddUpgradeToInventory( upgrade, ent->client->ps.stats ); + } + + if( upgrade == UP_BATTPACK ) + G_GiveClientMaxAmmo( ent, qtrue ); + + //subtract from funds + G_AddCreditToClient( ent->client, -(short)BG_Upgrade( upgrade )->price, qfalse ); + } + else + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); + + //update ClientInfo + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); +} + + +/* +================= +Cmd_Sell_f +================= +*/ +void Cmd_Sell_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + int i; + weapon_t weapon; + upgrade_t upgrade; + + trap_Argv( 1, s, sizeof( s ) ); + + //no armoury nearby + if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); + return; + } + + if( !Q_stricmpn( s, "weapon", 6 ) ) + weapon = ent->client->ps.stats[ STAT_WEAPON ]; + else + weapon = BG_WeaponByName( s )->number; + + upgrade = BG_UpgradeByName( s )->number; + + if( !upgrade && !Q_stricmp( s, "helmet" ) ) + { + if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, ent->client->ps.stats ) ) + upgrade = UP_HELMET_MK1; + else if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, ent->client->ps.stats ) ) + upgrade = UP_HELMET_MK2; + } + + if( weapon != WP_NONE ) + { + weapon_t selected = BG_GetPlayerWeapon( &ent->client->ps ); + + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + + //are we /allowed/ to sell this? + if( !BG_Weapon( weapon )->purchasable ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't sell this weapon\n\"" ); + return; + } + + //remove weapon if carried + if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) + { + //guard against selling the HBUILD weapons exploit + if( weapon == WP_HBUILD && ent->client->buildTimer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); + return; + } + + ent->client->ps.stats[ STAT_WEAPON ] = WP_NONE; + // Cancel ghost buildables + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + + //add to funds + G_AddCreditToClient( ent->client, (short)BG_Weapon( weapon )->price, qfalse ); + } + + //if we have this weapon selected, force a new selection + if( weapon == selected ) + G_ForceWeaponChange( ent, WP_NONE ); + } + else if( upgrade != UP_NONE ) + { + //are we /allowed/ to sell this? + if( !BG_Upgrade( upgrade )->purchasable ) + { + trap_SendServerCommand( ent-g_entities, "print \"You can't sell this item\n\"" ); + return; + } + //remove upgrade if carried + if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) + { + // shouldn't really need to test for this, but just to be safe + if( upgrade == UP_BATTLESUIT ) + { + vec3_t newOrigin; + + if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); + return; + } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; + ent->client->pers.classSelection = PCL_HUMAN; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + } + + //add to inventory + BG_RemoveUpgradeFromInventory( upgrade, ent->client->ps.stats ); + + if( upgrade == UP_BATTPACK ) + G_GiveClientMaxAmmo( ent, qtrue ); + + //add to funds + G_AddCreditToClient( ent->client, (short)BG_Upgrade( upgrade )->price, qfalse ); + } + } + else if( !Q_stricmp( s, "weapons" ) ) + { + weapon_t selected = BG_GetPlayerWeapon( &ent->client->ps ); + + if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) + return; + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + //guard against selling the HBUILD weapons exploit + if( i == WP_HBUILD && ent->client->buildTimer ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); + continue; + } + + if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && + BG_Weapon( i )->purchasable ) + { + ent->client->ps.stats[ STAT_WEAPON ] = WP_NONE; + + //add to funds + G_AddCreditToClient( ent->client, (short)BG_Weapon( i )->price, qfalse ); + } + + //if we have this weapon selected, force a new selection + if( i == selected ) + G_ForceWeaponChange( ent, WP_NONE ); + } + } + else if( !Q_stricmp( s, "upgrades" ) ) + { + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + //remove upgrade if carried + if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) && + BG_Upgrade( i )->purchasable ) + { + + // shouldn't really need to test for this, but just to be safe + if( i == UP_BATTLESUIT ) + { + vec3_t newOrigin; + + if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); + continue; + } + VectorCopy( newOrigin, ent->client->ps.origin ); + ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; + ent->client->pers.classSelection = PCL_HUMAN; + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + } + + BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats ); + + if( i == UP_BATTPACK ) + G_GiveClientMaxAmmo( ent, qtrue ); + + //add to funds + G_AddCreditToClient( ent->client, (short)BG_Upgrade( i )->price, qfalse ); + } + } + } + else + G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); + + //update ClientInfo + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); +} + +/* +================= +Cmd_CheckCuboidSize + +Check if the specified dimensions are valid. +================= +*/ +qboolean Cmd_CheckCuboidSize(vec3_t dims) +{ + if(g_cuboidSizeLimit.integer) + if(dims[0]>g_cuboidSizeLimit.integer||dims[1]>g_cuboidSizeLimit.integer||dims[2]>g_cuboidSizeLimit.integer) + return qfalse; + if(dims[0]*dims[1]*dims[2]client->cuboidSelection); + trap_SendServerCommand(ent->client-level.clients,va("cb3 %i\n",echo)); + G_RelayCuboidToSpectators(ent->client); + } + else + { + if(Cmd_CheckCuboidSize(ent->client->cuboidSelection)) + trap_SendServerCommand(ent->client-level.clients,va("cb3 %i %f %f %f\n",echo,ent->client->cuboidSelection[0],ent->client->cuboidSelection[1],ent->client->cuboidSelection[2])); + else + trap_SendServerCommand(ent->client-level.clients,va("cb3 %i %f %f %f\n",echo,g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer)); + } +} + + +/* +================= +Cmd_Build_f +================= +*/ +void Cmd_Build_f( gentity_t *ent ) +{ + char s[ MAX_TOKEN_CHARS ]; + buildable_t buildable; + float dist; + vec3_t origin, normal; + team_t team; + char buf[128]; + vec3_t dims; + + if( ent->client->pers.namelog->denyBuild ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED ); + return; + } + + if( ent->client->pers.teamSelection == level.surrenderTeam ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SURRENDER ); + return; + } + + trap_Argv( 1, s, sizeof( s ) ); + + buildable = BG_BuildableByName( s )->number; + + /* To allow players build cuboids with completely arbitrary + * dimensions (the current resizing method doesn't provide + * much precision) the build command was extended with the + * following syntax: + * build [building] [X] [Y] [Z] + * where X, Y and Z are respectively the cuboid's length + * on the X, Y and Z axis. + */ + if( BG_Buildable(buildable,NULL)->cuboid ) + { + if( trap_Argc() >= 5 ) + { + trap_Argv(2,s,sizeof(s)); + dims[0]=MAX(1,atof(s)); + trap_Argv(3,s,sizeof(s)); + dims[1]=MAX(1,atof(s)); + trap_Argv(4,s,sizeof(s)); + dims[2]=MAX(1,atof(s)); + if(!Cmd_CheckCuboidSize(dims)) + { + Com_sprintf(buf,sizeof(buf),"print \"^1error: invalid cuboid size (min volume: %i, max size: %s)\n\"", + CUBOID_MINVOLUME,(g_cuboidSizeLimit.integer?va("%ix%ix%i",g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer, g_cuboidSizeLimit.integer):"no limit")); + trap_SendServerCommand(ent->client-level.clients,buf); + return; + } + VectorCopy(dims,ent->client->cuboidSelection); + } + // client is building a cuboid for the first time so reset the selection to default + if(!Cmd_CheckCuboidSize(ent->client->cuboidSelection)) + { + ent->client->cuboidSelection[0]=32; + ent->client->cuboidSelection[1]=32; + ent->client->cuboidSelection[2]=32; + trap_SendServerCommand(ent->client-level.clients,"cb2 32 32 32"); + G_RelayCuboidToSpectators(ent->client); + } + + if(!BG_CuboidAllowed((team==TEAM_ALIENS?g_alienStage.integer:g_humanStage.integer))) + { + if(BG_CuboidMode()==1) + G_TriggerMenu(ent->client->ps.clientNum,MN_B_CUBOID_MODE1); + else + G_TriggerMenu(ent->client->ps.clientNum,MN_B_CUBOID_MODE2); + return; + } + } + + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); + return; + } + + team = ent->client->ps.stats[ STAT_TEAM ]; + + if( buildable != BA_NONE && + ( ( 1 << ent->client->ps.weapon ) & BG_Buildable( buildable, NULL )->buildWeapon ) && + BG_BuildableIsAllowed( buildable ) && + ( ( team == TEAM_ALIENS && BG_BuildableAllowedInStage( buildable, g_alienStage.integer ) ) || + ( team == TEAM_HUMANS && BG_BuildableAllowedInStage( buildable, g_humanStage.integer ) ) ) ) + { + dynMenu_t err; + dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; + + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + + //these are the errors displayed when the builder first selects something to use + switch( G_CanBuild( ent, buildable, dist, origin, normal, ent->client->cuboidSelection ) ) + { + // can place right away, set the blueprint and the valid togglebit + case IBE_NONE: + case IBE_TNODEWARN: + case IBE_RPTNOREAC: + case IBE_RPTPOWERHERE: + case IBE_SPWNWARN: + err = MN_NONE; + // we OR-in the selected builable later + ent->client->ps.stats[ STAT_BUILDABLE ] = SB_VALID_TOGGLEBIT; + break; + + // can't place yet but maybe soon: start with valid togglebit off + case IBE_NORMAL: + case IBE_NOCREEP: + case IBE_NOROOM: + case IBE_NOOVERMIND: + case IBE_NOPOWERHERE: + case IBE_NOSURF: + err = MN_NONE; + break; + + // more serious errors just pop a menu + case IBE_NOALIENBP: + err = MN_A_NOBP; + break; + + case IBE_ONEOVERMIND: + err = MN_A_ONEOVERMIND; + break; + + case IBE_ONEREACTOR: + err = MN_H_ONEREACTOR; + break; + + case IBE_NOHUMANBP: + err = MN_H_NOBP; + break; + + case IBE_NODCC: + err = MN_H_NODCC; + break; + + case IBE_PERMISSION: + err = MN_B_CANNOT; + break; + + case IBE_LASTSPAWN: + err = MN_B_LASTSPAWN; + break; + + case IBE_TOODENSE: + err = MN_B_TOODENSE; + break; + + default: + err = -1; // stop uninitialised warning + break; + } + + if( ( err == MN_A_NOBP || err == MN_H_NOBP ) && BG_Buildable(buildable,NULL)->cuboid ) + { + err = MN_NONE; + ent->client->ps.stats[ STAT_BUILDABLE ] = SB_VALID_TOGGLEBIT; + } + + if( err == MN_NONE || ent->client->pers.disableBlueprintErrors ) + { + trap_SendServerCommand(ent->client-level.clients,va("cb2 %f %f %f\n", + ent->client->cuboidSelection[0], + ent->client->cuboidSelection[1], + ent->client->cuboidSelection[2])); + G_RelayCuboidToSpectators(ent->client); + ent->client->ps.stats[ STAT_BUILDABLE ] |= buildable; + } + else + G_TriggerMenu( ent->client->ps.clientNum, err ); + } + else + G_TriggerMenu( ent->client->ps.clientNum, MN_B_CANNOT ); +} + +/* +================= +Cmd_Reload_f +================= +*/ +void Cmd_Reload_f( gentity_t *ent ) +{ + playerState_t *ps = &ent->client->ps; + int ammo; + + // weapon doesn't ever need reloading + if( BG_Weapon( ps->weapon )->infiniteAmmo ) + return; + + if( ps->clips <= 0 ) + return; + + if( BG_Weapon( ps->weapon )->usesEnergy && + BG_InventoryContainsUpgrade( UP_BATTPACK, ps->stats ) ) + ammo = BG_Weapon( ps->weapon )->maxAmmo * BATTPACK_MODIFIER; + else + ammo = BG_Weapon( ps->weapon )->maxAmmo; + + // don't reload when full + if( ps->ammo >= ammo ) + return; + + // the animation, ammo refilling etc. is handled by PM_Weapon + if( ent->client->ps.weaponstate != WEAPON_RELOADING ) + ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD; +} + +/* +================= +G_StopFromFollowing + +stops any other clients from following this one +called when a player leaves a team or dies +================= +*/ +void G_StopFromFollowing( gentity_t *ent ) +{ + int i; + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && + level.clients[ i ].sess.spectatorClient == ent->client->ps.clientNum ) + { + if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) + G_StopFollowing( &g_entities[ i ] ); + } + } +} + +/* +================= +G_StopFollowing + +If the client being followed leaves the game, or you just want to drop +to free floating spectator mode +================= +*/ +void G_StopFollowing( gentity_t *ent ) +{ + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; + + if( ent->client->pers.teamSelection == TEAM_NONE ) + { + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FREE; + } + else + { + vec3_t spawn_origin, spawn_angles; + + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_LOCKED; + + if( ent->client->pers.teamSelection == TEAM_ALIENS ) + G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( ent->client->pers.teamSelection == TEAM_HUMANS ) + G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, ent->client->ps.origin ); + G_SetClientViewAngle( ent, spawn_angles ); + } + ent->client->sess.spectatorClient = -1; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + ent->client->ps.stats[ STAT_STATE ] = 0; + ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; + ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); + ent->client->ps.viewangles[ PITCH ] = 0.0f; + ent->client->ps.clientNum = ent - g_entities; + ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit; + + CalculateRanks( ); +} + +/* +================= +G_FollowLockView + +Client is still following a player, but that player has gone to spectator +mode and cannot be followed for the moment +================= +*/ +void G_FollowLockView( gentity_t *ent ) +{ + vec3_t spawn_origin, spawn_angles; + int clientNum; + + clientNum = ent->client->sess.spectatorClient; + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FOLLOW; + ent->client->ps.clientNum = clientNum; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; + ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; + ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + ent->client->ps.viewangles[ PITCH ] = 0.0f; + + // Put the view at the team spectator lock position + if( level.clients[ clientNum ].pers.teamSelection == TEAM_ALIENS ) + G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( level.clients[ clientNum ].pers.teamSelection == TEAM_HUMANS ) + G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, ent->client->ps.origin ); + G_SetClientViewAngle( ent, spawn_angles ); +} + +/* +================= +G_FollowNewClient + +This was a really nice, elegant function. Then I fucked it up. +================= +*/ +qboolean G_FollowNewClient( gentity_t *ent, int dir ) +{ + int clientnum = ent->client->sess.spectatorClient; + int original = clientnum; + qboolean selectAny = qfalse; + + if( dir > 1 ) + dir = 1; + else if( dir < -1 ) + dir = -1; + else if( dir == 0 ) + return qtrue; + + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + return qfalse; + + // select any if no target exists + if( clientnum < 0 || clientnum >= level.maxclients ) + { + clientnum = original = 0; + selectAny = qtrue; + } + + do + { + clientnum += dir; + + if( clientnum >= level.maxclients ) + clientnum = 0; + + if( clientnum < 0 ) + clientnum = level.maxclients - 1; + + // can't follow self + if( &g_entities[ clientnum ] == ent ) + continue; + + // avoid selecting existing follow target + if( clientnum == original && !selectAny ) + continue; //effectively break; + + // can only follow connected clients + if( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) + continue; + + // can't follow a spectator + if( level.clients[ clientnum ].pers.teamSelection == TEAM_NONE ) + continue; + + // if stickyspec is disabled, can't follow someone in queue either + if( !ent->client->pers.stickySpec && + level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) + continue; + + // can only follow teammates when dead and on a team + if( ent->client->pers.teamSelection != TEAM_NONE && + ( level.clients[ clientnum ].pers.teamSelection != + ent->client->pers.teamSelection ) ) + continue; + + // this is good, we can use it + ent->client->sess.spectatorClient = clientnum; + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + + // if this client is in the spawn queue, we need to do something special + if( level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) + G_FollowLockView( ent ); + + return qtrue; + + } while( clientnum != original ); + + return qfalse; +} + +/* +================= +G_ToggleFollow +================= +*/ +void G_ToggleFollow( gentity_t *ent ) +{ + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + else + G_FollowNewClient( ent, 1 ); +} + +/* +================= +Cmd_Follow_f +================= +*/ +void Cmd_Follow_f( gentity_t *ent ) +{ + int i; + char arg[ MAX_NAME_LENGTH ]; + + // won't work unless spectating + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + return; + + if( trap_Argc( ) != 2 ) + { + G_ToggleFollow( ent ); + } + else + { + char err[ MAX_STRING_CHARS ]; + trap_Argv( 1, arg, sizeof( arg ) ); + + i = G_ClientNumberFromString( arg, err, sizeof( err ) ); + + if( i == -1 ) + { + trap_SendServerCommand( ent - g_entities, + va( "print \"follow: %s\"", err ) ); + return; + } + + // can't follow self + if( &level.clients[ i ] == ent->client ) + return; + + // can't follow another spectator if sticky spec is off + if( !ent->client->pers.stickySpec && + level.clients[ i ].sess.spectatorState != SPECTATOR_NOT ) + return; + + // if not on team spectator, you can only follow teammates + if( ent->client->pers.teamSelection != TEAM_NONE && + ( level.clients[ i ].pers.teamSelection != + ent->client->pers.teamSelection ) ) + return; + + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + ent->client->sess.spectatorClient = i; + } +} + +/* +================= +Cmd_FollowCycle_f +================= +*/ +void Cmd_FollowCycle_f( gentity_t *ent ) +{ + char args[ 11 ]; + int dir = 1; + + trap_Argv( 0, args, sizeof( args ) ); + if( Q_stricmp( args, "followprev" ) == 0 ) + dir = -1; + + // won't work unless spectating + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + return; + + G_FollowNewClient( ent, dir ); +} + +static void Cmd_Ignore_f( gentity_t *ent ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ]; + char cmd[ 9 ]; + int matches = 0; + int i; + qboolean ignore = qfalse; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( Q_stricmp( cmd, "ignore" ) == 0 ) + ignore = qtrue; + + if( trap_Argc() < 2 ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "usage: %s [clientNum | partial name match]\n\"", cmd ) ); + return; + } + + Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); + matches = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); + if( matches < 1 ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "%s: no clients match the name '%s'\n\"", cmd, name ) ); + return; + } + + for( i = 0; i < matches; i++ ) + { + if( ignore ) + { + if( !Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) ) + { + Com_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] ); + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "ignore: added %s^7 to your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + else + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "ignore: %s^7 is already on your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + } + else + { + if( Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) ) + { + Com_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] ); + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "unignore: removed %s^7 from your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + else + { + trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" + "unignore: %s^7 is not on your ignore list\n\"", + level.clients[ pids[ i ] ].pers.netname ) ); + } + } + } +} + +/* +================= +Cmd_ListMaps_f + +List all maps on the server +================= +*/ + +static int SortMaps( const void *a, const void *b ) +{ + return strcmp( *(char **)a, *(char **)b ); +} + +#define MAX_MAPLIST_MAPS 256 +#define MAX_MAPLIST_ROWS 9 +void Cmd_ListMaps_f( gentity_t *ent ) +{ + char search[ 16 ] = {""}; + char fileList[ 4096 ] = {""}; + char *fileSort[ MAX_MAPLIST_MAPS ]; + char *filePtr, *p; + int numFiles; + int fileLen = 0; + int shown = 0; + int count = 0; + int page = 0; + int pages; + int row, rows; + int start, i, j; + + if( trap_Argc( ) > 1 ) + { + trap_Argv( 1, search, sizeof( search ) ); + for( p = search; ( *p ) && isdigit( *p ); p++ ); + if( !( *p ) ) + { + page = atoi( search ); + search[ 0 ] = '\0'; + } + else if( trap_Argc( ) > 2 ) + { + char lp[ 8 ]; + trap_Argv( 2, lp, sizeof( lp ) ); + page = atoi( lp ); + } + + if( page > 0 ) + page--; + else if( page < 0 ) + page = 0; + } + + numFiles = trap_FS_GetFileList( "maps/", ".bsp", + fileList, sizeof( fileList ) ); + filePtr = fileList; + for( i = 0; i < numFiles && count < MAX_MAPLIST_MAPS; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + if ( fileLen < 5 ) + continue; + + filePtr[ fileLen - 4 ] = '\0'; + + if( search[ 0 ] && !strstr( filePtr, search ) ) + continue; + + fileSort[ count ] = filePtr; + count++; + } + qsort( fileSort, count, sizeof( fileSort[ 0 ] ), SortMaps ); + + rows = ( count + 2 ) / 3; + pages = MAX( 1, ( rows + MAX_MAPLIST_ROWS - 1 ) / MAX_MAPLIST_ROWS ); + if( page >= pages ) + page = pages - 1; + + start = page * MAX_MAPLIST_ROWS * 3; + if( count < start + ( 3 * MAX_MAPLIST_ROWS ) ) + rows = ( count - start + 2 ) / 3; + else + rows = MAX_MAPLIST_ROWS; + + ADMBP_begin( ); + for( row = 0; row < rows; row++ ) + { + for( i = start + row, j = 0; i < count && j < 3; i += rows, j++ ) + { + ADMBP( va( "^7 %-20s", fileSort[ i ] ) ); + shown++; + } + ADMBP( "\n" ); + } + if ( search[ 0 ] ) + ADMBP( va( "^3listmaps: ^7found %d maps matching '%s^7'", count, search ) ); + else + ADMBP( va( "^3listmaps: ^7listing %d of %d maps", shown, count ) ); + if( pages > 1 ) + ADMBP( va( ", page %d of %d", page + 1, pages ) ); + if( page + 1 < pages ) + ADMBP( va( ", use 'listmaps %s%s%d' to see more", + search, ( search[ 0 ] ) ? " ": "", page + 2 ) ); + ADMBP( ".\n" ); + ADMBP_end( ); +} + +/* +================= +Cmd_Test_f +================= +*/ +void Cmd_Test_f( gentity_t *humanPlayer ) +{ +} + +/* +================= +Cmd_Damage_f + +Deals damage to you (for testing), arguments: [damage] [dx] [dy] [dz] +The dx/dy arguments describe the damage point's offset from the entity origin +================= +*/ +void Cmd_Damage_f( gentity_t *ent ) +{ + vec3_t point; + char arg[ 16 ]; + float dx = 0.0f, dy = 0.0f, dz = 100.0f; + int damage = 100; + qboolean nonloc = qtrue; + + if( trap_Argc() > 1 ) + { + trap_Argv( 1, arg, sizeof( arg ) ); + damage = atoi( arg ); + } + if( trap_Argc() > 4 ) + { + trap_Argv( 2, arg, sizeof( arg ) ); + dx = atof( arg ); + trap_Argv( 3, arg, sizeof( arg ) ); + dy = atof( arg ); + trap_Argv( 4, arg, sizeof( arg ) ); + dz = atof( arg ); + nonloc = qfalse; + } + VectorCopy( ent->s.origin, point ); + point[ 0 ] += dx; + point[ 1 ] += dy; + point[ 2 ] += dz; + G_Damage( ent, NULL, NULL, NULL, point, damage, + ( nonloc ? DAMAGE_NO_LOCDAMAGE : 0 ), MOD_TARGET_LASER ); +} + +/* +================== +G_FloodLimited + +Determine whether a user is flood limited, and adjust their flood demerits +Print them a warning message if they are over the limit +Return is time in msec until the user can speak again +================== +*/ +int G_FloodLimited( gentity_t *ent ) +{ + int deltatime, ms; + + if( g_floodMinTime.integer <= 0 ) + return 0; + + // handles !ent + if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) + return 0; + + deltatime = level.time - ent->client->pers.floodTime; + + ent->client->pers.floodDemerits += g_floodMinTime.integer - deltatime; + if( ent->client->pers.floodDemerits < 0 ) + ent->client->pers.floodDemerits = 0; + ent->client->pers.floodTime = level.time; + + ms = ent->client->pers.floodDemerits - g_floodMaxDemerits.integer; + if( ms <= 0 ) + return 0; + trap_SendServerCommand( ent - g_entities, va( "print \"You are flooding: " + "please wait %d second%s before trying again\n", + ( ms + 999 ) / 1000, ( ms > 1000 ) ? "s" : "" ) ); + return ms; +} + +commands_t cmds[ ] = { + { "a", CMD_MESSAGE|CMD_INTERMISSION, Cmd_AdminMessage_f }, + { "build", CMD_TEAM|CMD_LIVING, Cmd_Build_f }, + { "buy", CMD_HUMAN|CMD_LIVING, Cmd_Buy_f }, + { "callteamvote", CMD_MESSAGE|CMD_TEAM, Cmd_CallVote_f }, + { "callvote", CMD_MESSAGE, Cmd_CallVote_f }, + { "cb", 0, Cmd_Cb_f }, //NOTE: it's a command used only by cgame + { "class", CMD_TEAM, Cmd_Class_f }, + { "damage", CMD_CHEAT|CMD_LIVING, Cmd_Damage_f }, + { "deconstruct", CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, + { "destroy", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, + { "follow", CMD_SPEC, Cmd_Follow_f }, + { "follownext", CMD_SPEC, Cmd_FollowCycle_f }, + { "followprev", CMD_SPEC, Cmd_FollowCycle_f }, + { "give", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Give_f }, + { "god", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_God_f }, + { "ignore", 0, Cmd_Ignore_f }, + { "itemact", CMD_HUMAN|CMD_LIVING, Cmd_ActivateItem_f }, + { "itemdeact", CMD_HUMAN|CMD_LIVING, Cmd_DeActivateItem_f }, + { "itemtoggle", CMD_HUMAN|CMD_LIVING, Cmd_ToggleItem_f }, + { "kill", CMD_TEAM|CMD_LIVING, Cmd_Kill_f }, + { "levelshot", CMD_CHEAT, Cmd_LevelShot_f }, + { "listmaps", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListMaps_f }, + { "m", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, + { "mt", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, + { "noclip", CMD_CHEAT_TEAM, Cmd_Noclip_f }, + { "notarget", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Notarget_f }, + { "reload", CMD_HUMAN|CMD_LIVING, Cmd_Reload_f }, + { "say", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, + { "say_area", CMD_MESSAGE|CMD_TEAM|CMD_LIVING, Cmd_SayArea_f }, + { "say_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, + { "score", CMD_INTERMISSION, ScoreboardMessage }, + { "sell", CMD_HUMAN|CMD_LIVING, Cmd_Sell_f }, + { "setviewpos", CMD_CHEAT_TEAM, Cmd_SetViewpos_f }, + { "team", 0, Cmd_Team_f }, + { "teamvote", CMD_TEAM, Cmd_Vote_f }, + { "test", CMD_CHEAT, Cmd_Test_f }, + { "unignore", 0, Cmd_Ignore_f }, + { "vote", 0, Cmd_Vote_f }, + { "vsay", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "vsay_local", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "vsay_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "where", 0, Cmd_Where_f } +}; +static size_t numCmds = sizeof( cmds ) / sizeof( cmds[ 0 ] ); + +/* +================= +ClientCommand +================= +*/ +void ClientCommand( int clientNum ) +{ + gentity_t *ent; + char cmd[ MAX_TOKEN_CHARS ]; + commands_t *command; + + ent = g_entities + clientNum; + if( !ent->client || ent->client->pers.connected != CON_CONNECTED ) + return; // not fully in game yet + + trap_Argv( 0, cmd, sizeof( cmd ) ); + + command = bsearch( cmd, cmds, numCmds, sizeof( cmds[ 0 ] ), cmdcmp ); + + if( !command ) + { + if( !G_admin_cmd_check( ent ) ) + trap_SendServerCommand( clientNum, + va( "print \"Unknown command %s\n\"", cmd ) ); + return; + } + + // do tests here to reduce the amount of repeated code + + if( !( command->cmdFlags & CMD_INTERMISSION ) && + ( level.intermissiontime || level.pausedTime ) ) + return; + + if( command->cmdFlags & CMD_CHEAT && !g_cheats.integer ) + { + G_TriggerMenu( clientNum, MN_CMD_CHEAT ); + return; + } + + if( command->cmdFlags & CMD_MESSAGE && ( ent->client->pers.namelog->muted || + G_FloodLimited( ent ) ) ) + return; + + if( command->cmdFlags & CMD_TEAM && + ent->client->pers.teamSelection == TEAM_NONE ) + { + G_TriggerMenu( clientNum, MN_CMD_TEAM ); + return; + } + + if( command->cmdFlags & CMD_CHEAT_TEAM && !g_cheats.integer && + ent->client->pers.teamSelection != TEAM_NONE ) + { + G_TriggerMenu( clientNum, MN_CMD_CHEAT_TEAM ); + return; + } + + if( command->cmdFlags & CMD_SPEC && + ent->client->sess.spectatorState == SPECTATOR_NOT ) + { + G_TriggerMenu( clientNum, MN_CMD_SPEC ); + return; + } + + if( command->cmdFlags & CMD_ALIEN && + ent->client->pers.teamSelection != TEAM_ALIENS ) + { + G_TriggerMenu( clientNum, MN_CMD_ALIEN ); + return; + } + + if( command->cmdFlags & CMD_HUMAN && + ent->client->pers.teamSelection != TEAM_HUMANS ) + { + G_TriggerMenu( clientNum, MN_CMD_HUMAN ); + return; + } + + if( command->cmdFlags & CMD_LIVING && + ( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || + ent->client->sess.spectatorState != SPECTATOR_NOT ) ) + { + G_TriggerMenu( clientNum, MN_CMD_LIVING ); + return; + } + + command->cmdHandler( ent ); +} + +void G_ListCommands( gentity_t *ent ) +{ + int i; + char out[ MAX_STRING_CHARS ] = ""; + int len, outlen; + + outlen = 0; + + for( i = 0; i < numCmds; i++ ) + { + len = strlen( cmds[ i ].cmdName ) + 1; + if( len + outlen >= sizeof( out ) - 1 ) + { + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); + outlen = 0; + } + + strcpy( out + outlen, va( " %s", cmds[ i ].cmdName ) ); + outlen += len; + } + + trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); + G_admin_cmdlist( ent ); +} + +void G_DecolorString( char *in, char *out, int len ) +{ + qboolean decolor = qtrue; + + len--; + + while( *in && len > 0 ) { + if( *in == DECOLOR_OFF || *in == DECOLOR_ON ) + { + decolor = ( *in == DECOLOR_ON ); + in++; + continue; + } + if( Q_IsColorString( in ) && decolor ) { + in += 2; + continue; + } + *out++ = *in++; + len--; + } + *out = '\0'; +} + +void G_UnEscapeString( char *in, char *out, int len ) +{ + len--; + + while( *in && len > 0 ) + { + if( *in >= ' ' || *in == '\n' ) + { + *out++ = *in; + len--; + } + in++; + } + *out = '\0'; +} + +void Cmd_PrivateMessage_f( gentity_t *ent ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ]; + char cmd[ 12 ]; + char text[ MAX_STRING_CHARS ]; + char *msg; + char color; + int i, pcount; + int count = 0; + qboolean teamonly = qfalse; + char recipients[ MAX_STRING_CHARS ] = ""; + + if( !g_privateMessages.integer && ent ) + { + ADMP( "Sorry, but private messages have been disabled\n" ); + return; + } + + trap_Argv( 0, cmd, sizeof( cmd ) ); + if( trap_Argc( ) < 3 ) + { + ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) ); + return; + } + + if( !Q_stricmp( cmd, "mt" ) ) + teamonly = qtrue; + + trap_Argv( 1, name, sizeof( name ) ); + msg = ConcatArgs( 2 ); + pcount = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); + + G_CensorString( text, msg, sizeof( text ), ent ); + + // send the message + for( i = 0; i < pcount; i++ ) + { + if( G_SayTo( ent, &g_entities[ pids[ i ] ], + teamonly ? SAY_TPRIVMSG : SAY_PRIVMSG, text ) ) + { + count++; + Q_strcat( recipients, sizeof( recipients ), va( "%s" S_COLOR_WHITE ", ", + level.clients[ pids[ i ] ].pers.netname ) ); + } + } + + // report the results + color = teamonly ? COLOR_CYAN : COLOR_YELLOW; + + if( !count ) + ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n", + name ) ); + else + { + ADMP( va( "^%cPrivate message: ^7%s\n", color, text ) ); + // remove trailing ", " + recipients[ strlen( recipients ) - 2 ] = '\0'; + ADMP( va( "^%csent to %i player%s: " S_COLOR_WHITE "%s\n", color, count, + count == 1 ? "" : "s", recipients ) ); + + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\" \"%s\": ^%c%s\n", + ( teamonly ) ? "TPrivMsg" : "PrivMsg", + ( ent ) ? ent - g_entities : -1, + ( ent ) ? ent->client->pers.netname : "console", + name, color, msg ); + } +} + +/* +================= +Cmd_AdminMessage_f + +Send a message to all active admins +================= +*/ +void Cmd_AdminMessage_f( gentity_t *ent ) +{ + // Check permissions and add the appropriate user [prefix] + if( !G_admin_permission( ent, ADMF_ADMINCHAT ) ) + { + if( !g_publicAdminMessages.integer ) + { + ADMP( "Sorry, but use of /a by non-admins has been disabled.\n" ); + return; + } + else + { + ADMP( "Your message has been sent to any available admins " + "and to the server logs.\n" ); + } + } + + if( trap_Argc( ) < 2 ) + { + ADMP( "usage: a [message]\n" ); + return; + } + + G_AdminMessage( ent, ConcatArgs( 1 ) ); +} + diff --git a/src/game/g_combat.c b/src/game/g_combat.c new file mode 100644 index 0000000..c381f91 --- /dev/null +++ b/src/game/g_combat.c @@ -0,0 +1,1470 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +damageRegion_t g_damageRegions[ PCL_NUM_CLASSES ][ MAX_DAMAGE_REGIONS ]; +int g_numDamageRegions[ PCL_NUM_CLASSES ]; + +damageRegion_t g_armourRegions[ UP_NUM_UPGRADES ][ MAX_DAMAGE_REGIONS ]; +int g_numArmourRegions[ UP_NUM_UPGRADES ]; + +/* +============ +AddScore + +Adds score to the client +============ +*/ +void AddScore( gentity_t *ent, int score ) +{ + if( !ent->client ) + return; + + // make alien and human scores equivalent + if ( ent->client->pers.teamSelection == TEAM_ALIENS ) + { + score = rint( ((float)score) / 2.0f ); + } + + // scale values down to fit the scoreboard better + score = rint( ((float)score) / 50.0f ); + + ent->client->ps.persistant[ PERS_SCORE ] += score; + CalculateRanks( ); +} + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) +{ + + if ( attacker && attacker != self ) + self->client->ps.stats[ STAT_VIEWLOCK ] = attacker - g_entities; + else if( inflictor && inflictor != self ) + self->client->ps.stats[ STAT_VIEWLOCK ] = inflictor - g_entities; + else + self->client->ps.stats[ STAT_VIEWLOCK ] = self - g_entities; +} + +// these are just for logging, the client prints its own messages +char *modNames[ ] = +{ + "MOD_UNKNOWN", + "MOD_SHOTGUN", + "MOD_BLASTER", + "MOD_PAINSAW", + "MOD_MACHINEGUN", + "MOD_CHAINGUN", + "MOD_PRIFLE", + "MOD_MDRIVER", + "MOD_LASGUN", + "MOD_LCANNON", + "MOD_LCANNON_SPLASH", + "MOD_FLAMER", + "MOD_FLAMER_SPLASH", + "MOD_GRENADE", + "MOD_WATER", + "MOD_SLIME", + "MOD_LAVA", + "MOD_CRUSH", + "MOD_TELEFRAG", + "MOD_FALLING", + "MOD_SUICIDE", + "MOD_TARGET_LASER", + "MOD_TRIGGER_HURT", + + "MOD_ABUILDER_CLAW", + "MOD_LEVEL0_BITE", + "MOD_LEVEL1_CLAW", + "MOD_LEVEL1_PCLOUD", + "MOD_LEVEL3_CLAW", + "MOD_LEVEL3_POUNCE", + "MOD_LEVEL3_BOUNCEBALL", + "MOD_LEVEL2_CLAW", + "MOD_LEVEL2_ZAP", + "MOD_LEVEL4_CLAW", + "MOD_LEVEL4_TRAMPLE", + "MOD_LEVEL4_CRUSH", + + "MOD_SLOWBLOB", + "MOD_POISON", + "MOD_SWARM", + + "MOD_HSPAWN", + "MOD_TESLAGEN", + "MOD_MGTURRET", + "MOD_REACTOR", + + "MOD_ASPAWN", + "MOD_ATUBE", + "MOD_OVERMIND", + "MOD_DECONSTRUCT", + "MOD_REPLACE", + "MOD_NOCREEP" +}; + +/* +================== +G_RewardAttackers + +Function to distribute rewards to entities that killed this one. +Returns the total damage dealt. +================== +*/ +float G_RewardAttackers( gentity_t *self ) +{ + float value, totalDamage = 0; + int team, i, maxHealth = 0; + int alienCredits = 0, humanCredits = 0; + gentity_t *player; + + // Total up all the damage done by non-teammates + for( i = 0; i < level.maxclients; i++ ) + { + player = g_entities + i; + + if( !OnSameTeam( self, player ) || + self->buildableTeam != player->client->ps.stats[ STAT_TEAM ] ) + totalDamage += (float)self->credits[ i ]; + } + + if( totalDamage <= 0.0f ) + return 0.0f; + + // Only give credits for killing players and buildables + if( self->client ) + { + value = BG_GetValueOfPlayer( &self->client->ps ); + team = self->client->pers.teamSelection; + maxHealth = self->client->ps.stats[ STAT_MAX_HEALTH ]; + } + else if( self->s.eType == ET_BUILDABLE ) + { + value = BG_Buildable( self->s.modelindex, self->cuboidSize )->value; + + // only give partial credits for a buildable not yet completed + if( !self->spawned ) + { + value *= (float)( level.time - self->buildTime ) / + BG_Buildable( self->s.modelindex, self->cuboidSize )->buildTime; + } + + team = self->buildableTeam; + maxHealth = BG_Buildable( self->s.modelindex, self->cuboidSize )->health; + } + else + return totalDamage; + + // Give credits and empty the array + for( i = 0; i < level.maxclients; i++ ) + { + int stageValue = value * self->credits[ i ] / totalDamage; + player = g_entities + i; + + if( player->client->pers.teamSelection != team ) + { + if( totalDamage < maxHealth ) + stageValue *= totalDamage / maxHealth; + + if( !self->credits[ i ] || player->client->ps.stats[ STAT_TEAM ] == team ) + continue; + + AddScore( player, stageValue ); + + // killing buildables earns score, but not credits + if( self->s.eType != ET_BUILDABLE ) + { + G_AddCreditToClient( player->client, stageValue, qtrue ); + + // add to stage counters + if( player->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + alienCredits += stageValue; + else if( player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + humanCredits += stageValue; + } + } + self->credits[ i ] = 0; + } + + if( alienCredits ) + { + trap_Cvar_Set( "g_alienCredits", + va( "%d", g_alienCredits.integer + alienCredits ) ); + trap_Cvar_Update( &g_alienCredits ); + } + if( humanCredits ) + { + trap_Cvar_Set( "g_humanCredits", + va( "%d", g_humanCredits.integer + humanCredits ) ); + trap_Cvar_Update( &g_humanCredits ); + } + + return totalDamage; +} + +/* +================== +player_die +================== +*/ +void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) +{ + gentity_t *ent, *ent2; + int anim; + int killer; + int i; + char *killerName, *obit; + vec3_t dir; + + if( self->client->ps.pm_type == PM_DEAD ) + return; + + if( level.intermissiontime ) + return; + + self->client->ps.pm_type = PM_DEAD; + self->suicideTime = 0; + + if( attacker ) + { + killer = attacker->s.number; + + if( attacker->client ) + killerName = attacker->client->pers.netname; + else + killerName = ""; + } + else + { + killer = ENTITYNUM_WORLD; + killerName = ""; + } + + if( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) + // fall back on the number + obit = va( "%d", meansOfDeath ); + else + obit = modNames[ meansOfDeath ]; + + G_LogPrintf( "Die: %d %d %s: %s" S_COLOR_WHITE " killed %s\n", + killer, + self - g_entities, + obit, + killerName, + self->client->pers.netname ); + + // deactivate all upgrades + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + BG_DeactivateUpgrade( i, self->client->ps.stats ); + + // kill all player's buildables if they havent spawned yet + // this should eliminate build timer hacks for ever + dir[0] = dir[1] = 0.0f; + dir[2] = 1.0f; + + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent == self ) + continue; + + if( ent->spawned ) + continue; + + if( ent->builtBy != self->client->ps.clientNum ) + continue; + + G_Damage( ent, self, attacker, dir, dir, ent->health, 0, MOD_DECONSTRUCT ); + } + + // broadcast the death event to everyone + ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); + ent->s.eventParm = meansOfDeath; + ent->s.otherEntityNum = self->s.number; + ent->s.otherEntityNum2 = killer; + ent->r.svFlags = SVF_BROADCAST; // send to everyone + + self->enemy = attacker; + self->client->ps.persistant[ PERS_KILLED ]++; + + if( attacker && attacker->client ) + { + attacker->client->lastkilled_client = self->s.number; + + if( ( attacker == self || OnSameTeam( self, attacker ) ) && meansOfDeath != MOD_HSPAWN ) + { + //punish team kills and suicides + if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + G_AddCreditToClient( attacker->client, -ALIEN_TK_SUICIDE_PENALTY, qtrue ); + AddScore( attacker, -ALIEN_TK_SUICIDE_PENALTY ); + } + else if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + G_AddCreditToClient( attacker->client, -HUMAN_TK_SUICIDE_PENALTY, qtrue ); + AddScore( attacker, -HUMAN_TK_SUICIDE_PENALTY ); + } + } + } + else if( attacker->s.eType != ET_BUILDABLE ) + { + if( self->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + AddScore( self, -ALIEN_TK_SUICIDE_PENALTY ); + else if( self->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + AddScore( self, -HUMAN_TK_SUICIDE_PENALTY ); + } + + // give credits for killing this player + G_RewardAttackers( self ); + + ScoreboardMessage( self ); // show scores + + // send updated scores to any clients that are following this one, + // or they would get stale scoreboards + for( i = 0 ; i < level.maxclients ; i++ ) + { + gclient_t *client; + + client = &level.clients[ i ]; + if( client->pers.connected != CON_CONNECTED ) + continue; + + if( client->sess.spectatorState == SPECTATOR_NOT ) + continue; + + if( client->sess.spectatorClient == self->s.number ) + ScoreboardMessage( g_entities + i ); + } + + VectorCopy( self->s.origin, self->client->pers.lastDeathLocation ); + + self->takedamage = qfalse; // can still be gibbed + + self->s.weapon = WP_NONE; + self->r.contents = CONTENTS_CORPSE; + + self->s.angles[ PITCH ] = 0; + self->s.angles[ ROLL ] = 0; + self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ]; + + if( meansOfDeath != MOD_ALIEN_HATCH ) //don't look at the alien that jumped out of your chest, it screws up the camera + LookAtKiller( self, inflictor, attacker ); + + VectorCopy( self->s.angles, self->client->ps.viewangles ); + + self->s.loopSound = 0; + + self->r.maxs[ 2 ] = -8; + + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + self->client->respawnTime = level.time + 1700; + + // clear misc + memset( self->client->ps.misc, 0, sizeof( self->client->ps.misc ) ); + + { + // normal death + static int i; + + if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + switch( i ) + { + case 0: + anim = BOTH_DEATH1; + break; + case 1: + anim = BOTH_DEATH2; + break; + case 2: + default: + anim = BOTH_DEATH3; + break; + } + } + else + { + switch( i ) + { + case 0: + anim = NSPA_DEATH1; + break; + case 1: + anim = NSPA_DEATH2; + break; + case 2: + default: + anim = NSPA_DEATH3; + break; + } + } + + self->client->ps.legsAnim = + ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + + if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + self->client->ps.torsoAnim = + ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + } + + // use own entityid if killed by non-client to prevent uint8_t overflow + G_AddEvent( self, EV_DEATH1 + i, + ( killer < MAX_CLIENTS ) ? killer : self - g_entities ); + + // globally cycle through the different death animations + i = ( i + 1 ) % 3; + } + + trap_LinkEntity( self ); +} + +/* +=============== +G_ParseDmgScript +=============== +*/ +static int G_ParseDmgScript( damageRegion_t *regions, char *buf ) +{ + char *token; + float angleSpan, heightSpan; + int count; + + for( count = 0; ; count++ ) + { + token = COM_Parse( &buf ); + if( !token[ 0 ] ) + break; + + if( strcmp( token, "{" ) ) + { + COM_ParseError( "Missing {" ); + break; + } + + if( count >= MAX_DAMAGE_REGIONS ) + { + COM_ParseError( "Max damage regions exceeded" ); + break; + } + + // defaults + regions[ count ].name[ 0 ] = '\0'; + regions[ count ].minHeight = 0.0f; + regions[ count ].maxHeight = 1.0f; + regions[ count ].minAngle = 0.0f; + regions[ count ].maxAngle = 360.0f; + regions[ count ].modifier = 1.0f; + regions[ count ].crouch = qfalse; + + while( 1 ) + { + token = COM_ParseExt( &buf, qtrue ); + + if( !token[ 0 ] ) + { + COM_ParseError( "Unexpected end of file" ); + break; + } + + if( !Q_stricmp( token, "}" ) ) + { + break; + } + else if( !strcmp( token, "name" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( token[ 0 ] ) + Q_strncpyz( regions[ count ].name, token, + sizeof( regions[ count ].name ) ); + } + else if( !strcmp( token, "minHeight" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "0" ); + regions[ count ].minHeight = atof( token ); + } + else if( !strcmp( token, "maxHeight" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "100" ); + regions[ count ].maxHeight = atof( token ); + } + else if( !strcmp( token, "minAngle" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "0" ); + regions[ count ].minAngle = atoi( token ); + } + else if( !strcmp( token, "maxAngle" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "360" ); + regions[ count ].maxAngle = atoi( token ); + } + else if( !strcmp( token, "modifier" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "1.0" ); + regions[ count ].modifier = atof( token ); + } + else if( !strcmp( token, "crouch" ) ) + { + regions[ count ].crouch = qtrue; + } + else + { + COM_ParseWarning("Unknown token \"%s\"", token); + } + } + + // Angle portion covered + angleSpan = regions[ count ].maxAngle - regions[ count ].minAngle; + if( angleSpan < 0.0f ) + angleSpan += 360.0f; + angleSpan /= 360.0f; + + // Height portion covered + heightSpan = regions[ count ].maxHeight - regions[ count ].minHeight; + if( heightSpan < 0.0f ) + heightSpan = -heightSpan; + if( heightSpan > 1.0f ) + heightSpan = 1.0f; + + regions[ count ].area = angleSpan * heightSpan; + if( !regions[ count ].area ) + regions[ count ].area = 0.00001f; + } + + return count; +} + +/* +============ +GetRegionDamageModifier +============ +*/ +static float GetRegionDamageModifier( gentity_t *targ, int class, int piece ) +{ + damageRegion_t *regions, *overlap; + float modifier = 0.0f, areaSum = 0.0f; + int j, i; + qboolean crouch; + + crouch = targ->client->ps.pm_flags & PMF_DUCKED; + overlap = &g_damageRegions[ class ][ piece ]; + + if( g_debugDamage.integer > 2 ) + G_Printf( "GetRegionDamageModifier():\n" + ". bodyRegion = [%d %d %f %f] (%s)\n" + ". modifier = %f\n", + overlap->minAngle, overlap->maxAngle, + overlap->minHeight, overlap->maxHeight, + overlap->name, overlap->modifier ); + + // Find the armour layer modifier, assuming that none of the armour regions + // overlap and that any areas that are not covered have a modifier of 1.0 + for( j = UP_NONE + 1; j < UP_NUM_UPGRADES; j++ ) + { + if( !BG_InventoryContainsUpgrade( j, targ->client->ps.stats ) || + !g_numArmourRegions[ j ] ) + continue; + regions = g_armourRegions[ j ]; + + for( i = 0; i < g_numArmourRegions[ j ]; i++ ) + { + float overlapMaxA, regionMinA, regionMaxA, angleSpan, heightSpan, area; + + if( regions[ i ].crouch != crouch ) + continue; + + // Convert overlap angle to 0 to max + overlapMaxA = overlap->maxAngle - overlap->minAngle; + if( overlapMaxA < 0.0f ) + overlapMaxA += 360.0f; + + // Convert region angles to match overlap + regionMinA = regions[ i ].minAngle - overlap->minAngle; + if( regionMinA < 0.0f ) + regionMinA += 360.0f; + regionMaxA = regions[ i ].maxAngle - overlap->minAngle; + if( regionMaxA < 0.0f ) + regionMaxA += 360.0f; + + // Overlapping Angle portion + if( regionMinA <= regionMaxA ) + { + angleSpan = 0.0f; + if( regionMinA < overlapMaxA ) + { + if( regionMaxA > overlapMaxA ) + regionMaxA = overlapMaxA; + angleSpan = regionMaxA - regionMinA; + } + } + else + { + if( regionMaxA > overlapMaxA ) + regionMaxA = overlapMaxA; + angleSpan = regionMaxA; + if( regionMinA < overlapMaxA ) + angleSpan += overlapMaxA - regionMinA; + } + angleSpan /= 360.0f; + + // Overlapping height portion + heightSpan = MIN( overlap->maxHeight, regions[ i ].maxHeight ) - + MAX( overlap->minHeight, regions[ i ].minHeight ); + if( heightSpan < 0.0f ) + heightSpan = 0.0f; + if( heightSpan > 1.0f ) + heightSpan = 1.0f; + + if( g_debugDamage.integer > 2 ) + G_Printf( ". armourRegion = [%d %d %f %f] (%s)\n" + ". . modifier = %f\n" + ". . angleSpan = %f\n" + ". . heightSpan = %f\n", + regions[ i ].minAngle, regions[ i ].maxAngle, + regions[ i ].minHeight, regions[ i ].maxHeight, + regions[ i ].name, regions[ i ].modifier, + angleSpan, heightSpan ); + + areaSum += area = angleSpan * heightSpan; + modifier += regions[ i ].modifier * area; + } + } + + if( g_debugDamage.integer > 2 ) + G_Printf( ". areaSum = %f\n" + ". armourModifier = %f\n", areaSum, modifier ); + + return overlap->modifier * ( overlap->area + modifier - areaSum ); +} + +/* +============ +GetNonLocDamageModifier +============ +*/ +static float GetNonLocDamageModifier( gentity_t *targ, int class ) +{ + float modifier = 0.0f, area = 0.0f, scale = 0.0f; + int i; + qboolean crouch; + + // For every body region, use stretch-armor formula to apply armour modifier + // for any overlapping area that armour shares with the body region + crouch = targ->client->ps.pm_flags & PMF_DUCKED; + for( i = 0; i < g_numDamageRegions[ class ]; i++ ) + { + damageRegion_t *region; + + region = &g_damageRegions[ class ][ i ]; + + if( region->crouch != crouch ) + continue; + + modifier += GetRegionDamageModifier( targ, class, i ); + + scale += region->modifier * region->area; + area += region->area; + + } + + modifier = !scale ? 1.0f : 1.0f + ( modifier / scale - 1.0f ) * area; + + if( g_debugDamage.integer > 1 ) + G_Printf( "GetNonLocDamageModifier() modifier:%f, area:%f, scale:%f\n", + modifier, area, scale ); + + return modifier; +} + +/* +============ +GetPointDamageModifier + +Returns the damage region given an angle and a height proportion +============ +*/ +static float GetPointDamageModifier( gentity_t *targ, damageRegion_t *regions, + int len, float angle, float height ) +{ + float modifier = 1.0f; + int i; + + for( i = 0; i < len; i++ ) + { + if( regions[ i ].crouch != ( targ->client->ps.pm_flags & PMF_DUCKED ) ) + continue; + + // Angle must be within range + if( ( regions[ i ].minAngle <= regions[ i ].maxAngle && + ( angle < regions[ i ].minAngle || + angle > regions[ i ].maxAngle ) ) || + ( regions[ i ].minAngle > regions[ i ].maxAngle && + angle > regions[ i ].maxAngle && angle < regions[ i ].minAngle ) ) + continue; + + // Height must be within range + if( height < regions[ i ].minHeight || height > regions[ i ].maxHeight ) + continue; + + modifier *= regions[ i ].modifier; + } + + if( g_debugDamage.integer ) + G_Printf( "GetDamageRegionModifier(angle = %f, height = %f): %f\n", + angle, height, modifier ); + + return modifier; +} + +/* +============ +G_CalcDamageModifier +============ +*/ +static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *attacker, int class, int dflags ) +{ + vec3_t targOrigin, bulletPath, bulletAngle, pMINUSfloor, floor, normal; + float clientHeight, hitRelative, hitRatio, modifier; + int hitRotation, i; + + if( point == NULL ) + return 1.0f; + + // Don't need to calculate angles and height for non-locational damage + if( dflags & DAMAGE_NO_LOCDAMAGE ) + return GetNonLocDamageModifier( targ, class ); + + // Get the point location relative to the floor under the target + if( g_unlagged.integer && targ->client && targ->client->unlaggedCalc.used ) + VectorCopy( targ->client->unlaggedCalc.origin, targOrigin ); + else + VectorCopy( targ->r.currentOrigin, targOrigin ); + + BG_GetClientNormal( &targ->client->ps, normal ); + VectorMA( targOrigin, targ->r.mins[ 2 ], normal, floor ); + VectorSubtract( point, floor, pMINUSfloor ); + + // Get the proportion of the target height where the hit landed + clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ]; + + if( !clientHeight ) + clientHeight = 1.0f; + + hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal ); + + if( hitRelative < 0.0f ) + hitRelative = 0.0f; + + if( hitRelative > clientHeight ) + hitRelative = clientHeight; + + hitRatio = hitRelative / clientHeight; + + // Get the yaw of the attack relative to the target's view yaw + VectorSubtract( point, targOrigin, bulletPath ); + vectoangles( bulletPath, bulletAngle ); + + hitRotation = AngleNormalize360( targ->client->ps.viewangles[ YAW ] - + bulletAngle[ YAW ] ); + + // Get modifiers from the target's damage regions + modifier = GetPointDamageModifier( targ, g_damageRegions[ class ], + g_numDamageRegions[ class ], + hitRotation, hitRatio ); + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) ) + { + modifier *= GetPointDamageModifier( targ, g_armourRegions[ i ], + g_numArmourRegions[ i ], + hitRotation, hitRatio ); + } + } + + return modifier; +} + + +/* +============ +G_InitDamageLocations +============ +*/ +void G_InitDamageLocations( void ) +{ + char *modelName; + char filename[ MAX_QPATH ]; + int i; + int len; + fileHandle_t fileHandle; + char buffer[ MAX_DAMAGE_REGION_TEXT ]; + + for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) + { + modelName = BG_ClassConfig( i )->modelName; + Com_sprintf( filename, sizeof( filename ), + "models/players/%s/locdamage.cfg", modelName ); + + len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); + if ( !fileHandle ) + { + G_Printf( S_COLOR_RED "file not found: %s\n", filename ); + continue; + } + + if( len >= MAX_DAMAGE_REGION_TEXT ) + { + G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", + filename, len, MAX_DAMAGE_REGION_TEXT ); + trap_FS_FCloseFile( fileHandle ); + continue; + } + + COM_BeginParseSession( filename ); + + trap_FS_Read( buffer, len, fileHandle ); + buffer[len] = 0; + trap_FS_FCloseFile( fileHandle ); + + g_numDamageRegions[ i ] = G_ParseDmgScript( g_damageRegions[ i ], buffer ); + } + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + modelName = BG_Upgrade( i )->name; + Com_sprintf( filename, sizeof( filename ), "armour/%s.armour", modelName ); + + len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); + + //no file - no parsage + if ( !fileHandle ) + continue; + + if( len >= MAX_DAMAGE_REGION_TEXT ) + { + G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", + filename, len, MAX_DAMAGE_REGION_TEXT ); + trap_FS_FCloseFile( fileHandle ); + continue; + } + + COM_BeginParseSession( filename ); + + trap_FS_Read( buffer, len, fileHandle ); + buffer[len] = 0; + trap_FS_FCloseFile( fileHandle ); + + g_numArmourRegions[ i ] = G_ParseDmgScript( g_armourRegions[ i ], buffer ); + } +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +inflictor, attacker, dir, and point can be NULL for environmental effects + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ + +// team is the team that is immune to this damage +void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, + vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team ) +{ + if( targ->client && ( team != targ->client->ps.stats[ STAT_TEAM ] ) ) + G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod ); +} + +void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, + vec3_t dir, vec3_t point, int damage, int dflags, int mod ) +{ + gclient_t *client; + int take; + int asave = 0; + int knockback; + + // Can't deal damage sometimes + if( !targ->takedamage || targ->health <= 0 || level.intermissionQueued ) + return; + + if( !inflictor ) + inflictor = &g_entities[ ENTITYNUM_WORLD ]; + + if( !attacker ) + attacker = &g_entities[ ENTITYNUM_WORLD ]; + + // shootable doors / buttons don't actually have any health + if( targ->s.eType == ET_MOVER ) + { + if( targ->use && ( targ->moverState == MOVER_POS1 || + targ->moverState == ROTATOR_POS1 ) ) + targ->use( targ, inflictor, attacker ); + + return; + } + + client = targ->client; + if( client && client->noclip ) + return; + + if( !dir ) + dflags |= DAMAGE_NO_KNOCKBACK; + else + VectorNormalize( dir ); + + knockback = damage; + + if( inflictor->s.weapon != WP_NONE ) + { + knockback = (int)( (float)knockback * + BG_Weapon( inflictor->s.weapon )->knockbackScale ); + } + + if( targ->client ) + { + knockback = (int)( (float)knockback * + BG_Class( targ->client->ps.stats[ STAT_CLASS ] )->knockbackScale ); + } + + // Too much knockback from falling really far makes you "bounce" and + // looks silly. However, none at all also looks bad. Cap it. + if( mod == MOD_FALLING && knockback > 50 ) + knockback = 50; + + if( knockback > 200 ) + knockback = 200; + + if( targ->flags & FL_NO_KNOCKBACK ) + knockback = 0; + + if( dflags & DAMAGE_NO_KNOCKBACK ) + knockback = 0; + + // figure momentum add, even if the damage won't be taken + if( knockback && targ->client ) + { + vec3_t kvel; + float mass; + + mass = 200; + + VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel ); + VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); + + // set the timer so that the other client can't cancel + // out the movement immediately + if( !targ->client->ps.pm_time ) + { + int t; + + t = knockback * 2; + if( t < 50 ) + t = 50; + + if( t > 200 ) + t = 200; + + targ->client->ps.pm_time = t; + targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + } + + // don't do friendly fire on movement attacks + if( ( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || + mod == MOD_LEVEL4_CRUSH ) && + targ->s.eType == ET_BUILDABLE && targ->buildableTeam == TEAM_ALIENS ) + { + return; + } + + // check for completely getting out of the damage + if( !( dflags & DAMAGE_NO_PROTECTION ) ) + { + + // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target + // if the attacker was on the same team + if( targ != attacker && OnSameTeam( targ, attacker ) ) + { + // don't do friendly fire on movement attacks + if( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || + mod == MOD_LEVEL4_CRUSH ) + return; + + // if dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage + if( g_dretchPunt.integer && + targ->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0 ) + { + vec3_t dir, push; + + VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, dir ); + VectorNormalizeFast( dir ); + VectorScale( dir, ( damage * 10.0f ), push ); + push[2] = 64.0f; + VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity ); + return; + } + + // check if friendly fire has been disabled + if( !g_friendlyFire.integer ) + { + return; + } + } + + if( targ->s.eType == ET_BUILDABLE && attacker->client && + mod != MOD_DECONSTRUCT && mod != MOD_SUICIDE && + mod != MOD_REPLACE && mod != MOD_NOCREEP ) + { + if( targ->buildableTeam == attacker->client->pers.teamSelection && + !g_friendlyBuildableFire.integer ) + { + return; + } + + // base is under attack warning if DCC'd + if( targ->buildableTeam == TEAM_HUMANS && G_FindDCC( targ ) && + level.time > level.humanBaseAttackTimer ) + { + level.humanBaseAttackTimer = level.time + DC_ATTACK_PERIOD; + G_BroadcastEvent( EV_DCC_ATTACK, 0 ); + } + } + + // check for godmode + if ( targ->flags & FL_GODMODE ) + return; + } + + // add to the attacker's hit counter + if( attacker->client && targ != attacker && targ->health > 0 + && targ->s.eType != ET_MISSILE + && targ->s.eType != ET_GENERAL ) + { + if( OnSameTeam( targ, attacker ) ) + attacker->client->ps.persistant[ PERS_HITS ]--; + else + attacker->client->ps.persistant[ PERS_HITS ]++; + } + + take = damage; + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if( client ) + { + if( attacker ) + client->ps.persistant[ PERS_ATTACKER ] = attacker->s.number; + else + client->ps.persistant[ PERS_ATTACKER ] = ENTITYNUM_WORLD; + + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + + if( dir ) + { + VectorCopy ( dir, client->damage_from ); + client->damage_fromWorld = qfalse; + } + else + { + VectorCopy ( targ->r.currentOrigin, client->damage_from ); + client->damage_fromWorld = qtrue; + } + + // set the last client who damaged the target + targ->client->lasthurt_client = attacker->s.number; + targ->client->lasthurt_mod = mod; + take = (int)( take * G_CalcDamageModifier( point, targ, attacker, + client->ps.stats[ STAT_CLASS ], + dflags ) + 0.5f ); + + //if boosted poison every attack + if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + { + if( (targ->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) && + mod != MOD_LEVEL2_ZAP && mod != MOD_POISON && + mod != MOD_LEVEL1_PCLOUD && mod != MOD_HSPAWN && + mod != MOD_ASPAWN && targ->client->poisonImmunityTime < level.time ) + { + targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED; + targ->client->lastPoisonTime = level.time; + targ->client->lastPoisonClient = attacker; + } + } + } + + if( take < 1 ) + take = 1; + + if( g_debugDamage.integer ) + { + G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number, + targ->health, take, asave ); + } + + // do the damage + if( take ) + { + targ->health = targ->health - take; + + if( targ->client ) + targ->client->ps.stats[ STAT_HEALTH ] = targ->health; + + targ->lastDamageTime = level.time; + targ->nextRegenTime = level.time + ALIEN_REGEN_DAMAGE_TIME; + + // add to the attackers "account" on the target + if( attacker->client && attacker != targ ) + targ->credits[ attacker->client->ps.clientNum ] += take; + + if( targ->health <= 0 ) + { + if( client ) + targ->flags |= FL_NO_KNOCKBACK; + + if( targ->health < -999 ) + targ->health = -999; + + targ->enemy = attacker; + targ->die( targ, inflictor, attacker, take, mod ); + return; + } + else if( targ->pain ) + { + //if(targ->s.eType==ET_BUILDABLE&&BG_Buildable(targ->s.modelindex,NULL)->cuboid) + // VectorCopy(point,targ->s.angles2); + targ->pain( targ, attacker, take ); + } + } +} + + +/* +============ +CanDamage + +Returns qtrue if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage( gentity_t *targ, vec3_t origin ) +{ + vec3_t dest; + trace_t tr; + vec3_t midpoint; + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin is 0,0,0 + VectorAdd( targ->r.absmin, targ->r.absmax, midpoint ); + VectorScale( midpoint, 0.5, midpoint ); + + VectorCopy( midpoint, dest ); + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 || tr.entityNum == targ->s.number ) + return qtrue; + + // this should probably check in the plane of projection, + // rather than in world coordinate, and also include Z + VectorCopy( midpoint, dest ); + dest[ 0 ] += 15.0; + dest[ 1 ] += 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 ) + return qtrue; + + VectorCopy( midpoint, dest ); + dest[ 0 ] += 15.0; + dest[ 1 ] -= 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 ) + return qtrue; + + VectorCopy( midpoint, dest ); + dest[ 0 ] -= 15.0; + dest[ 1 ] += 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 ) + return qtrue; + + VectorCopy( midpoint, dest ); + dest[ 0 ] -= 15.0; + dest[ 1 ] -= 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 ) + return qtrue; + + return qfalse; +} + +/* +============ +G_SelectiveRadiusDamage +============ +*/ +qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, + float radius, gentity_t *ignore, int mod, int team ) +{ + float points, dist; + gentity_t *ent; + int entityList[ MAX_GENTITIES ]; + int numListedEntities; + vec3_t mins, maxs; + vec3_t v; + vec3_t dir; + int i, e; + qboolean hitClient = qfalse; + + if( radius < 1 ) + radius = 1; + + for( i = 0; i < 3; i++ ) + { + mins[ i ] = origin[ i ] - radius; + maxs[ i ] = origin[ i ] + radius; + } + + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for( e = 0; e < numListedEntities; e++ ) + { + ent = &g_entities[ entityList[ e ] ]; + + if( ent == ignore ) + continue; + + if( !ent->takedamage ) + continue; + + if( ent->flags & FL_NOTARGET ) + continue; + + // find the distance from the edge of the bounding box + for( i = 0 ; i < 3 ; i++ ) + { + if( origin[ i ] < ent->r.absmin[ i ] ) + v[ i ] = ent->r.absmin[ i ] - origin[ i ]; + else if( origin[ i ] > ent->r.absmax[ i ] ) + v[ i ] = origin[ i ] - ent->r.absmax[ i ]; + else + v[ i ] = 0; + } + + dist = VectorLength( v ); + if( dist >= radius ) + continue; + + points = damage * ( 1.0 - dist / radius ); + + if( CanDamage( ent, origin ) && ent->client && + ent->client->ps.stats[ STAT_TEAM ] != team ) + { + VectorSubtract( ent->r.currentOrigin, origin, dir ); + // push the center of mass higher than the origin so players + // get knocked into the air more + dir[ 2 ] += 24; + hitClient = qtrue; + G_Damage( ent, NULL, attacker, dir, origin, + (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod ); + } + } + + return hitClient; +} + + +/* +============ +G_RadiusDamage +============ +*/ +qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, + float radius, gentity_t *ignore, int mod ) +{ + float points, dist; + gentity_t *ent; + int entityList[ MAX_GENTITIES ]; + int numListedEntities; + vec3_t mins, maxs; + vec3_t v; + vec3_t dir; + int i, e; + qboolean hitClient = qfalse; + + if( radius < 1 ) + radius = 1; + + for( i = 0; i < 3; i++ ) + { + mins[ i ] = origin[ i ] - radius; + maxs[ i ] = origin[ i ] + radius; + } + + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for( e = 0; e < numListedEntities; e++ ) + { + ent = &g_entities[ entityList[ e ] ]; + + if( ent == ignore ) + continue; + + if( !ent->takedamage ) + continue; + + // find the distance from the edge of the bounding box + for( i = 0; i < 3; i++ ) + { + if( origin[ i ] < ent->r.absmin[ i ] ) + v[ i ] = ent->r.absmin[ i ] - origin[ i ]; + else if( origin[ i ] > ent->r.absmax[ i ] ) + v[ i ] = origin[ i ] - ent->r.absmax[ i ]; + else + v[ i ] = 0; + } + + dist = VectorLength( v ); + if( dist >= radius ) + continue; + + points = damage * ( 1.0 - dist / radius ); + + if( CanDamage( ent, origin ) ) + { + VectorSubtract( ent->r.currentOrigin, origin, dir ); + // push the center of mass higher than the origin so players + // get knocked into the air more + dir[ 2 ] += 24; + hitClient = qtrue; + G_Damage( ent, NULL, attacker, dir, origin, + (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod ); + } + } + + return hitClient; +} + +/* +================ +G_LogDestruction + +Log deconstruct/destroy events +================ +*/ +void G_LogDestruction( gentity_t *self, gentity_t *actor, int mod ) +{ + buildFate_t fate; + + switch( mod ) + { + case MOD_DECONSTRUCT: + fate = BF_DECONSTRUCT; + break; + case MOD_REPLACE: + fate = BF_REPLACE; + break; + case MOD_NOCREEP: + fate = ( actor->client ) ? BF_UNPOWER : BF_AUTO; + break; + default: + if( actor->client ) + { + if( actor->client->pers.teamSelection == + BG_Buildable( self->s.modelindex, NULL )->team ) + { + fate = BF_TEAMKILL; + } + else + fate = BF_DESTROY; + } + else + fate = BF_AUTO; + break; + } + G_BuildLogAuto( actor, self, fate ); + + // don't log when marked structures are removed + if( mod == MOD_REPLACE ) + return; + + G_LogPrintf( S_COLOR_YELLOW "Deconstruct: %d %d %s %s: %s %s by %s\n", + actor - g_entities, + self - g_entities, + BG_Buildable( self->s.modelindex, NULL )->name, + modNames[ mod ], + G_CuboidName(self->s.modelindex,self->cuboidSize,qtrue), + mod == MOD_DECONSTRUCT ? "deconstructed" : "destroyed", + actor->client ? actor->client->pers.netname : "" ); + + // No-power deaths for humans come after some minutes and it's confusing + // when the messages appear attributed to the deconner. Just don't print them. + if( mod == MOD_NOCREEP && actor->client && + actor->client->pers.teamSelection == TEAM_HUMANS ) + return; + + if( actor->client && actor->client->pers.teamSelection == + BG_Buildable( self->s.modelindex, NULL )->team ) + { + G_TeamCommand( actor->client->ps.stats[ STAT_TEAM ], + va( "print \"%s ^3%s^7 by %s\n\"", + G_CuboidName(self->s.modelindex,self->cuboidSize,qfalse), + mod == MOD_DECONSTRUCT ? "DECONSTRUCTED" : "DESTROYED", + actor->client->pers.netname ) ); + } + +} diff --git a/src/game/g_combat.c.orig b/src/game/g_combat.c.orig new file mode 100644 index 0000000..f4e89ee --- /dev/null +++ b/src/game/g_combat.c.orig @@ -0,0 +1,1468 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +damageRegion_t g_damageRegions[ PCL_NUM_CLASSES ][ MAX_DAMAGE_REGIONS ]; +int g_numDamageRegions[ PCL_NUM_CLASSES ]; + +damageRegion_t g_armourRegions[ UP_NUM_UPGRADES ][ MAX_DAMAGE_REGIONS ]; +int g_numArmourRegions[ UP_NUM_UPGRADES ]; + +/* +============ +AddScore + +Adds score to the client +============ +*/ +void AddScore( gentity_t *ent, int score ) +{ + if( !ent->client ) + return; + + // make alien and human scores equivalent + if ( ent->client->pers.teamSelection == TEAM_ALIENS ) + { + score = rint( ((float)score) / 2.0f ); + } + + // scale values down to fit the scoreboard better + score = rint( ((float)score) / 50.0f ); + + ent->client->ps.persistant[ PERS_SCORE ] += score; + CalculateRanks( ); +} + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) +{ + + if ( attacker && attacker != self ) + self->client->ps.stats[ STAT_VIEWLOCK ] = attacker - g_entities; + else if( inflictor && inflictor != self ) + self->client->ps.stats[ STAT_VIEWLOCK ] = inflictor - g_entities; + else + self->client->ps.stats[ STAT_VIEWLOCK ] = self - g_entities; +} + +// these are just for logging, the client prints its own messages +char *modNames[ ] = +{ + "MOD_UNKNOWN", + "MOD_SHOTGUN", + "MOD_BLASTER", + "MOD_PAINSAW", + "MOD_MACHINEGUN", + "MOD_CHAINGUN", + "MOD_PRIFLE", + "MOD_MDRIVER", + "MOD_LASGUN", + "MOD_LCANNON", + "MOD_LCANNON_SPLASH", + "MOD_FLAMER", + "MOD_FLAMER_SPLASH", + "MOD_GRENADE", + "MOD_WATER", + "MOD_SLIME", + "MOD_LAVA", + "MOD_CRUSH", + "MOD_TELEFRAG", + "MOD_FALLING", + "MOD_SUICIDE", + "MOD_TARGET_LASER", + "MOD_TRIGGER_HURT", + + "MOD_ABUILDER_CLAW", + "MOD_LEVEL0_BITE", + "MOD_LEVEL1_CLAW", + "MOD_LEVEL1_PCLOUD", + "MOD_LEVEL3_CLAW", + "MOD_LEVEL3_POUNCE", + "MOD_LEVEL3_BOUNCEBALL", + "MOD_LEVEL2_CLAW", + "MOD_LEVEL2_ZAP", + "MOD_LEVEL4_CLAW", + "MOD_LEVEL4_TRAMPLE", + "MOD_LEVEL4_CRUSH", + + "MOD_SLOWBLOB", + "MOD_POISON", + "MOD_SWARM", + + "MOD_HSPAWN", + "MOD_TESLAGEN", + "MOD_MGTURRET", + "MOD_REACTOR", + + "MOD_ASPAWN", + "MOD_ATUBE", + "MOD_OVERMIND", + "MOD_DECONSTRUCT", + "MOD_REPLACE", + "MOD_NOCREEP" +}; + +/* +================== +G_RewardAttackers + +Function to distribute rewards to entities that killed this one. +Returns the total damage dealt. +================== +*/ +float G_RewardAttackers( gentity_t *self ) +{ + float value, totalDamage = 0; + int team, i, maxHealth = 0; + int alienCredits = 0, humanCredits = 0; + gentity_t *player; + + // Total up all the damage done by non-teammates + for( i = 0; i < level.maxclients; i++ ) + { + player = g_entities + i; + + if( !OnSameTeam( self, player ) || + self->buildableTeam != player->client->ps.stats[ STAT_TEAM ] ) + totalDamage += (float)self->credits[ i ]; + } + + if( totalDamage <= 0.0f ) + return 0.0f; + + // Only give credits for killing players and buildables + if( self->client ) + { + value = BG_GetValueOfPlayer( &self->client->ps ); + team = self->client->pers.teamSelection; + maxHealth = self->client->ps.stats[ STAT_MAX_HEALTH ]; + } + else if( self->s.eType == ET_BUILDABLE ) + { + value = BG_Buildable( self->s.modelindex, self->cuboidSize )->value; + + // only give partial credits for a buildable not yet completed + if( !self->spawned ) + { + value *= (float)( level.time - self->buildTime ) / + BG_Buildable( self->s.modelindex, self->cuboidSize )->buildTime; + } + + team = self->buildableTeam; + maxHealth = BG_Buildable( self->s.modelindex, self->cuboidSize )->health; + } + else + return totalDamage; + + // Give credits and empty the array + for( i = 0; i < level.maxclients; i++ ) + { + int stageValue = value * self->credits[ i ] / totalDamage; + player = g_entities + i; + + if( player->client->pers.teamSelection != team ) + { + if( totalDamage < maxHealth ) + stageValue *= totalDamage / maxHealth; + + if( !self->credits[ i ] || player->client->ps.stats[ STAT_TEAM ] == team ) + continue; + + AddScore( player, stageValue ); + + // killing buildables earns score, but not credits + if( self->s.eType != ET_BUILDABLE ) + { + G_AddCreditToClient( player->client, stageValue, qtrue ); + + // add to stage counters + if( player->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + alienCredits += stageValue; + else if( player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + humanCredits += stageValue; + } + } + self->credits[ i ] = 0; + } + + if( alienCredits ) + { + trap_Cvar_Set( "g_alienCredits", + va( "%d", g_alienCredits.integer + alienCredits ) ); + trap_Cvar_Update( &g_alienCredits ); + } + if( humanCredits ) + { + trap_Cvar_Set( "g_humanCredits", + va( "%d", g_humanCredits.integer + humanCredits ) ); + trap_Cvar_Update( &g_humanCredits ); + } + + return totalDamage; +} + +/* +================== +player_die +================== +*/ +void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) +{ + gentity_t *ent, *ent2; + int anim; + int killer; + int i; + char *killerName, *obit; + vec3_t dir; + + if( self->client->ps.pm_type == PM_DEAD ) + return; + + if( level.intermissiontime ) + return; + + self->client->ps.pm_type = PM_DEAD; + self->suicideTime = 0; + + if( attacker ) + { + killer = attacker->s.number; + + if( attacker->client ) + killerName = attacker->client->pers.netname; + else + killerName = ""; + } + else + { + killer = ENTITYNUM_WORLD; + killerName = ""; + } + + if( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) + // fall back on the number + obit = va( "%d", meansOfDeath ); + else + obit = modNames[ meansOfDeath ]; + + G_LogPrintf( "Die: %d %d %s: %s" S_COLOR_WHITE " killed %s\n", + killer, + self - g_entities, + obit, + killerName, + self->client->pers.netname ); + + // deactivate all upgrades + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + BG_DeactivateUpgrade( i, self->client->ps.stats ); + + // kill all player's buildables if they havent spawned yet + // this should eliminate build timer hacks for ever + dir[0] = dir[1] = 0.0f; + dir[2] = 1.0f; + + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent == self ) + continue; + + if( ent->spawned ) + continue; + + if( ent->builtBy != self->client->ps.clientNum ) + continue; + + G_Damage( ent, self, attacker, dir, dir, ent->health, 0, MOD_DECONSTRUCT ); + } + + // broadcast the death event to everyone + ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); + ent->s.eventParm = meansOfDeath; + ent->s.otherEntityNum = self->s.number; + ent->s.otherEntityNum2 = killer; + ent->r.svFlags = SVF_BROADCAST; // send to everyone + + self->enemy = attacker; + self->client->ps.persistant[ PERS_KILLED ]++; + + if( attacker && attacker->client ) + { + attacker->client->lastkilled_client = self->s.number; + + if( ( attacker == self || OnSameTeam( self, attacker ) ) && meansOfDeath != MOD_HSPAWN ) + { + //punish team kills and suicides + if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + G_AddCreditToClient( attacker->client, -ALIEN_TK_SUICIDE_PENALTY, qtrue ); + AddScore( attacker, -ALIEN_TK_SUICIDE_PENALTY ); + } + else if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + G_AddCreditToClient( attacker->client, -HUMAN_TK_SUICIDE_PENALTY, qtrue ); + AddScore( attacker, -HUMAN_TK_SUICIDE_PENALTY ); + } + } + } + else if( attacker->s.eType != ET_BUILDABLE ) + { + if( self->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + AddScore( self, -ALIEN_TK_SUICIDE_PENALTY ); + else if( self->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + AddScore( self, -HUMAN_TK_SUICIDE_PENALTY ); + } + + // give credits for killing this player + G_RewardAttackers( self ); + + ScoreboardMessage( self ); // show scores + + // send updated scores to any clients that are following this one, + // or they would get stale scoreboards + for( i = 0 ; i < level.maxclients ; i++ ) + { + gclient_t *client; + + client = &level.clients[ i ]; + if( client->pers.connected != CON_CONNECTED ) + continue; + + if( client->sess.spectatorState == SPECTATOR_NOT ) + continue; + + if( client->sess.spectatorClient == self->s.number ) + ScoreboardMessage( g_entities + i ); + } + + VectorCopy( self->s.origin, self->client->pers.lastDeathLocation ); + + self->takedamage = qfalse; // can still be gibbed + + self->s.weapon = WP_NONE; + self->r.contents = CONTENTS_CORPSE; + + self->s.angles[ PITCH ] = 0; + self->s.angles[ ROLL ] = 0; + self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ]; + LookAtKiller( self, inflictor, attacker ); + + VectorCopy( self->s.angles, self->client->ps.viewangles ); + + self->s.loopSound = 0; + + self->r.maxs[ 2 ] = -8; + + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + self->client->respawnTime = level.time + 1700; + + // clear misc + memset( self->client->ps.misc, 0, sizeof( self->client->ps.misc ) ); + + { + // normal death + static int i; + + if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + switch( i ) + { + case 0: + anim = BOTH_DEATH1; + break; + case 1: + anim = BOTH_DEATH2; + break; + case 2: + default: + anim = BOTH_DEATH3; + break; + } + } + else + { + switch( i ) + { + case 0: + anim = NSPA_DEATH1; + break; + case 1: + anim = NSPA_DEATH2; + break; + case 2: + default: + anim = NSPA_DEATH3; + break; + } + } + + self->client->ps.legsAnim = + ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + + if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + self->client->ps.torsoAnim = + ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + } + + // use own entityid if killed by non-client to prevent uint8_t overflow + G_AddEvent( self, EV_DEATH1 + i, + ( killer < MAX_CLIENTS ) ? killer : self - g_entities ); + + // globally cycle through the different death animations + i = ( i + 1 ) % 3; + } + + trap_LinkEntity( self ); +} + +/* +=============== +G_ParseDmgScript +=============== +*/ +static int G_ParseDmgScript( damageRegion_t *regions, char *buf ) +{ + char *token; + float angleSpan, heightSpan; + int count; + + for( count = 0; ; count++ ) + { + token = COM_Parse( &buf ); + if( !token[ 0 ] ) + break; + + if( strcmp( token, "{" ) ) + { + COM_ParseError( "Missing {" ); + break; + } + + if( count >= MAX_DAMAGE_REGIONS ) + { + COM_ParseError( "Max damage regions exceeded" ); + break; + } + + // defaults + regions[ count ].name[ 0 ] = '\0'; + regions[ count ].minHeight = 0.0f; + regions[ count ].maxHeight = 1.0f; + regions[ count ].minAngle = 0.0f; + regions[ count ].maxAngle = 360.0f; + regions[ count ].modifier = 1.0f; + regions[ count ].crouch = qfalse; + + while( 1 ) + { + token = COM_ParseExt( &buf, qtrue ); + + if( !token[ 0 ] ) + { + COM_ParseError( "Unexpected end of file" ); + break; + } + + if( !Q_stricmp( token, "}" ) ) + { + break; + } + else if( !strcmp( token, "name" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( token[ 0 ] ) + Q_strncpyz( regions[ count ].name, token, + sizeof( regions[ count ].name ) ); + } + else if( !strcmp( token, "minHeight" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "0" ); + regions[ count ].minHeight = atof( token ); + } + else if( !strcmp( token, "maxHeight" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "100" ); + regions[ count ].maxHeight = atof( token ); + } + else if( !strcmp( token, "minAngle" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "0" ); + regions[ count ].minAngle = atoi( token ); + } + else if( !strcmp( token, "maxAngle" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "360" ); + regions[ count ].maxAngle = atoi( token ); + } + else if( !strcmp( token, "modifier" ) ) + { + token = COM_ParseExt( &buf, qfalse ); + if( !token[ 0 ] ) + strcpy( token, "1.0" ); + regions[ count ].modifier = atof( token ); + } + else if( !strcmp( token, "crouch" ) ) + { + regions[ count ].crouch = qtrue; + } + else + { + COM_ParseWarning("Unknown token \"%s\"", token); + } + } + + // Angle portion covered + angleSpan = regions[ count ].maxAngle - regions[ count ].minAngle; + if( angleSpan < 0.0f ) + angleSpan += 360.0f; + angleSpan /= 360.0f; + + // Height portion covered + heightSpan = regions[ count ].maxHeight - regions[ count ].minHeight; + if( heightSpan < 0.0f ) + heightSpan = -heightSpan; + if( heightSpan > 1.0f ) + heightSpan = 1.0f; + + regions[ count ].area = angleSpan * heightSpan; + if( !regions[ count ].area ) + regions[ count ].area = 0.00001f; + } + + return count; +} + +/* +============ +GetRegionDamageModifier +============ +*/ +static float GetRegionDamageModifier( gentity_t *targ, int class, int piece ) +{ + damageRegion_t *regions, *overlap; + float modifier = 0.0f, areaSum = 0.0f; + int j, i; + qboolean crouch; + + crouch = targ->client->ps.pm_flags & PMF_DUCKED; + overlap = &g_damageRegions[ class ][ piece ]; + + if( g_debugDamage.integer > 2 ) + G_Printf( "GetRegionDamageModifier():\n" + ". bodyRegion = [%d %d %f %f] (%s)\n" + ". modifier = %f\n", + overlap->minAngle, overlap->maxAngle, + overlap->minHeight, overlap->maxHeight, + overlap->name, overlap->modifier ); + + // Find the armour layer modifier, assuming that none of the armour regions + // overlap and that any areas that are not covered have a modifier of 1.0 + for( j = UP_NONE + 1; j < UP_NUM_UPGRADES; j++ ) + { + if( !BG_InventoryContainsUpgrade( j, targ->client->ps.stats ) || + !g_numArmourRegions[ j ] ) + continue; + regions = g_armourRegions[ j ]; + + for( i = 0; i < g_numArmourRegions[ j ]; i++ ) + { + float overlapMaxA, regionMinA, regionMaxA, angleSpan, heightSpan, area; + + if( regions[ i ].crouch != crouch ) + continue; + + // Convert overlap angle to 0 to max + overlapMaxA = overlap->maxAngle - overlap->minAngle; + if( overlapMaxA < 0.0f ) + overlapMaxA += 360.0f; + + // Convert region angles to match overlap + regionMinA = regions[ i ].minAngle - overlap->minAngle; + if( regionMinA < 0.0f ) + regionMinA += 360.0f; + regionMaxA = regions[ i ].maxAngle - overlap->minAngle; + if( regionMaxA < 0.0f ) + regionMaxA += 360.0f; + + // Overlapping Angle portion + if( regionMinA <= regionMaxA ) + { + angleSpan = 0.0f; + if( regionMinA < overlapMaxA ) + { + if( regionMaxA > overlapMaxA ) + regionMaxA = overlapMaxA; + angleSpan = regionMaxA - regionMinA; + } + } + else + { + if( regionMaxA > overlapMaxA ) + regionMaxA = overlapMaxA; + angleSpan = regionMaxA; + if( regionMinA < overlapMaxA ) + angleSpan += overlapMaxA - regionMinA; + } + angleSpan /= 360.0f; + + // Overlapping height portion + heightSpan = MIN( overlap->maxHeight, regions[ i ].maxHeight ) - + MAX( overlap->minHeight, regions[ i ].minHeight ); + if( heightSpan < 0.0f ) + heightSpan = 0.0f; + if( heightSpan > 1.0f ) + heightSpan = 1.0f; + + if( g_debugDamage.integer > 2 ) + G_Printf( ". armourRegion = [%d %d %f %f] (%s)\n" + ". . modifier = %f\n" + ". . angleSpan = %f\n" + ". . heightSpan = %f\n", + regions[ i ].minAngle, regions[ i ].maxAngle, + regions[ i ].minHeight, regions[ i ].maxHeight, + regions[ i ].name, regions[ i ].modifier, + angleSpan, heightSpan ); + + areaSum += area = angleSpan * heightSpan; + modifier += regions[ i ].modifier * area; + } + } + + if( g_debugDamage.integer > 2 ) + G_Printf( ". areaSum = %f\n" + ". armourModifier = %f\n", areaSum, modifier ); + + return overlap->modifier * ( overlap->area + modifier - areaSum ); +} + +/* +============ +GetNonLocDamageModifier +============ +*/ +static float GetNonLocDamageModifier( gentity_t *targ, int class ) +{ + float modifier = 0.0f, area = 0.0f, scale = 0.0f; + int i; + qboolean crouch; + + // For every body region, use stretch-armor formula to apply armour modifier + // for any overlapping area that armour shares with the body region + crouch = targ->client->ps.pm_flags & PMF_DUCKED; + for( i = 0; i < g_numDamageRegions[ class ]; i++ ) + { + damageRegion_t *region; + + region = &g_damageRegions[ class ][ i ]; + + if( region->crouch != crouch ) + continue; + + modifier += GetRegionDamageModifier( targ, class, i ); + + scale += region->modifier * region->area; + area += region->area; + + } + + modifier = !scale ? 1.0f : 1.0f + ( modifier / scale - 1.0f ) * area; + + if( g_debugDamage.integer > 1 ) + G_Printf( "GetNonLocDamageModifier() modifier:%f, area:%f, scale:%f\n", + modifier, area, scale ); + + return modifier; +} + +/* +============ +GetPointDamageModifier + +Returns the damage region given an angle and a height proportion +============ +*/ +static float GetPointDamageModifier( gentity_t *targ, damageRegion_t *regions, + int len, float angle, float height ) +{ + float modifier = 1.0f; + int i; + + for( i = 0; i < len; i++ ) + { + if( regions[ i ].crouch != ( targ->client->ps.pm_flags & PMF_DUCKED ) ) + continue; + + // Angle must be within range + if( ( regions[ i ].minAngle <= regions[ i ].maxAngle && + ( angle < regions[ i ].minAngle || + angle > regions[ i ].maxAngle ) ) || + ( regions[ i ].minAngle > regions[ i ].maxAngle && + angle > regions[ i ].maxAngle && angle < regions[ i ].minAngle ) ) + continue; + + // Height must be within range + if( height < regions[ i ].minHeight || height > regions[ i ].maxHeight ) + continue; + + modifier *= regions[ i ].modifier; + } + + if( g_debugDamage.integer ) + G_Printf( "GetDamageRegionModifier(angle = %f, height = %f): %f\n", + angle, height, modifier ); + + return modifier; +} + +/* +============ +G_CalcDamageModifier +============ +*/ +static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *attacker, int class, int dflags ) +{ + vec3_t targOrigin, bulletPath, bulletAngle, pMINUSfloor, floor, normal; + float clientHeight, hitRelative, hitRatio, modifier; + int hitRotation, i; + + if( point == NULL ) + return 1.0f; + + // Don't need to calculate angles and height for non-locational damage + if( dflags & DAMAGE_NO_LOCDAMAGE ) + return GetNonLocDamageModifier( targ, class ); + + // Get the point location relative to the floor under the target + if( g_unlagged.integer && targ->client && targ->client->unlaggedCalc.used ) + VectorCopy( targ->client->unlaggedCalc.origin, targOrigin ); + else + VectorCopy( targ->r.currentOrigin, targOrigin ); + + BG_GetClientNormal( &targ->client->ps, normal ); + VectorMA( targOrigin, targ->r.mins[ 2 ], normal, floor ); + VectorSubtract( point, floor, pMINUSfloor ); + + // Get the proportion of the target height where the hit landed + clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ]; + + if( !clientHeight ) + clientHeight = 1.0f; + + hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal ); + + if( hitRelative < 0.0f ) + hitRelative = 0.0f; + + if( hitRelative > clientHeight ) + hitRelative = clientHeight; + + hitRatio = hitRelative / clientHeight; + + // Get the yaw of the attack relative to the target's view yaw + VectorSubtract( point, targOrigin, bulletPath ); + vectoangles( bulletPath, bulletAngle ); + + hitRotation = AngleNormalize360( targ->client->ps.viewangles[ YAW ] - + bulletAngle[ YAW ] ); + + // Get modifiers from the target's damage regions + modifier = GetPointDamageModifier( targ, g_damageRegions[ class ], + g_numDamageRegions[ class ], + hitRotation, hitRatio ); + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) ) + { + modifier *= GetPointDamageModifier( targ, g_armourRegions[ i ], + g_numArmourRegions[ i ], + hitRotation, hitRatio ); + } + } + + return modifier; +} + + +/* +============ +G_InitDamageLocations +============ +*/ +void G_InitDamageLocations( void ) +{ + char *modelName; + char filename[ MAX_QPATH ]; + int i; + int len; + fileHandle_t fileHandle; + char buffer[ MAX_DAMAGE_REGION_TEXT ]; + + for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) + { + modelName = BG_ClassConfig( i )->modelName; + Com_sprintf( filename, sizeof( filename ), + "models/players/%s/locdamage.cfg", modelName ); + + len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); + if ( !fileHandle ) + { + G_Printf( S_COLOR_RED "file not found: %s\n", filename ); + continue; + } + + if( len >= MAX_DAMAGE_REGION_TEXT ) + { + G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", + filename, len, MAX_DAMAGE_REGION_TEXT ); + trap_FS_FCloseFile( fileHandle ); + continue; + } + + COM_BeginParseSession( filename ); + + trap_FS_Read( buffer, len, fileHandle ); + buffer[len] = 0; + trap_FS_FCloseFile( fileHandle ); + + g_numDamageRegions[ i ] = G_ParseDmgScript( g_damageRegions[ i ], buffer ); + } + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + modelName = BG_Upgrade( i )->name; + Com_sprintf( filename, sizeof( filename ), "armour/%s.armour", modelName ); + + len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); + + //no file - no parsage + if ( !fileHandle ) + continue; + + if( len >= MAX_DAMAGE_REGION_TEXT ) + { + G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", + filename, len, MAX_DAMAGE_REGION_TEXT ); + trap_FS_FCloseFile( fileHandle ); + continue; + } + + COM_BeginParseSession( filename ); + + trap_FS_Read( buffer, len, fileHandle ); + buffer[len] = 0; + trap_FS_FCloseFile( fileHandle ); + + g_numArmourRegions[ i ] = G_ParseDmgScript( g_armourRegions[ i ], buffer ); + } +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +inflictor, attacker, dir, and point can be NULL for environmental effects + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ + +// team is the team that is immune to this damage +void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, + vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team ) +{ + if( targ->client && ( team != targ->client->ps.stats[ STAT_TEAM ] ) ) + G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod ); +} + +void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, + vec3_t dir, vec3_t point, int damage, int dflags, int mod ) +{ + gclient_t *client; + int take; + int asave = 0; + int knockback; + + // Can't deal damage sometimes + if( !targ->takedamage || targ->health <= 0 || level.intermissionQueued ) + return; + + if( !inflictor ) + inflictor = &g_entities[ ENTITYNUM_WORLD ]; + + if( !attacker ) + attacker = &g_entities[ ENTITYNUM_WORLD ]; + + // shootable doors / buttons don't actually have any health + if( targ->s.eType == ET_MOVER ) + { + if( targ->use && ( targ->moverState == MOVER_POS1 || + targ->moverState == ROTATOR_POS1 ) ) + targ->use( targ, inflictor, attacker ); + + return; + } + + client = targ->client; + if( client && client->noclip ) + return; + + if( !dir ) + dflags |= DAMAGE_NO_KNOCKBACK; + else + VectorNormalize( dir ); + + knockback = damage; + + if( inflictor->s.weapon != WP_NONE ) + { + knockback = (int)( (float)knockback * + BG_Weapon( inflictor->s.weapon )->knockbackScale ); + } + + if( targ->client ) + { + knockback = (int)( (float)knockback * + BG_Class( targ->client->ps.stats[ STAT_CLASS ] )->knockbackScale ); + } + + // Too much knockback from falling really far makes you "bounce" and + // looks silly. However, none at all also looks bad. Cap it. + if( mod == MOD_FALLING && knockback > 50 ) + knockback = 50; + + if( knockback > 200 ) + knockback = 200; + + if( targ->flags & FL_NO_KNOCKBACK ) + knockback = 0; + + if( dflags & DAMAGE_NO_KNOCKBACK ) + knockback = 0; + + // figure momentum add, even if the damage won't be taken + if( knockback && targ->client ) + { + vec3_t kvel; + float mass; + + mass = 200; + + VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel ); + VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); + + // set the timer so that the other client can't cancel + // out the movement immediately + if( !targ->client->ps.pm_time ) + { + int t; + + t = knockback * 2; + if( t < 50 ) + t = 50; + + if( t > 200 ) + t = 200; + + targ->client->ps.pm_time = t; + targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + } + + // don't do friendly fire on movement attacks + if( ( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || + mod == MOD_LEVEL4_CRUSH ) && + targ->s.eType == ET_BUILDABLE && targ->buildableTeam == TEAM_ALIENS ) + { + return; + } + + // check for completely getting out of the damage + if( !( dflags & DAMAGE_NO_PROTECTION ) ) + { + + // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target + // if the attacker was on the same team + if( targ != attacker && OnSameTeam( targ, attacker ) ) + { + // don't do friendly fire on movement attacks + if( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || + mod == MOD_LEVEL4_CRUSH ) + return; + + // if dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage + if( g_dretchPunt.integer && + targ->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0 ) + { + vec3_t dir, push; + + VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, dir ); + VectorNormalizeFast( dir ); + VectorScale( dir, ( damage * 10.0f ), push ); + push[2] = 64.0f; + VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity ); + return; + } + + // check if friendly fire has been disabled + if( !g_friendlyFire.integer ) + { + return; + } + } + + if( targ->s.eType == ET_BUILDABLE && attacker->client && + mod != MOD_DECONSTRUCT && mod != MOD_SUICIDE && + mod != MOD_REPLACE && mod != MOD_NOCREEP ) + { + if( targ->buildableTeam == attacker->client->pers.teamSelection && + !g_friendlyBuildableFire.integer ) + { + return; + } + + // base is under attack warning if DCC'd + if( targ->buildableTeam == TEAM_HUMANS && G_FindDCC( targ ) && + level.time > level.humanBaseAttackTimer ) + { + level.humanBaseAttackTimer = level.time + DC_ATTACK_PERIOD; + G_BroadcastEvent( EV_DCC_ATTACK, 0 ); + } + } + + // check for godmode + if ( targ->flags & FL_GODMODE ) + return; + } + + // add to the attacker's hit counter + if( attacker->client && targ != attacker && targ->health > 0 + && targ->s.eType != ET_MISSILE + && targ->s.eType != ET_GENERAL ) + { + if( OnSameTeam( targ, attacker ) ) + attacker->client->ps.persistant[ PERS_HITS ]--; + else + attacker->client->ps.persistant[ PERS_HITS ]++; + } + + take = damage; + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if( client ) + { + if( attacker ) + client->ps.persistant[ PERS_ATTACKER ] = attacker->s.number; + else + client->ps.persistant[ PERS_ATTACKER ] = ENTITYNUM_WORLD; + + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + + if( dir ) + { + VectorCopy ( dir, client->damage_from ); + client->damage_fromWorld = qfalse; + } + else + { + VectorCopy ( targ->r.currentOrigin, client->damage_from ); + client->damage_fromWorld = qtrue; + } + + // set the last client who damaged the target + targ->client->lasthurt_client = attacker->s.number; + targ->client->lasthurt_mod = mod; + take = (int)( take * G_CalcDamageModifier( point, targ, attacker, + client->ps.stats[ STAT_CLASS ], + dflags ) + 0.5f ); + + //if boosted poison every attack + if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) + { + if( (targ->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) && + mod != MOD_LEVEL2_ZAP && mod != MOD_POISON && + mod != MOD_LEVEL1_PCLOUD && mod != MOD_HSPAWN && + mod != MOD_ASPAWN && targ->client->poisonImmunityTime < level.time ) + { + targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED; + targ->client->lastPoisonTime = level.time; + targ->client->lastPoisonClient = attacker; + } + } + } + + if( take < 1 ) + take = 1; + + if( g_debugDamage.integer ) + { + G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number, + targ->health, take, asave ); + } + + // do the damage + if( take ) + { + targ->health = targ->health - take; + + if( targ->client ) + targ->client->ps.stats[ STAT_HEALTH ] = targ->health; + + targ->lastDamageTime = level.time; + targ->nextRegenTime = level.time + ALIEN_REGEN_DAMAGE_TIME; + + // add to the attackers "account" on the target + if( attacker->client && attacker != targ ) + targ->credits[ attacker->client->ps.clientNum ] += take; + + if( targ->health <= 0 ) + { + if( client ) + targ->flags |= FL_NO_KNOCKBACK; + + if( targ->health < -999 ) + targ->health = -999; + + targ->enemy = attacker; + targ->die( targ, inflictor, attacker, take, mod ); + return; + } + else if( targ->pain ) + { + //if(targ->s.eType==ET_BUILDABLE&&BG_Buildable(targ->s.modelindex,NULL)->cuboid) + // VectorCopy(point,targ->s.angles2); + targ->pain( targ, attacker, take ); + } + } +} + + +/* +============ +CanDamage + +Returns qtrue if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage( gentity_t *targ, vec3_t origin ) +{ + vec3_t dest; + trace_t tr; + vec3_t midpoint; + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin is 0,0,0 + VectorAdd( targ->r.absmin, targ->r.absmax, midpoint ); + VectorScale( midpoint, 0.5, midpoint ); + + VectorCopy( midpoint, dest ); + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 || tr.entityNum == targ->s.number ) + return qtrue; + + // this should probably check in the plane of projection, + // rather than in world coordinate, and also include Z + VectorCopy( midpoint, dest ); + dest[ 0 ] += 15.0; + dest[ 1 ] += 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 ) + return qtrue; + + VectorCopy( midpoint, dest ); + dest[ 0 ] += 15.0; + dest[ 1 ] -= 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 ) + return qtrue; + + VectorCopy( midpoint, dest ); + dest[ 0 ] -= 15.0; + dest[ 1 ] += 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 ) + return qtrue; + + VectorCopy( midpoint, dest ); + dest[ 0 ] -= 15.0; + dest[ 1 ] -= 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if( tr.fraction == 1.0 ) + return qtrue; + + return qfalse; +} + +/* +============ +G_SelectiveRadiusDamage +============ +*/ +qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, + float radius, gentity_t *ignore, int mod, int team ) +{ + float points, dist; + gentity_t *ent; + int entityList[ MAX_GENTITIES ]; + int numListedEntities; + vec3_t mins, maxs; + vec3_t v; + vec3_t dir; + int i, e; + qboolean hitClient = qfalse; + + if( radius < 1 ) + radius = 1; + + for( i = 0; i < 3; i++ ) + { + mins[ i ] = origin[ i ] - radius; + maxs[ i ] = origin[ i ] + radius; + } + + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for( e = 0; e < numListedEntities; e++ ) + { + ent = &g_entities[ entityList[ e ] ]; + + if( ent == ignore ) + continue; + + if( !ent->takedamage ) + continue; + + if( ent->flags & FL_NOTARGET ) + continue; + + // find the distance from the edge of the bounding box + for( i = 0 ; i < 3 ; i++ ) + { + if( origin[ i ] < ent->r.absmin[ i ] ) + v[ i ] = ent->r.absmin[ i ] - origin[ i ]; + else if( origin[ i ] > ent->r.absmax[ i ] ) + v[ i ] = origin[ i ] - ent->r.absmax[ i ]; + else + v[ i ] = 0; + } + + dist = VectorLength( v ); + if( dist >= radius ) + continue; + + points = damage * ( 1.0 - dist / radius ); + + if( CanDamage( ent, origin ) && ent->client && + ent->client->ps.stats[ STAT_TEAM ] != team ) + { + VectorSubtract( ent->r.currentOrigin, origin, dir ); + // push the center of mass higher than the origin so players + // get knocked into the air more + dir[ 2 ] += 24; + hitClient = qtrue; + G_Damage( ent, NULL, attacker, dir, origin, + (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod ); + } + } + + return hitClient; +} + + +/* +============ +G_RadiusDamage +============ +*/ +qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, + float radius, gentity_t *ignore, int mod ) +{ + float points, dist; + gentity_t *ent; + int entityList[ MAX_GENTITIES ]; + int numListedEntities; + vec3_t mins, maxs; + vec3_t v; + vec3_t dir; + int i, e; + qboolean hitClient = qfalse; + + if( radius < 1 ) + radius = 1; + + for( i = 0; i < 3; i++ ) + { + mins[ i ] = origin[ i ] - radius; + maxs[ i ] = origin[ i ] + radius; + } + + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for( e = 0; e < numListedEntities; e++ ) + { + ent = &g_entities[ entityList[ e ] ]; + + if( ent == ignore ) + continue; + + if( !ent->takedamage ) + continue; + + // find the distance from the edge of the bounding box + for( i = 0; i < 3; i++ ) + { + if( origin[ i ] < ent->r.absmin[ i ] ) + v[ i ] = ent->r.absmin[ i ] - origin[ i ]; + else if( origin[ i ] > ent->r.absmax[ i ] ) + v[ i ] = origin[ i ] - ent->r.absmax[ i ]; + else + v[ i ] = 0; + } + + dist = VectorLength( v ); + if( dist >= radius ) + continue; + + points = damage * ( 1.0 - dist / radius ); + + if( CanDamage( ent, origin ) ) + { + VectorSubtract( ent->r.currentOrigin, origin, dir ); + // push the center of mass higher than the origin so players + // get knocked into the air more + dir[ 2 ] += 24; + hitClient = qtrue; + G_Damage( ent, NULL, attacker, dir, origin, + (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod ); + } + } + + return hitClient; +} + +/* +================ +G_LogDestruction + +Log deconstruct/destroy events +================ +*/ +void G_LogDestruction( gentity_t *self, gentity_t *actor, int mod ) +{ + buildFate_t fate; + + switch( mod ) + { + case MOD_DECONSTRUCT: + fate = BF_DECONSTRUCT; + break; + case MOD_REPLACE: + fate = BF_REPLACE; + break; + case MOD_NOCREEP: + fate = ( actor->client ) ? BF_UNPOWER : BF_AUTO; + break; + default: + if( actor->client ) + { + if( actor->client->pers.teamSelection == + BG_Buildable( self->s.modelindex, NULL )->team ) + { + fate = BF_TEAMKILL; + } + else + fate = BF_DESTROY; + } + else + fate = BF_AUTO; + break; + } + G_BuildLogAuto( actor, self, fate ); + + // don't log when marked structures are removed + if( mod == MOD_REPLACE ) + return; + + G_LogPrintf( S_COLOR_YELLOW "Deconstruct: %d %d %s %s: %s %s by %s\n", + actor - g_entities, + self - g_entities, + BG_Buildable( self->s.modelindex, NULL )->name, + modNames[ mod ], + G_CuboidName(self->s.modelindex,self->cuboidSize,qtrue), + mod == MOD_DECONSTRUCT ? "deconstructed" : "destroyed", + actor->client ? actor->client->pers.netname : "" ); + + // No-power deaths for humans come after some minutes and it's confusing + // when the messages appear attributed to the deconner. Just don't print them. + if( mod == MOD_NOCREEP && actor->client && + actor->client->pers.teamSelection == TEAM_HUMANS ) + return; + + if( actor->client && actor->client->pers.teamSelection == + BG_Buildable( self->s.modelindex, NULL )->team ) + { + G_TeamCommand( actor->client->ps.stats[ STAT_TEAM ], + va( "print \"%s ^3%s^7 by %s\n\"", + G_CuboidName(self->s.modelindex,self->cuboidSize,qfalse), + mod == MOD_DECONSTRUCT ? "DECONSTRUCTED" : "DESTROYED", + actor->client->pers.netname ) ); + } + +} diff --git a/src/game/g_local.h b/src/game/g_local.h new file mode 100644 index 0000000..a8c78b0 --- /dev/null +++ b/src/game/g_local.h @@ -0,0 +1,1270 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// g_local.h -- local definitions for game module + +#include "../qcommon/q_shared.h" +#include "bg_public.h" +#include "g_public.h" + +typedef struct gentity_s gentity_t; +typedef struct gclient_s gclient_t; + +#include "g_admin.h" + +//================================================================== + +#define INFINITE 1000000 + +#define FRAMETIME 100 // msec +#define CARNAGE_REWARD_TIME 3000 +#define REWARD_SPRITE_TIME 2000 + +#define INTERMISSION_DELAY_TIME 1000 +#define SP_INTERMISSION_DELAY_TIME 5000 + +// gentity->flags +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_DROPPED_ITEM 0x00001000 +#define FL_NO_BOTS 0x00002000 // spawn point not for bot use +#define FL_NO_HUMANS 0x00004000 // spawn point just for bots +#define FL_FORCE_GESTURE 0x00008000 // spawn point just for bots + +// movers are things like doors, plats, buttons, etc +typedef enum +{ + MOVER_POS1, + MOVER_POS2, + MOVER_1TO2, + MOVER_2TO1, + + ROTATOR_POS1, + ROTATOR_POS2, + ROTATOR_1TO2, + ROTATOR_2TO1, + + MODEL_POS1, + MODEL_POS2, + MODEL_1TO2, + MODEL_2TO1 +} moverState_t; + +#define SP_PODIUM_MODEL "models/mapobjects/podium/podium4.md3" + +//============================================================================ + +struct gentity_s +{ + entityState_t s; // communicated by server to clients + entityShared_t r; // shared by both the server system and game + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + //================================ + + struct gclient_s *client; // NULL if not a client + + qboolean inuse; + + char *classname; // set in QuakeEd + int spawnflags; // set in QuakeEd + + qboolean neverFree; // if true, FreeEntity will only unlink + // bodyque uses this + + int flags; // FL_* variables + + char *model; + char *model2; + int freetime; // level.time when the object was freed + + int eventTime; // events will be cleared EVENT_VALID_MSEC after set + qboolean freeAfterEvent; + qboolean unlinkAfterEvent; + + qboolean physicsObject; // if true, it can be pushed by movers and fall off edges + // all game items are physicsObjects, + float physicsBounce; // 1.0 = continuous bounce, 0.0 = no bounce + int clipmask; // brushes with this content value will be collided against + // when moving. items and corpses do not collide against + // players, for instance + + // movers + moverState_t moverState; + int soundPos1; + int sound1to2; + int sound2to1; + int soundPos2; + int soundLoop; + gentity_t *parent; + gentity_t *nextTrain; + gentity_t *prevTrain; + vec3_t pos1, pos2; + float rotatorAngle; + gentity_t *clipBrush; // clipping brush for model doors + + char *message; + + int timestamp; // body queue sinking, etc + + float angle; // set in editor, -1 = up, -2 = down + char *target; + char *targetname; + char *team; + char *targetShaderName; + char *targetShaderNewName; + gentity_t *target_ent; + + float speed; + float lastSpeed; // used by trains that have been restarted + vec3_t movedir; + + // acceleration evaluation + qboolean evaluateAcceleration; + vec3_t oldVelocity; + vec3_t acceleration; + vec3_t oldAccel; + vec3_t jerk; + + int nextthink; + void (*think)( gentity_t *self ); + void (*reached)( gentity_t *self ); // movers call this when hitting endpoint + void (*blocked)( gentity_t *self, gentity_t *other ); + void (*touch)( gentity_t *self, gentity_t *other, trace_t *trace ); + void (*use)( gentity_t *self, gentity_t *other, gentity_t *activator ); + void (*pain)( gentity_t *self, gentity_t *attacker, int damage ); + void (*die)( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); + + int pain_debounce_time; + int fly_sound_debounce_time; // wind tunnel + int last_move_time; + + int health; + int healthLeft; // (used for buildings being built) health remaining for the construction to end + int lastHealth; // currently only used for overmind + + qboolean takedamage; + + int damage; + int splashDamage; // quad will increase this without increasing radius + int splashRadius; + int methodOfDeath; + int splashMethodOfDeath; + + int count; + + gentity_t *chain; + gentity_t *enemy; + gentity_t *activator; + gentity_t *teamchain; // next entity in team + gentity_t *teammaster; // master of the team + + int watertype; + int waterlevel; + + int noise_index; + + // timing variables + float wait; + float random; + + team_t stageTeam; + stage_t stageStage; + + team_t buildableTeam; // buildable item team + gentity_t *parentNode; // for creep and defence/spawn dependencies + qboolean active; // for power repeater, but could be useful elsewhere + qboolean locked; // used for turret tracking + qboolean powered; // for human buildables + int builtBy; // clientNum of person that built this + int dcc; // number of controlling dccs + qboolean spawned; // whether or not this buildable has finished spawning + int shrunkTime; // time when a barricade shrunk or zero + int buildTime; // when this buildable was built + int animTime; // last animation change + int time1000; // timer evaluated every second + qboolean deconstruct; // deconstruct if no BP left + int deconstructTime; // time at which structure marked + int overmindAttackTimer; + int overmindDyingTimer; + int overmindSpawnsTimer; + int nextPhysicsTime; // buildables don't need to check what they're sitting on + // every single frame.. so only do it periodically + int clientSpawnTime; // the time until this spawn can spawn a client + + int credits[ MAX_CLIENTS ]; // human credits for each client + int killedBy; // clientNum of killer + + gentity_t *targeted; // true if the player is currently a valid target of a turret + vec3_t turretAim; // aim vector for turrets + vec3_t turretAimRate; // track turn speed for norfenturrets + int turretSpinupTime; // spinup delay for norfenturrets + + vec4_t animation; // animated map objects + + qboolean nonSegModel; // this entity uses a nonsegmented player model + + buildable_t bTriggers[ BA_NUM_BUILDABLES ]; // which buildables are triggers + class_t cTriggers[ PCL_NUM_CLASSES ]; // which classes are triggers + weapon_t wTriggers[ WP_NUM_WEAPONS ]; // which weapons are triggers + upgrade_t uTriggers[ UP_NUM_UPGRADES ]; // which upgrades are triggers + + int triggerGravity; // gravity for this trigger + + int suicideTime; // when the client will suicide + + int lastDamageTime; + int nextRegenTime; + + qboolean ownerClear; // used for missle tracking + + qboolean pointAgainstWorld; // don't use the bbox for map collisions + + int buildPointZone; // index for zone + int usesBuildPointZone; // does it use a zone? + + vec3_t cuboidSize; +}; + +typedef enum +{ + CON_DISCONNECTED, + CON_CONNECTING, + CON_CONNECTED +} clientConnected_t; + +// the auto following clients don't follow a specific client +// number, but instead follow the first two active players +#define FOLLOW_ACTIVE1 -1 +#define FOLLOW_ACTIVE2 -2 + +// client data that stays across multiple levels or tournament restarts +// this is achieved by writing all the data to cvar strings at game shutdown +// time and reading them back at connection time. Anything added here +// MUST be dealt with in G_InitSessionData() / G_ReadSessionData() / G_WriteSessionData() +typedef struct +{ + int spectatorTime; // for determining next-in-line to play + spectatorState_t spectatorState; + int spectatorClient; // for chasecam and follow mode + team_t restartTeam; //for !restart keepteams and !restart switchteams + clientList_t ignoreList; +} clientSession_t; + +// namelog +#define MAX_NAMELOG_NAMES 5 +#define MAX_NAMELOG_ADDRS 5 +typedef struct namelog_s +{ + struct namelog_s *next; + char name[ MAX_NAMELOG_NAMES ][ MAX_NAME_LENGTH ]; + addr_t ip[ MAX_NAMELOG_ADDRS ]; + char guid[ 33 ]; + int slot; + qboolean banned; + + int nameOffset; + int nameChangeTime; + int nameChanges; + int voteCount; + + qboolean muted; + qboolean denyBuild; + + int score; + int credits; + team_t team; + + int id; +} namelog_t; + +// client data that stays across multiple respawns, but is cleared +// on each level change or team change at ClientBegin() +typedef struct +{ + clientConnected_t connected; + usercmd_t cmd; // we would lose angles if not persistant + qboolean localClient; // true if "ip" info key is "localhost" + qboolean stickySpec; // don't stop spectating a player after they get killed + qboolean pmoveFixed; // + char netname[ MAX_NAME_LENGTH ]; + int enterTime; // level.time the client entered the game + int location; // player locations + qboolean teamInfo; // send team overlay updates? + float flySpeed; // for spectator/noclip moves + qboolean disableBlueprintErrors; // should the buildable blueprint never be hidden from the players? + + class_t classSelection; // player class (copied to ent->client->ps.stats[ STAT_CLASS ] once spawned) + float evolveHealthFraction; + weapon_t humanItemSelection; // humans have a starting item + team_t teamSelection; // player team (copied to ps.stats[ STAT_TEAM ]) + + int teamChangeTime; // level.time of last team change + namelog_t *namelog; + g_admin_admin_t *admin; + + int aliveSeconds; // time player has been alive in seconds + qboolean hasHealed; // has healed a player (basi regen aura) in the last 10sec (for score use) + + // used to save persistant[] values while in SPECTATOR_FOLLOW mode + int credit; + + int voted; + int vote; + + // flood protection + int floodDemerits; + int floodTime; + + vec3_t lastDeathLocation; + char guid[ 33 ]; + addr_t ip; + char voice[ MAX_VOICE_NAME_LEN ]; + qboolean useUnlagged; + // keep track of other players' info for tinfo + char cinfo[ MAX_CLIENTS ][ 16 ]; +} clientPersistant_t; + +#define MAX_UNLAGGED_MARKERS 10 +typedef struct unlagged_s { + vec3_t origin; + vec3_t mins; + vec3_t maxs; + qboolean used; +} unlagged_t; + +#define MAX_TRAMPLE_BUILDABLES_TRACKED 20 +// this structure is cleared on each ClientSpawn(), +// except for 'client->pers' and 'client->sess' +struct gclient_s +{ + // ps MUST be the first element, because the server expects it + playerState_t ps; // communicated by server to clients + + // exported into pmove, but not communicated to clients + pmoveExt_t pmext; + + // the rest of the structure is private to game + clientPersistant_t pers; + clientSession_t sess; + + qboolean readyToExit; // wishes to leave the intermission + + qboolean noclip; + + int lastCmdTime; // level.time of last usercmd_t, for EF_CONNECTION + // we can't just use pers.lastCommand.time, because + // of the g_sycronousclients case + int buttons; + int oldbuttons; + int latched_buttons; + + vec3_t oldOrigin; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + qboolean damage_fromWorld; // if true, don't use the damage_from vector + + // + int lastkilled_client;// last client that this client killed + int lasthurt_client; // last client that damaged this client + int lasthurt_mod; // type of damage the client did + + // timers + int respawnTime; // can respawn when time > this + int inactivityTime; // kick players when time > this + qboolean inactivityWarning;// qtrue if the five seoond warning has been given + int rewardTime; // clear the EF_AWARD_IMPRESSIVE, etc when time > this + int boostedTime; // last time we touched a booster + + int airOutTime; + + qboolean fireHeld; // used for hook + qboolean fire2Held; // used for alt fire + gentity_t *hook; // grapple hook if out + + int switchTeamTime; // time the player switched teams + + int time100; // timer for 100ms interval events + int time1000; // timer for one second interval events + int time10000; // timer for ten second interval events + + char *areabits; + + int lastPoisonTime; + int poisonImmunityTime; + gentity_t *lastPoisonClient; + int lastPoisonCloudedTime; + int grabExpiryTime; + int lastLockTime; + int lastSlowTime; + int lastMedKitTime; + int medKitHealthToRestore; + int medKitIncrementTime; + int lastCreepSlowTime; // time until creep can be removed + + int bioresHealTimer; + + qboolean charging; + + int lastFlameBall; // s.number of the last flame ball fired + + unlagged_t unlaggedHist[ MAX_UNLAGGED_MARKERS ]; + unlagged_t unlaggedBackup; + unlagged_t unlaggedCalc; + int unlaggedTime; + + float voiceEnthusiasm; + char lastVoiceCmd[ MAX_VOICE_CMD_LEN ]; + + int lcannonStartTime; + int trampleBuildablesHitPos; + int trampleBuildablesHit[ MAX_TRAMPLE_BUILDABLES_TRACKED ]; + + int lastCrushTime; // Tyrant crush + int lastRantBombTime; // last time client got a tyrant bomb + + vec3_t cuboidSelection; + int buildTimer; + + qboolean isImpregnated; // has a spawn implanted + int impregnationTime; + int impregnatedBy; + qboolean isImplantMature; // ready to spawn enemy troops +}; + + +typedef struct spawnQueue_s +{ + int clients[ MAX_CLIENTS ]; + + int front, back; +} spawnQueue_t; + +#define QUEUE_PLUS1(x) (((x)+1)%MAX_CLIENTS) +#define QUEUE_MINUS1(x) (((x)+MAX_CLIENTS-1)%MAX_CLIENTS) + +void G_InitSpawnQueue( spawnQueue_t *sq ); +int G_GetSpawnQueueLength( spawnQueue_t *sq ); +int G_PopSpawnQueue( spawnQueue_t *sq ); +int G_PeekSpawnQueue( spawnQueue_t *sq ); +qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum ); +qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum ); +qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum ); +int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum ); +void G_PrintSpawnQueue( spawnQueue_t *sq ); + + +#define MAX_DAMAGE_REGION_TEXT 8192 +#define MAX_DAMAGE_REGIONS 16 + +// build point zone +typedef struct +{ + int active; + + int totalBuildPoints; + int queuedBuildPoints; + int nextQueueTime; +} buildPointZone_t; + +// store locational damage regions +typedef struct damageRegion_s +{ + char name[ 32 ]; + float area, modifier, minHeight, maxHeight; + int minAngle, maxAngle; + qboolean crouch; +} damageRegion_t; + +//status of the warning of certain events +typedef enum +{ + TW_NOT = 0, + TW_IMMINENT, + TW_PASSED +} timeWarning_t; + +// fate of a buildable +typedef enum +{ + BF_CONSTRUCT, + BF_DECONSTRUCT, + BF_REPLACE, + BF_DESTROY, + BF_TEAMKILL, + BF_UNPOWER, + BF_AUTO +} buildFate_t; + +// data needed to revert a change in layout +typedef struct +{ + int time; + buildFate_t fate; + namelog_t *actor; + buildable_t modelindex; + qboolean deconstruct; + int deconstructTime; + vec3_t origin; + vec3_t angles; + vec3_t origin2; + vec3_t angles2; + buildable_t powerSource; + int powerValue; +} buildLog_t; + +// +// this structure is cleared as each map is entered +// +#define MAX_SPAWN_VARS 64 +#define MAX_SPAWN_VARS_CHARS 4096 +#define MAX_BUILDLOG 128 + +typedef struct +{ + struct gclient_s *clients; // [maxclients] + + struct gentity_s *gentities; + int gentitySize; + int num_entities; // current number, <= MAX_GENTITIES + + int warmupTime; // restart match at this time + + fileHandle_t logFile; + + // store latched cvars here that we want to get at often + int maxclients; + + int framenum; + int time; // in msec + int previousTime; // so movers can back up when blocked + int frameMsec; // trap_Milliseconds() at end frame + + int startTime; // level.time the map was started + + int teamScores[ NUM_TEAMS ]; + int lastTeamLocationTime; // last time of client team location update + + qboolean newSession; // don't use any old session data, because + // we changed gametype + + qboolean restarted; // waiting for a map_restart to fire + + int numConnectedClients; + int numNonSpectatorClients; // includes connecting clients + int numPlayingClients; // connected, non-spectators + int sortedClients[MAX_CLIENTS]; // sorted by score + + int snd_fry; // sound index for standing in lava + + int warmupModificationCount; // for detecting if g_warmup is changed + + // voting state + int voteThreshold[ NUM_TEAMS ]; // need at least this percent to pass + char voteString[ NUM_TEAMS ][ MAX_STRING_CHARS ]; + char voteDisplayString[ NUM_TEAMS ][ MAX_STRING_CHARS ]; + int voteTime[ NUM_TEAMS ]; // level.time vote was called + int voteExecuteTime[ NUM_TEAMS ]; // time the vote is executed + int voteDelay[ NUM_TEAMS ]; // it doesn't make sense to always delay vote execution + int voteYes[ NUM_TEAMS ]; + int voteNo[ NUM_TEAMS ]; + int numVotingClients[ NUM_TEAMS ];// set by CalculateRanks + + // spawn variables + qboolean spawning; // the G_Spawn*() functions are valid + int numSpawnVars; + char *spawnVars[ MAX_SPAWN_VARS ][ 2 ]; // key / value pairs + int numSpawnVarChars; + char spawnVarChars[ MAX_SPAWN_VARS_CHARS ]; + + // intermission state + int intermissionQueued; // intermission was qualified, but + // wait INTERMISSION_DELAY_TIME before + // actually going there so the last + // frag can be watched. Disable future + // kills during this delay + int intermissiontime; // time the intermission was started + char *changemap; + qboolean readyToExit; // at least one client wants to exit + int exitTime; + vec3_t intermission_origin; // also used for spectator spawns + vec3_t intermission_angle; + + gentity_t *locationHead; // head of the location list + + int numAlienSpawns; + int numHumanSpawns; + + int numAlienImplantedSpawns; // number of impregnated humans + int numHumanImplantedSpawns; // number of impregnated aliens (NOTE: no microtelenodes yet, spawning works though) + + int numAlienClients; + int numHumanClients; + + float averageNumAlienClients; + int numAlienSamples; + float averageNumHumanClients; + int numHumanSamples; + + int numLiveAlienClients; + int numLiveHumanClients; + + int alienBuildPoints; + int alienBuildPointQueue; + int alienNextQueueTime; + int humanBuildPoints; + int humanBuildPointQueue; + int humanNextQueueTime; + + buildPointZone_t *buildPointZones; + + gentity_t *markedBuildables[ MAX_GENTITIES ]; + int numBuildablesForRemoval; + + int alienKills; + int humanKills; + + qboolean overmindMuted; + + int humanBaseAttackTimer; + + team_t lastWin; + + int suddenDeathBeginTime; + timeWarning_t suddenDeathWarning; + timeWarning_t timelimitWarning; + + spawnQueue_t alienSpawnQueue; + spawnQueue_t humanSpawnQueue; + + int alienStage2Time; + int alienStage3Time; + int humanStage2Time; + int humanStage3Time; + + qboolean uncondAlienWin; + qboolean uncondHumanWin; + qboolean alienTeamLocked; + qboolean humanTeamLocked; + int pausedTime; + + int unlaggedIndex; + int unlaggedTimes[ MAX_UNLAGGED_MARKERS ]; + + char layout[ MAX_QPATH ]; + + team_t surrenderTeam; + int lastTeamImbalancedTime; + int numTeamImbalanceWarnings; + + voice_t *voices; + + emoticon_t emoticons[ MAX_EMOTICONS ]; + int emoticonCount; + + namelog_t *namelogs; + + buildLog_t buildLog[ MAX_BUILDLOG ]; + int buildId; + int numBuildLogs; +} level_locals_t; + +#define CMD_CHEAT 0x0001 +#define CMD_CHEAT_TEAM 0x0002 // is a cheat when used on a team +#define CMD_MESSAGE 0x0004 // sends message to others (skip when muted) +#define CMD_TEAM 0x0008 // must be on a team +#define CMD_SPEC 0x0010 // must be a spectator +#define CMD_ALIEN 0x0020 +#define CMD_HUMAN 0x0040 +#define CMD_LIVING 0x0080 +#define CMD_INTERMISSION 0x0100 // valid during intermission + +typedef struct +{ + char *cmdName; + int cmdFlags; + void ( *cmdHandler )( gentity_t *ent ); +} commands_t; + +// +// g_spawn.c +// +qboolean G_SpawnString( const char *key, const char *defaultString, char **out ); +// spawn string returns a temporary reference, you must CopyString() if you want to keep it +qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out ); +qboolean G_SpawnInt( const char *key, const char *defaultString, int *out ); +qboolean G_SpawnVector( const char *key, const char *defaultString, float *out ); +void G_SpawnEntitiesFromString( void ); +char *G_NewString( const char *string ); + +// +// g_cmds.c +// + +#define DECOLOR_OFF '\16' +#define DECOLOR_ON '\17' + +void G_StopFollowing( gentity_t *ent ); +void G_StopFromFollowing( gentity_t *ent ); +void G_FollowLockView( gentity_t *ent ); +qboolean G_FollowNewClient( gentity_t *ent, int dir ); +void G_ToggleFollow( gentity_t *ent ); +int G_ClientNumberFromString( char *s, char *err, int len ); +int G_ClientNumbersFromString( char *s, int *plist, int max ); +char *ConcatArgs( int start ); +char *ConcatArgsPrintable( int start ); +void G_Say( gentity_t *ent, saymode_t mode, const char *chatText ); +void G_DecolorString( char *in, char *out, int len ); +void G_UnEscapeString( char *in, char *out, int len ); +void G_SanitiseString( char *in, char *out, int len ); +void Cmd_PrivateMessage_f( gentity_t *ent ); +void Cmd_ListMaps_f( gentity_t *ent ); +void Cmd_Test_f( gentity_t *ent ); +void Cmd_AdminMessage_f( gentity_t *ent ); +int G_FloodLimited( gentity_t *ent ); +void G_ListCommands( gentity_t *ent ); +void G_LoadCensors( void ); +void G_CensorString( char *out, const char *in, int len, gentity_t *ent ); + +// +// g_physics.c +// +void G_Physics( gentity_t *ent, int msec ); + +// +// g_buildable.c +// + +#define MAX_ALIEN_BBOX 25 + +typedef enum +{ + IBE_NONE, + + IBE_NOOVERMIND, + IBE_ONEOVERMIND, + IBE_NOALIENBP, + IBE_SPWNWARN, // not currently used + IBE_NOCREEP, + + IBE_ONEREACTOR, + IBE_NOPOWERHERE, + IBE_TNODEWARN, // not currently used + IBE_RPTNOREAC, + IBE_RPTPOWERHERE, + IBE_NOHUMANBP, + IBE_NODCC, + + IBE_NORMAL, // too steep + IBE_NOROOM, + IBE_PERMISSION, + IBE_LASTSPAWN, + IBE_NOSURF, + IBE_TOODENSE, + + IBE_MAXERRORS +} itemBuildError_t; + +gentity_t *G_CheckSpawnPoint( int spawnNum, const vec3_t origin, + const vec3_t normal, buildable_t spawn, + vec3_t spawnOrigin ); + +buildable_t G_IsPowered( vec3_t origin ); +qboolean G_IsDCCBuilt( void ); +int G_FindDCC( gentity_t *self ); +gentity_t *G_Reactor( void ); +gentity_t *G_Overmind( void ); +qboolean G_FindCreep( gentity_t *self ); + +void G_BuildableThink( gentity_t *ent, int msec ); +qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ); +void G_ClearDeconMarks( void ); +itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin, vec3_t normal, vec3_t cuboidSize ); +qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable, vec3_t cuboidSize ); +void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ); +void G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ); +void G_SpawnBuildable(gentity_t *ent, buildable_t buildable); +void FinishSpawningBuildable( gentity_t *ent ); +void G_LayoutSave( char *name ); +int G_LayoutList( const char *map, char *list, int len ); +void G_LayoutSelect( void ); +void G_LayoutLoad( void ); +void G_BaseSelfDestruct( team_t team ); +int G_NextQueueTime( int queuedBP, int totalBP, int queueBaseRate ); +void G_QueueBuildPoints( gentity_t *self ); +int G_GetBuildPoints( const vec3_t pos, team_t team ); +int G_GetMarkedBuildPoints( const vec3_t pos, team_t team ); +qboolean G_FindPower( gentity_t *self, qboolean searchUnspawned ); +gentity_t *G_PowerEntityForPoint( const vec3_t origin ); +gentity_t *G_PowerEntityForEntity( gentity_t *ent ); +gentity_t *G_RepeaterEntityForPoint( vec3_t origin ); +gentity_t *G_InPowerZone( gentity_t *self ); +buildLog_t *G_BuildLogNew( gentity_t *actor, buildFate_t fate ); +void G_BuildLogSet( buildLog_t *log, gentity_t *ent ); +void G_BuildLogAuto( gentity_t *actor, gentity_t *buildable, buildFate_t fate ); +void G_BuildLogRevert( int id ); +const char *G_CuboidName(buildable_t buildable, const vec3_t cuboidSize, qboolean verbose); +void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 ); + +// +// g_utils.c +// +//addr_t in g_admin.h for g_admin_ban_t +qboolean G_AddressParse( const char *str, addr_t *addr ); +qboolean G_AddressCompare( const addr_t *a, const addr_t *b ); + +int G_ParticleSystemIndex( char *name ); +int G_ShaderIndex( char *name ); +int G_ModelIndex( char *name ); +int G_SoundIndex( char *name ); +void G_KillBox (gentity_t *ent); +gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match); +gentity_t *G_PickTarget (char *targetname); +void G_UseTargets (gentity_t *ent, gentity_t *activator); +void G_SetMovedir ( vec3_t angles, vec3_t movedir); + +void G_InitGentity( gentity_t *e ); +gentity_t *G_Spawn( void ); +gentity_t *G_TempEntity( vec3_t origin, int event ); +void G_Sound( gentity_t *ent, int channel, int soundIndex ); +void G_FreeEntity( gentity_t *e ); +qboolean G_EntitiesFree( void ); + +void G_TouchTriggers( gentity_t *ent ); +void G_TouchSolids( gentity_t *ent ); + +float *tv( float x, float y, float z ); +char *vtos( const vec3_t v ); + +float vectoyaw( const vec3_t vec ); + +void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ); +void G_AddEvent( gentity_t *ent, int event, int eventParm ); +void G_BroadcastEvent( int event, int eventParm ); +void G_SetOrigin( gentity_t *ent, vec3_t origin ); +void AddRemap(const char *oldShader, const char *newShader, float timeOffset); +const char *BuildShaderStateConfig( void ); + + +qboolean G_ClientIsLagging( gclient_t *client ); + +void G_TriggerMenu( int clientNum, dynMenu_t menu ); +void G_TriggerMenuArgs( int clientNum, dynMenu_t menu, int arg ); +void G_CloseMenus( int clientNum ); + +qboolean G_Visible( gentity_t *ent1, gentity_t *ent2, int contents ); +gentity_t *G_ClosestEnt( vec3_t origin, gentity_t **entities, int numEntities ); + +void G_RecalcBuildTimer( gclient_t *client ); + +// g_combat.c +// +qboolean CanDamage( gentity_t *targ, vec3_t origin ); +void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, + vec3_t dir, vec3_t point, int damage, int dflags, int mod ); +void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, + vec3_t point, int damage, int dflags, int mod, int team ); +qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius, + gentity_t *ignore, int mod ); +qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius, + gentity_t *ignore, int mod, int team ); +float G_RewardAttackers( gentity_t *self ); +void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ); +void AddScore( gentity_t *ent, int score ); +void G_LogDestruction( gentity_t *self, gentity_t *actor, int mod ); + +void G_InitDamageLocations( void ); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_NO_KNOCKBACK 0x00000004 // do not affect velocity, just view angles +#define DAMAGE_NO_PROTECTION 0x00000008 // armor, shields, invulnerability, and godmode have no effect +#define DAMAGE_NO_LOCDAMAGE 0x00000010 // do not apply locational damage + +// +// g_missile.c +// +void G_RunMissile( gentity_t *ent ); + +gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t aimdir ); +gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius, int speed ); +gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_rantBomb( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir ); + + +// +// g_mover.c +// +void G_RunMover( gentity_t *ent ); +void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ); +void manualTriggerSpectator( gentity_t *trigger, gentity_t *player ); + +// +// g_trigger.c +// +void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace ); +void G_Checktrigger_stages( team_t team, stage_t stage ); + + +// +// g_misc.c +// +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ); +void ShineTorch( gentity_t *self ); + +// +// g_weapon.c +// + +typedef struct zap_s +{ + qboolean used; + + gentity_t *creator; + gentity_t *targets[ LEVEL2_AREAZAP_MAX_TARGETS ]; + int numTargets; + float distances[ LEVEL2_AREAZAP_MAX_TARGETS ]; + + int timeToLive; + + gentity_t *effectChannel; +} zap_t; + +void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon ); +void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo ); +void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ); +void SnapVectorTowards( vec3_t v, vec3_t to ); +qboolean CheckVenomAttack( gentity_t *ent ); +void CheckGrabAttack( gentity_t *ent ); +qboolean CheckPounceAttack( gentity_t *ent ); +void CheckCkitRepair( gentity_t *ent ); +void G_ChargeAttack( gentity_t *ent, gentity_t *victim ); +void G_CrushAttack( gentity_t *ent, gentity_t *victim ); +void G_UpdateZaps( int msec ); +void G_ClearPlayerZapEffects( gentity_t *player ); + + +// +// g_client.c +// +void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ); +team_t TeamCount( int ignoreClientNum, int team ); +void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ); +gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles ); +gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ); +gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles ); +gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ); +void respawn( gentity_t *ent ); +void BeginIntermission( void ); +void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ); +void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); +qboolean SpotWouldTelefrag( gentity_t *spot ); +void G_ReplaceBuildablesBuilder(int from, int to); +void G_RestoreClientBuildables( gentity_t *ent ); +void G_SaveClientBuildables( gentity_t *ent ); +// +// g_svcmds.c +// +qboolean ConsoleCommand( void ); +void G_RegisterCommands( void ); +void G_UnregisterCommands( void ); + +// +// g_weapon.c +// +void FireWeapon( gentity_t *ent ); +void FireWeapon2( gentity_t *ent ); +void FireWeapon3( gentity_t *ent ); + +// +// g_main.c +// +void ScoreboardMessage( gentity_t *client ); +void MoveClientToIntermission( gentity_t *client ); +void G_MapConfigs( const char *mapname ); +void CalculateRanks( void ); +void FindIntermissionPoint( void ); +void G_RunThink( gentity_t *ent ); +void G_AdminMessage( gentity_t *ent, const char *string ); +void QDECL G_LogPrintf( const char *fmt, ... ); +void SendScoreboardMessageToAllClients( void ); +void QDECL G_Printf( const char *fmt, ... ); +void QDECL G_Error( const char *fmt, ... ); +void G_Vote( gentity_t *ent, team_t team, qboolean voting ); +void G_ExecuteVote( team_t team ); +void G_CheckVote( team_t team ); +void LogExit( const char *string ); +int G_TimeTilSuddenDeath( void ); + +// +// g_client.c +// +char *ClientConnect( int clientNum, qboolean firstTime ); +char *ClientUserinfoChanged( int clientNum, qboolean forceName ); +void ClientDisconnect( int clientNum ); +void ClientBegin( int clientNum ); +void ClientCommand( int clientNum ); + +// +// g_active.c +// +void G_UnlaggedStore( void ); +void G_UnlaggedClear( gentity_t *ent ); +void G_UnlaggedCalc( int time, gentity_t *skipEnt ); +void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range ); +void G_UnlaggedOff( void ); +void ClientThink( int clientNum ); +void ClientEndFrame( gentity_t *ent ); +void G_RunClient( gentity_t *ent ); + +// +// g_team.c +// +team_t G_TeamFromString( char *str ); +void G_TeamCommand( team_t team, char *cmd ); +qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ); +void G_LeaveTeam( gentity_t *self ); +void G_ChangeTeam( gentity_t *ent, team_t newTeam ); +gentity_t *Team_GetLocation( gentity_t *ent ); +void TeamplayInfoMessage( gentity_t *ent ); +void CheckTeamStatus( void ); +void G_UpdateTeamConfigStrings( void ); + +// +// g_session.c +// +void G_ReadSessionData( gclient_t *client ); +void G_InitSessionData( gclient_t *client, char *userinfo ); +void G_WriteSessionData( void ); + +// +// g_maprotation.c +// +void G_PrintRotations( void ); +void G_AdvanceMapRotation( int depth ); +qboolean G_StartMapRotation( char *name, qboolean advance, + qboolean putOnStack, qboolean reset_index, int depth ); +void G_StopMapRotation( void ); +qboolean G_MapRotationActive( void ); +void G_InitMapRotations( void ); +void G_ShutdownMapRotations( void ); +qboolean G_MapExists( char *name ); +void G_ClearRotationStack( void ); + +// +// g_namelog.c +// + +void G_namelog_connect( gclient_t *client ); +void G_namelog_disconnect( gclient_t *client ); +void G_namelog_restore( gclient_t *client ); +void G_namelog_update_score( gclient_t *client ); +void G_namelog_update_name( gclient_t *client ); +void G_namelog_cleanup( void ); + +// +// g_items.c +// + +gentity_t *G_ItemSpawn(const vec3_t origin, int type, int subtype, int mag); +gentity_t *G_ItemEject(const vec3_t origin_center, float origin_dispersal, float speed, int type, int subtype, int mag); + +//some maxs +#define MAX_FILEPATH 144 + +extern level_locals_t level; +extern gentity_t g_entities[ MAX_GENTITIES ]; + +#define FOFS(x) ((size_t)&(((gentity_t *)0)->x)) + +extern vmCvar_t g_dedicated; +extern vmCvar_t g_cheats; +extern vmCvar_t g_maxclients; // allow this many total, including spectators +extern vmCvar_t g_maxGameClients; // allow this many active +extern vmCvar_t g_restarted; +extern vmCvar_t g_lockTeamsAtStart; +extern vmCvar_t g_minNameChangePeriod; +extern vmCvar_t g_maxNameChanges; + +extern vmCvar_t g_timelimit; +extern vmCvar_t g_suddenDeathTime; +extern vmCvar_t g_friendlyFire; +extern vmCvar_t g_friendlyBuildableFire; +extern vmCvar_t g_dretchPunt; +extern vmCvar_t g_password; +extern vmCvar_t g_needpass; +extern vmCvar_t g_gravity; +extern vmCvar_t g_speed; +extern vmCvar_t g_knockback; +extern vmCvar_t g_inactivity; +extern vmCvar_t g_debugMove; +extern vmCvar_t g_debugDamage; +extern vmCvar_t g_synchronousClients; +extern vmCvar_t g_motd; +extern vmCvar_t g_warmup; +extern vmCvar_t g_doWarmup; +extern vmCvar_t g_allowVote; +extern vmCvar_t g_voteLimit; +extern vmCvar_t g_suddenDeathVotePercent; +extern vmCvar_t g_suddenDeathVoteDelay; +extern vmCvar_t g_teamForceBalance; +extern vmCvar_t g_smoothClients; +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; + +extern vmCvar_t g_alienBuildPoints; +extern vmCvar_t g_alienBuildQueueTime; +extern vmCvar_t g_humanBuildPoints; +extern vmCvar_t g_humanBuildQueueTime; +extern vmCvar_t g_humanRepeaterBuildPoints; +extern vmCvar_t g_humanRepeaterBuildQueueTime; +extern vmCvar_t g_humanRepeaterMaxZones; +extern vmCvar_t g_humanStage; +extern vmCvar_t g_humanCredits; +extern vmCvar_t g_humanMaxStage; +extern vmCvar_t g_humanStage2Threshold; +extern vmCvar_t g_humanStage3Threshold; +extern vmCvar_t g_alienStage; +extern vmCvar_t g_alienCredits; +extern vmCvar_t g_alienMaxStage; +extern vmCvar_t g_alienStage2Threshold; +extern vmCvar_t g_alienStage3Threshold; +extern vmCvar_t g_teamImbalanceWarnings; +extern vmCvar_t g_freeFundPeriod; + +extern vmCvar_t g_unlagged; + +extern vmCvar_t g_disabledEquipment; +extern vmCvar_t g_disabledClasses; +extern vmCvar_t g_disabledBuildables; + +extern vmCvar_t g_markDeconstruct; + +extern vmCvar_t g_debugMapRotation; +extern vmCvar_t g_currentMapRotation; +extern vmCvar_t g_mapRotationNodes; +extern vmCvar_t g_mapRotationStack; +extern vmCvar_t g_nextMap; +extern vmCvar_t g_initialMapRotation; +extern vmCvar_t g_sayAreaRange; + +extern vmCvar_t g_debugVoices; +extern vmCvar_t g_voiceChats; + +extern vmCvar_t g_floodMaxDemerits; +extern vmCvar_t g_floodMinTime; + +extern vmCvar_t g_shove; + +extern vmCvar_t g_mapConfigs; + +extern vmCvar_t g_layouts; +extern vmCvar_t g_layoutAuto; + +extern vmCvar_t g_emoticonsAllowedInNames; + +extern vmCvar_t g_admin; +extern vmCvar_t g_adminTempBan; +extern vmCvar_t g_adminMaxBan; + +extern vmCvar_t g_privateMessages; +extern vmCvar_t g_specChat; +extern vmCvar_t g_publicAdminMessages; +extern vmCvar_t g_allowTeamOverlay; + +extern vmCvar_t g_censorship; + +extern vmCvar_t g_unlimited; +extern vmCvar_t g_instantBuild; +extern vmCvar_t g_cuboidSizeLimit; + +extern vmCvar_t g_buildableDensityLimit; +extern vmCvar_t g_buildableDensityLimitRange; + +void trap_Print( const char *fmt ); +void trap_Error( const char *fmt ); +int trap_Milliseconds( void ); +int trap_RealTime( qtime_t *qtime ); +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t +void trap_SendConsoleCommand( int exec_when, const char *text ); +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ); +void trap_Cvar_Update( vmCvar_t *cvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +int trap_Cvar_VariableIntegerValue( const char *var_name ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +void trap_LocateGameData( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *gameClients, int sizeofGameClient ); +void trap_DropClient( int clientNum, const char *reason ); +void trap_SendServerCommand( int clientNum, const char *text ); +void trap_SetConfigstring( int num, const char *string ); +void trap_GetConfigstring( int num, char *buffer, int bufferSize ); +void trap_SetConfigstringRestrictions( int num, const clientList_t *clientList ); +void trap_GetUserinfo( int num, char *buffer, int bufferSize ); +void trap_SetUserinfo( int num, const char *buffer ); +void trap_GetServerinfo( char *buffer, int bufferSize ); +void trap_SetBrushModel( gentity_t *ent, const char *name ); +void trap_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, + const vec3_t end, int passEntityNum, int contentmask ); +int trap_PointContents( const vec3_t point, int passEntityNum ); +qboolean trap_InPVS( const vec3_t p1, const vec3_t p2 ); +qboolean trap_InPVSIgnorePortals( const vec3_t p1, const vec3_t p2 ); +void trap_AdjustAreaPortalState( gentity_t *ent, qboolean open ); +qboolean trap_AreasConnected( int area1, int area2 ); +void trap_LinkEntity( gentity_t *ent ); +void trap_UnlinkEntity( gentity_t *ent ); +int trap_EntitiesInBox( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ); +qboolean trap_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); +int trap_BotAllocateClient( void ); +void trap_BotFreeClient( int clientNum ); +void trap_GetUsercmd( int clientNum, usercmd_t *cmd ); +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); + +void trap_SnapVector( float *v ); +void trap_SendGameStat( const char *data ); + +void trap_AddCommand( const char *cmdName ); +void trap_RemoveCommand( const char *cmdName ); diff --git a/src/game/g_main.c b/src/game/g_main.c new file mode 100644 index 0000000..4bc03b5 --- /dev/null +++ b/src/game/g_main.c @@ -0,0 +1,2565 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +level_locals_t level; + +typedef struct +{ + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; + int modificationCount; // for tracking changes + qboolean trackChange; // track this variable, and announce if changed + /* certain cvars can be set in worldspawn, but we don't want those values to + persist, so keep track of non-worldspawn changes and restore that on map + end. unfortunately, if the server crashes, the value set in worldspawn may + persist */ + char *explicit; +} cvarTable_t; + +gentity_t g_entities[ MAX_GENTITIES ]; +gclient_t g_clients[ MAX_CLIENTS ]; + +vmCvar_t g_timelimit; +vmCvar_t g_suddenDeathTime; +vmCvar_t g_friendlyFire; +vmCvar_t g_friendlyBuildableFire; +vmCvar_t g_dretchPunt; +vmCvar_t g_password; +vmCvar_t g_needpass; +vmCvar_t g_maxclients; +vmCvar_t g_maxGameClients; +vmCvar_t g_dedicated; +vmCvar_t g_speed; +vmCvar_t g_gravity; +vmCvar_t g_cheats; +vmCvar_t g_knockback; +vmCvar_t g_inactivity; +vmCvar_t g_debugMove; +vmCvar_t g_debugDamage; +vmCvar_t g_motd; +vmCvar_t g_synchronousClients; +vmCvar_t g_warmup; +vmCvar_t g_doWarmup; +vmCvar_t g_restarted; +vmCvar_t g_lockTeamsAtStart; +vmCvar_t g_logFile; +vmCvar_t g_logFileSync; +vmCvar_t g_allowVote; +vmCvar_t g_voteLimit; +vmCvar_t g_suddenDeathVotePercent; +vmCvar_t g_suddenDeathVoteDelay; +vmCvar_t g_teamForceBalance; +vmCvar_t g_smoothClients; +vmCvar_t pmove_fixed; +vmCvar_t pmove_msec; +vmCvar_t g_minNameChangePeriod; +vmCvar_t g_maxNameChanges; + +vmCvar_t g_alienBuildPoints; +vmCvar_t g_alienBuildQueueTime; +vmCvar_t g_humanBuildPoints; +vmCvar_t g_humanBuildQueueTime; +vmCvar_t g_humanRepeaterBuildPoints; +vmCvar_t g_humanRepeaterBuildQueueTime; +vmCvar_t g_humanRepeaterMaxZones; +vmCvar_t g_humanStage; +vmCvar_t g_humanCredits; +vmCvar_t g_humanMaxStage; +vmCvar_t g_humanStage2Threshold; +vmCvar_t g_humanStage3Threshold; +vmCvar_t g_alienStage; +vmCvar_t g_alienCredits; +vmCvar_t g_alienMaxStage; +vmCvar_t g_alienStage2Threshold; +vmCvar_t g_alienStage3Threshold; +vmCvar_t g_teamImbalanceWarnings; +vmCvar_t g_freeFundPeriod; + +vmCvar_t g_unlagged; + +vmCvar_t g_disabledEquipment; +vmCvar_t g_disabledClasses; +vmCvar_t g_disabledBuildables; + +vmCvar_t g_markDeconstruct; + +vmCvar_t g_debugMapRotation; +vmCvar_t g_currentMapRotation; +vmCvar_t g_mapRotationNodes; +vmCvar_t g_mapRotationStack; +vmCvar_t g_nextMap; +vmCvar_t g_initialMapRotation; + +vmCvar_t g_debugVoices; +vmCvar_t g_voiceChats; + +vmCvar_t g_shove; + +vmCvar_t g_mapConfigs; +vmCvar_t g_sayAreaRange; + +vmCvar_t g_floodMaxDemerits; +vmCvar_t g_floodMinTime; + +vmCvar_t g_layouts; +vmCvar_t g_layoutAuto; + +vmCvar_t g_emoticonsAllowedInNames; + +vmCvar_t g_admin; +vmCvar_t g_adminTempBan; +vmCvar_t g_adminMaxBan; + +vmCvar_t g_privateMessages; +vmCvar_t g_specChat; +vmCvar_t g_publicAdminMessages; +vmCvar_t g_allowTeamOverlay; + +vmCvar_t g_censorship; + +vmCvar_t g_tag; + +vmCvar_t g_unlimited; +vmCvar_t g_instantBuild; +vmCvar_t g_cuboidSizeLimit; +vmCvar_t g_cuboidMode; + +vmCvar_t g_buildableDensityLimit; +vmCvar_t g_buildableDensityLimitRange; + +// copy cvars that can be set in worldspawn so they can be restored later +static char cv_gravity[ MAX_CVAR_VALUE_STRING ]; +static char cv_humanMaxStage[ MAX_CVAR_VALUE_STRING ]; +static char cv_alienMaxStage[ MAX_CVAR_VALUE_STRING ]; + +static cvarTable_t gameCvarTable[ ] = +{ + // don't override the cheat state set by the system + { &g_cheats, "sv_cheats", "", 0, 0, qfalse }, + + // noset vars + { NULL, "gamename", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse }, + { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse }, + { &g_lockTeamsAtStart, "g_lockTeamsAtStart", "0", CVAR_ROM, 0, qfalse }, + { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + { NULL, "P", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + + // latched vars + + { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse }, + + // change anytime vars + { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse }, + + { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + { &g_suddenDeathTime, "g_suddenDeathTime", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + + { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse }, + + { &g_friendlyFire, "g_friendlyFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qtrue }, + + { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE, 0, qtrue }, + + { &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue }, + { &g_doWarmup, "g_doWarmup", "0", CVAR_ARCHIVE, 0, qtrue }, + { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse }, + { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse }, + + { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + + { &g_dedicated, "dedicated", "0", 0, 0, qfalse }, + + { &g_speed, "g_speed", "320", 0, 0, qtrue }, + { &g_gravity, "g_gravity", "800", 0, 0, qtrue, cv_gravity }, + { &g_knockback, "g_knockback", "1000", 0, 0, qtrue }, + { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue }, + { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse }, + { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse }, + { &g_motd, "g_motd", "", 0, 0, qfalse }, + + { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse }, + { &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "74", CVAR_ARCHIVE, 0, qfalse }, + { &g_suddenDeathVoteDelay, "g_suddenDeathVoteDelay", "180", CVAR_ARCHIVE, 0, qfalse }, + { &g_minNameChangePeriod, "g_minNameChangePeriod", "5", 0, 0, qfalse}, + { &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse}, + + { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse}, + { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse}, + { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, + + { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, 0, 0, qfalse }, + { &g_alienBuildQueueTime, "g_alienBuildQueueTime", DEFAULT_ALIEN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, 0, 0, qfalse }, + { &g_humanBuildQueueTime, "g_humanBuildQueueTime", DEFAULT_HUMAN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanRepeaterBuildPoints, "g_humanRepeaterBuildPoints", DEFAULT_HUMAN_REPEATER_BUILDPOINTS, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanRepeaterMaxZones, "g_humanRepeaterMaxZones", DEFAULT_HUMAN_REPEATER_MAX_ZONES, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanRepeaterBuildQueueTime, "g_humanRepeaterBuildQueueTime", DEFAULT_HUMAN_REPEATER_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse }, + { &g_humanCredits, "g_humanCredits", "0", 0, 0, qfalse }, + { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse, cv_humanMaxStage }, + { &g_humanStage2Threshold, "g_humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, 0, 0, qfalse }, + { &g_humanStage3Threshold, "g_humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, 0, 0, qfalse }, + { &g_alienStage, "g_alienStage", "0", 0, 0, qfalse }, + { &g_alienCredits, "g_alienCredits", "0", 0, 0, qfalse }, + { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse, cv_alienMaxStage }, + { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse }, + { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse }, + { &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse }, + { &g_freeFundPeriod, "g_freeFundPeriod", DEFAULT_FREEKILL_PERIOD, CVAR_ARCHIVE, 0, qtrue }, + + { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + + { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + + { &g_sayAreaRange, "g_sayAreaRange", "1000", CVAR_ARCHIVE, 0, qtrue }, + + { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse }, + { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse }, + + { &g_markDeconstruct, "g_markDeconstruct", "3", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + + { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse }, + { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING + { &g_mapRotationNodes, "g_mapRotationNodes", "", CVAR_ROM, 0, qfalse }, + { &g_mapRotationStack, "g_mapRotationStack", "", CVAR_ROM, 0, qfalse }, + { &g_nextMap, "g_nextMap", "", 0 , 0, qtrue }, + { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse }, + { &g_debugVoices, "g_debugVoices", "0", 0, 0, qfalse }, + { &g_voiceChats, "g_voiceChats", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_shove, "g_shove", "0.0", CVAR_ARCHIVE, 0, qfalse }, + { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse }, + { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse }, + + { &g_layouts, "g_layouts", "", CVAR_LATCH, 0, qfalse }, + { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse }, + + { &g_emoticonsAllowedInNames, "g_emoticonsAllowedInNames", "1", CVAR_LATCH|CVAR_ARCHIVE, 0, qfalse }, + + { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse }, + { &g_adminTempBan, "g_adminTempBan", "2m", CVAR_ARCHIVE, 0, qfalse }, + { &g_adminMaxBan, "g_adminMaxBan", "2w", CVAR_ARCHIVE, 0, qfalse }, + + { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_specChat, "g_specChat", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_publicAdminMessages, "g_publicAdminMessages", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_allowTeamOverlay, "g_allowTeamOverlay", "1", CVAR_ARCHIVE, 0, qtrue }, + + { &g_censorship, "g_censorship", "", CVAR_ARCHIVE, 0, qfalse }, + + { &g_tag, "g_tag", "gpp", CVAR_INIT, 0, qfalse }, + + { &g_unlimited, "g_unlimited", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + { &g_instantBuild, "g_instantBuild", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + { &g_cuboidSizeLimit, "g_cuboidSizeLimit", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_cuboidMode, "g_cuboidMode", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_buildableDensityLimit, "g_buildableDensityLimit", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_buildableDensityLimitRange, "g_buildableDensityLimitRange", "0", CVAR_ARCHIVE, 0, qfalse } +}; + +static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); + + +void G_InitGame( int levelTime, int randomSeed, int restart ); +void G_RunFrame( int levelTime ); +void G_ShutdownGame( int restart ); +void CheckExitRules( void ); + +void G_CountSpawns( void ); +void G_CalculateBuildPoints( void ); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, + int arg5, int arg6, int arg7, int arg8, int arg9, + int arg10, int arg11 ) +{ + switch( command ) + { + case GAME_INIT: + G_InitGame( arg0, arg1, arg2 ); + return 0; + + case GAME_SHUTDOWN: + G_ShutdownGame( arg0 ); + return 0; + + case GAME_CLIENT_CONNECT: + return (intptr_t)ClientConnect( arg0, arg1 ); + + case GAME_CLIENT_THINK: + ClientThink( arg0 ); + return 0; + + case GAME_CLIENT_USERINFO_CHANGED: + ClientUserinfoChanged( arg0, qfalse ); + return 0; + + case GAME_CLIENT_DISCONNECT: + ClientDisconnect( arg0 ); + return 0; + + case GAME_CLIENT_BEGIN: + ClientBegin( arg0 ); + return 0; + + case GAME_CLIENT_COMMAND: + ClientCommand( arg0 ); + return 0; + + case GAME_RUN_FRAME: + G_RunFrame( arg0 ); + return 0; + + case GAME_CONSOLE_COMMAND: + return ConsoleCommand( ); + } + + return -1; +} + + +void QDECL G_Printf( const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + trap_Print( text ); +} + +void QDECL G_Error( const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + trap_Error( text ); +} + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. +Entity teams are used for item groups and multi-entity mover groups. + +All but the first will have the FL_TEAMSLAVE flag set and teammaster field set +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams( void ) +{ + gentity_t *e, *e2; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + + for( i = 1, e = g_entities+i; i < level.num_entities; i++, e++ ) + { + if( !e->inuse ) + continue; + + if( !e->team ) + continue; + + if( e->flags & FL_TEAMSLAVE ) + continue; + + e->teammaster = e; + c++; + c2++; + + for( j = i + 1, e2 = e + 1; j < level.num_entities; j++, e2++ ) + { + if( !e2->inuse ) + continue; + + if( !e2->team ) + continue; + + if( e2->flags & FL_TEAMSLAVE ) + continue; + + if( !strcmp( e->team, e2->team ) ) + { + c2++; + e2->teamchain = e->teamchain; + e->teamchain = e2; + e2->teammaster = e; + e2->flags |= FL_TEAMSLAVE; + + // make sure that targets only point at the master + if( e2->targetname ) + { + e->targetname = e2->targetname; + e2->targetname = NULL; + } + } + } + } + + G_Printf( "%i teams with %i entities\n", c, c2 ); +} + + +/* +================= +G_RegisterCvars +================= +*/ +void G_RegisterCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) + { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + + if( cv->vmCvar ) + cv->modificationCount = cv->vmCvar->modificationCount; + + if( cv->explicit ) + strcpy( cv->explicit, cv->vmCvar->string ); + } +} + +/* +================= +G_UpdateCvars +================= +*/ +void G_UpdateCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) + { + if( cv->vmCvar ) + { + trap_Cvar_Update( cv->vmCvar ); + + if( cv->modificationCount != cv->vmCvar->modificationCount ) + { + cv->modificationCount = cv->vmCvar->modificationCount; + + if( cv->trackChange ) + trap_SendServerCommand( -1, va( "print \"Server: %s changed to %s\n\"", + cv->cvarName, cv->vmCvar->string ) ); + + if( !level.spawning && cv->explicit ) + strcpy( cv->explicit, cv->vmCvar->string ); + } + } + } +} + +/* +================= +G_RestoreCvars +================= +*/ +void G_RestoreCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) + { + if( cv->vmCvar && cv->explicit ) + trap_Cvar_Set( cv->cvarName, cv->explicit ); + } +} + +/* +================= +G_MapConfigs +================= +*/ +void G_MapConfigs( const char *mapname ) +{ + + if( !g_mapConfigs.string[0] ) + return; + + if( trap_Cvar_VariableIntegerValue( "g_mapConfigsLoaded" ) ) + return; + + trap_SendConsoleCommand( EXEC_APPEND, + va( "exec \"%s/default.cfg\"\n", g_mapConfigs.string ) ); + + trap_SendConsoleCommand( EXEC_APPEND, + va( "exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname ) ); + + trap_Cvar_Set( "g_mapConfigsLoaded", "1" ); +} + +/* +================ +G_CheckCuboidConfig + +even tokens are map names +odd tokens are cuboid modes +================ +*/ +void G_CheckCuboidConfig(char* mapname) +{ + char* token; + char config[MAX_CVAR_VALUE_STRING]; //should be enough for few maps + qboolean type=qfalse; + qboolean found=qfalse; + int mode; + + mode=trap_Cvar_VariableIntegerValue("g_cuboidDefaultMode"); + trap_Cvar_VariableStringBuffer("g_cuboidConfig",config,sizeof(config)); + + while(1) + { + COM_Parse(&token); + + if(!token) + break; + + if(!type) + if(!Q_stricmp(token,mapname)) + found=qtrue; + else + if(found) + { + mode=atoi(token); + break; + } + } + Com_Printf("cuboids are %s%s^7 on %s\n",(mode==0?"^2ENABLED":(mode==1?"^1DISABLED ON S1":"^1DISABLED")),(found?"":" BY DEFAULT"),mapname); +} + +/* +============ +G_InitGame + +============ +*/ +void G_InitGame( int levelTime, int randomSeed, int restart ) +{ + int i; + char map[ MAX_CVAR_VALUE_STRING ] = {""}; + + srand( randomSeed ); + + G_RegisterCvars( ); + + G_Printf( "------- Game Initialization -------\n" ); + G_Printf( "gamename: %s\n", GAME_VERSION ); + G_Printf( "gamedate: %s\n", __DATE__ ); + + BG_InitMemory( ); + + // set some level globals + memset( &level, 0, sizeof( level ) ); + level.time = levelTime; + level.startTime = levelTime; + level.alienStage2Time = level.alienStage3Time = + level.humanStage2Time = level.humanStage3Time = level.startTime; + + level.snd_fry = G_SoundIndex( "sound/misc/fry.wav" ); // FIXME standing in lava / slime + + if( g_logFile.string[ 0 ] ) + { + if( g_logFileSync.integer ) + trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND_SYNC ); + else + trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND ); + + if( !level.logFile ) + G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logFile.string ); + else + { + char serverinfo[ MAX_INFO_STRING ]; + qtime_t qt; + int t; + + trap_GetServerinfo( serverinfo, sizeof( serverinfo ) ); + + G_LogPrintf( "------------------------------------------------------------\n" ); + G_LogPrintf( "InitGame: %s\n", serverinfo ); + + t = trap_RealTime( &qt ); + G_LogPrintf("RealTime: %04i/%02i/%02i %02i:%02i:%02i\n", + qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, + qt.tm_hour, qt.tm_min, qt.tm_sec ); + + } + } + else + G_Printf( "Not logging to disk\n" ); + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + G_MapConfigs( map ); + + // we're done with g_mapConfigs, so reset this for the next map + trap_Cvar_Set( "g_mapConfigsLoaded", "0" ); + + G_RegisterCommands( ); + G_admin_readconfig( NULL ); + G_LoadCensors( ); + + // initialize all entities for this game + memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[ 0 ] ) ); + level.gentities = g_entities; + + // initialize all clients for this game + level.maxclients = g_maxclients.integer; + memset( g_clients, 0, MAX_CLIENTS * sizeof( g_clients[ 0 ] ) ); + level.clients = g_clients; + + // set client fields on player ents + for( i = 0; i < level.maxclients; i++ ) + g_entities[ i ].client = level.clients + i; + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + level.num_entities = MAX_CLIENTS; + + // let the server system know where the entites are + trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), + &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) ); + + level.emoticonCount = BG_LoadEmoticons( level.emoticons, MAX_EMOTICONS ); + + trap_SetConfigstring( CS_INTERMISSION, "0" ); + + // test to see if a custom buildable layout will be loaded + G_LayoutSelect( ); + + // this has to be flipped after the first UpdateCvars + level.spawning = qtrue; + // parse the key/value pairs and spawn gentities + G_SpawnEntitiesFromString( ); + + // load up a custom building layout if there is one + G_LayoutLoad( ); + + // find out g_cuboidMode value + G_CheckCuboidConfig( map ); + + // the map might disable some things + BG_InitAllowedGameElements( ); + + // general initialization + G_FindTeams( ); + + BG_InitClassConfigs( ); + BG_InitBuildableConfigs( ); + G_InitDamageLocations( ); + G_InitMapRotations( ); + G_InitSpawnQueue( &level.alienSpawnQueue ); + G_InitSpawnQueue( &level.humanSpawnQueue ); + + if( g_debugMapRotation.integer ) + G_PrintRotations( ); + + level.voices = BG_VoiceInit( ); + BG_PrintVoices( level.voices, g_debugVoices.integer ); + + //reset stages + trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) ); + trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) ); + trap_Cvar_Set( "g_alienCredits", 0 ); + trap_Cvar_Set( "g_humanCredits", 0 ); + level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; + + G_Printf( "-----------------------------------\n" ); + + // So the server counts the spawns without a client attached + G_CountSpawns( ); + + G_UpdateTeamConfigStrings( ); + + if( g_lockTeamsAtStart.integer ) + { + level.alienTeamLocked = qtrue; + level.humanTeamLocked = qtrue; + trap_Cvar_Set( "g_lockTeamsAtStart", "0" ); + } +} + +/* +================== +G_ClearVotes + +remove all currently active votes +================== +*/ +static void G_ClearVotes( void ) +{ + int i; + memset( level.voteTime, 0, sizeof( level.voteTime ) ); + for( i = 0; i < NUM_TEAMS; i++ ) + { + trap_SetConfigstring( CS_VOTE_TIME + i, "" ); + trap_SetConfigstring( CS_VOTE_STRING + i, "" ); + } +} + +/* +================= +G_ShutdownGame +================= +*/ +void G_ShutdownGame( int restart ) +{ + // in case of a map_restart + G_ClearVotes( ); + + G_RestoreCvars( ); + + G_Printf( "==== ShutdownGame ====\n" ); + + if( level.logFile ) + { + G_LogPrintf( "ShutdownGame:\n" ); + G_LogPrintf( "------------------------------------------------------------\n" ); + trap_FS_FCloseFile( level.logFile ); + level.logFile = 0; + } + + // write all the client session data so we can get it back + G_WriteSessionData( ); + + G_admin_cleanup( ); + G_namelog_cleanup( ); + G_UnregisterCommands( ); + + G_ShutdownMapRotations( ); + + level.restarted = qfalse; + level.surrenderTeam = TEAM_NONE; + trap_SetConfigstring( CS_WINNER, "" ); +} + + + +//=================================================================== + +void QDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, error ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); + va_end( argptr ); + + G_Error( "%s", text ); +} + +void QDECL Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + G_Printf( "%s", text ); +} + +/* +======================================================================== + +PLAYER COUNTING / SCORE SORTING + +======================================================================== +*/ + + +/* +============= +SortRanks + +============= +*/ +int QDECL SortRanks( const void *a, const void *b ) +{ + gclient_t *ca, *cb; + + ca = &level.clients[ *(int *)a ]; + cb = &level.clients[ *(int *)b ]; + + // then sort by score + if( ca->ps.persistant[ PERS_SCORE ] > cb->ps.persistant[ PERS_SCORE ] ) + return -1; + if( ca->ps.persistant[ PERS_SCORE ] < cb->ps.persistant[ PERS_SCORE ] ) + return 1; + else + return 0; +} + +/* +============ +G_InitSpawnQueue + +Initialise a spawn queue +============ +*/ +void G_InitSpawnQueue( spawnQueue_t *sq ) +{ + int i; + + sq->back = sq->front = 0; + sq->back = QUEUE_MINUS1( sq->back ); + + //0 is a valid clientNum, so use something else + for( i = 0; i < MAX_CLIENTS; i++ ) + sq->clients[ i ] = -1; +} + +/* +============ +G_GetSpawnQueueLength + +Return the length of a spawn queue +============ +*/ +int G_GetSpawnQueueLength( spawnQueue_t *sq ) +{ + int length = sq->back - sq->front + 1; + + while( length < 0 ) + length += MAX_CLIENTS; + + while( length >= MAX_CLIENTS ) + length -= MAX_CLIENTS; + + return length; +} + +/* +============ +G_PopSpawnQueue + +Remove from front element from a spawn queue +============ +*/ +int G_PopSpawnQueue( spawnQueue_t *sq ) +{ + int clientNum = sq->clients[ sq->front ]; + + if( G_GetSpawnQueueLength( sq ) > 0 ) + { + sq->clients[ sq->front ] = -1; + sq->front = QUEUE_PLUS1( sq->front ); + G_StopFollowing( g_entities + clientNum ); + g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED; + + return clientNum; + } + else + return -1; +} + +/* +============ +G_PeekSpawnQueue + +Look at front element from a spawn queue +============ +*/ +int G_PeekSpawnQueue( spawnQueue_t *sq ) +{ + return sq->clients[ sq->front ]; +} + +/* +============ +G_SearchSpawnQueue + +Look to see if clientNum is already in the spawnQueue +============ +*/ +qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum ) +{ + int i; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + if( sq->clients[ i ] == clientNum ) + return qtrue; + } + + return qfalse; +} + +/* +============ +G_PushSpawnQueue + +Add an element to the back of the spawn queue +============ +*/ +qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum ) +{ + // don't add the same client more than once + if( G_SearchSpawnQueue( sq, clientNum ) ) + return qfalse; + + sq->back = QUEUE_PLUS1( sq->back ); + sq->clients[ sq->back ] = clientNum; + + g_entities[ clientNum ].client->ps.pm_flags |= PMF_QUEUED; + return qtrue; +} + +/* +============ +G_RemoveFromSpawnQueue + +remove a specific client from a spawn queue +============ +*/ +qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum ) +{ + int i = sq->front; + + if( G_GetSpawnQueueLength( sq ) ) + { + do + { + if( sq->clients[ i ] == clientNum ) + { + //and this kids is why it would have + //been better to use an LL for internal + //representation + do + { + sq->clients[ i ] = sq->clients[ QUEUE_PLUS1( i ) ]; + + i = QUEUE_PLUS1( i ); + } while( i != QUEUE_PLUS1( sq->back ) ); + + sq->back = QUEUE_MINUS1( sq->back ); + g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED; + + return qtrue; + } + + i = QUEUE_PLUS1( i ); + } while( i != QUEUE_PLUS1( sq->back ) ); + } + + return qfalse; +} + +/* +============ +G_GetPosInSpawnQueue + +Get the position of a client in a spawn queue +============ +*/ +int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum ) +{ + int i = sq->front; + + if( G_GetSpawnQueueLength( sq ) ) + { + do + { + if( sq->clients[ i ] == clientNum ) + { + if( i < sq->front ) + return i + MAX_CLIENTS - sq->front; + else + return i - sq->front; + } + + i = QUEUE_PLUS1( i ); + } while( i != QUEUE_PLUS1( sq->back ) ); + } + + return -1; +} + +/* +============ +G_PrintSpawnQueue + +Print the contents of a spawn queue +============ +*/ +void G_PrintSpawnQueue( spawnQueue_t *sq ) +{ + int i = sq->front; + int length = G_GetSpawnQueueLength( sq ); + + G_Printf( "l:%d f:%d b:%d :", length, sq->front, sq->back ); + + if( length > 0 ) + { + do + { + if( sq->clients[ i ] == -1 ) + G_Printf( "*:" ); + else + G_Printf( "%d:", sq->clients[ i ] ); + + i = QUEUE_PLUS1( i ); + } while( i != QUEUE_PLUS1( sq->back ) ); + } + + G_Printf( "\n" ); +} + +/* +============ +G_SpawnClients + +Spawn queued clients +============ +*/ +void G_SpawnClients( team_t team ) +{ + int clientNum; + gentity_t *ent, *spawn; + vec3_t spawn_origin, spawn_angles; + spawnQueue_t *sq = NULL; + int numSpawns = 0; + + if( team == TEAM_ALIENS ) + { + sq = &level.alienSpawnQueue; + numSpawns = level.numAlienSpawns + level.numAlienImplantedSpawns; + } + else if( team == TEAM_HUMANS ) + { + sq = &level.humanSpawnQueue; + numSpawns = level.numHumanSpawns + level.numHumanImplantedSpawns; + } + + if( G_GetSpawnQueueLength( sq ) > 0 && numSpawns > 0 ) + { + clientNum = G_PeekSpawnQueue( sq ); + ent = &g_entities[ clientNum ]; + + if( ( spawn = G_SelectTremulousSpawnPoint( team, + ent->client->pers.lastDeathLocation, + spawn_origin, spawn_angles ) ) ) + { + clientNum = G_PopSpawnQueue( sq ); + + if( clientNum < 0 ) + return; + + ent = &g_entities[ clientNum ]; + + ent->client->sess.spectatorState = SPECTATOR_NOT; + ClientUserinfoChanged( clientNum, qfalse ); + ClientSpawn( ent, spawn, spawn_origin, spawn_angles ); + } + } +} + +/* +============ +G_CountSpawns + +Counts the number of spawns for each team +============ +*/ +void G_CountSpawns( void ) +{ + int i; + gentity_t *ent; + + level.numAlienSpawns = 0; + level.numAlienImplantedSpawns = 0; + level.numHumanSpawns = 0; + level.numHumanImplantedSpawns = 0; + for( i = 0, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( ent->s.eType == ET_BUILDABLE && ent->health > 0 ) + { + if( ent->s.modelindex == BA_A_SPAWN ) + level.numAlienSpawns++; + + else if( ent->s.modelindex == BA_H_SPAWN ) + level.numHumanSpawns++; + } + else if( ent->client && ent->client->sess.spectatorState == SPECTATOR_NOT && + ent->client->ps.stats[ STAT_HEALTH ] > 0 && + ent->client->isImpregnated && ent->client->isImplantMature ) + { + if( ent->client->pers.teamSelection == TEAM_HUMANS ) + level.numAlienImplantedSpawns++; + + else if( ent->client->pers.teamSelection == TEAM_ALIENS ) + level.numHumanImplantedSpawns++; + } + } +} + + +/* +============ +G_TimeTilSuddenDeath +============ +*/ +#define SUDDENDEATHWARNING 60000 +int G_TimeTilSuddenDeath( void ) +{ + if( ( !g_suddenDeathTime.integer && level.suddenDeathBeginTime == 0 ) || + ( level.suddenDeathBeginTime < 0 ) ) + return SUDDENDEATHWARNING + 1; // Always some time away + + return ( ( level.suddenDeathBeginTime ) - ( level.time - level.startTime ) ); +} + + +#define PLAYER_COUNT_MOD 5.0f + +/* +============ +G_CalculateBuildPoints + +Recalculate the quantity of building points available to the teams +============ +*/ +void G_CalculateBuildPoints( void ) +{ + int i; + buildable_t buildable; + buildPointZone_t *zone; + + // BP queue updates + while( level.alienBuildPointQueue > 0 && + level.alienNextQueueTime < level.time ) + { + level.alienBuildPointQueue--; + level.alienNextQueueTime += G_NextQueueTime( level.alienBuildPointQueue, + g_alienBuildPoints.integer, + g_alienBuildQueueTime.integer ); + } + + while( level.humanBuildPointQueue > 0 && + level.humanNextQueueTime < level.time ) + { + level.humanBuildPointQueue--; + level.humanNextQueueTime += G_NextQueueTime( level.humanBuildPointQueue, + g_humanBuildPoints.integer, + g_humanBuildQueueTime.integer ); + } + + // Sudden Death checks + if( G_TimeTilSuddenDeath( ) <= 0 && level.suddenDeathWarning < TW_PASSED ) + { + G_LogPrintf( "Beginning Sudden Death\n" ); + trap_SendServerCommand( -1, "cp \"Sudden Death!\"" ); + trap_SendServerCommand( -1, "print \"Beginning Sudden Death.\n\"" ); + level.suddenDeathWarning = TW_PASSED; + G_ClearDeconMarks( ); + + // Clear blueprints, or else structs that cost 0 BP can still be built after SD + for( i = 0; i < level.maxclients; i++ ) + { + if( g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) + g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + } + } + else if( G_TimeTilSuddenDeath( ) <= SUDDENDEATHWARNING && + level.suddenDeathWarning < TW_IMMINENT ) + { + trap_SendServerCommand( -1, va( "cp \"Sudden Death in %d seconds!\"", + (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); + trap_SendServerCommand( -1, va( "print \"Sudden Death will begin in %d seconds.\n\"", + (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); + level.suddenDeathWarning = TW_IMMINENT; + } + + level.humanBuildPoints = g_humanBuildPoints.integer - level.humanBuildPointQueue; + level.alienBuildPoints = g_alienBuildPoints.integer - level.alienBuildPointQueue; + + // Reset buildPointZones + for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) + { + buildPointZone_t *zone = &level.buildPointZones[ i ]; + + zone->active = qfalse; + zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; + } + + // Iterate through entities + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + gentity_t *ent = &g_entities[ i ]; + buildPointZone_t *zone; + buildable_t buildable; + int cost; + + if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD ) + continue; + + // mark a zone as active + if( ent->usesBuildPointZone ) + { + assert( ent->buildPointZone >= 0 && ent->buildPointZone < g_humanRepeaterMaxZones.integer ); + + zone = &level.buildPointZones[ ent->buildPointZone ]; + zone->active = qtrue; + } + + // Subtract the BP from the appropriate pool + buildable = ent->s.modelindex; + cost = BG_Buildable( buildable, ent->cuboidSize )->buildPoints; + + if( ent->buildableTeam == TEAM_ALIENS ) + level.alienBuildPoints -= cost; + if( buildable == BA_H_REPEATER ) + level.humanBuildPoints -= cost; + else if( buildable != BA_H_REACTOR ) + { + gentity_t *power = G_PowerEntityForEntity( ent ); + + if( power ) + { + if( power->s.modelindex == BA_H_REACTOR ) + level.humanBuildPoints -= cost; + else if( power->s.modelindex == BA_H_REPEATER && power->usesBuildPointZone ) + level.buildPointZones[ power->buildPointZone ].totalBuildPoints -= cost; + } + } + } + + // Finally, update repeater zones and their queues + // note that this has to be done after the used BP is calculated + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + gentity_t *ent = &g_entities[ i ]; + + if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD || + ent->buildableTeam != TEAM_HUMANS ) + continue; + + buildable = ent->s.modelindex; + + if( buildable != BA_H_REPEATER ) + continue; + + if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) + { + zone = &level.buildPointZones[ ent->buildPointZone ]; + + if( G_TimeTilSuddenDeath( ) > 0 ) + { + // BP queue updates + while( zone->queuedBuildPoints > 0 && + zone->nextQueueTime < level.time ) + { + zone->queuedBuildPoints--; + zone->nextQueueTime += G_NextQueueTime( zone->queuedBuildPoints, + zone->totalBuildPoints, + g_humanRepeaterBuildQueueTime.integer ); + } + } + else + { + zone->totalBuildPoints = zone->queuedBuildPoints = 0; + } + } + } + + if( level.humanBuildPoints < 0 ) + level.humanBuildPoints = 0; + + if( level.alienBuildPoints < 0 ) + level.alienBuildPoints = 0; +} + +/* +============ +G_CalculateStages +============ +*/ +void G_CalculateStages( void ) +{ + float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD; + float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD; + int alienNextStageThreshold, humanNextStageThreshold; + static int lastAlienStageModCount = 1; + static int lastHumanStageModCount = 1; + + if( alienPlayerCountMod < 0.1f ) + alienPlayerCountMod = 0.1f; + + if( humanPlayerCountMod < 0.1f ) + humanPlayerCountMod = 0.1f; + + if( g_alienCredits.integer >= + (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ) && + g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) + { + trap_Cvar_Set( "g_alienStage", va( "%d", S2 ) ); + level.alienStage2Time = level.time; + lastAlienStageModCount = g_alienStage.modificationCount; + G_LogPrintf("Stage: A 2: Aliens reached Stage 2\n"); + } + + if( g_alienCredits.integer >= + (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ) && + g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) + { + trap_Cvar_Set( "g_alienStage", va( "%d", S3 ) ); + level.alienStage3Time = level.time; + lastAlienStageModCount = g_alienStage.modificationCount; + G_LogPrintf("Stage: A 3: Aliens reached Stage 3\n"); + } + + if( g_humanCredits.integer >= + (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ) && + g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) + { + trap_Cvar_Set( "g_humanStage", va( "%d", S2 ) ); + level.humanStage2Time = level.time; + lastHumanStageModCount = g_humanStage.modificationCount; + G_LogPrintf("Stage: H 2: Humans reached Stage 2\n"); + } + + if( g_humanCredits.integer >= + (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ) && + g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) + { + trap_Cvar_Set( "g_humanStage", va( "%d", S3 ) ); + level.humanStage3Time = level.time; + lastHumanStageModCount = g_humanStage.modificationCount; + G_LogPrintf("Stage: H 3: Humans reached Stage 3\n"); + } + + if( g_alienStage.modificationCount > lastAlienStageModCount ) + { + G_Checktrigger_stages( TEAM_ALIENS, g_alienStage.integer ); + + if( g_alienStage.integer == S2 ) + level.alienStage2Time = level.time; + else if( g_alienStage.integer == S3 ) + level.alienStage3Time = level.time; + + lastAlienStageModCount = g_alienStage.modificationCount; + } + + if( g_humanStage.modificationCount > lastHumanStageModCount ) + { + G_Checktrigger_stages( TEAM_HUMANS, g_humanStage.integer ); + + if( g_humanStage.integer == S2 ) + level.humanStage2Time = level.time; + else if( g_humanStage.integer == S3 ) + level.humanStage3Time = level.time; + + lastHumanStageModCount = g_humanStage.modificationCount; + } + + if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) + alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ); + else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) + alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ); + else + alienNextStageThreshold = -1; + + if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) + humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ); + else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) + humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ); + else + humanNextStageThreshold = -1; + + // save a lot of bandwidth by rounding thresholds up to the nearest 100 + if( alienNextStageThreshold > 0 ) + alienNextStageThreshold = ceil( (float)alienNextStageThreshold / 100 ) * 100; + + if( humanNextStageThreshold > 0 ) + humanNextStageThreshold = ceil( (float)humanNextStageThreshold / 100 ) * 100; + + trap_SetConfigstring( CS_ALIEN_STAGES, va( "%d %d %d", + g_alienStage.integer, g_alienCredits.integer, + alienNextStageThreshold ) ); + + trap_SetConfigstring( CS_HUMAN_STAGES, va( "%d %d %d", + g_humanStage.integer, g_humanCredits.integer, + humanNextStageThreshold ) ); +} + +/* +============ +CalculateAvgPlayers + +Calculates the average number of players playing this game +============ +*/ +void G_CalculateAvgPlayers( void ) +{ + //there are no clients or only spectators connected, so + //reset the number of samples in order to avoid the situation + //where the average tends to 0 + if( !level.numAlienClients ) + { + level.numAlienSamples = 0; + trap_Cvar_Set( "g_alienCredits", "0" ); + } + + if( !level.numHumanClients ) + { + level.numHumanSamples = 0; + trap_Cvar_Set( "g_humanCredits", "0" ); + } + + //calculate average number of clients for stats + level.averageNumAlienClients = + ( ( level.averageNumAlienClients * level.numAlienSamples ) + + level.numAlienClients ) / + (float)( level.numAlienSamples + 1 ); + level.numAlienSamples++; + + level.averageNumHumanClients = + ( ( level.averageNumHumanClients * level.numHumanSamples ) + + level.numHumanClients ) / + (float)( level.numHumanSamples + 1 ); + level.numHumanSamples++; +} + +/* +============ +CalculateRanks + +Recalculates the score ranks of all players +This will be called on every client connect, begin, disconnect, death, +and team change. +============ +*/ +void CalculateRanks( void ) +{ + int i; + char P[ MAX_CLIENTS + 1 ] = {""}; + + level.numConnectedClients = 0; + level.numPlayingClients = 0; + memset( level.numVotingClients, 0, sizeof( level.numVotingClients ) ); + level.numAlienClients = 0; + level.numHumanClients = 0; + level.numLiveAlienClients = 0; + level.numLiveHumanClients = 0; + + for( i = 0; i < level.maxclients; i++ ) + { + P[ i ] = '-'; + if ( level.clients[ i ].pers.connected != CON_DISCONNECTED ) + { + level.sortedClients[ level.numConnectedClients ] = i; + level.numConnectedClients++; + P[ i ] = (char)'0' + level.clients[ i ].pers.teamSelection; + + level.numVotingClients[ TEAM_NONE ]++; + + if( level.clients[ i ].pers.connected != CON_CONNECTED ) + continue; + + if( level.clients[ i ].pers.teamSelection != TEAM_NONE ) + { + level.numPlayingClients++; + if( level.clients[ i ].pers.teamSelection == TEAM_ALIENS ) + { + level.numAlienClients++; + if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) + level.numLiveAlienClients++; + } + else if( level.clients[ i ].pers.teamSelection == TEAM_HUMANS ) + { + level.numHumanClients++; + if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) + level.numLiveHumanClients++; + } + } + } + } + level.numNonSpectatorClients = level.numLiveAlienClients + + level.numLiveHumanClients; + level.numVotingClients[ TEAM_ALIENS ] = level.numAlienClients; + level.numVotingClients[ TEAM_HUMANS ] = level.numHumanClients; + P[ i ] = '\0'; + trap_Cvar_Set( "P", P ); + + qsort( level.sortedClients, level.numConnectedClients, + sizeof( level.sortedClients[ 0 ] ), SortRanks ); + + // see if it is time to end the level + CheckExitRules( ); + + // if we are at the intermission, send the new info to everyone + if( level.intermissiontime ) + SendScoreboardMessageToAllClients( ); +} + + +/* +======================================================================== + +MAP CHANGING + +======================================================================== +*/ + +/* +======================== +SendScoreboardMessageToAllClients + +Do this at BeginIntermission time and whenever ranks are recalculated +due to enters/exits/forced team changes +======================== +*/ +void SendScoreboardMessageToAllClients( void ) +{ + int i; + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + ScoreboardMessage( g_entities + i ); + } +} + +/* +======================== +MoveClientToIntermission + +When the intermission starts, this will be called for all players. +If a new client connects, this will be called after the spawn function. +======================== +*/ +void MoveClientToIntermission( gentity_t *ent ) +{ + // take out of follow mode if needed + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + + // move to the spot + VectorCopy( level.intermission_origin, ent->s.origin ); + VectorCopy( level.intermission_origin, ent->client->ps.origin ); + VectorCopy( level.intermission_angle, ent->client->ps.viewangles ); + ent->client->ps.pm_type = PM_INTERMISSION; + + // clean up powerup info + memset( ent->client->ps.misc, 0, sizeof( ent->client->ps.misc ) ); + + ent->client->ps.eFlags = 0; + ent->s.eFlags = 0; + ent->s.eType = ET_GENERAL; + ent->s.modelindex = 0; + ent->s.loopSound = 0; + ent->s.event = 0; + ent->r.contents = 0; +} + +/* +================== +FindIntermissionPoint + +This is also used for spectator spawns +================== +*/ +void FindIntermissionPoint( void ) +{ + gentity_t *ent, *target; + vec3_t dir; + + // find the intermission spot + ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" ); + + if( !ent ) + { // the map creator forgot to put in an intermission point... + G_SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle ); + } + else + { + VectorCopy( ent->s.origin, level.intermission_origin ); + VectorCopy( ent->s.angles, level.intermission_angle ); + // if it has a target, look towards it + if( ent->target ) + { + target = G_PickTarget( ent->target ); + + if( target ) + { + VectorSubtract( target->s.origin, level.intermission_origin, dir ); + vectoangles( dir, level.intermission_angle ); + } + } + } + +} + +/* +================== +BeginIntermission +================== +*/ +void BeginIntermission( void ) +{ + int i; + gentity_t *client; + + if( level.intermissiontime ) + return; // already active + + level.intermissiontime = level.time; + + G_ClearVotes( ); + + G_UpdateTeamConfigStrings( ); + + FindIntermissionPoint( ); + + // move all clients to the intermission point + for( i = 0; i < level.maxclients; i++ ) + { + client = g_entities + i; + + if( !client->inuse ) + continue; + + // respawn if dead + if( client->health <= 0 ) + respawn(client); + + MoveClientToIntermission( client ); + } + + // send the current scoring to all clients + SendScoreboardMessageToAllClients( ); +} + + +/* +============= +ExitLevel + +When the intermission has been exited, the server is either moved +to a new map based on the map rotation or the current map restarted +============= +*/ +void ExitLevel( void ) +{ + int i; + gclient_t *cl; + + if ( G_MapExists( g_nextMap.string ) ) + trap_SendConsoleCommand( EXEC_APPEND, va("map \"%s\"\n", g_nextMap.string ) ); + else if( G_MapRotationActive( ) ) + G_AdvanceMapRotation( 0 ); + else + trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); + + trap_Cvar_Set( "g_nextMap", "" ); + + level.restarted = qtrue; + level.changemap = NULL; + level.intermissiontime = 0; + + // reset all the scores so we don't enter the intermission again + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + if( cl->pers.connected != CON_CONNECTED ) + continue; + + cl->ps.persistant[ PERS_SCORE ] = 0; + } + + // we need to do this here before chaning to CON_CONNECTING + G_WriteSessionData( ); + + // change all client states to connecting, so the early players into the + // next level will know the others aren't done reconnecting + for( i = 0; i < g_maxclients.integer; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + level.clients[ i ].pers.connected = CON_CONNECTING; + } + +} + +/* +================= +G_AdminMessage + +Print to all active server admins, and to the logfile, and to the server console +================= +*/ +void G_AdminMessage( gentity_t *ent, const char *msg ) +{ + char string[ 1024 ]; + int i; + + Com_sprintf( string, sizeof( string ), "chat %d %d \"%s\"", + ent ? ent - g_entities : -1, + G_admin_permission( ent, ADMF_ADMINCHAT ) ? SAY_ADMINS : SAY_ADMINS_PUBLIC, + msg ); + + // Send to all appropriate clients + for( i = 0; i < level.maxclients; i++ ) + if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) + trap_SendServerCommand( i, string ); + + // Send to the logfile and server console + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_MAGENTA "%s\n", + G_admin_permission( ent, ADMF_ADMINCHAT ) ? "AdminMsg" : "AdminMsgPublic", + ent ? ent - g_entities : -1, ent ? ent->client->pers.netname : "console", + msg ); +} + + +/* +================= +G_LogPrintf + +Print to the logfile with a time stamp if it is open, and to the server console +================= +*/ +void QDECL G_LogPrintf( const char *fmt, ... ) +{ + va_list argptr; + char string[ 1024 ], decolored[ 1024 ]; + int min, tens, sec; + + sec = ( level.time - level.startTime ) / 1000; + + min = sec / 60; + sec -= min * 60; + tens = sec / 10; + sec -= tens * 10; + + Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); + + va_start( argptr, fmt ); + Q_vsnprintf( string + 7, sizeof( string ) - 7, fmt, argptr ); + va_end( argptr ); + + if( g_dedicated.integer ) + { + G_UnEscapeString( string, decolored, sizeof( decolored ) ); + G_Printf( "%s", decolored + 7 ); + } + + if( !level.logFile ) + return; + + G_DecolorString( string, decolored, sizeof( decolored ) ); + trap_FS_Write( decolored, strlen( decolored ), level.logFile ); +} + +/* +================= +G_SendGameStat +================= +*/ +void G_SendGameStat( team_t team ) +{ + char map[ MAX_STRING_CHARS ]; + char teamChar; + char data[ BIG_INFO_STRING ]; + char entry[ MAX_STRING_CHARS ]; + int i, dataLength, entryLength; + gclient_t *cl; + + // games with cheats enabled are not very good for balance statistics + if( g_cheats.integer ) + return; + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + + switch( team ) + { + case TEAM_ALIENS: teamChar = 'A'; break; + case TEAM_HUMANS: teamChar = 'H'; break; + case TEAM_NONE: teamChar = 'L'; break; + default: return; + } + + Com_sprintf( data, BIG_INFO_STRING, + "%s %s T:%c A:%f H:%f M:%s D:%d SD:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d", + Q3_VERSION, + g_tag.string, + teamChar, + level.averageNumAlienClients, + level.averageNumHumanClients, + map, + level.time - level.startTime, + G_TimeTilSuddenDeath( ), + g_alienStage.integer, + level.alienStage2Time - level.startTime, + level.alienStage3Time - level.startTime, + g_humanStage.integer, + level.humanStage2Time - level.startTime, + level.humanStage3Time - level.startTime, + level.numConnectedClients ); + + dataLength = strlen( data ); + + for( i = 0; i < level.numConnectedClients; i++ ) + { + int ping; + + cl = &level.clients[ level.sortedClients[ i ] ]; + + if( cl->pers.connected == CON_CONNECTING ) + ping = -1; + else + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + + switch( cl->ps.stats[ STAT_TEAM ] ) + { + case TEAM_ALIENS: teamChar = 'A'; break; + case TEAM_HUMANS: teamChar = 'H'; break; + case TEAM_NONE: teamChar = 'S'; break; + default: return; + } + + Com_sprintf( entry, MAX_STRING_CHARS, + " \"%s\" %c %d %d %d", + cl->pers.netname, + teamChar, + cl->ps.persistant[ PERS_SCORE ], + ping, + ( level.time - cl->pers.enterTime ) / 60000 ); + + entryLength = strlen( entry ); + + if( dataLength + entryLength >= BIG_INFO_STRING ) + break; + + strcpy( data + dataLength, entry ); + dataLength += entryLength; + } + + trap_SendGameStat( data ); +} + +/* +================ +LogExit + +Append information about this game to the log file +================ +*/ +void LogExit( const char *string ) +{ + int i, numSorted; + gclient_t *cl; + gentity_t *ent; + + G_LogPrintf( "Exit: %s\n", string ); + + level.intermissionQueued = level.time; + + // this will keep the clients from playing any voice sounds + // that will get cut off when the queued intermission starts + trap_SetConfigstring( CS_INTERMISSION, "1" ); + + // don't send more than 32 scores (FIXME?) + numSorted = level.numConnectedClients; + if( numSorted > 32 ) + numSorted = 32; + + for( i = 0; i < numSorted; i++ ) + { + int ping; + + cl = &level.clients[ level.sortedClients[ i ] ]; + + if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE ) + continue; + + if( cl->pers.connected == CON_CONNECTING ) + continue; + + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + + G_LogPrintf( "score: %i ping: %i client: %i %s\n", + cl->ps.persistant[ PERS_SCORE ], ping, level.sortedClients[ i ], + cl->pers.netname ); + + } + + for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( !Q_stricmp( ent->classname, "trigger_win" ) ) + { + if( level.lastWin == ent->stageTeam ) + ent->use( ent, ent, ent ); + } + } + + G_SendGameStat( level.lastWin ); +} + + +/* +================= +CheckIntermissionExit + +The level will stay at the intermission for a minimum of 5 seconds +If all players wish to continue, the level will then exit. +If one or more players have not acknowledged the continue, the game will +wait 10 seconds before going on. +================= +*/ +void CheckIntermissionExit( void ) +{ + int ready, notReady; + int i; + gclient_t *cl; + clientList_t readyMasks; + + //if no clients are connected, just exit + if( level.numConnectedClients == 0 ) + { + ExitLevel( ); + return; + } + + // see which players are ready + ready = 0; + notReady = 0; + Com_Memset( &readyMasks, 0, sizeof( readyMasks ) ); + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + + if( cl->pers.connected != CON_CONNECTED ) + continue; + + if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE ) + continue; + + if( cl->readyToExit ) + { + ready++; + + Com_ClientListAdd( &readyMasks, i ); + } + else + notReady++; + } + + trap_SetConfigstring( CS_CLIENTS_READY, Com_ClientListString( &readyMasks ) ); + + // never exit in less than five seconds + if( level.time < level.intermissiontime + 5000 ) + return; + + // never let intermission go on for over 1 minute + if( level.time > level.intermissiontime + 60000 ) + { + ExitLevel( ); + return; + } + + // if nobody wants to go, clear timer + if( ready == 0 && notReady > 0 ) + { + level.readyToExit = qfalse; + return; + } + + // if everyone wants to go, go now + if( notReady == 0 ) + { + ExitLevel( ); + return; + } + + // the first person to ready starts the thirty second timeout + if( !level.readyToExit ) + { + level.readyToExit = qtrue; + level.exitTime = level.time; + } + + // if we have waited thirty seconds since at least one player + // wanted to exit, go ahead + if( level.time < level.exitTime + 30000 ) + return; + + ExitLevel( ); +} + +/* +============= +ScoreIsTied +============= +*/ +qboolean ScoreIsTied( void ) +{ + int a, b; + + if( level.numPlayingClients < 2 ) + return qfalse; + + a = level.clients[ level.sortedClients[ 0 ] ].ps.persistant[ PERS_SCORE ]; + b = level.clients[ level.sortedClients[ 1 ] ].ps.persistant[ PERS_SCORE ]; + + return a == b; +} + +/* +================= +CheckExitRules + +There will be a delay between the time the exit is qualified for +and the time everyone is moved to the intermission spot, so you +can see the last frag. +================= +*/ +void CheckExitRules( void ) +{ + // if at the intermission, wait for all non-bots to + // signal ready, then go to next level + if( level.intermissiontime ) + { + CheckIntermissionExit( ); + return; + } + + if( level.intermissionQueued ) + { + if( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME ) + { + level.intermissionQueued = 0; + BeginIntermission( ); + } + + return; + } + + if( g_timelimit.integer ) + { + if( level.time - level.startTime >= g_timelimit.integer * 60000 ) + { + level.lastWin = TEAM_NONE; + trap_SendServerCommand( -1, "print \"Timelimit hit\n\"" ); + trap_SetConfigstring( CS_WINNER, "Stalemate" ); + LogExit( "Timelimit hit." ); + return; + } + else if( level.time - level.startTime >= ( g_timelimit.integer - 5 ) * 60000 && + level.timelimitWarning < TW_IMMINENT ) + { + trap_SendServerCommand( -1, "cp \"5 minutes remaining!\"" ); + level.timelimitWarning = TW_IMMINENT; + } + else if( level.time - level.startTime >= ( g_timelimit.integer - 1 ) * 60000 && + level.timelimitWarning < TW_PASSED ) + { + trap_SendServerCommand( -1, "cp \"1 minute remaining!\"" ); + level.timelimitWarning = TW_PASSED; + } + } + + if( level.uncondHumanWin || + ( !level.uncondAlienWin && + ( level.time > level.startTime + 1000 ) && + ( level.numAlienSpawns == 0 ) && + ( level.numAlienImplantedSpawns == 0 ) && + ( level.numLiveAlienClients == 0 ) ) ) + { + //humans win + level.lastWin = TEAM_HUMANS; + trap_SendServerCommand( -1, "print \"Humans win\n\""); + trap_SetConfigstring( CS_WINNER, "Humans Win" ); + LogExit( "Humans win." ); + } + else if( level.uncondAlienWin || + ( ( level.time > level.startTime + 1000 ) && + ( level.numHumanSpawns == 0 ) && + ( level.numHumanImplantedSpawns == 0 ) && + ( level.numLiveHumanClients == 0 ) ) ) + { + //aliens win + level.lastWin = TEAM_ALIENS; + trap_SendServerCommand( -1, "print \"Aliens win\n\""); + trap_SetConfigstring( CS_WINNER, "Aliens Win" ); + LogExit( "Aliens win." ); + } +} + +/* +================== +G_Vote +================== +*/ +void G_Vote( gentity_t *ent, team_t team, qboolean voting ) +{ + if( !level.voteTime[ team ] ) + return; + + if( voting && ent->client->pers.voted & ( 1 << team ) ) + return; + + if( !voting && !( ent->client->pers.voted & ( 1 << team ) ) ) + return; + + ent->client->pers.voted |= 1 << team; + + if( ent->client->pers.vote & ( 1 << team ) ) + { + if( voting ) + level.voteYes[ team ]++; + else + level.voteYes[ team ]--; + + trap_SetConfigstring( CS_VOTE_YES + team, + va( "%d", level.voteYes[ team ] ) ); + } + else + { + if( voting ) + level.voteNo[ team ]++; + else + level.voteNo[ team ]--; + + trap_SetConfigstring( CS_VOTE_NO + team, + va( "%d", level.voteNo[ team ] ) ); + } +} + + +/* +======================================================================== + +FUNCTIONS CALLED EVERY FRAME + +======================================================================== +*/ + + +void G_ExecuteVote( team_t team ) +{ + level.voteExecuteTime[ team ] = 0; + + trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", + level.voteString[ team ] ) ); + + if( !Q_stricmpn( level.voteString[ team ], "map", 3 ) ) + level.restarted = qtrue; +} + +/* +================== +G_CheckVote +================== +*/ +void G_CheckVote( team_t team ) +{ + float votePassThreshold = (float)level.voteThreshold[ team ] / 100.0f; + qboolean pass = qfalse; + char *msg; + int i; + + if( level.voteExecuteTime[ team ] && + level.voteExecuteTime[ team ] < level.time ) + { + G_ExecuteVote( team ); + } + + if( !level.voteTime[ team ] ) + return; + + if( ( level.time - level.voteTime[ team ] >= VOTE_TIME ) || + ( level.voteYes[ team ] + level.voteNo[ team ] == level.numVotingClients[ team ] ) ) + { + pass = ( level.voteYes[ team ] && + (float)level.voteYes[ team ] / ( (float)level.voteYes[ team ] + (float)level.voteNo[ team ] ) > votePassThreshold ); + } + else + { + if( (float)level.voteYes[ team ] > + (float)level.numVotingClients[ team ] * votePassThreshold ) + { + pass = qtrue; + } + else if( (float)level.voteNo[ team ] <= + (float)level.numVotingClients[ team ] * ( 1.0f - votePassThreshold ) ) + { + return; + } + } + + if( pass ) + level.voteExecuteTime[ team ] = level.time + level.voteDelay[ team ]; + + G_LogPrintf( "EndVote: %s %s %d %d %d\n", + team == TEAM_NONE ? "global" : BG_TeamName( team ), + pass ? "pass" : "fail", + level.voteYes[ team ], level.voteNo[ team ], level.numVotingClients[ team ] ); + + msg = va( "print \"%sote %sed (%d - %d)\n\"", + team == TEAM_NONE ? "V" : "Team v", pass ? "pass" : "fail", + level.voteYes[ team ], level.voteNo[ team ] ); + + if( team == TEAM_NONE ) + trap_SendServerCommand( -1, msg ); + else + G_TeamCommand( team, msg ); + + level.voteTime[ team ] = 0; + level.voteYes[ team ] = 0; + level.voteNo[ team ] = 0; + + for( i = 0; i < level.maxclients; i++ ) + level.clients[ i ].pers.voted &= ~( 1 << team ); + + trap_SetConfigstring( CS_VOTE_TIME + team, "" ); + trap_SetConfigstring( CS_VOTE_STRING + team, "" ); + trap_SetConfigstring( CS_VOTE_YES + team, "0" ); + trap_SetConfigstring( CS_VOTE_NO + team, "0" ); +} + + +/* +================== +CheckCvars +================== +*/ +void CheckCvars( void ) +{ + static int lastPasswordModCount = -1; + static int lastMarkDeconModCount = -1; + static int lastSDTimeModCount = -1; + static int lastNumZones = 0; + + if( g_password.modificationCount != lastPasswordModCount ) + { + lastPasswordModCount = g_password.modificationCount; + + if( *g_password.string && Q_stricmp( g_password.string, "none" ) ) + trap_Cvar_Set( "g_needpass", "1" ); + else + trap_Cvar_Set( "g_needpass", "0" ); + } + + // Unmark any structures for deconstruction when + // the server setting is changed + if( g_markDeconstruct.modificationCount != lastMarkDeconModCount ) + { + lastMarkDeconModCount = g_markDeconstruct.modificationCount; + G_ClearDeconMarks( ); + } + + // If we change g_suddenDeathTime during a map, we need to update + // when sd will begin + if( g_suddenDeathTime.modificationCount != lastSDTimeModCount ) + { + lastSDTimeModCount = g_suddenDeathTime.modificationCount; + level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; + } + + // If the number of zones changes, we need a new array + if( g_humanRepeaterMaxZones.integer != lastNumZones ) + { + buildPointZone_t *newZones; + size_t newsize = g_humanRepeaterMaxZones.integer * sizeof( buildPointZone_t ); + size_t oldsize = lastNumZones * sizeof( buildPointZone_t ); + + newZones = BG_Alloc( newsize ); + if( level.buildPointZones ) + { + Com_Memcpy( newZones, level.buildPointZones, MIN( oldsize, newsize ) ); + BG_Free( level.buildPointZones ); + } + + level.buildPointZones = newZones; + lastNumZones = g_humanRepeaterMaxZones.integer; + } + + level.frameMsec = trap_Milliseconds( ); +} + +/* +============= +G_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +void G_RunThink( gentity_t *ent ) +{ + float thinktime; + + thinktime = ent->nextthink; + if( thinktime <= 0 ) + return; + + if( thinktime > level.time ) + return; + + ent->nextthink = 0; + if( !ent->think ) + G_Error( "NULL ent->think" ); + + ent->think( ent ); +} + +/* +============= +G_EvaluateAcceleration + +Calculates the acceleration for an entity +============= +*/ +void G_EvaluateAcceleration( gentity_t *ent, int msec ) +{ + vec3_t deltaVelocity; + vec3_t deltaAccel; + + VectorSubtract( ent->s.pos.trDelta, ent->oldVelocity, deltaVelocity ); + VectorScale( deltaVelocity, 1.0f / (float)msec, ent->acceleration ); + + VectorSubtract( ent->acceleration, ent->oldAccel, deltaAccel ); + VectorScale( deltaAccel, 1.0f / (float)msec, ent->jerk ); + + VectorCopy( ent->s.pos.trDelta, ent->oldVelocity ); + VectorCopy( ent->acceleration, ent->oldAccel ); +} + +/* +================ +G_RunFrame + +Advances the non-player objects in the world +================ +*/ +void G_RunFrame( int levelTime ) +{ + int i; + gentity_t *ent; + int msec; + static int ptime3000 = 0; + + // if we are waiting for the level to restart, do nothing + if( level.restarted ) + return; + + if( level.pausedTime ) + { + msec = levelTime - level.time - level.pausedTime; + level.pausedTime = levelTime - level.time; + + ptime3000 += msec; + while( ptime3000 > 3000 ) + { + ptime3000 -= 3000; + trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" ); + + if( level.pausedTime >= 110000 && level.pausedTime <= 119000 ) + trap_SendServerCommand( -1, va( "print \"Server: Game will auto-unpause in %d seconds\n\"", + (int) ( (float) ( 120000 - level.pausedTime ) / 1000.0f ) ) ); + } + + // Prevents clients from getting lagged-out messages + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + level.clients[ i ].ps.commandTime = levelTime; + } + + if( level.pausedTime > 120000 ) + { + trap_SendServerCommand( -1, "print \"Server: The game has been unpaused automatically (2 minute max)\n\"" ); + trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); + level.pausedTime = 0; + } + + return; + } + + level.framenum++; + level.previousTime = level.time; + level.time = levelTime; + msec = level.time - level.previousTime; + + // get any cvar changes + G_UpdateCvars( ); + CheckCvars( ); + // now we are done spawning + level.spawning = qfalse; + + // + // go through all allocated objects + // + ent = &g_entities[ 0 ]; + + for( i = 0; i < level.num_entities; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + // clear events that are too old + if( level.time - ent->eventTime > EVENT_VALID_MSEC ) + { + if( ent->s.event ) + { + ent->s.event = 0; // &= EV_EVENT_BITS; + if ( ent->client ) + { + ent->client->ps.externalEvent = 0; + //ent->client->ps.events[0] = 0; + //ent->client->ps.events[1] = 0; + } + } + + if( ent->freeAfterEvent ) + { + // tempEntities or dropped items completely go away after their event + G_FreeEntity( ent ); + continue; + } + else if( ent->unlinkAfterEvent ) + { + // items that will respawn will hide themselves after their pickup event + ent->unlinkAfterEvent = qfalse; + trap_UnlinkEntity( ent ); + } + } + + // temporary entities don't think + if( ent->freeAfterEvent ) + continue; + + // calculate the acceleration of this entity + if( ent->evaluateAcceleration ) + G_EvaluateAcceleration( ent, msec ); + + if( !ent->r.linked && ent->neverFree ) + continue; + + if( ent->s.eType == ET_MISSILE ) + { + G_RunMissile( ent ); + continue; + } + + if( ent->s.eType == ET_BUILDABLE ) + { + G_BuildableThink( ent, msec ); + continue; + } + + if( ent->s.eType == ET_CORPSE || ent->physicsObject ) + { + G_Physics( ent, msec ); + continue; + } + + if( ent->s.eType == ET_MOVER ) + { + G_RunMover( ent ); + continue; + } + + if( i < MAX_CLIENTS ) + { + G_RunClient( ent ); + continue; + } + + G_RunThink( ent ); + } + + // perform final fixups on the players + ent = &g_entities[ 0 ]; + + for( i = 0; i < level.maxclients; i++, ent++ ) + { + if( ent->inuse ) + ClientEndFrame( ent ); + } + + // save position information for all active clients + G_UnlaggedStore( ); + + G_CountSpawns( ); + G_CalculateBuildPoints( ); + G_CalculateStages( ); + + G_SpawnClients( TEAM_ALIENS ); + G_SpawnClients( TEAM_HUMANS ); + G_CalculateAvgPlayers( ); + G_UpdateZaps( msec ); + + // see if it is time to end the level + CheckExitRules( ); + + // update to team status? + CheckTeamStatus( ); + + // cancel vote if timed out + for( i = 0; i < NUM_TEAMS; i++ ) + G_CheckVote( i ); + + level.frameMsec = trap_Milliseconds(); +} + diff --git a/src/game/g_main.c.orig b/src/game/g_main.c.orig new file mode 100644 index 0000000..33784c9 --- /dev/null +++ b/src/game/g_main.c.orig @@ -0,0 +1,2548 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +level_locals_t level; + +typedef struct +{ + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; + int modificationCount; // for tracking changes + qboolean trackChange; // track this variable, and announce if changed + /* certain cvars can be set in worldspawn, but we don't want those values to + persist, so keep track of non-worldspawn changes and restore that on map + end. unfortunately, if the server crashes, the value set in worldspawn may + persist */ + char *explicit; +} cvarTable_t; + +gentity_t g_entities[ MAX_GENTITIES ]; +gclient_t g_clients[ MAX_CLIENTS ]; + +vmCvar_t g_timelimit; +vmCvar_t g_suddenDeathTime; +vmCvar_t g_friendlyFire; +vmCvar_t g_friendlyBuildableFire; +vmCvar_t g_dretchPunt; +vmCvar_t g_password; +vmCvar_t g_needpass; +vmCvar_t g_maxclients; +vmCvar_t g_maxGameClients; +vmCvar_t g_dedicated; +vmCvar_t g_speed; +vmCvar_t g_gravity; +vmCvar_t g_cheats; +vmCvar_t g_knockback; +vmCvar_t g_inactivity; +vmCvar_t g_debugMove; +vmCvar_t g_debugDamage; +vmCvar_t g_motd; +vmCvar_t g_synchronousClients; +vmCvar_t g_warmup; +vmCvar_t g_doWarmup; +vmCvar_t g_restarted; +vmCvar_t g_lockTeamsAtStart; +vmCvar_t g_logFile; +vmCvar_t g_logFileSync; +vmCvar_t g_allowVote; +vmCvar_t g_voteLimit; +vmCvar_t g_suddenDeathVotePercent; +vmCvar_t g_suddenDeathVoteDelay; +vmCvar_t g_teamForceBalance; +vmCvar_t g_smoothClients; +vmCvar_t pmove_fixed; +vmCvar_t pmove_msec; +vmCvar_t g_minNameChangePeriod; +vmCvar_t g_maxNameChanges; + +vmCvar_t g_alienBuildPoints; +vmCvar_t g_alienBuildQueueTime; +vmCvar_t g_humanBuildPoints; +vmCvar_t g_humanBuildQueueTime; +vmCvar_t g_humanRepeaterBuildPoints; +vmCvar_t g_humanRepeaterBuildQueueTime; +vmCvar_t g_humanRepeaterMaxZones; +vmCvar_t g_humanStage; +vmCvar_t g_humanCredits; +vmCvar_t g_humanMaxStage; +vmCvar_t g_humanStage2Threshold; +vmCvar_t g_humanStage3Threshold; +vmCvar_t g_alienStage; +vmCvar_t g_alienCredits; +vmCvar_t g_alienMaxStage; +vmCvar_t g_alienStage2Threshold; +vmCvar_t g_alienStage3Threshold; +vmCvar_t g_teamImbalanceWarnings; +vmCvar_t g_freeFundPeriod; + +vmCvar_t g_unlagged; + +vmCvar_t g_disabledEquipment; +vmCvar_t g_disabledClasses; +vmCvar_t g_disabledBuildables; + +vmCvar_t g_markDeconstruct; + +vmCvar_t g_debugMapRotation; +vmCvar_t g_currentMapRotation; +vmCvar_t g_mapRotationNodes; +vmCvar_t g_mapRotationStack; +vmCvar_t g_nextMap; +vmCvar_t g_initialMapRotation; + +vmCvar_t g_debugVoices; +vmCvar_t g_voiceChats; + +vmCvar_t g_shove; + +vmCvar_t g_mapConfigs; +vmCvar_t g_sayAreaRange; + +vmCvar_t g_floodMaxDemerits; +vmCvar_t g_floodMinTime; + +vmCvar_t g_layouts; +vmCvar_t g_layoutAuto; + +vmCvar_t g_emoticonsAllowedInNames; + +vmCvar_t g_admin; +vmCvar_t g_adminTempBan; +vmCvar_t g_adminMaxBan; + +vmCvar_t g_privateMessages; +vmCvar_t g_specChat; +vmCvar_t g_publicAdminMessages; +vmCvar_t g_allowTeamOverlay; + +vmCvar_t g_censorship; + +vmCvar_t g_tag; + +vmCvar_t g_unlimited; +vmCvar_t g_instantBuild; +vmCvar_t g_cuboidSizeLimit; +vmCvar_t g_cuboidMode; + +vmCvar_t g_buildableDensityLimit; +vmCvar_t g_buildableDensityLimitRange; + +// copy cvars that can be set in worldspawn so they can be restored later +static char cv_gravity[ MAX_CVAR_VALUE_STRING ]; +static char cv_humanMaxStage[ MAX_CVAR_VALUE_STRING ]; +static char cv_alienMaxStage[ MAX_CVAR_VALUE_STRING ]; + +static cvarTable_t gameCvarTable[ ] = +{ + // don't override the cheat state set by the system + { &g_cheats, "sv_cheats", "", 0, 0, qfalse }, + + // noset vars + { NULL, "gamename", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse }, + { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse }, + { &g_lockTeamsAtStart, "g_lockTeamsAtStart", "0", CVAR_ROM, 0, qfalse }, + { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + { NULL, "P", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + + // latched vars + + { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse }, + + // change anytime vars + { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse }, + + { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + { &g_suddenDeathTime, "g_suddenDeathTime", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + + { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse }, + + { &g_friendlyFire, "g_friendlyFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qtrue }, + + { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE, 0, qtrue }, + + { &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue }, + { &g_doWarmup, "g_doWarmup", "0", CVAR_ARCHIVE, 0, qtrue }, + { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse }, + { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse }, + + { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + + { &g_dedicated, "dedicated", "0", 0, 0, qfalse }, + + { &g_speed, "g_speed", "320", 0, 0, qtrue }, + { &g_gravity, "g_gravity", "800", 0, 0, qtrue, cv_gravity }, + { &g_knockback, "g_knockback", "1000", 0, 0, qtrue }, + { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue }, + { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse }, + { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse }, + { &g_motd, "g_motd", "", 0, 0, qfalse }, + + { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse }, + { &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "74", CVAR_ARCHIVE, 0, qfalse }, + { &g_suddenDeathVoteDelay, "g_suddenDeathVoteDelay", "180", CVAR_ARCHIVE, 0, qfalse }, + { &g_minNameChangePeriod, "g_minNameChangePeriod", "5", 0, 0, qfalse}, + { &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse}, + + { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse}, + { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse}, + { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, + + { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, 0, 0, qfalse }, + { &g_alienBuildQueueTime, "g_alienBuildQueueTime", DEFAULT_ALIEN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, 0, 0, qfalse }, + { &g_humanBuildQueueTime, "g_humanBuildQueueTime", DEFAULT_HUMAN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanRepeaterBuildPoints, "g_humanRepeaterBuildPoints", DEFAULT_HUMAN_REPEATER_BUILDPOINTS, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanRepeaterMaxZones, "g_humanRepeaterMaxZones", DEFAULT_HUMAN_REPEATER_MAX_ZONES, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanRepeaterBuildQueueTime, "g_humanRepeaterBuildQueueTime", DEFAULT_HUMAN_REPEATER_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, + { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse }, + { &g_humanCredits, "g_humanCredits", "0", 0, 0, qfalse }, + { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse, cv_humanMaxStage }, + { &g_humanStage2Threshold, "g_humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, 0, 0, qfalse }, + { &g_humanStage3Threshold, "g_humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, 0, 0, qfalse }, + { &g_alienStage, "g_alienStage", "0", 0, 0, qfalse }, + { &g_alienCredits, "g_alienCredits", "0", 0, 0, qfalse }, + { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse, cv_alienMaxStage }, + { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse }, + { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse }, + { &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse }, + { &g_freeFundPeriod, "g_freeFundPeriod", DEFAULT_FREEKILL_PERIOD, CVAR_ARCHIVE, 0, qtrue }, + + { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + + { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, + + { &g_sayAreaRange, "g_sayAreaRange", "1000", CVAR_ARCHIVE, 0, qtrue }, + + { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse }, + { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse }, + + { &g_markDeconstruct, "g_markDeconstruct", "3", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + + { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse }, + { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING + { &g_mapRotationNodes, "g_mapRotationNodes", "", CVAR_ROM, 0, qfalse }, + { &g_mapRotationStack, "g_mapRotationStack", "", CVAR_ROM, 0, qfalse }, + { &g_nextMap, "g_nextMap", "", 0 , 0, qtrue }, + { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse }, + { &g_debugVoices, "g_debugVoices", "0", 0, 0, qfalse }, + { &g_voiceChats, "g_voiceChats", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_shove, "g_shove", "0.0", CVAR_ARCHIVE, 0, qfalse }, + { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse }, + { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse }, + + { &g_layouts, "g_layouts", "", CVAR_LATCH, 0, qfalse }, + { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse }, + + { &g_emoticonsAllowedInNames, "g_emoticonsAllowedInNames", "1", CVAR_LATCH|CVAR_ARCHIVE, 0, qfalse }, + + { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse }, + { &g_adminTempBan, "g_adminTempBan", "2m", CVAR_ARCHIVE, 0, qfalse }, + { &g_adminMaxBan, "g_adminMaxBan", "2w", CVAR_ARCHIVE, 0, qfalse }, + + { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_specChat, "g_specChat", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_publicAdminMessages, "g_publicAdminMessages", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_allowTeamOverlay, "g_allowTeamOverlay", "1", CVAR_ARCHIVE, 0, qtrue }, + + { &g_censorship, "g_censorship", "", CVAR_ARCHIVE, 0, qfalse }, + + { &g_tag, "g_tag", "gpp", CVAR_INIT, 0, qfalse }, + + { &g_unlimited, "g_unlimited", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + { &g_instantBuild, "g_instantBuild", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + { &g_cuboidSizeLimit, "g_cuboidSizeLimit", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_cuboidMode, "g_cuboidMode", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_buildableDensityLimit, "g_buildableDensityLimit", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_buildableDensityLimitRange, "g_buildableDensityLimitRange", "0", CVAR_ARCHIVE, 0, qfalse } +}; + +static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); + + +void G_InitGame( int levelTime, int randomSeed, int restart ); +void G_RunFrame( int levelTime ); +void G_ShutdownGame( int restart ); +void CheckExitRules( void ); + +void G_CountSpawns( void ); +void G_CalculateBuildPoints( void ); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, + int arg5, int arg6, int arg7, int arg8, int arg9, + int arg10, int arg11 ) +{ + switch( command ) + { + case GAME_INIT: + G_InitGame( arg0, arg1, arg2 ); + return 0; + + case GAME_SHUTDOWN: + G_ShutdownGame( arg0 ); + return 0; + + case GAME_CLIENT_CONNECT: + return (intptr_t)ClientConnect( arg0, arg1 ); + + case GAME_CLIENT_THINK: + ClientThink( arg0 ); + return 0; + + case GAME_CLIENT_USERINFO_CHANGED: + ClientUserinfoChanged( arg0, qfalse ); + return 0; + + case GAME_CLIENT_DISCONNECT: + ClientDisconnect( arg0 ); + return 0; + + case GAME_CLIENT_BEGIN: + ClientBegin( arg0 ); + return 0; + + case GAME_CLIENT_COMMAND: + ClientCommand( arg0 ); + return 0; + + case GAME_RUN_FRAME: + G_RunFrame( arg0 ); + return 0; + + case GAME_CONSOLE_COMMAND: + return ConsoleCommand( ); + } + + return -1; +} + + +void QDECL G_Printf( const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + trap_Print( text ); +} + +void QDECL G_Error( const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + trap_Error( text ); +} + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. +Entity teams are used for item groups and multi-entity mover groups. + +All but the first will have the FL_TEAMSLAVE flag set and teammaster field set +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams( void ) +{ + gentity_t *e, *e2; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + + for( i = 1, e = g_entities+i; i < level.num_entities; i++, e++ ) + { + if( !e->inuse ) + continue; + + if( !e->team ) + continue; + + if( e->flags & FL_TEAMSLAVE ) + continue; + + e->teammaster = e; + c++; + c2++; + + for( j = i + 1, e2 = e + 1; j < level.num_entities; j++, e2++ ) + { + if( !e2->inuse ) + continue; + + if( !e2->team ) + continue; + + if( e2->flags & FL_TEAMSLAVE ) + continue; + + if( !strcmp( e->team, e2->team ) ) + { + c2++; + e2->teamchain = e->teamchain; + e->teamchain = e2; + e2->teammaster = e; + e2->flags |= FL_TEAMSLAVE; + + // make sure that targets only point at the master + if( e2->targetname ) + { + e->targetname = e2->targetname; + e2->targetname = NULL; + } + } + } + } + + G_Printf( "%i teams with %i entities\n", c, c2 ); +} + + +/* +================= +G_RegisterCvars +================= +*/ +void G_RegisterCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) + { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + + if( cv->vmCvar ) + cv->modificationCount = cv->vmCvar->modificationCount; + + if( cv->explicit ) + strcpy( cv->explicit, cv->vmCvar->string ); + } +} + +/* +================= +G_UpdateCvars +================= +*/ +void G_UpdateCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) + { + if( cv->vmCvar ) + { + trap_Cvar_Update( cv->vmCvar ); + + if( cv->modificationCount != cv->vmCvar->modificationCount ) + { + cv->modificationCount = cv->vmCvar->modificationCount; + + if( cv->trackChange ) + trap_SendServerCommand( -1, va( "print \"Server: %s changed to %s\n\"", + cv->cvarName, cv->vmCvar->string ) ); + + if( !level.spawning && cv->explicit ) + strcpy( cv->explicit, cv->vmCvar->string ); + } + } + } +} + +/* +================= +G_RestoreCvars +================= +*/ +void G_RestoreCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) + { + if( cv->vmCvar && cv->explicit ) + trap_Cvar_Set( cv->cvarName, cv->explicit ); + } +} + +/* +================= +G_MapConfigs +================= +*/ +void G_MapConfigs( const char *mapname ) +{ + + if( !g_mapConfigs.string[0] ) + return; + + if( trap_Cvar_VariableIntegerValue( "g_mapConfigsLoaded" ) ) + return; + + trap_SendConsoleCommand( EXEC_APPEND, + va( "exec \"%s/default.cfg\"\n", g_mapConfigs.string ) ); + + trap_SendConsoleCommand( EXEC_APPEND, + va( "exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname ) ); + + trap_Cvar_Set( "g_mapConfigsLoaded", "1" ); +} + +/* +================ +G_CheckCuboidConfig + +even tokens are map names +odd tokens are cuboid modes +================ +*/ +void G_CheckCuboidConfig(char* mapname) +{ + char* token; + char config[MAX_CVAR_VALUE_STRING]; //should be enough for few maps + qboolean type=qfalse; + qboolean found=qfalse; + int mode; + + mode=trap_Cvar_VariableIntegerValue("g_cuboidDefaultMode"); + trap_Cvar_VariableStringBuffer("g_cuboidConfig",config,sizeof(config)); + + while(1) + { + COM_Parse(&token); + + if(!token) + break; + + if(!type) + if(!Q_stricmp(token,mapname)) + found=qtrue; + else + if(found) + { + mode=atoi(token); + break; + } + } + Com_Printf("cuboids are %s%s^7 on %s\n",(mode==0?"^2ENABLED":(mode==1?"^1DISABLED ON S1":"^1DISABLED")),(found?"":" BY DEFAULT"),mapname); +} + +/* +============ +G_InitGame + +============ +*/ +void G_InitGame( int levelTime, int randomSeed, int restart ) +{ + int i; + char map[ MAX_CVAR_VALUE_STRING ] = {""}; + + srand( randomSeed ); + + G_RegisterCvars( ); + + G_Printf( "------- Game Initialization -------\n" ); + G_Printf( "gamename: %s\n", GAME_VERSION ); + G_Printf( "gamedate: %s\n", __DATE__ ); + + BG_InitMemory( ); + + // set some level globals + memset( &level, 0, sizeof( level ) ); + level.time = levelTime; + level.startTime = levelTime; + level.alienStage2Time = level.alienStage3Time = + level.humanStage2Time = level.humanStage3Time = level.startTime; + + level.snd_fry = G_SoundIndex( "sound/misc/fry.wav" ); // FIXME standing in lava / slime + + if( g_logFile.string[ 0 ] ) + { + if( g_logFileSync.integer ) + trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND_SYNC ); + else + trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND ); + + if( !level.logFile ) + G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logFile.string ); + else + { + char serverinfo[ MAX_INFO_STRING ]; + qtime_t qt; + int t; + + trap_GetServerinfo( serverinfo, sizeof( serverinfo ) ); + + G_LogPrintf( "------------------------------------------------------------\n" ); + G_LogPrintf( "InitGame: %s\n", serverinfo ); + + t = trap_RealTime( &qt ); + G_LogPrintf("RealTime: %04i/%02i/%02i %02i:%02i:%02i\n", + qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, + qt.tm_hour, qt.tm_min, qt.tm_sec ); + + } + } + else + G_Printf( "Not logging to disk\n" ); + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + G_MapConfigs( map ); + + // we're done with g_mapConfigs, so reset this for the next map + trap_Cvar_Set( "g_mapConfigsLoaded", "0" ); + + G_RegisterCommands( ); + G_admin_readconfig( NULL ); + G_LoadCensors( ); + + // initialize all entities for this game + memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[ 0 ] ) ); + level.gentities = g_entities; + + // initialize all clients for this game + level.maxclients = g_maxclients.integer; + memset( g_clients, 0, MAX_CLIENTS * sizeof( g_clients[ 0 ] ) ); + level.clients = g_clients; + + // set client fields on player ents + for( i = 0; i < level.maxclients; i++ ) + g_entities[ i ].client = level.clients + i; + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + level.num_entities = MAX_CLIENTS; + + // let the server system know where the entites are + trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), + &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) ); + + level.emoticonCount = BG_LoadEmoticons( level.emoticons, MAX_EMOTICONS ); + + trap_SetConfigstring( CS_INTERMISSION, "0" ); + + // test to see if a custom buildable layout will be loaded + G_LayoutSelect( ); + + // this has to be flipped after the first UpdateCvars + level.spawning = qtrue; + // parse the key/value pairs and spawn gentities + G_SpawnEntitiesFromString( ); + + // load up a custom building layout if there is one + G_LayoutLoad( ); + + // find out g_cuboidMode value + G_CheckCuboidConfig( map ); + + // the map might disable some things + BG_InitAllowedGameElements( ); + + // general initialization + G_FindTeams( ); + + BG_InitClassConfigs( ); + BG_InitBuildableConfigs( ); + G_InitDamageLocations( ); + G_InitMapRotations( ); + G_InitSpawnQueue( &level.alienSpawnQueue ); + G_InitSpawnQueue( &level.humanSpawnQueue ); + + if( g_debugMapRotation.integer ) + G_PrintRotations( ); + + level.voices = BG_VoiceInit( ); + BG_PrintVoices( level.voices, g_debugVoices.integer ); + + //reset stages + trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) ); + trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) ); + trap_Cvar_Set( "g_alienCredits", 0 ); + trap_Cvar_Set( "g_humanCredits", 0 ); + level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; + + G_Printf( "-----------------------------------\n" ); + + // So the server counts the spawns without a client attached + G_CountSpawns( ); + + G_UpdateTeamConfigStrings( ); + + if( g_lockTeamsAtStart.integer ) + { + level.alienTeamLocked = qtrue; + level.humanTeamLocked = qtrue; + trap_Cvar_Set( "g_lockTeamsAtStart", "0" ); + } +} + +/* +================== +G_ClearVotes + +remove all currently active votes +================== +*/ +static void G_ClearVotes( void ) +{ + int i; + memset( level.voteTime, 0, sizeof( level.voteTime ) ); + for( i = 0; i < NUM_TEAMS; i++ ) + { + trap_SetConfigstring( CS_VOTE_TIME + i, "" ); + trap_SetConfigstring( CS_VOTE_STRING + i, "" ); + } +} + +/* +================= +G_ShutdownGame +================= +*/ +void G_ShutdownGame( int restart ) +{ + // in case of a map_restart + G_ClearVotes( ); + + G_RestoreCvars( ); + + G_Printf( "==== ShutdownGame ====\n" ); + + if( level.logFile ) + { + G_LogPrintf( "ShutdownGame:\n" ); + G_LogPrintf( "------------------------------------------------------------\n" ); + trap_FS_FCloseFile( level.logFile ); + level.logFile = 0; + } + + // write all the client session data so we can get it back + G_WriteSessionData( ); + + G_admin_cleanup( ); + G_namelog_cleanup( ); + G_UnregisterCommands( ); + + G_ShutdownMapRotations( ); + + level.restarted = qfalse; + level.surrenderTeam = TEAM_NONE; + trap_SetConfigstring( CS_WINNER, "" ); +} + + + +//=================================================================== + +void QDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, error ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); + va_end( argptr ); + + G_Error( "%s", text ); +} + +void QDECL Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + G_Printf( "%s", text ); +} + +/* +======================================================================== + +PLAYER COUNTING / SCORE SORTING + +======================================================================== +*/ + + +/* +============= +SortRanks + +============= +*/ +int QDECL SortRanks( const void *a, const void *b ) +{ + gclient_t *ca, *cb; + + ca = &level.clients[ *(int *)a ]; + cb = &level.clients[ *(int *)b ]; + + // then sort by score + if( ca->ps.persistant[ PERS_SCORE ] > cb->ps.persistant[ PERS_SCORE ] ) + return -1; + if( ca->ps.persistant[ PERS_SCORE ] < cb->ps.persistant[ PERS_SCORE ] ) + return 1; + else + return 0; +} + +/* +============ +G_InitSpawnQueue + +Initialise a spawn queue +============ +*/ +void G_InitSpawnQueue( spawnQueue_t *sq ) +{ + int i; + + sq->back = sq->front = 0; + sq->back = QUEUE_MINUS1( sq->back ); + + //0 is a valid clientNum, so use something else + for( i = 0; i < MAX_CLIENTS; i++ ) + sq->clients[ i ] = -1; +} + +/* +============ +G_GetSpawnQueueLength + +Return the length of a spawn queue +============ +*/ +int G_GetSpawnQueueLength( spawnQueue_t *sq ) +{ + int length = sq->back - sq->front + 1; + + while( length < 0 ) + length += MAX_CLIENTS; + + while( length >= MAX_CLIENTS ) + length -= MAX_CLIENTS; + + return length; +} + +/* +============ +G_PopSpawnQueue + +Remove from front element from a spawn queue +============ +*/ +int G_PopSpawnQueue( spawnQueue_t *sq ) +{ + int clientNum = sq->clients[ sq->front ]; + + if( G_GetSpawnQueueLength( sq ) > 0 ) + { + sq->clients[ sq->front ] = -1; + sq->front = QUEUE_PLUS1( sq->front ); + G_StopFollowing( g_entities + clientNum ); + g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED; + + return clientNum; + } + else + return -1; +} + +/* +============ +G_PeekSpawnQueue + +Look at front element from a spawn queue +============ +*/ +int G_PeekSpawnQueue( spawnQueue_t *sq ) +{ + return sq->clients[ sq->front ]; +} + +/* +============ +G_SearchSpawnQueue + +Look to see if clientNum is already in the spawnQueue +============ +*/ +qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum ) +{ + int i; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + if( sq->clients[ i ] == clientNum ) + return qtrue; + } + + return qfalse; +} + +/* +============ +G_PushSpawnQueue + +Add an element to the back of the spawn queue +============ +*/ +qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum ) +{ + // don't add the same client more than once + if( G_SearchSpawnQueue( sq, clientNum ) ) + return qfalse; + + sq->back = QUEUE_PLUS1( sq->back ); + sq->clients[ sq->back ] = clientNum; + + g_entities[ clientNum ].client->ps.pm_flags |= PMF_QUEUED; + return qtrue; +} + +/* +============ +G_RemoveFromSpawnQueue + +remove a specific client from a spawn queue +============ +*/ +qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum ) +{ + int i = sq->front; + + if( G_GetSpawnQueueLength( sq ) ) + { + do + { + if( sq->clients[ i ] == clientNum ) + { + //and this kids is why it would have + //been better to use an LL for internal + //representation + do + { + sq->clients[ i ] = sq->clients[ QUEUE_PLUS1( i ) ]; + + i = QUEUE_PLUS1( i ); + } while( i != QUEUE_PLUS1( sq->back ) ); + + sq->back = QUEUE_MINUS1( sq->back ); + g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED; + + return qtrue; + } + + i = QUEUE_PLUS1( i ); + } while( i != QUEUE_PLUS1( sq->back ) ); + } + + return qfalse; +} + +/* +============ +G_GetPosInSpawnQueue + +Get the position of a client in a spawn queue +============ +*/ +int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum ) +{ + int i = sq->front; + + if( G_GetSpawnQueueLength( sq ) ) + { + do + { + if( sq->clients[ i ] == clientNum ) + { + if( i < sq->front ) + return i + MAX_CLIENTS - sq->front; + else + return i - sq->front; + } + + i = QUEUE_PLUS1( i ); + } while( i != QUEUE_PLUS1( sq->back ) ); + } + + return -1; +} + +/* +============ +G_PrintSpawnQueue + +Print the contents of a spawn queue +============ +*/ +void G_PrintSpawnQueue( spawnQueue_t *sq ) +{ + int i = sq->front; + int length = G_GetSpawnQueueLength( sq ); + + G_Printf( "l:%d f:%d b:%d :", length, sq->front, sq->back ); + + if( length > 0 ) + { + do + { + if( sq->clients[ i ] == -1 ) + G_Printf( "*:" ); + else + G_Printf( "%d:", sq->clients[ i ] ); + + i = QUEUE_PLUS1( i ); + } while( i != QUEUE_PLUS1( sq->back ) ); + } + + G_Printf( "\n" ); +} + +/* +============ +G_SpawnClients + +Spawn queued clients +============ +*/ +void G_SpawnClients( team_t team ) +{ + int clientNum; + gentity_t *ent, *spawn; + vec3_t spawn_origin, spawn_angles; + spawnQueue_t *sq = NULL; + int numSpawns = 0; + + if( team == TEAM_ALIENS ) + { + sq = &level.alienSpawnQueue; + numSpawns = level.numAlienSpawns; + } + else if( team == TEAM_HUMANS ) + { + sq = &level.humanSpawnQueue; + numSpawns = level.numHumanSpawns; + } + + if( G_GetSpawnQueueLength( sq ) > 0 && numSpawns > 0 ) + { + clientNum = G_PeekSpawnQueue( sq ); + ent = &g_entities[ clientNum ]; + + if( ( spawn = G_SelectTremulousSpawnPoint( team, + ent->client->pers.lastDeathLocation, + spawn_origin, spawn_angles ) ) ) + { + clientNum = G_PopSpawnQueue( sq ); + + if( clientNum < 0 ) + return; + + ent = &g_entities[ clientNum ]; + + ent->client->sess.spectatorState = SPECTATOR_NOT; + ClientUserinfoChanged( clientNum, qfalse ); + ClientSpawn( ent, spawn, spawn_origin, spawn_angles ); + } + } +} + +/* +============ +G_CountSpawns + +Counts the number of spawns for each team +============ +*/ +void G_CountSpawns( void ) +{ + int i; + gentity_t *ent; + + level.numAlienSpawns = 0; + level.numHumanSpawns = 0; + for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + { + if( !ent->inuse || ent->s.eType != ET_BUILDABLE || ent->health <= 0 ) + continue; + + if( ent->s.modelindex == BA_A_SPAWN ) + level.numAlienSpawns++; + + if( ent->s.modelindex == BA_H_SPAWN ) + level.numHumanSpawns++; + } +} + + +/* +============ +G_TimeTilSuddenDeath +============ +*/ +#define SUDDENDEATHWARNING 60000 +int G_TimeTilSuddenDeath( void ) +{ + if( ( !g_suddenDeathTime.integer && level.suddenDeathBeginTime == 0 ) || + ( level.suddenDeathBeginTime < 0 ) ) + return SUDDENDEATHWARNING + 1; // Always some time away + + return ( ( level.suddenDeathBeginTime ) - ( level.time - level.startTime ) ); +} + + +#define PLAYER_COUNT_MOD 5.0f + +/* +============ +G_CalculateBuildPoints + +Recalculate the quantity of building points available to the teams +============ +*/ +void G_CalculateBuildPoints( void ) +{ + int i; + buildable_t buildable; + buildPointZone_t *zone; + + // BP queue updates + while( level.alienBuildPointQueue > 0 && + level.alienNextQueueTime < level.time ) + { + level.alienBuildPointQueue--; + level.alienNextQueueTime += G_NextQueueTime( level.alienBuildPointQueue, + g_alienBuildPoints.integer, + g_alienBuildQueueTime.integer ); + } + + while( level.humanBuildPointQueue > 0 && + level.humanNextQueueTime < level.time ) + { + level.humanBuildPointQueue--; + level.humanNextQueueTime += G_NextQueueTime( level.humanBuildPointQueue, + g_humanBuildPoints.integer, + g_humanBuildQueueTime.integer ); + } + + // Sudden Death checks + if( G_TimeTilSuddenDeath( ) <= 0 && level.suddenDeathWarning < TW_PASSED ) + { + G_LogPrintf( "Beginning Sudden Death\n" ); + trap_SendServerCommand( -1, "cp \"Sudden Death!\"" ); + trap_SendServerCommand( -1, "print \"Beginning Sudden Death.\n\"" ); + level.suddenDeathWarning = TW_PASSED; + G_ClearDeconMarks( ); + + // Clear blueprints, or else structs that cost 0 BP can still be built after SD + for( i = 0; i < level.maxclients; i++ ) + { + if( g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) + g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + } + } + else if( G_TimeTilSuddenDeath( ) <= SUDDENDEATHWARNING && + level.suddenDeathWarning < TW_IMMINENT ) + { + trap_SendServerCommand( -1, va( "cp \"Sudden Death in %d seconds!\"", + (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); + trap_SendServerCommand( -1, va( "print \"Sudden Death will begin in %d seconds.\n\"", + (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); + level.suddenDeathWarning = TW_IMMINENT; + } + + level.humanBuildPoints = g_humanBuildPoints.integer - level.humanBuildPointQueue; + level.alienBuildPoints = g_alienBuildPoints.integer - level.alienBuildPointQueue; + + // Reset buildPointZones + for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) + { + buildPointZone_t *zone = &level.buildPointZones[ i ]; + + zone->active = qfalse; + zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; + } + + // Iterate through entities + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + gentity_t *ent = &g_entities[ i ]; + buildPointZone_t *zone; + buildable_t buildable; + int cost; + + if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD ) + continue; + + // mark a zone as active + if( ent->usesBuildPointZone ) + { + assert( ent->buildPointZone >= 0 && ent->buildPointZone < g_humanRepeaterMaxZones.integer ); + + zone = &level.buildPointZones[ ent->buildPointZone ]; + zone->active = qtrue; + } + + // Subtract the BP from the appropriate pool + buildable = ent->s.modelindex; + cost = BG_Buildable( buildable, ent->cuboidSize )->buildPoints; + + if( ent->buildableTeam == TEAM_ALIENS ) + level.alienBuildPoints -= cost; + if( buildable == BA_H_REPEATER ) + level.humanBuildPoints -= cost; + else if( buildable != BA_H_REACTOR ) + { + gentity_t *power = G_PowerEntityForEntity( ent ); + + if( power ) + { + if( power->s.modelindex == BA_H_REACTOR ) + level.humanBuildPoints -= cost; + else if( power->s.modelindex == BA_H_REPEATER && power->usesBuildPointZone ) + level.buildPointZones[ power->buildPointZone ].totalBuildPoints -= cost; + } + } + } + + // Finally, update repeater zones and their queues + // note that this has to be done after the used BP is calculated + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + gentity_t *ent = &g_entities[ i ]; + + if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD || + ent->buildableTeam != TEAM_HUMANS ) + continue; + + buildable = ent->s.modelindex; + + if( buildable != BA_H_REPEATER ) + continue; + + if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) + { + zone = &level.buildPointZones[ ent->buildPointZone ]; + + if( G_TimeTilSuddenDeath( ) > 0 ) + { + // BP queue updates + while( zone->queuedBuildPoints > 0 && + zone->nextQueueTime < level.time ) + { + zone->queuedBuildPoints--; + zone->nextQueueTime += G_NextQueueTime( zone->queuedBuildPoints, + zone->totalBuildPoints, + g_humanRepeaterBuildQueueTime.integer ); + } + } + else + { + zone->totalBuildPoints = zone->queuedBuildPoints = 0; + } + } + } + + if( level.humanBuildPoints < 0 ) + level.humanBuildPoints = 0; + + if( level.alienBuildPoints < 0 ) + level.alienBuildPoints = 0; +} + +/* +============ +G_CalculateStages +============ +*/ +void G_CalculateStages( void ) +{ + float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD; + float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD; + int alienNextStageThreshold, humanNextStageThreshold; + static int lastAlienStageModCount = 1; + static int lastHumanStageModCount = 1; + + if( alienPlayerCountMod < 0.1f ) + alienPlayerCountMod = 0.1f; + + if( humanPlayerCountMod < 0.1f ) + humanPlayerCountMod = 0.1f; + + if( g_alienCredits.integer >= + (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ) && + g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) + { + trap_Cvar_Set( "g_alienStage", va( "%d", S2 ) ); + level.alienStage2Time = level.time; + lastAlienStageModCount = g_alienStage.modificationCount; + G_LogPrintf("Stage: A 2: Aliens reached Stage 2\n"); + } + + if( g_alienCredits.integer >= + (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ) && + g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) + { + trap_Cvar_Set( "g_alienStage", va( "%d", S3 ) ); + level.alienStage3Time = level.time; + lastAlienStageModCount = g_alienStage.modificationCount; + G_LogPrintf("Stage: A 3: Aliens reached Stage 3\n"); + } + + if( g_humanCredits.integer >= + (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ) && + g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) + { + trap_Cvar_Set( "g_humanStage", va( "%d", S2 ) ); + level.humanStage2Time = level.time; + lastHumanStageModCount = g_humanStage.modificationCount; + G_LogPrintf("Stage: H 2: Humans reached Stage 2\n"); + } + + if( g_humanCredits.integer >= + (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ) && + g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) + { + trap_Cvar_Set( "g_humanStage", va( "%d", S3 ) ); + level.humanStage3Time = level.time; + lastHumanStageModCount = g_humanStage.modificationCount; + G_LogPrintf("Stage: H 3: Humans reached Stage 3\n"); + } + + if( g_alienStage.modificationCount > lastAlienStageModCount ) + { + G_Checktrigger_stages( TEAM_ALIENS, g_alienStage.integer ); + + if( g_alienStage.integer == S2 ) + level.alienStage2Time = level.time; + else if( g_alienStage.integer == S3 ) + level.alienStage3Time = level.time; + + lastAlienStageModCount = g_alienStage.modificationCount; + } + + if( g_humanStage.modificationCount > lastHumanStageModCount ) + { + G_Checktrigger_stages( TEAM_HUMANS, g_humanStage.integer ); + + if( g_humanStage.integer == S2 ) + level.humanStage2Time = level.time; + else if( g_humanStage.integer == S3 ) + level.humanStage3Time = level.time; + + lastHumanStageModCount = g_humanStage.modificationCount; + } + + if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) + alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ); + else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) + alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ); + else + alienNextStageThreshold = -1; + + if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) + humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ); + else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) + humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ); + else + humanNextStageThreshold = -1; + + // save a lot of bandwidth by rounding thresholds up to the nearest 100 + if( alienNextStageThreshold > 0 ) + alienNextStageThreshold = ceil( (float)alienNextStageThreshold / 100 ) * 100; + + if( humanNextStageThreshold > 0 ) + humanNextStageThreshold = ceil( (float)humanNextStageThreshold / 100 ) * 100; + + trap_SetConfigstring( CS_ALIEN_STAGES, va( "%d %d %d", + g_alienStage.integer, g_alienCredits.integer, + alienNextStageThreshold ) ); + + trap_SetConfigstring( CS_HUMAN_STAGES, va( "%d %d %d", + g_humanStage.integer, g_humanCredits.integer, + humanNextStageThreshold ) ); +} + +/* +============ +CalculateAvgPlayers + +Calculates the average number of players playing this game +============ +*/ +void G_CalculateAvgPlayers( void ) +{ + //there are no clients or only spectators connected, so + //reset the number of samples in order to avoid the situation + //where the average tends to 0 + if( !level.numAlienClients ) + { + level.numAlienSamples = 0; + trap_Cvar_Set( "g_alienCredits", "0" ); + } + + if( !level.numHumanClients ) + { + level.numHumanSamples = 0; + trap_Cvar_Set( "g_humanCredits", "0" ); + } + + //calculate average number of clients for stats + level.averageNumAlienClients = + ( ( level.averageNumAlienClients * level.numAlienSamples ) + + level.numAlienClients ) / + (float)( level.numAlienSamples + 1 ); + level.numAlienSamples++; + + level.averageNumHumanClients = + ( ( level.averageNumHumanClients * level.numHumanSamples ) + + level.numHumanClients ) / + (float)( level.numHumanSamples + 1 ); + level.numHumanSamples++; +} + +/* +============ +CalculateRanks + +Recalculates the score ranks of all players +This will be called on every client connect, begin, disconnect, death, +and team change. +============ +*/ +void CalculateRanks( void ) +{ + int i; + char P[ MAX_CLIENTS + 1 ] = {""}; + + level.numConnectedClients = 0; + level.numPlayingClients = 0; + memset( level.numVotingClients, 0, sizeof( level.numVotingClients ) ); + level.numAlienClients = 0; + level.numHumanClients = 0; + level.numLiveAlienClients = 0; + level.numLiveHumanClients = 0; + + for( i = 0; i < level.maxclients; i++ ) + { + P[ i ] = '-'; + if ( level.clients[ i ].pers.connected != CON_DISCONNECTED ) + { + level.sortedClients[ level.numConnectedClients ] = i; + level.numConnectedClients++; + P[ i ] = (char)'0' + level.clients[ i ].pers.teamSelection; + + level.numVotingClients[ TEAM_NONE ]++; + + if( level.clients[ i ].pers.connected != CON_CONNECTED ) + continue; + + if( level.clients[ i ].pers.teamSelection != TEAM_NONE ) + { + level.numPlayingClients++; + if( level.clients[ i ].pers.teamSelection == TEAM_ALIENS ) + { + level.numAlienClients++; + if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) + level.numLiveAlienClients++; + } + else if( level.clients[ i ].pers.teamSelection == TEAM_HUMANS ) + { + level.numHumanClients++; + if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) + level.numLiveHumanClients++; + } + } + } + } + level.numNonSpectatorClients = level.numLiveAlienClients + + level.numLiveHumanClients; + level.numVotingClients[ TEAM_ALIENS ] = level.numAlienClients; + level.numVotingClients[ TEAM_HUMANS ] = level.numHumanClients; + P[ i ] = '\0'; + trap_Cvar_Set( "P", P ); + + qsort( level.sortedClients, level.numConnectedClients, + sizeof( level.sortedClients[ 0 ] ), SortRanks ); + + // see if it is time to end the level + CheckExitRules( ); + + // if we are at the intermission, send the new info to everyone + if( level.intermissiontime ) + SendScoreboardMessageToAllClients( ); +} + + +/* +======================================================================== + +MAP CHANGING + +======================================================================== +*/ + +/* +======================== +SendScoreboardMessageToAllClients + +Do this at BeginIntermission time and whenever ranks are recalculated +due to enters/exits/forced team changes +======================== +*/ +void SendScoreboardMessageToAllClients( void ) +{ + int i; + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + ScoreboardMessage( g_entities + i ); + } +} + +/* +======================== +MoveClientToIntermission + +When the intermission starts, this will be called for all players. +If a new client connects, this will be called after the spawn function. +======================== +*/ +void MoveClientToIntermission( gentity_t *ent ) +{ + // take out of follow mode if needed + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); + + // move to the spot + VectorCopy( level.intermission_origin, ent->s.origin ); + VectorCopy( level.intermission_origin, ent->client->ps.origin ); + VectorCopy( level.intermission_angle, ent->client->ps.viewangles ); + ent->client->ps.pm_type = PM_INTERMISSION; + + // clean up powerup info + memset( ent->client->ps.misc, 0, sizeof( ent->client->ps.misc ) ); + + ent->client->ps.eFlags = 0; + ent->s.eFlags = 0; + ent->s.eType = ET_GENERAL; + ent->s.modelindex = 0; + ent->s.loopSound = 0; + ent->s.event = 0; + ent->r.contents = 0; +} + +/* +================== +FindIntermissionPoint + +This is also used for spectator spawns +================== +*/ +void FindIntermissionPoint( void ) +{ + gentity_t *ent, *target; + vec3_t dir; + + // find the intermission spot + ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" ); + + if( !ent ) + { // the map creator forgot to put in an intermission point... + G_SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle ); + } + else + { + VectorCopy( ent->s.origin, level.intermission_origin ); + VectorCopy( ent->s.angles, level.intermission_angle ); + // if it has a target, look towards it + if( ent->target ) + { + target = G_PickTarget( ent->target ); + + if( target ) + { + VectorSubtract( target->s.origin, level.intermission_origin, dir ); + vectoangles( dir, level.intermission_angle ); + } + } + } + +} + +/* +================== +BeginIntermission +================== +*/ +void BeginIntermission( void ) +{ + int i; + gentity_t *client; + + if( level.intermissiontime ) + return; // already active + + level.intermissiontime = level.time; + + G_ClearVotes( ); + + G_UpdateTeamConfigStrings( ); + + FindIntermissionPoint( ); + + // move all clients to the intermission point + for( i = 0; i < level.maxclients; i++ ) + { + client = g_entities + i; + + if( !client->inuse ) + continue; + + // respawn if dead + if( client->health <= 0 ) + respawn(client); + + MoveClientToIntermission( client ); + } + + // send the current scoring to all clients + SendScoreboardMessageToAllClients( ); +} + + +/* +============= +ExitLevel + +When the intermission has been exited, the server is either moved +to a new map based on the map rotation or the current map restarted +============= +*/ +void ExitLevel( void ) +{ + int i; + gclient_t *cl; + + if ( G_MapExists( g_nextMap.string ) ) + trap_SendConsoleCommand( EXEC_APPEND, va("map \"%s\"\n", g_nextMap.string ) ); + else if( G_MapRotationActive( ) ) + G_AdvanceMapRotation( 0 ); + else + trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); + + trap_Cvar_Set( "g_nextMap", "" ); + + level.restarted = qtrue; + level.changemap = NULL; + level.intermissiontime = 0; + + // reset all the scores so we don't enter the intermission again + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + if( cl->pers.connected != CON_CONNECTED ) + continue; + + cl->ps.persistant[ PERS_SCORE ] = 0; + } + + // we need to do this here before chaning to CON_CONNECTING + G_WriteSessionData( ); + + // change all client states to connecting, so the early players into the + // next level will know the others aren't done reconnecting + for( i = 0; i < g_maxclients.integer; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + level.clients[ i ].pers.connected = CON_CONNECTING; + } + +} + +/* +================= +G_AdminMessage + +Print to all active server admins, and to the logfile, and to the server console +================= +*/ +void G_AdminMessage( gentity_t *ent, const char *msg ) +{ + char string[ 1024 ]; + int i; + + Com_sprintf( string, sizeof( string ), "chat %d %d \"%s\"", + ent ? ent - g_entities : -1, + G_admin_permission( ent, ADMF_ADMINCHAT ) ? SAY_ADMINS : SAY_ADMINS_PUBLIC, + msg ); + + // Send to all appropriate clients + for( i = 0; i < level.maxclients; i++ ) + if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) + trap_SendServerCommand( i, string ); + + // Send to the logfile and server console + G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_MAGENTA "%s\n", + G_admin_permission( ent, ADMF_ADMINCHAT ) ? "AdminMsg" : "AdminMsgPublic", + ent ? ent - g_entities : -1, ent ? ent->client->pers.netname : "console", + msg ); +} + + +/* +================= +G_LogPrintf + +Print to the logfile with a time stamp if it is open, and to the server console +================= +*/ +void QDECL G_LogPrintf( const char *fmt, ... ) +{ + va_list argptr; + char string[ 1024 ], decolored[ 1024 ]; + int min, tens, sec; + + sec = ( level.time - level.startTime ) / 1000; + + min = sec / 60; + sec -= min * 60; + tens = sec / 10; + sec -= tens * 10; + + Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); + + va_start( argptr, fmt ); + Q_vsnprintf( string + 7, sizeof( string ) - 7, fmt, argptr ); + va_end( argptr ); + + if( g_dedicated.integer ) + { + G_UnEscapeString( string, decolored, sizeof( decolored ) ); + G_Printf( "%s", decolored + 7 ); + } + + if( !level.logFile ) + return; + + G_DecolorString( string, decolored, sizeof( decolored ) ); + trap_FS_Write( decolored, strlen( decolored ), level.logFile ); +} + +/* +================= +G_SendGameStat +================= +*/ +void G_SendGameStat( team_t team ) +{ + char map[ MAX_STRING_CHARS ]; + char teamChar; + char data[ BIG_INFO_STRING ]; + char entry[ MAX_STRING_CHARS ]; + int i, dataLength, entryLength; + gclient_t *cl; + + // games with cheats enabled are not very good for balance statistics + if( g_cheats.integer ) + return; + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + + switch( team ) + { + case TEAM_ALIENS: teamChar = 'A'; break; + case TEAM_HUMANS: teamChar = 'H'; break; + case TEAM_NONE: teamChar = 'L'; break; + default: return; + } + + Com_sprintf( data, BIG_INFO_STRING, + "%s %s T:%c A:%f H:%f M:%s D:%d SD:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d", + Q3_VERSION, + g_tag.string, + teamChar, + level.averageNumAlienClients, + level.averageNumHumanClients, + map, + level.time - level.startTime, + G_TimeTilSuddenDeath( ), + g_alienStage.integer, + level.alienStage2Time - level.startTime, + level.alienStage3Time - level.startTime, + g_humanStage.integer, + level.humanStage2Time - level.startTime, + level.humanStage3Time - level.startTime, + level.numConnectedClients ); + + dataLength = strlen( data ); + + for( i = 0; i < level.numConnectedClients; i++ ) + { + int ping; + + cl = &level.clients[ level.sortedClients[ i ] ]; + + if( cl->pers.connected == CON_CONNECTING ) + ping = -1; + else + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + + switch( cl->ps.stats[ STAT_TEAM ] ) + { + case TEAM_ALIENS: teamChar = 'A'; break; + case TEAM_HUMANS: teamChar = 'H'; break; + case TEAM_NONE: teamChar = 'S'; break; + default: return; + } + + Com_sprintf( entry, MAX_STRING_CHARS, + " \"%s\" %c %d %d %d", + cl->pers.netname, + teamChar, + cl->ps.persistant[ PERS_SCORE ], + ping, + ( level.time - cl->pers.enterTime ) / 60000 ); + + entryLength = strlen( entry ); + + if( dataLength + entryLength >= BIG_INFO_STRING ) + break; + + strcpy( data + dataLength, entry ); + dataLength += entryLength; + } + + trap_SendGameStat( data ); +} + +/* +================ +LogExit + +Append information about this game to the log file +================ +*/ +void LogExit( const char *string ) +{ + int i, numSorted; + gclient_t *cl; + gentity_t *ent; + + G_LogPrintf( "Exit: %s\n", string ); + + level.intermissionQueued = level.time; + + // this will keep the clients from playing any voice sounds + // that will get cut off when the queued intermission starts + trap_SetConfigstring( CS_INTERMISSION, "1" ); + + // don't send more than 32 scores (FIXME?) + numSorted = level.numConnectedClients; + if( numSorted > 32 ) + numSorted = 32; + + for( i = 0; i < numSorted; i++ ) + { + int ping; + + cl = &level.clients[ level.sortedClients[ i ] ]; + + if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE ) + continue; + + if( cl->pers.connected == CON_CONNECTING ) + continue; + + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + + G_LogPrintf( "score: %i ping: %i client: %i %s\n", + cl->ps.persistant[ PERS_SCORE ], ping, level.sortedClients[ i ], + cl->pers.netname ); + + } + + for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( !Q_stricmp( ent->classname, "trigger_win" ) ) + { + if( level.lastWin == ent->stageTeam ) + ent->use( ent, ent, ent ); + } + } + + G_SendGameStat( level.lastWin ); +} + + +/* +================= +CheckIntermissionExit + +The level will stay at the intermission for a minimum of 5 seconds +If all players wish to continue, the level will then exit. +If one or more players have not acknowledged the continue, the game will +wait 10 seconds before going on. +================= +*/ +void CheckIntermissionExit( void ) +{ + int ready, notReady; + int i; + gclient_t *cl; + clientList_t readyMasks; + + //if no clients are connected, just exit + if( level.numConnectedClients == 0 ) + { + ExitLevel( ); + return; + } + + // see which players are ready + ready = 0; + notReady = 0; + Com_Memset( &readyMasks, 0, sizeof( readyMasks ) ); + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + + if( cl->pers.connected != CON_CONNECTED ) + continue; + + if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE ) + continue; + + if( cl->readyToExit ) + { + ready++; + + Com_ClientListAdd( &readyMasks, i ); + } + else + notReady++; + } + + trap_SetConfigstring( CS_CLIENTS_READY, Com_ClientListString( &readyMasks ) ); + + // never exit in less than five seconds + if( level.time < level.intermissiontime + 5000 ) + return; + + // never let intermission go on for over 1 minute + if( level.time > level.intermissiontime + 60000 ) + { + ExitLevel( ); + return; + } + + // if nobody wants to go, clear timer + if( ready == 0 && notReady > 0 ) + { + level.readyToExit = qfalse; + return; + } + + // if everyone wants to go, go now + if( notReady == 0 ) + { + ExitLevel( ); + return; + } + + // the first person to ready starts the thirty second timeout + if( !level.readyToExit ) + { + level.readyToExit = qtrue; + level.exitTime = level.time; + } + + // if we have waited thirty seconds since at least one player + // wanted to exit, go ahead + if( level.time < level.exitTime + 30000 ) + return; + + ExitLevel( ); +} + +/* +============= +ScoreIsTied +============= +*/ +qboolean ScoreIsTied( void ) +{ + int a, b; + + if( level.numPlayingClients < 2 ) + return qfalse; + + a = level.clients[ level.sortedClients[ 0 ] ].ps.persistant[ PERS_SCORE ]; + b = level.clients[ level.sortedClients[ 1 ] ].ps.persistant[ PERS_SCORE ]; + + return a == b; +} + +/* +================= +CheckExitRules + +There will be a delay between the time the exit is qualified for +and the time everyone is moved to the intermission spot, so you +can see the last frag. +================= +*/ +void CheckExitRules( void ) +{ + // if at the intermission, wait for all non-bots to + // signal ready, then go to next level + if( level.intermissiontime ) + { + CheckIntermissionExit( ); + return; + } + + if( level.intermissionQueued ) + { + if( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME ) + { + level.intermissionQueued = 0; + BeginIntermission( ); + } + + return; + } + + if( g_timelimit.integer ) + { + if( level.time - level.startTime >= g_timelimit.integer * 60000 ) + { + level.lastWin = TEAM_NONE; + trap_SendServerCommand( -1, "print \"Timelimit hit\n\"" ); + trap_SetConfigstring( CS_WINNER, "Stalemate" ); + LogExit( "Timelimit hit." ); + return; + } + else if( level.time - level.startTime >= ( g_timelimit.integer - 5 ) * 60000 && + level.timelimitWarning < TW_IMMINENT ) + { + trap_SendServerCommand( -1, "cp \"5 minutes remaining!\"" ); + level.timelimitWarning = TW_IMMINENT; + } + else if( level.time - level.startTime >= ( g_timelimit.integer - 1 ) * 60000 && + level.timelimitWarning < TW_PASSED ) + { + trap_SendServerCommand( -1, "cp \"1 minute remaining!\"" ); + level.timelimitWarning = TW_PASSED; + } + } + + if( level.uncondHumanWin || + ( !level.uncondAlienWin && + ( level.time > level.startTime + 1000 ) && + ( level.numAlienSpawns == 0 ) && + ( level.numLiveAlienClients == 0 ) ) ) + { + //humans win + level.lastWin = TEAM_HUMANS; + trap_SendServerCommand( -1, "print \"Humans win\n\""); + trap_SetConfigstring( CS_WINNER, "Humans Win" ); + LogExit( "Humans win." ); + } + else if( level.uncondAlienWin || + ( ( level.time > level.startTime + 1000 ) && + ( level.numHumanSpawns == 0 ) && + ( level.numLiveHumanClients == 0 ) ) ) + { + //aliens win + level.lastWin = TEAM_ALIENS; + trap_SendServerCommand( -1, "print \"Aliens win\n\""); + trap_SetConfigstring( CS_WINNER, "Aliens Win" ); + LogExit( "Aliens win." ); + } +} + +/* +================== +G_Vote +================== +*/ +void G_Vote( gentity_t *ent, team_t team, qboolean voting ) +{ + if( !level.voteTime[ team ] ) + return; + + if( voting && ent->client->pers.voted & ( 1 << team ) ) + return; + + if( !voting && !( ent->client->pers.voted & ( 1 << team ) ) ) + return; + + ent->client->pers.voted |= 1 << team; + + if( ent->client->pers.vote & ( 1 << team ) ) + { + if( voting ) + level.voteYes[ team ]++; + else + level.voteYes[ team ]--; + + trap_SetConfigstring( CS_VOTE_YES + team, + va( "%d", level.voteYes[ team ] ) ); + } + else + { + if( voting ) + level.voteNo[ team ]++; + else + level.voteNo[ team ]--; + + trap_SetConfigstring( CS_VOTE_NO + team, + va( "%d", level.voteNo[ team ] ) ); + } +} + + +/* +======================================================================== + +FUNCTIONS CALLED EVERY FRAME + +======================================================================== +*/ + + +void G_ExecuteVote( team_t team ) +{ + level.voteExecuteTime[ team ] = 0; + + trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", + level.voteString[ team ] ) ); + + if( !Q_stricmpn( level.voteString[ team ], "map", 3 ) ) + level.restarted = qtrue; +} + +/* +================== +G_CheckVote +================== +*/ +void G_CheckVote( team_t team ) +{ + float votePassThreshold = (float)level.voteThreshold[ team ] / 100.0f; + qboolean pass = qfalse; + char *msg; + int i; + + if( level.voteExecuteTime[ team ] && + level.voteExecuteTime[ team ] < level.time ) + { + G_ExecuteVote( team ); + } + + if( !level.voteTime[ team ] ) + return; + + if( ( level.time - level.voteTime[ team ] >= VOTE_TIME ) || + ( level.voteYes[ team ] + level.voteNo[ team ] == level.numVotingClients[ team ] ) ) + { + pass = ( level.voteYes[ team ] && + (float)level.voteYes[ team ] / ( (float)level.voteYes[ team ] + (float)level.voteNo[ team ] ) > votePassThreshold ); + } + else + { + if( (float)level.voteYes[ team ] > + (float)level.numVotingClients[ team ] * votePassThreshold ) + { + pass = qtrue; + } + else if( (float)level.voteNo[ team ] <= + (float)level.numVotingClients[ team ] * ( 1.0f - votePassThreshold ) ) + { + return; + } + } + + if( pass ) + level.voteExecuteTime[ team ] = level.time + level.voteDelay[ team ]; + + G_LogPrintf( "EndVote: %s %s %d %d %d\n", + team == TEAM_NONE ? "global" : BG_TeamName( team ), + pass ? "pass" : "fail", + level.voteYes[ team ], level.voteNo[ team ], level.numVotingClients[ team ] ); + + msg = va( "print \"%sote %sed (%d - %d)\n\"", + team == TEAM_NONE ? "V" : "Team v", pass ? "pass" : "fail", + level.voteYes[ team ], level.voteNo[ team ] ); + + if( team == TEAM_NONE ) + trap_SendServerCommand( -1, msg ); + else + G_TeamCommand( team, msg ); + + level.voteTime[ team ] = 0; + level.voteYes[ team ] = 0; + level.voteNo[ team ] = 0; + + for( i = 0; i < level.maxclients; i++ ) + level.clients[ i ].pers.voted &= ~( 1 << team ); + + trap_SetConfigstring( CS_VOTE_TIME + team, "" ); + trap_SetConfigstring( CS_VOTE_STRING + team, "" ); + trap_SetConfigstring( CS_VOTE_YES + team, "0" ); + trap_SetConfigstring( CS_VOTE_NO + team, "0" ); +} + + +/* +================== +CheckCvars +================== +*/ +void CheckCvars( void ) +{ + static int lastPasswordModCount = -1; + static int lastMarkDeconModCount = -1; + static int lastSDTimeModCount = -1; + static int lastNumZones = 0; + + if( g_password.modificationCount != lastPasswordModCount ) + { + lastPasswordModCount = g_password.modificationCount; + + if( *g_password.string && Q_stricmp( g_password.string, "none" ) ) + trap_Cvar_Set( "g_needpass", "1" ); + else + trap_Cvar_Set( "g_needpass", "0" ); + } + + // Unmark any structures for deconstruction when + // the server setting is changed + if( g_markDeconstruct.modificationCount != lastMarkDeconModCount ) + { + lastMarkDeconModCount = g_markDeconstruct.modificationCount; + G_ClearDeconMarks( ); + } + + // If we change g_suddenDeathTime during a map, we need to update + // when sd will begin + if( g_suddenDeathTime.modificationCount != lastSDTimeModCount ) + { + lastSDTimeModCount = g_suddenDeathTime.modificationCount; + level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; + } + + // If the number of zones changes, we need a new array + if( g_humanRepeaterMaxZones.integer != lastNumZones ) + { + buildPointZone_t *newZones; + size_t newsize = g_humanRepeaterMaxZones.integer * sizeof( buildPointZone_t ); + size_t oldsize = lastNumZones * sizeof( buildPointZone_t ); + + newZones = BG_Alloc( newsize ); + if( level.buildPointZones ) + { + Com_Memcpy( newZones, level.buildPointZones, MIN( oldsize, newsize ) ); + BG_Free( level.buildPointZones ); + } + + level.buildPointZones = newZones; + lastNumZones = g_humanRepeaterMaxZones.integer; + } + + level.frameMsec = trap_Milliseconds( ); +} + +/* +============= +G_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +void G_RunThink( gentity_t *ent ) +{ + float thinktime; + + thinktime = ent->nextthink; + if( thinktime <= 0 ) + return; + + if( thinktime > level.time ) + return; + + ent->nextthink = 0; + if( !ent->think ) + G_Error( "NULL ent->think" ); + + ent->think( ent ); +} + +/* +============= +G_EvaluateAcceleration + +Calculates the acceleration for an entity +============= +*/ +void G_EvaluateAcceleration( gentity_t *ent, int msec ) +{ + vec3_t deltaVelocity; + vec3_t deltaAccel; + + VectorSubtract( ent->s.pos.trDelta, ent->oldVelocity, deltaVelocity ); + VectorScale( deltaVelocity, 1.0f / (float)msec, ent->acceleration ); + + VectorSubtract( ent->acceleration, ent->oldAccel, deltaAccel ); + VectorScale( deltaAccel, 1.0f / (float)msec, ent->jerk ); + + VectorCopy( ent->s.pos.trDelta, ent->oldVelocity ); + VectorCopy( ent->acceleration, ent->oldAccel ); +} + +/* +================ +G_RunFrame + +Advances the non-player objects in the world +================ +*/ +void G_RunFrame( int levelTime ) +{ + int i; + gentity_t *ent; + int msec; + static int ptime3000 = 0; + + // if we are waiting for the level to restart, do nothing + if( level.restarted ) + return; + + if( level.pausedTime ) + { + msec = levelTime - level.time - level.pausedTime; + level.pausedTime = levelTime - level.time; + + ptime3000 += msec; + while( ptime3000 > 3000 ) + { + ptime3000 -= 3000; + trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" ); + + if( level.pausedTime >= 110000 && level.pausedTime <= 119000 ) + trap_SendServerCommand( -1, va( "print \"Server: Game will auto-unpause in %d seconds\n\"", + (int) ( (float) ( 120000 - level.pausedTime ) / 1000.0f ) ) ); + } + + // Prevents clients from getting lagged-out messages + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + level.clients[ i ].ps.commandTime = levelTime; + } + + if( level.pausedTime > 120000 ) + { + trap_SendServerCommand( -1, "print \"Server: The game has been unpaused automatically (2 minute max)\n\"" ); + trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); + level.pausedTime = 0; + } + + return; + } + + level.framenum++; + level.previousTime = level.time; + level.time = levelTime; + msec = level.time - level.previousTime; + + // get any cvar changes + G_UpdateCvars( ); + CheckCvars( ); + // now we are done spawning + level.spawning = qfalse; + + // + // go through all allocated objects + // + ent = &g_entities[ 0 ]; + + for( i = 0; i < level.num_entities; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + // clear events that are too old + if( level.time - ent->eventTime > EVENT_VALID_MSEC ) + { + if( ent->s.event ) + { + ent->s.event = 0; // &= EV_EVENT_BITS; + if ( ent->client ) + { + ent->client->ps.externalEvent = 0; + //ent->client->ps.events[0] = 0; + //ent->client->ps.events[1] = 0; + } + } + + if( ent->freeAfterEvent ) + { + // tempEntities or dropped items completely go away after their event + G_FreeEntity( ent ); + continue; + } + else if( ent->unlinkAfterEvent ) + { + // items that will respawn will hide themselves after their pickup event + ent->unlinkAfterEvent = qfalse; + trap_UnlinkEntity( ent ); + } + } + + // temporary entities don't think + if( ent->freeAfterEvent ) + continue; + + // calculate the acceleration of this entity + if( ent->evaluateAcceleration ) + G_EvaluateAcceleration( ent, msec ); + + if( !ent->r.linked && ent->neverFree ) + continue; + + if( ent->s.eType == ET_MISSILE ) + { + G_RunMissile( ent ); + continue; + } + + if( ent->s.eType == ET_BUILDABLE ) + { + G_BuildableThink( ent, msec ); + continue; + } + + if( ent->s.eType == ET_CORPSE || ent->physicsObject ) + { + G_Physics( ent, msec ); + continue; + } + + if( ent->s.eType == ET_MOVER ) + { + G_RunMover( ent ); + continue; + } + + if( i < MAX_CLIENTS ) + { + G_RunClient( ent ); + continue; + } + + G_RunThink( ent ); + } + + // perform final fixups on the players + ent = &g_entities[ 0 ]; + + for( i = 0; i < level.maxclients; i++, ent++ ) + { + if( ent->inuse ) + ClientEndFrame( ent ); + } + + // save position information for all active clients + G_UnlaggedStore( ); + + G_CountSpawns( ); + G_CalculateBuildPoints( ); + G_CalculateStages( ); + + G_SpawnClients( TEAM_ALIENS ); + G_SpawnClients( TEAM_HUMANS ); + G_CalculateAvgPlayers( ); + G_UpdateZaps( msec ); + + // see if it is time to end the level + CheckExitRules( ); + + // update to team status? + CheckTeamStatus( ); + + // cancel vote if timed out + for( i = 0; i < NUM_TEAMS; i++ ) + G_CheckVote( i ); + + level.frameMsec = trap_Milliseconds(); +} + diff --git a/src/game/g_maprotation.c b/src/game/g_maprotation.c new file mode 100644 index 0000000..a8a9ea2 --- /dev/null +++ b/src/game/g_maprotation.c @@ -0,0 +1,1264 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// g_maprotation.c -- the map rotation system + +#include "g_local.h" + +#define MAX_MAP_ROTATIONS 64 +#define MAX_MAP_ROTATION_MAPS 256 + +#define NOT_ROTATING -1 + +typedef enum +{ + CV_ERR, + CV_RANDOM, + CV_NUMCLIENTS, + CV_LASTWIN +} conditionVariable_t; + +typedef enum +{ + CO_LT, + CO_EQ, + CO_GT +} conditionOp_t; + +typedef struct condition_s +{ + struct node_s *target; + + conditionVariable_t lhs; + conditionOp_t op; + + int numClients; + team_t lastWin; +} condition_t; + +typedef struct map_s +{ + char name[ MAX_QPATH ]; + + char postCommand[ MAX_STRING_CHARS ]; + char layouts[ MAX_CVAR_VALUE_STRING ]; +} map_t; + +typedef struct label_s +{ + char name[ MAX_QPATH ]; +} label_t; + +typedef enum +{ + NT_MAP, + NT_CONDITION, + NT_GOTO, + NT_RESUME, + NT_LABEL, + NT_RETURN +} nodeType_t; + +typedef struct node_s +{ + nodeType_t type; + + union + { + map_t map; + condition_t condition; + label_t label; + } u; + +} node_t; + +typedef struct mapRotation_s +{ + char name[ MAX_QPATH ]; + + node_t *nodes[ MAX_MAP_ROTATION_MAPS ]; + int numNodes; + int currentNode; +} mapRotation_t; + +typedef struct mapRotations_s +{ + mapRotation_t rotations[ MAX_MAP_ROTATIONS ]; + int numRotations; +} mapRotations_t; + +static mapRotations_t mapRotations; + +static int G_NodeIndexAfter( int currentNode, int rotation ); + +/* +=============== +G_MapExists + +Check if a map exists +=============== +*/ +qboolean G_MapExists( char *name ) +{ + return trap_FS_FOpenFile( va( "maps/%s.bsp", name ), NULL, FS_READ ); +} + +/* +=============== +G_RotationExists + +Check if a rotation exists +=============== +*/ +static qboolean G_RotationExists( char *name ) +{ + int i; + + for( i = 0; i < mapRotations.numRotations; i++ ) + { + if( Q_strncmp( mapRotations.rotations[ i ].name, name, MAX_QPATH ) == 0 ) + return qtrue; + } + + return qfalse; +} + +/* +=============== +G_LabelExists + +Check if a label exists in a rotation +=============== +*/ +static qboolean G_LabelExists( int rotation, char *name ) +{ + mapRotation_t *mr = &mapRotations.rotations[ rotation ]; + int i; + + for( i = 0; i < mr->numNodes; i++ ) + { + node_t *node = mr->nodes[ i ]; + + if( node->type == NT_LABEL && + !Q_stricmp( name, node->u.label.name ) ) + return qtrue; + + if( node->type == NT_MAP && + !Q_stricmp( name, node->u.map.name ) ) + return qtrue; + } + + return qfalse; +} + +/* +=============== +G_AllocateNode + +Allocate memory for a node_t +=============== +*/ +static node_t *G_AllocateNode( void ) +{ + node_t *node = BG_Alloc( sizeof( node_t ) ); + + return node; +} + +/* +=============== +G_ParseMapCommandSection + +Parse a map rotation command section +=============== +*/ +static qboolean G_ParseMapCommandSection( node_t *node, char **text_p ) +{ + char *token; + map_t *map = &node->u.map; + int commandLength = 0; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !*token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "}" ) ) + { + if( commandLength > 0 ) + { + // Replace last ; with \n + map->postCommand[ commandLength - 1 ] = '\n'; + } + + return qtrue; //reached the end of this command section + } + + if( !Q_stricmp( token, "layouts" ) ) + { + token = COM_ParseExt( text_p, qfalse ); + map->layouts[ 0 ] = '\0'; + + while( token[ 0 ] != 0 ) + { + Q_strcat( map->layouts, sizeof( map->layouts ), token ); + Q_strcat( map->layouts, sizeof( map->layouts ), " " ); + token = COM_ParseExt( text_p, qfalse ); + } + + continue; + } + + // Parse the rest of the line into map->postCommand + Q_strcat( map->postCommand, sizeof( map->postCommand ), token ); + Q_strcat( map->postCommand, sizeof( map->postCommand ), " " ); + + token = COM_ParseExt( text_p, qfalse ); + + while( token[ 0 ] != 0 ) + { + Q_strcat( map->postCommand, sizeof( map->postCommand ), token ); + Q_strcat( map->postCommand, sizeof( map->postCommand ), " " ); + token = COM_ParseExt( text_p, qfalse ); + } + + commandLength = strlen( map->postCommand ); + map->postCommand[ commandLength - 1 ] = ';'; + } + + return qfalse; +} + +/* +=============== +G_ParseNode + +Parse a node +=============== +*/ +static qboolean G_ParseNode( node_t **node, char *token, char **text_p, qboolean conditional ) +{ + if( !Q_stricmp( token, "if" ) ) + { + condition_t *condition; + + (*node)->type = NT_CONDITION; + condition = &(*node)->u.condition; + + token = COM_Parse( text_p ); + + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "numClients" ) ) + { + condition->lhs = CV_NUMCLIENTS; + + token = COM_Parse( text_p ); + + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "<" ) ) + condition->op = CO_LT; + else if( !Q_stricmp( token, ">" ) ) + condition->op = CO_GT; + else if( !Q_stricmp( token, "=" ) ) + condition->op = CO_EQ; + else + { + G_Printf( S_COLOR_RED "ERROR: invalid operator in expression: %s\n", token ); + return qfalse; + } + + token = COM_Parse( text_p ); + + if( !*token ) + return qfalse; + + condition->numClients = atoi( token ); + } + else if( !Q_stricmp( token, "lastWin" ) ) + { + condition->lhs = CV_LASTWIN; + + token = COM_Parse( text_p ); + + if( !*token ) + return qfalse; + + if( !Q_stricmp( token, "aliens" ) ) + condition->lastWin = TEAM_ALIENS; + else if( !Q_stricmp( token, "humans" ) ) + condition->lastWin = TEAM_HUMANS; + else + { + G_Printf( S_COLOR_RED "ERROR: invalid right hand side in expression: %s\n", token ); + return qfalse; + } + } + else if( !Q_stricmp( token, "random" ) ) + condition->lhs = CV_RANDOM; + else + { + G_Printf( S_COLOR_RED "ERROR: invalid left hand side in expression: %s\n", token ); + return qfalse; + } + + token = COM_Parse( text_p ); + + if( !*token ) + return qfalse; + + condition->target = G_AllocateNode( ); + *node = condition->target; + + return G_ParseNode( node, token, text_p, qtrue ); + } + else if( !Q_stricmp( token, "return" ) ) + { + (*node)->type = NT_RETURN; + } + else if( !Q_stricmp( token, "goto" ) || + !Q_stricmp( token, "resume" ) ) + { + label_t *label; + + if( !Q_stricmp( token, "goto" ) ) + (*node)->type = NT_GOTO; + else + (*node)->type = NT_RESUME; + label = &(*node)->u.label; + + token = COM_Parse( text_p ); + + if( !*token ) + { + G_Printf( S_COLOR_RED "ERROR: goto or resume without label\n" ); + return qfalse; + } + + Q_strncpyz( label->name, token, sizeof( label->name ) ); + } + else if( *token == '#' || conditional ) + { + label_t *label; + + (*node)->type = ( conditional ) ? NT_GOTO : NT_LABEL; + label = &(*node)->u.label; + + Q_strncpyz( label->name, token, sizeof( label->name ) ); + } + else + { + map_t *map; + + (*node)->type = NT_MAP; + map = &(*node)->u.map; + + Q_strncpyz( map->name, token, sizeof( map->name ) ); + map->postCommand[ 0 ] = '\0'; + } + + return qtrue; +} + +/* +=============== +G_ParseMapRotation + +Parse a map rotation section +=============== +*/ +static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p ) +{ + char *token; + node_t *node = NULL; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !*token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "{" ) ) + { + if( node == NULL ) + { + G_Printf( S_COLOR_RED "ERROR: map command section with no associated map\n" ); + return qfalse; + } + + if( !G_ParseMapCommandSection( node, text_p ) ) + { + G_Printf( S_COLOR_RED "ERROR: failed to parse map command section\n" ); + return qfalse; + } + + continue; + } + else if( !Q_stricmp( token, "}" ) ) + { + // Reached the end of this map rotation + return qtrue; + } + + if( mr->numNodes == MAX_MAP_ROTATION_MAPS ) + { + G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n", + MAX_MAP_ROTATION_MAPS ); + return qfalse; + } + + node = G_AllocateNode( ); + mr->nodes[ mr->numNodes++ ] = node; + + if( !G_ParseNode( &node, token, text_p, qfalse ) ) + return qfalse; + } + + return qfalse; +} + +/* +=============== +G_ParseMapRotationFile + +Load the map rotations from a map rotation file +=============== +*/ +static qboolean G_ParseMapRotationFile( const char *fileName ) +{ + char *text_p; + int i, j; + int len; + char *token; + char text[ 20000 ]; + char mrName[ MAX_QPATH ]; + qboolean mrNameSet = qfalse; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + G_Printf( S_COLOR_RED "ERROR: map rotation file %s is %s\n", fileName, + len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !*token ) + break; + + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "{" ) ) + { + if( mrNameSet ) + { + //check for name space clashes + if( G_RotationExists( mrName ) ) + { + G_Printf( S_COLOR_RED "ERROR: a map rotation is already named %s\n", mrName ); + return qfalse; + } + + if( mapRotations.numRotations == MAX_MAP_ROTATIONS ) + { + G_Printf( S_COLOR_RED "ERROR: maximum number of map rotations (%d) reached\n", + MAX_MAP_ROTATIONS ); + return qfalse; + } + + Q_strncpyz( mapRotations.rotations[ mapRotations.numRotations ].name, mrName, MAX_QPATH ); + + if( !G_ParseMapRotation( &mapRotations.rotations[ mapRotations.numRotations ], &text_p ) ) + { + G_Printf( S_COLOR_RED "ERROR: %s: failed to parse map rotation %s\n", fileName, mrName ); + return qfalse; + } + + mapRotations.numRotations++; + + //start parsing map rotations again + mrNameSet = qfalse; + + continue; + } + else + { + G_Printf( S_COLOR_RED "ERROR: unnamed map rotation\n" ); + return qfalse; + } + } + + if( !mrNameSet ) + { + Q_strncpyz( mrName, token, sizeof( mrName ) ); + mrNameSet = qtrue; + } + else + { + G_Printf( S_COLOR_RED "ERROR: map rotation already named\n" ); + return qfalse; + } + } + + for( i = 0; i < mapRotations.numRotations; i++ ) + { + mapRotation_t *mr = &mapRotations.rotations[ i ]; + int mapCount = 0; + + for( j = 0; j < mr->numNodes; j++ ) + { + node_t *node = mr->nodes[ j ]; + + if( node->type == NT_MAP ) + { + mapCount++; + if( !G_MapExists( node->u.map.name ) ) + { + G_Printf( S_COLOR_RED "ERROR: rotation map \"%s\" doesn't exist\n", + node->u.map.name ); + return qfalse; + } + continue; + } + else if( node->type == NT_RETURN ) + continue; + else if( node->type == NT_LABEL ) + continue; + else while( node->type == NT_CONDITION ) + node = node->u.condition.target; + + if( ( node->type == NT_GOTO || node->type == NT_RESUME ) && + !G_LabelExists( i, node->u.label.name ) && + !G_RotationExists( node->u.label.name ) ) + { + G_Printf( S_COLOR_RED "ERROR: goto destination named \"%s\" doesn't exist\n", + node->u.label.name ); + return qfalse; + } + } + + if( mapCount == 0 ) + { + G_Printf( S_COLOR_RED "ERROR: rotation \"%s\" needs at least one map entry\n", + mr->name ); + return qfalse; + } + } + + return qtrue; +} + +/* +=============== +G_PrintSpaces +=============== +*/ +static void G_PrintSpaces( int spaces ) +{ + int i; + + for( i = 0; i < spaces; i++ ) + G_Printf( " " ); +} + +/* +=============== +G_PrintRotations + +Print the parsed map rotations +=============== +*/ +void G_PrintRotations( void ) +{ + int i, j; + int size = sizeof( mapRotations ); + + G_Printf( "Map rotations as parsed:\n\n" ); + + for( i = 0; i < mapRotations.numRotations; i++ ) + { + mapRotation_t *mr = &mapRotations.rotations[ i ]; + + G_Printf( "rotation: %s\n{\n", mr->name ); + + size += mr->numNodes * sizeof( node_t ); + + for( j = 0; j < mr->numNodes; j++ ) + { + node_t *node = mr->nodes[ j ]; + int indentation = 0; + + while( node->type == NT_CONDITION ) + { + G_PrintSpaces( indentation ); + G_Printf( " condition\n" ); + node = node->u.condition.target; + + size += sizeof( node_t ); + + indentation += 2; + } + + G_PrintSpaces( indentation ); + + switch( node->type ) + { + case NT_MAP: + G_Printf( " %s\n", node->u.map.name ); + + if( strlen( node->u.map.postCommand ) > 0 ) + G_Printf( " command: %s", node->u.map.postCommand ); + + break; + + case NT_RETURN: + G_Printf( " return\n" ); + break; + + case NT_LABEL: + G_Printf( " label: %s\n", node->u.label.name ); + break; + + case NT_GOTO: + G_Printf( " goto: %s\n", node->u.label.name ); + break; + + case NT_RESUME: + G_Printf( " resume: %s\n", node->u.label.name ); + break; + + default: + break; + } + } + + G_Printf( "}\n" ); + } + + G_Printf( "Total memory used: %d bytes\n", size ); +} + +/* +=============== +G_ClearRotationStack + +Clear the rotation stack +=============== +*/ +void G_ClearRotationStack( void ) +{ + trap_Cvar_Set( "g_mapRotationStack", "" ); + trap_Cvar_Update( &g_mapRotationStack ); +} + +/* +=============== +G_PushRotationStack + +Push the rotation stack +=============== +*/ +static void G_PushRotationStack( int rotation ) +{ + char text[ MAX_CVAR_VALUE_STRING ]; + + Com_sprintf( text, sizeof( text ), "%d %s", + rotation, g_mapRotationStack.string ); + trap_Cvar_Set( "g_mapRotationStack", text ); + trap_Cvar_Update( &g_mapRotationStack ); +} + +/* +=============== +G_PopRotationStack + +Pop the rotation stack +=============== +*/ +static int G_PopRotationStack( void ) +{ + int value = -1; + char text[ MAX_CVAR_VALUE_STRING ]; + char *text_p, *token; + + Q_strncpyz( text, g_mapRotationStack.string, sizeof( text ) ); + + text_p = text; + token = COM_Parse( &text_p ); + + if( *token ) + value = atoi( token ); + + if( text_p ) + { + while ( *text_p == ' ' ) + text_p++; + trap_Cvar_Set( "g_mapRotationStack", text_p ); + trap_Cvar_Update( &g_mapRotationStack ); + } + else + G_ClearRotationStack( ); + + return value; +} + +/* +=============== +G_RotationNameByIndex + +Returns the name of a rotation by its index +=============== +*/ +static char *G_RotationNameByIndex( int index ) +{ + if( index >= 0 && index < mapRotations.numRotations ) + return mapRotations.rotations[ index ].name; + return NULL; +} + +/* +=============== +G_CurrentNodeIndexArray + +Fill a static array with the current node of each rotation +=============== +*/ +static int *G_CurrentNodeIndexArray( void ) +{ + static int currentNode[ MAX_MAP_ROTATIONS ]; + int i = 0; + char text[ MAX_MAP_ROTATIONS * 2 ]; + char *text_p, *token; + + Q_strncpyz( text, g_mapRotationNodes.string, sizeof( text ) ); + + text_p = text; + + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !*token ) + break; + + currentNode[ i++ ] = atoi( token ); + } + + return currentNode; +} + +/* +=============== +G_SetCurrentNodeByIndex + +Set the current map in some rotation +=============== +*/ +static void G_SetCurrentNodeByIndex( int currentNode, int rotation ) +{ + char text[ MAX_MAP_ROTATIONS * 4 ] = { 0 }; + int *p = G_CurrentNodeIndexArray( ); + int i; + + p[ rotation ] = currentNode; + + for( i = 0; i < mapRotations.numRotations; i++ ) + Q_strcat( text, sizeof( text ), va( "%d ", p[ i ] ) ); + + trap_Cvar_Set( "g_mapRotationNodes", text ); + trap_Cvar_Update( &g_mapRotationNodes ); +} + +/* +=============== +G_CurrentNodeIndex + +Return the current node index in some rotation +=============== +*/ +static int G_CurrentNodeIndex( int rotation ) +{ + int *p = G_CurrentNodeIndexArray( ); + + return p[ rotation ]; +} + +/* +=============== +G_NodeByIndex + +Return a node in a rotation by its index +=============== +*/ +static node_t *G_NodeByIndex( int index, int rotation ) +{ + if( rotation >= 0 && rotation < mapRotations.numRotations && + index >= 0 && index < mapRotations.rotations[ rotation ].numNodes ) + return mapRotations.rotations[ rotation ].nodes[ index ]; + + return NULL; +} + +/* +=============== +G_IssueMapChange + +Send commands to the server to actually change the map +=============== +*/ +static void G_IssueMapChange( int index, int rotation ) +{ + node_t *node = mapRotations.rotations[ rotation ].nodes[ index ]; + map_t *map = &node->u.map; + + // allow a manually defined g_layouts setting to override the maprotation + if( !g_layouts.string[ 0 ] && map->layouts[ 0 ] ) + { + trap_Cvar_Set( "g_layouts", map->layouts ); + } + + trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n", map->name ) ); + + // Load up map defaults if g_mapConfigs is set + G_MapConfigs( map->name ); + + if( strlen( map->postCommand ) > 0 ) + trap_SendConsoleCommand( EXEC_APPEND, map->postCommand ); +} + +/* +=============== +G_GotoLabel + +Resolve the label of some condition +=============== +*/ +static qboolean G_GotoLabel( int rotation, int nodeIndex, char *name, + qboolean reset_index, int depth ) +{ + node_t *node; + int i; + + // Search the rotation names... + if( G_StartMapRotation( name, qtrue, qtrue, reset_index, depth ) ) + return qtrue; + + // ...then try labels in the rotation + for( i = 0; i < mapRotations.rotations[ rotation ].numNodes; i++ ) + { + node = mapRotations.rotations[ rotation ].nodes[ i ]; + + if( node->type == NT_LABEL && !Q_stricmp( node->u.label.name, name ) ) + { + G_SetCurrentNodeByIndex( G_NodeIndexAfter( i, rotation ), rotation ); + G_AdvanceMapRotation( depth ); + return qtrue; + } + } + + // finally check for a map by name + for( i = 0; i < mapRotations.rotations[ rotation ].numNodes; i++ ) + { + nodeIndex = G_NodeIndexAfter( nodeIndex, rotation ); + node = mapRotations.rotations[ rotation ].nodes[ nodeIndex ]; + + if( node->type == NT_MAP && !Q_stricmp( node->u.map.name, name ) ) + { + G_SetCurrentNodeByIndex( nodeIndex, rotation ); + G_AdvanceMapRotation( depth ); + return qtrue; + } + } + + return qfalse; +} + +/* +=============== +G_EvaluateMapCondition + +Evaluate a map condition +=============== +*/ +static qboolean G_EvaluateMapCondition( condition_t **condition ) +{ + qboolean result = qfalse; + condition_t *localCondition = *condition; + + switch( localCondition->lhs ) + { + case CV_RANDOM: + result = rand( ) / ( RAND_MAX / 2 + 1 ); + break; + + case CV_NUMCLIENTS: + switch( localCondition->op ) + { + case CO_LT: + result = level.numConnectedClients < localCondition->numClients; + break; + + case CO_GT: + result = level.numConnectedClients > localCondition->numClients; + break; + + case CO_EQ: + result = level.numConnectedClients == localCondition->numClients; + break; + } + break; + + case CV_LASTWIN: + result = level.lastWin == localCondition->lastWin; + break; + + default: + case CV_ERR: + G_Printf( S_COLOR_RED "ERROR: malformed map switch localCondition\n" ); + break; + } + + if( localCondition->target->type == NT_CONDITION ) + { + *condition = &localCondition->target->u.condition; + + return result && G_EvaluateMapCondition( condition ); + } + + return result; +} + +/* +=============== +G_NodeIndexAfter +=============== +*/ +static int G_NodeIndexAfter( int currentNode, int rotation ) +{ + mapRotation_t *mr = &mapRotations.rotations[ rotation ]; + + return ( currentNode + 1 ) % mr->numNodes; +} + +/* +=============== +G_StepMapRotation + +Run one node of a map rotation +=============== +*/ +qboolean G_StepMapRotation( int rotation, int nodeIndex, int depth ) +{ + node_t *node; + condition_t *condition; + int returnRotation; + qboolean step = qtrue; + + node = G_NodeByIndex( nodeIndex, rotation ); + depth++; + + // guard against inifinite loop in conditional code + if( depth > 32 && node->type != NT_MAP ) + { + if( depth > 64 ) + { + G_Printf( S_COLOR_RED "ERROR: infinite loop protection stopped at map rotation %s\n", + G_RotationNameByIndex( rotation ) ); + return qfalse; + } + + G_Printf( S_COLOR_YELLOW "WARNING: possible infinite loop in map rotation %s\n", + G_RotationNameByIndex( rotation ) ); + return qtrue; + } + + while( step ) + { + step = qfalse; + switch( node->type ) + { + case NT_CONDITION: + condition = &node->u.condition; + + if( G_EvaluateMapCondition( &condition ) ) + { + node = condition->target; + step = qtrue; + continue; + } + break; + + case NT_RETURN: + returnRotation = G_PopRotationStack( ); + if( returnRotation >= 0 ) + { + G_SetCurrentNodeByIndex( + G_NodeIndexAfter( nodeIndex, rotation ), rotation ); + if( G_StartMapRotation( G_RotationNameByIndex( returnRotation ), + qtrue, qfalse, qfalse, depth ) ) + { + return qfalse; + } + } + break; + + case NT_MAP: + if( G_MapExists( node->u.map.name ) ) + { + G_SetCurrentNodeByIndex( + G_NodeIndexAfter( nodeIndex, rotation ), rotation ); + G_IssueMapChange( nodeIndex, rotation ); + return qfalse; + } + + G_Printf( S_COLOR_YELLOW "WARNING: skipped missing map %s in rotation %s\n", + node->u.map.name, G_RotationNameByIndex( rotation ) ); + break; + + case NT_LABEL: + break; + + case NT_GOTO: + case NT_RESUME: + G_SetCurrentNodeByIndex( + G_NodeIndexAfter( nodeIndex, rotation ), rotation ); + if ( G_GotoLabel( rotation, nodeIndex, node->u.label.name, + ( node->type == NT_GOTO ), depth ) ) + return qfalse; + + G_Printf( S_COLOR_YELLOW "WARNING: label, map, or rotation %s not found in %s\n", + node->u.label.name, G_RotationNameByIndex( rotation ) ); + break; + } + } + + return qtrue; +} + +/* +=============== +G_AdvanceMapRotation + +Increment the current map rotation +=============== +*/ +void G_AdvanceMapRotation( int depth ) +{ + node_t *node; + int rotation; + int nodeIndex; + + rotation = g_currentMapRotation.integer; + if( rotation < 0 || rotation >= MAX_MAP_ROTATIONS ) + return; + + nodeIndex = G_CurrentNodeIndex( rotation ); + node = G_NodeByIndex( nodeIndex, rotation ); + if( !node ) + { + G_Printf( S_COLOR_YELLOW "WARNING: index incorrect for map rotation %s, trying 0\n", + G_RotationNameByIndex( rotation) ); + nodeIndex = 0; + node = G_NodeByIndex( nodeIndex, rotation ); + } + + while( node && G_StepMapRotation( rotation, nodeIndex, depth ) ) + { + nodeIndex = G_NodeIndexAfter( nodeIndex, rotation ); + node = G_NodeByIndex( nodeIndex, rotation ); + depth++; + } + + if( !node ) + G_Printf( S_COLOR_RED "ERROR: unexpected end of maprotation '%s'\n", + G_RotationNameByIndex( rotation) ); +} + +/* +=============== +G_StartMapRotation + +Switch to a new map rotation +=============== +*/ +qboolean G_StartMapRotation( char *name, qboolean advance, + qboolean putOnStack, qboolean reset_index, int depth ) +{ + int i; + int currentRotation = g_currentMapRotation.integer; + + for( i = 0; i < mapRotations.numRotations; i++ ) + { + if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) ) + { + if( putOnStack && currentRotation >= 0 ) + G_PushRotationStack( currentRotation ); + + trap_Cvar_Set( "g_currentMapRotation", va( "%d", i ) ); + trap_Cvar_Update( &g_currentMapRotation ); + + if( advance ) + { + if( reset_index ) + G_SetCurrentNodeByIndex( 0, i ); + + G_AdvanceMapRotation( depth ); + } + + break; + } + } + + if( i == mapRotations.numRotations ) + return qfalse; + else + return qtrue; +} + +/* +=============== +G_StopMapRotation + +Stop the current map rotation +=============== +*/ +void G_StopMapRotation( void ) +{ + trap_Cvar_Set( "g_currentMapRotation", va( "%d", NOT_ROTATING ) ); + trap_Cvar_Update( &g_currentMapRotation ); +} + +/* +=============== +G_MapRotationActive + +Test if any map rotation is currently active +=============== +*/ +qboolean G_MapRotationActive( void ) +{ + return ( g_currentMapRotation.integer != NOT_ROTATING ); +} + +/* +=============== +G_InitMapRotations + +Load and initialise the map rotations +=============== +*/ +void G_InitMapRotations( void ) +{ + const char *fileName = "maprotation.cfg"; + + // Load the file if it exists + if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) ) + { + if( !G_ParseMapRotationFile( fileName ) ) + G_Printf( S_COLOR_RED "ERROR: failed to parse %s file\n", fileName ); + } + else + G_Printf( "%s file not found.\n", fileName ); + + if( g_currentMapRotation.integer == NOT_ROTATING ) + { + if( g_initialMapRotation.string[ 0 ] != 0 ) + { + G_StartMapRotation( g_initialMapRotation.string, qfalse, qtrue, qfalse, 0 ); + + trap_Cvar_Set( "g_initialMapRotation", "" ); + trap_Cvar_Update( &g_initialMapRotation ); + } + } +} + +/* +=============== +G_FreeNode + +Free up memory used by a node +=============== +*/ +void G_FreeNode( node_t *node ) +{ + if( node->type == NT_CONDITION ) + G_FreeNode( node->u.condition.target ); + + BG_Free( node ); +} + +/* +=============== +G_ShutdownMapRotations + +Free up memory used by map rotations +=============== +*/ +void G_ShutdownMapRotations( void ) +{ + int i, j; + + for( i = 0; i < mapRotations.numRotations; i++ ) + { + mapRotation_t *mr = &mapRotations.rotations[ i ]; + + for( j = 0; j < mr->numNodes; j++ ) + { + node_t *node = mr->nodes[ j ]; + + G_FreeNode( node ); + } + } +} diff --git a/src/game/g_misc.c b/src/game/g_misc.c new file mode 100644 index 0000000..861fc64 --- /dev/null +++ b/src/game/g_misc.c @@ -0,0 +1,440 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities. +*/ + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. +*/ +void SP_info_null( gentity_t *self ) +{ + G_FreeEntity( self ); +} + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for in-game calculation, like jumppad targets. +target_position does the same thing +*/ +void SP_info_notnull( gentity_t *self ) +{ + G_SetOrigin( self, self->s.origin ); +} + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear +Non-displayed light. +"light" overrides the default 300 intensity. +Linear checbox gives linear falloff instead of inverse square +Lights pointed at a target will be spotlights. +"radius" overrides the default 64 unit radius of a spotlight at the target point. +*/ +void SP_light( gentity_t *self ) +{ + G_FreeEntity( self ); +} + + + +/* +================================================================================= + +TELEPORTERS + +================================================================================= +*/ + +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) +{ + // unlink to make sure it can't possibly interfere with G_KillBox + trap_UnlinkEntity( player ); + + VectorCopy( origin, player->client->ps.origin ); + player->client->ps.origin[ 2 ] += 1; + + // spit the player out + AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); + VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity ); + player->client->ps.pm_time = 160; // hold time + player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + + // toggle the teleport bit so the client knows to not lerp + player->client->ps.eFlags ^= EF_TELEPORT_BIT; + G_UnlaggedClear( player ); + + // cut all relevant zap beams + G_ClearPlayerZapEffects( player ); + + // set angles + G_SetClientViewAngle( player, angles ); + + // save results of pmove + BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); + + // use the precise origin for linking + VectorCopy( player->client->ps.origin, player->r.currentOrigin ); + + if( player->client->sess.spectatorState == SPECTATOR_NOT ) + { + // kill anything at the destination + G_KillBox( player ); + + trap_LinkEntity (player); + } +} + + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +Now that we don't have teleport destination pads, this is just +an info_notnull +*/ +void SP_misc_teleporter_dest( gentity_t *ent ) +{ +} + + +//=========================================================== + +/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) +"model" arbitrary .md3 file to display +*/ +void SP_misc_model( gentity_t *ent ) +{ +#if 0 + ent->s.modelindex = G_ModelIndex( ent->model ); + VectorSet (ent->mins, -16, -16, -16); + VectorSet (ent->maxs, 16, 16, 16); + trap_LinkEntity (ent); + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); +#else + G_FreeEntity( ent ); +#endif +} + +//=========================================================== + +void locateCamera( gentity_t *ent ) +{ + vec3_t dir; + gentity_t *target; + gentity_t *owner; + + owner = G_PickTarget( ent->target ); + if( !owner ) + { + G_Printf( "Couldn't find target for misc_portal_surface\n" ); + G_FreeEntity( ent ); + return; + } + ent->r.ownerNum = owner->s.number; + + // frame holds the rotate speed + if( owner->spawnflags & 1 ) + ent->s.frame = 25; + else if( owner->spawnflags & 2 ) + ent->s.frame = 75; + + // swing camera ? + if( owner->spawnflags & 4 ) + { + // set to 0 for no rotation at all + ent->s.misc = 0; + } + else + ent->s.misc = 1; + + // clientNum holds the rotate offset + ent->s.clientNum = owner->s.clientNum; + + VectorCopy( owner->s.origin, ent->s.origin2 ); + + // see if the portal_camera has a target + target = G_PickTarget( owner->target ); + if( target ) + { + VectorSubtract( target->s.origin, owner->s.origin, dir ); + VectorNormalize( dir ); + } + else + G_SetMovedir( owner->s.angles, dir ); + + ent->s.eventParm = DirToByte( dir ); +} + +/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) +The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted. +This must be within 64 world units of the surface! +*/ +void SP_misc_portal_surface( gentity_t *ent ) +{ + VectorClear( ent->r.mins ); + VectorClear( ent->r.maxs ); + trap_LinkEntity( ent ); + + ent->r.svFlags = SVF_PORTAL; + ent->s.eType = ET_PORTAL; + + if( !ent->target ) + { + VectorCopy( ent->s.origin, ent->s.origin2 ); + } + else + { + ent->think = locateCamera; + ent->nextthink = level.time + 100; + } +} + +/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing + +The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view. +"roll" an angle modifier to orient the camera around the target vector; +*/ +void SP_misc_portal_camera( gentity_t *ent ) +{ + float roll; + + VectorClear( ent->r.mins ); + VectorClear( ent->r.maxs ); + trap_LinkEntity( ent ); + + G_SpawnFloat( "roll", "0", &roll ); + + ent->s.clientNum = roll / 360.0f * 256; +} + +/* +====================================================================== + + NEAT EFFECTS AND STUFF FOR TREMULOUS + +====================================================================== +*/ + +void SP_toggle_particle_system( gentity_t *self ) +{ + //toggle EF_NODRAW + self->s.eFlags ^= EF_NODRAW; + + self->nextthink = 0; +} + +/* +=============== +SP_use_particle_system + +Use function for particle_system +=============== +*/ +void SP_use_particle_system( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + SP_toggle_particle_system( self ); + + if( self->wait > 0.0f ) + { + self->think = SP_toggle_particle_system; + self->nextthink = level.time + (int)( self->wait * 1000 ); + } +} + +/* +=============== +SP_spawn_particle_system + +Spawn function for particle system +=============== +*/ +void SP_misc_particle_system( gentity_t *self ) +{ + char *s; + + G_SetOrigin( self, self->s.origin ); + + G_SpawnString( "psName", "", &s ); + G_SpawnFloat( "wait", "0", &self->wait ); + + //add the particle system to the client precache list + self->s.modelindex = G_ParticleSystemIndex( s ); + + if( self->spawnflags & 1 ) + self->s.eFlags |= EF_NODRAW; + + self->use = SP_use_particle_system; + self->s.eType = ET_PARTICLE_SYSTEM; + trap_LinkEntity( self ); +} + +/* +=============== +SP_use_anim_model + +Use function for anim model +=============== +*/ +void SP_use_anim_model( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->spawnflags & 1 ) + { + //if spawnflag 1 is set + //toggle EF_NODRAW + if( self->s.eFlags & EF_NODRAW ) + self->s.eFlags &= ~EF_NODRAW; + else + self->s.eFlags |= EF_NODRAW; + } + else + { + //if the animation loops then toggle the animation + //toggle EF_MOVER_STOP + if( self->s.eFlags & EF_MOVER_STOP ) + self->s.eFlags &= ~EF_MOVER_STOP; + else + self->s.eFlags |= EF_MOVER_STOP; + } +} + +/* +=============== +SP_misc_anim_model + +Spawn function for anim model +=============== +*/ +void SP_misc_anim_model( gentity_t *self ) +{ + self->s.misc = (int)self->animation[ 0 ]; + self->s.weapon = (int)self->animation[ 1 ]; + self->s.torsoAnim = (int)self->animation[ 2 ]; + self->s.legsAnim = (int)self->animation[ 3 ]; + + self->s.angles2[ 0 ] = self->pos2[ 0 ]; + + //add the model to the client precache list + self->s.modelindex = G_ModelIndex( self->model ); + + self->use = SP_use_anim_model; + + self->s.eType = ET_ANIMMAPOBJ; + + // spawn with animation stopped + if( self->spawnflags & 2 ) + self->s.eFlags |= EF_MOVER_STOP; + + trap_LinkEntity( self ); +} + +/* +=============== +SP_use_light_flare + +Use function for light flare +=============== +*/ +void SP_use_light_flare( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + self->s.eFlags ^= EF_NODRAW; +} + +/* +=============== +findEmptySpot + +Finds an empty spot radius units from origin +============== +*/ +static void findEmptySpot( vec3_t origin, float radius, vec3_t spot ) +{ + int i, j, k; + vec3_t delta, test, total; + trace_t tr; + + VectorClear( total ); + + //54(!) traces to test for empty spots + for( i = -1; i <= 1; i++ ) + { + for( j = -1; j <= 1; j++ ) + { + for( k = -1; k <= 1; k++ ) + { + VectorSet( delta, ( i * radius ), + ( j * radius ), + ( k * radius ) ); + + VectorAdd( origin, delta, test ); + + trap_Trace( &tr, test, NULL, NULL, test, -1, MASK_SOLID ); + + if( !tr.allsolid ) + { + trap_Trace( &tr, test, NULL, NULL, origin, -1, MASK_SOLID ); + VectorScale( delta, tr.fraction, delta ); + VectorAdd( total, delta, total ); + } + } + } + } + + VectorNormalize( total ); + VectorScale( total, radius, total ); + VectorAdd( origin, total, spot ); +} + +/* +=============== +SP_misc_light_flare + +Spawn function for light flare +=============== +*/ +void SP_misc_light_flare( gentity_t *self ) +{ + self->s.eType = ET_LIGHTFLARE; + self->s.modelindex = G_ShaderIndex( self->targetShaderName ); + VectorCopy( self->pos2, self->s.origin2 ); + + //try to find a spot near to the flare which is empty. This + //is used to facilitate visibility testing + findEmptySpot( self->s.origin, 8.0f, self->s.angles2 ); + + self->use = SP_use_light_flare; + + G_SpawnFloat( "speed", "200", &self->speed ); + self->s.time = self->speed; + + G_SpawnInt( "mindist", "0", &self->s.generic1 ); + + if( self->spawnflags & 1 ) + self->s.eFlags |= EF_NODRAW; + + trap_LinkEntity( self ); +} diff --git a/src/game/g_missile.c b/src/game/g_missile.c new file mode 100644 index 0000000..e409596 --- /dev/null +++ b/src/game/g_missile.c @@ -0,0 +1,913 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +#define MISSILE_PRESTEP_TIME 50 + +/* +================ +G_BounceMissile + +================ +*/ +void G_BounceMissile( gentity_t *ent, trace_t *trace ) +{ + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2 * dot, trace->plane.normal, ent->s.pos.trDelta ); + + if( ent->s.eFlags & EF_BOUNCE_HALF ) + { + VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta ); + // check for stop + if( trace->plane.normal[ 2 ] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 ) + { + G_SetOrigin( ent, trace->endpos ); + return; + } + } + + VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin ); + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; +} + + +/* +================ +G_ExplodeMissile + +Explode a missile without an impact +================ +*/ +void G_ExplodeMissile( gentity_t *ent ) +{ + vec3_t dir; + vec3_t origin; + + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + SnapVector( origin ); + G_SetOrigin( ent, origin ); + + // we don't have a valid direction, so just point straight up + dir[ 0 ] = dir[ 1 ] = 0; + dir[ 2 ] = 1; + + ent->s.eType = ET_GENERAL; + + if( ent->s.weapon != WP_LOCKBLOB_LAUNCHER && + ent->s.weapon != WP_FLAMER ) + G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); + + ent->freeAfterEvent = qtrue; + + // splash damage + if( ent->splashDamage ) + G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, + ent->splashRadius, ent, ent->splashMethodOfDeath ); + + trap_LinkEntity( ent ); +} + +void AHive_ReturnToHive( gentity_t *self ); + +/* +================ +G_MissileImpact + +================ +*/ +void G_MissileImpact( gentity_t *ent, trace_t *trace ) +{ + gentity_t *other, *attacker; + qboolean returnAfterDamage = qfalse; + vec3_t dir; + + other = &g_entities[ trace->entityNum ]; + attacker = &g_entities[ ent->r.ownerNum ]; + + // check for bounce + if( !other->takedamage && + ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) + { + G_BounceMissile( ent, trace ); + + //only play a sound if requested + if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) ) + G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 ); + + return; + } + + if( !strcmp( ent->classname, "grenade" ) ) + { + //grenade doesn't explode on impact + G_BounceMissile( ent, trace ); + + //only play a sound if requested + if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) ) + G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 ); + + return; + } + else if( !strcmp( ent->classname, "lockblob" ) ) + { + if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + other->client->ps.stats[ STAT_STATE ] |= SS_BLOBLOCKED; + other->client->lastLockTime = level.time; + AngleVectors( other->client->ps.viewangles, dir, NULL, NULL ); + other->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir ); + } + } + else if( !strcmp( ent->classname, "slowblob" ) ) + { + if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + other->client->ps.stats[ STAT_STATE ] |= SS_SLOWLOCKED; + other->client->lastSlowTime = level.time; + AngleVectors( other->client->ps.viewangles, dir, NULL, NULL ); + other->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir ); + + if( !other->client->isImpregnated ) + { + float chance; + + if( !BG_InventoryContainsUpgrade( UP_BATTLESUIT, other->client->ps.stats ) ) //never impregnate suits + { + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, other->client->ps.stats ) ) + chance = ALIEN_IMPREGNATION_CHANCE_ARMORED; + else + chance = ALIEN_IMPREGNATION_CHANCE_NAKED; + + if( random() < chance ) + { + other->client->isImpregnated = qtrue; + other->client->isImplantMature = qfalse; + other->client->impregnationTime = level.time; + other->client->impregnatedBy = attacker->s.number; + + trap_SendServerCommand( attacker-g_entities, "cp \"[egg] An egg was implanted! [egg]\"" ); + trap_SendServerCommand( attacker-g_entities, "print \"An egg was implanted!\n\"" ); + + G_LogPrintf( "Impregnation (alien): %d %d: %s impregnated %s\n", + attacker->s.number, + other->s.number, + attacker->client->pers.netname, + other->client->pers.netname ); + } + } + } + } + } + else if( !strcmp( ent->classname, "hive" ) ) + { + if( other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_HIVE ) + { + if( !ent->parent ) + G_Printf( S_COLOR_YELLOW "WARNING: hive entity has no parent in G_MissileImpact\n" ); + else + ent->parent->active = qfalse; + + G_FreeEntity( ent ); + return; + } + else + { + //prevent collision with the client when returning + ent->r.ownerNum = other->s.number; + + ent->think = G_ExplodeMissile; + ent->nextthink = level.time + FRAMETIME; + + //only damage humans + if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + returnAfterDamage = qtrue; + else + return; + } + } + + // impact damage + if( other->takedamage ) + { + // FIXME: wrong damage direction? + if( ent->damage ) + { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity ); + if( VectorLength( velocity ) == 0 ) + velocity[ 2 ] = 1; // stepped on a grenade + + G_Damage( other, ent, attacker, velocity, ent->s.origin, ent->damage, + DAMAGE_NO_LOCDAMAGE, ent->methodOfDeath ); + } + } + + if( returnAfterDamage ) + return; + + // is it cheaper in bandwidth to just remove this ent and create a new + // one, rather than changing the missile into the explosion? + + if( other->takedamage && + ( other->s.eType == ET_PLAYER || other->s.eType == ET_BUILDABLE ) ) + { + G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) ); + ent->s.otherEntityNum = other->s.number; + } + else if( trace->surfaceFlags & SURF_METALSTEPS ) + G_AddEvent( ent, EV_MISSILE_MISS_METAL, DirToByte( trace->plane.normal ) ); + else + G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) ); + + ent->freeAfterEvent = qtrue; + + // change over to a normal entity right at the point of impact + ent->s.eType = ET_GENERAL; + + SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth + + G_SetOrigin( ent, trace->endpos ); + + // splash damage (doesn't apply to person directly hit) + if( ent->splashDamage ) + G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius, + other, ent->splashMethodOfDeath ); + + trap_LinkEntity( ent ); +} + + +/* +================ +G_RunMissile + +================ +*/ +void G_RunMissile( gentity_t *ent ) +{ + vec3_t origin; + trace_t tr; + int passent; + qboolean impact = qfalse; + + // get current position + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + + // ignore interactions with the missile owner + passent = ent->r.ownerNum; + + // general trace to see if we hit anything at all + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, + origin, passent, ent->clipmask ); + + if( tr.startsolid || tr.allsolid ) + { + tr.fraction = 0.0f; + VectorCopy( ent->r.currentOrigin, tr.endpos ); + } + + if( tr.fraction < 1.0f ) + { + if( !ent->pointAgainstWorld || tr.contents & CONTENTS_BODY ) + { + // We hit an entity or we don't care + impact = qtrue; + } + else + { + trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, origin, + passent, ent->clipmask ); + + if( tr.fraction < 1.0f ) + { + // Hit the world with point trace + impact = qtrue; + } + else + { + if( tr.contents & CONTENTS_BODY ) + { + // Hit an entity + impact = qtrue; + } + else + { + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, + origin, passent, CONTENTS_BODY ); + + if( tr.fraction < 1.0f ) + impact = qtrue; + } + } + } + } + + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + if( impact ) + { + if( tr.surfaceFlags & SURF_NOIMPACT ) + { + // Never explode or bounce on sky + G_FreeEntity( ent ); + return; + } + + G_MissileImpact( ent, &tr ); + + if( ent->s.eType != ET_MISSILE ) + return; // exploded + } + + ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into... + trap_LinkEntity( ent ); + ent->r.contents = 0; //...encoding bbox information + + // check think function after bouncing + G_RunThink( ent ); +} + + +//============================================================================= + +/* +================= +fire_flamer + +================= +*/ +gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + vec3_t pvel; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->classname = "flame"; + bolt->pointAgainstWorld = qfalse; + bolt->nextthink = level.time + FLAMER_LIFETIME; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_FLAMER; + bolt->s.generic1 = self->s.generic1; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = FLAMER_DMG; + bolt->splashDamage = FLAMER_SPLASHDAMAGE; + bolt->splashRadius = FLAMER_RADIUS; + bolt->methodOfDeath = MOD_FLAMER; + bolt->splashMethodOfDeath = MOD_FLAMER_SPLASH; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -FLAMER_SIZE; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = FLAMER_SIZE; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( self->client->ps.velocity, FLAMER_LAG, pvel ); + VectorMA( pvel, FLAMER_SPEED, dir, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +//============================================================================= + +/* +================= +fire_blaster + +================= +*/ +gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->classname = "blaster"; + bolt->pointAgainstWorld = qtrue; + bolt->nextthink = level.time + 10000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_BLASTER; + bolt->s.generic1 = self->s.generic1; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = BLASTER_DMG; + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_BLASTER; + bolt->splashMethodOfDeath = MOD_BLASTER; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -BLASTER_SIZE; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = BLASTER_SIZE; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, BLASTER_SPEED, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +//============================================================================= + +/* +================= +fire_pulseRifle + +================= +*/ +gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->classname = "pulse"; + bolt->pointAgainstWorld = qtrue; + bolt->nextthink = level.time + 10000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_PULSE_RIFLE; + bolt->s.generic1 = self->s.generic1; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = PRIFLE_DMG; + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_PRIFLE; + bolt->splashMethodOfDeath = MOD_PRIFLE; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -PRIFLE_SIZE; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = PRIFLE_SIZE; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, PRIFLE_SPEED, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +//============================================================================= + +/* +================= +fire_luciferCannon + +================= +*/ +gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, + int damage, int radius, int speed ) +{ + gentity_t *bolt; + float charge; + + VectorNormalize( dir ); + + bolt = G_Spawn( ); + bolt->classname = "lcannon"; + bolt->pointAgainstWorld = qtrue; + + if( damage == LCANNON_DAMAGE ) + bolt->nextthink = level.time; + else + bolt->nextthink = level.time + 10000; + + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_LUCIFER_CANNON; + bolt->s.generic1 = self->s.generic1; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = damage; + bolt->splashDamage = damage / 2; + bolt->splashRadius = radius; + bolt->methodOfDeath = MOD_LCANNON; + bolt->splashMethodOfDeath = MOD_LCANNON_SPLASH; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + + // Give the missile a small bounding box + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = + -LCANNON_SIZE; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = + -bolt->r.mins[ 0 ]; + + // Pass the missile charge through + charge = (float)( damage - LCANNON_SECONDARY_DAMAGE ) / LCANNON_DAMAGE; + bolt->s.torsoAnim = charge * 255; + if( bolt->s.torsoAnim < 0 ) + bolt->s.torsoAnim = 0; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, speed, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +/* +================= +launch_grenade + +================= +*/ +gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize( dir ); + + bolt = G_Spawn( ); + bolt->classname = "grenade"; + bolt->pointAgainstWorld = qfalse; + bolt->nextthink = level.time + 5000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_GRENADE; + bolt->s.eFlags = EF_BOUNCE_HALF; + bolt->s.generic1 = WPM_PRIMARY; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = GRENADE_DAMAGE; + bolt->splashDamage = GRENADE_DAMAGE; + bolt->splashRadius = GRENADE_RANGE; + bolt->methodOfDeath = MOD_GRENADE; + bolt->splashMethodOfDeath = MOD_GRENADE; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -3.0f; + bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = 3.0f; + bolt->s.time = level.time; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, GRENADE_SPEED, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} +//============================================================================= + + + +/* +================ +AHive_SearchAndDestroy + +Adjust the trajectory to point towards the target +================ +*/ +void AHive_SearchAndDestroy( gentity_t *self ) +{ + vec3_t dir; + trace_t tr; + gentity_t *ent; + int i; + float d, nearest; + + if( level.time > self->timestamp ) + { + VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); + self->s.pos.trType = TR_STATIONARY; + self->s.pos.trTime = level.time; + + self->think = G_ExplodeMissile; + self->nextthink = level.time + 50; + self->parent->active = qfalse; //allow the parent to start again + return; + } + + nearest = DistanceSquared( self->r.currentOrigin, self->target_ent->r.currentOrigin ); + //find the closest human + for( i = 0; i < MAX_CLIENTS; i++ ) + { + ent = &g_entities[ i ]; + + if( ent->flags & FL_NOTARGET ) + continue; + + if( ent->client && + ent->health > 0 && + ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + nearest > (d = DistanceSquared( ent->r.currentOrigin, self->r.currentOrigin ) ) ) + { + trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, + ent->r.currentOrigin, self->r.ownerNum, self->clipmask ); + if( tr.entityNum != ENTITYNUM_WORLD ) + { + nearest = d; + self->target_ent = ent; + } + } + } + VectorSubtract( self->target_ent->r.currentOrigin, self->r.currentOrigin, dir ); + VectorNormalize( dir ); + + //change direction towards the player + VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta ); + SnapVector( self->s.pos.trDelta ); // save net bandwidth + VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); + self->s.pos.trTime = level.time; + + self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD; +} + +/* +================= +fire_hive +================= +*/ +gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize ( dir ); + + bolt = G_Spawn( ); + bolt->classname = "hive"; + bolt->pointAgainstWorld = qfalse; + bolt->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD; + bolt->think = AHive_SearchAndDestroy; + bolt->s.eType = ET_MISSILE; + bolt->s.eFlags |= EF_BOUNCE | EF_NO_BOUNCE_SOUND; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_HIVE; + bolt->s.generic1 = WPM_PRIMARY; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = HIVE_DMG; + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_SWARM; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = self->target_ent; + bolt->timestamp = level.time + HIVE_LIFETIME; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, HIVE_SPEED, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +//============================================================================= + +/* +================= +fire_lockblob +================= +*/ +gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize ( dir ); + + bolt = G_Spawn( ); + bolt->classname = "lockblob"; + bolt->pointAgainstWorld = qtrue; + bolt->nextthink = level.time + 15000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_LOCKBLOB_LAUNCHER; + bolt->s.generic1 = WPM_PRIMARY; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = 0; + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_UNKNOWN; //doesn't do damage so will never kill + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, 500, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +/* +================= +fire_slowBlob +================= +*/ +gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize ( dir ); + + bolt = G_Spawn( ); + bolt->classname = "slowblob"; + bolt->pointAgainstWorld = qtrue; + bolt->nextthink = level.time + 15000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_ABUILD2; + bolt->s.generic1 = self->s.generic1; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = ABUILDER_BLOB_DMG; + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_SLOWBLOB; + bolt->splashMethodOfDeath = MOD_SLOWBLOB; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, ABUILDER_BLOB_SPEED, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +/* +================= +fire_paraLockBlob +================= +*/ +gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize ( dir ); + + bolt = G_Spawn( ); + bolt->classname = "lockblob"; + bolt->pointAgainstWorld = qtrue; + bolt->nextthink = level.time + 15000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_LOCKBLOB_LAUNCHER; + bolt->s.generic1 = self->s.generic1; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = 0; + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, LOCKBLOB_SPEED, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +/* +================= +fire_bounceBall +================= +*/ +gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize ( dir ); + + bolt = G_Spawn( ); + bolt->classname = "bounceball"; + bolt->pointAgainstWorld = qtrue; + bolt->nextthink = level.time + 3000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_ALEVEL3_UPG; + bolt->s.generic1 = self->s.generic1; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = LEVEL3_BOUNCEBALL_DMG; + bolt->splashDamage = LEVEL3_BOUNCEBALL_DMG; + bolt->splashRadius = LEVEL3_BOUNCEBALL_RADIUS; + bolt->methodOfDeath = MOD_LEVEL3_BOUNCEBALL; + bolt->splashMethodOfDeath = MOD_LEVEL3_BOUNCEBALL; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, LEVEL3_BOUNCEBALL_SPEED, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +/* +================= +fire_rantBomb +================= +*/ +gentity_t *fire_rantBomb( gentity_t *self, vec3_t start, vec3_t dir ) +{ + gentity_t *bolt; + + VectorNormalize ( dir ); + + bolt = G_Spawn( ); + bolt->classname = "rantbomb"; + bolt->pointAgainstWorld = qtrue; + bolt->nextthink = level.time + 3000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_ALEVEL4; + bolt->s.generic1 = self->s.generic1; //weaponMode + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = LEVEL4_BOMB_DMG; + bolt->splashDamage = LEVEL4_BOMB_DMG; + bolt->splashRadius = LEVEL4_BOMB_RADIUS; + bolt->methodOfDeath = MOD_LEVEL4_BOMB; + bolt->splashMethodOfDeath = MOD_LEVEL4_BOMB; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, LEVEL4_BOMB_SPEED, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} diff --git a/src/game/g_mover.c b/src/game/g_mover.c new file mode 100644 index 0000000..6c92302 --- /dev/null +++ b/src/game/g_mover.c @@ -0,0 +1,2485 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + + + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +void MatchTeam( gentity_t *teamLeader, int moverState, int time ); + +typedef struct +{ + gentity_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; + +pushed_t pushed[ MAX_GENTITIES ], *pushed_p; + + +/* +============ +G_TestEntityPosition + +============ +*/ +gentity_t *G_TestEntityPosition( gentity_t *ent ) +{ + trace_t tr; + int mask; + + if( ent->clipmask ) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + if( ent->client ) + trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask ); + else + trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask ); + + if( tr.startsolid ) + return &g_entities[ tr.entityNum ]; + + return NULL; +} + +/* +================ +G_CreateRotationMatrix +================ +*/ +void G_CreateRotationMatrix( vec3_t angles, vec3_t matrix[ 3 ] ) +{ + AngleVectors( angles, matrix[ 0 ], matrix[ 1 ], matrix[ 2 ] ); + VectorInverse( matrix[ 1 ] ); +} + +/* +================ +G_TransposeMatrix +================ +*/ +void G_TransposeMatrix( vec3_t matrix[ 3 ], vec3_t transpose[ 3 ] ) +{ + int i, j; + + for( i = 0; i < 3; i++ ) + { + for( j = 0; j < 3; j++ ) + { + transpose[ i ][ j ] = matrix[ j ][ i ]; + } + } +} + +/* +================ +G_RotatePoint +================ +*/ +void G_RotatePoint( vec3_t point, vec3_t matrix[ 3 ] ) +{ + vec3_t tvec; + + VectorCopy( point, tvec ); + point[ 0 ] = DotProduct( matrix[ 0 ], tvec ); + point[ 1 ] = DotProduct( matrix[ 1 ], tvec ); + point[ 2 ] = DotProduct( matrix[ 2 ], tvec ); +} + +/* +================== +G_TryPushingEntity + +Returns qfalse if the move is blocked +================== +*/ +qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) +{ + vec3_t matrix[ 3 ], transpose[ 3 ]; + vec3_t org, org2, move2; + gentity_t *block; + + // EF_MOVER_STOP will just stop when contacting another entity + // instead of pushing it, but entities can still ride on top of it + if( ( pusher->s.eFlags & EF_MOVER_STOP ) && + check->s.groundEntityNum != pusher->s.number ) + return qfalse; + + //don't try to move buildables unless standing on a mover + //if( check->s.eType == ET_BUILDABLE && + // check->s.groundEntityNum != pusher->s.number ) + // return qfalse; + + // save off the old position + if( pushed_p > &pushed[ MAX_GENTITIES ] ) + G_Error( "pushed_p > &pushed[MAX_GENTITIES]" ); + + pushed_p->ent = check; + VectorCopy( check->s.pos.trBase, pushed_p->origin ); + VectorCopy( check->s.apos.trBase, pushed_p->angles ); + + if( check->client ) + { + pushed_p->deltayaw = check->client->ps.delta_angles[ YAW ]; + VectorCopy( check->client->ps.origin, pushed_p->origin ); + } + pushed_p++; + + // try moving the contacted entity + // figure movement due to the pusher's amove + G_CreateRotationMatrix( amove, transpose ); + G_TransposeMatrix( transpose, matrix ); + + if( check->client ) + VectorSubtract( check->client->ps.origin, pusher->r.currentOrigin, org ); + else + VectorSubtract( check->s.pos.trBase, pusher->r.currentOrigin, org ); + + VectorCopy( org, org2 ); + G_RotatePoint( org2, matrix ); + VectorSubtract( org2, org, move2 ); + // add movement + VectorAdd( check->s.pos.trBase, move, check->s.pos.trBase ); + VectorAdd( check->s.pos.trBase, move2, check->s.pos.trBase ); + + if( check->client ) + { + VectorAdd( check->client->ps.origin, move, check->client->ps.origin ); + VectorAdd( check->client->ps.origin, move2, check->client->ps.origin ); + // make sure the client's view rotates when on a rotating mover + check->client->ps.delta_angles[ YAW ] += ANGLE2SHORT( amove[ YAW ] ); + } + + // may have pushed them off an edge + if( check->s.groundEntityNum != pusher->s.number ) + check->s.groundEntityNum = -1; + + block = G_TestEntityPosition( check ); + + if( !block ) + { + // pushed ok + if( check->client ) + VectorCopy( check->client->ps.origin, check->r.currentOrigin ); + else + VectorCopy( check->s.pos.trBase, check->r.currentOrigin ); + + trap_LinkEntity( check ); + return qtrue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // Sliding trapdoors can cause this. + VectorCopy( ( pushed_p - 1 )->origin, check->s.pos.trBase ); + + if( check->client ) + VectorCopy( ( pushed_p - 1 )->origin, check->client->ps.origin ); + + VectorCopy( ( pushed_p - 1 )->angles, check->s.apos.trBase ); + block = G_TestEntityPosition( check ); + + if( !block ) + { + check->s.groundEntityNum = -1; + pushed_p--; + return qtrue; + } + + // blocked + return qfalse; +} + + +/* +============ +G_MoverPush + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +If qfalse is returned, *obstacle will be the blocking entity +============ +*/ +qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) +{ + int i, e; + gentity_t *check; + vec3_t mins, maxs; + pushed_t *p; + int entityList[ MAX_GENTITIES ]; + int listedEntities; + vec3_t totalMins, totalMaxs; + + *obstacle = NULL; + + + // mins/maxs are the bounds at the destination + // totalMins / totalMaxs are the bounds for the entire move + if( pusher->r.currentAngles[ 0 ] || pusher->r.currentAngles[ 1 ] || pusher->r.currentAngles[ 2 ] + || amove[ 0 ] || amove[ 1 ] || amove[ 2 ] ) + { + float radius; + + radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs ); + + for( i = 0 ; i < 3 ; i++ ) + { + mins[ i ] = pusher->r.currentOrigin[ i ] + move[ i ] - radius; + maxs[ i ] = pusher->r.currentOrigin[ i ] + move[ i ] + radius; + totalMins[ i ] = mins[ i ] - move[ i ]; + totalMaxs[ i ] = maxs[ i ] - move[ i ]; + } + } + else + { + for( i = 0; i < 3; i++ ) + { + mins[ i ] = pusher->r.absmin[ i ] + move[ i ]; + maxs[ i ] = pusher->r.absmax[ i ] + move[ i ]; + } + + VectorCopy( pusher->r.absmin, totalMins ); + VectorCopy( pusher->r.absmax, totalMaxs ); + for( i = 0; i < 3; i++ ) + { + if( move[ i ] > 0 ) + totalMaxs[ i ] += move[ i ]; + else + totalMins[ i ] += move[ i ]; + } + } + + // unlink the pusher so we don't get it in the entityList + trap_UnlinkEntity( pusher ); + + listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES ); + + // move the pusher to it's final position + VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin ); + VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles ); + trap_LinkEntity( pusher ); + + // see if any solid entities are inside the final position + for( e = 0 ; e < listedEntities ; e++ ) + { + check = &g_entities[ entityList[ e ] ]; + + // only push items and players + if( check->s.eType != ET_BUILDABLE && check->s.eType != ET_CORPSE && + check->s.eType != ET_PLAYER && !check->physicsObject ) + continue; + + // if the entity is standing on the pusher, it will definitely be moved + if( check->s.groundEntityNum != pusher->s.number ) + { + // see if the ent needs to be tested + if( check->r.absmin[ 0 ] >= maxs[ 0 ] + || check->r.absmin[ 1 ] >= maxs[ 1 ] + || check->r.absmin[ 2 ] >= maxs[ 2 ] + || check->r.absmax[ 0 ] <= mins[ 0 ] + || check->r.absmax[ 1 ] <= mins[ 1 ] + || check->r.absmax[ 2 ] <= mins[ 2 ] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + // this does allow a fast moving object to pass through a thin entity... + if( !G_TestEntityPosition( check ) ) + continue; + } + + // the entity needs to be pushed + if( G_TryPushingEntity( check, pusher, move, amove ) ) + continue; + + // the move was blocked an entity + + // bobbing entities are instant-kill and never get blocked + if( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) + { + G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH ); + continue; + } + + + // save off the obstacle so we can call the block function (crush, etc) + *obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for( p = pushed_p - 1; p >= pushed; p-- ) + { + VectorCopy( p->origin, p->ent->s.pos.trBase ); + VectorCopy( p->angles, p->ent->s.apos.trBase ); + + if( p->ent->client ) + { + p->ent->client->ps.delta_angles[ YAW ] = p->deltayaw; + VectorCopy( p->origin, p->ent->client->ps.origin ); + } + + trap_LinkEntity( p->ent ); + } + + return qfalse; + } + + return qtrue; +} + + +/* +================= +G_MoverTeam +================= +*/ +void G_MoverTeam( gentity_t *ent ) +{ + vec3_t move, amove; + gentity_t *part, *obstacle; + vec3_t origin, angles; + + obstacle = NULL; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out + pushed_p = pushed; + for( part = ent; part; part = part->teamchain ) + { + // get current position + BG_EvaluateTrajectory( &part->s.pos, level.time, origin ); + BG_EvaluateTrajectory( &part->s.apos, level.time, angles ); + VectorSubtract( origin, part->r.currentOrigin, move ); + VectorSubtract( angles, part->r.currentAngles, amove ); + if( !G_MoverPush( part, move, amove, &obstacle ) ) + break; // move was blocked + } + + if( part ) + { + // go back to the previous position + for( part = ent; part; part = part->teamchain ) + { + part->s.pos.trTime += level.time - level.previousTime; + part->s.apos.trTime += level.time - level.previousTime; + BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin ); + BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles ); + trap_LinkEntity( part ); + } + + // if the pusher has a "blocked" function, call it + if( ent->blocked ) + ent->blocked( ent, obstacle ); + + return; + } + + // the move succeeded + for( part = ent; part; part = part->teamchain ) + { + // call the reached function if time is at or past end point + if( part->s.pos.trType == TR_LINEAR_STOP ) + { + if( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) + { + if( part->reached ) + part->reached( part ); + } + } + if ( part->s.apos.trType == TR_LINEAR_STOP ) { + if ( level.time >= part->s.apos.trTime + part->s.apos.trDuration ) { + if ( part->reached ) { + part->reached( part ); + } + } + } + } +} + +/* +================ +G_RunMover + +================ +*/ +void G_RunMover( gentity_t *ent ) +{ + // if not a team captain, don't do anything, because + // the captain will handle everything + if( ent->flags & FL_TEAMSLAVE ) + return; + + // if stationary at one of the positions, don't move anything + if( ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) && + ent->moverState < MODEL_POS1 ) //yuck yuck hack + G_MoverTeam( ent ); + + // check think function + G_RunThink( ent ); +} + +/* +============================================================================ + +GENERAL MOVERS + +Doors, plats, and buttons are all binary (two position) movers +Pos1 is "at rest", pos2 is "activated" +============================================================================ +*/ + +/* +=============== +SetMoverState +=============== +*/ +void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) +{ + vec3_t delta; + float f; + + ent->moverState = moverState; + + ent->s.pos.trTime = time; + ent->s.apos.trTime = time; + + switch( moverState ) + { + case MOVER_POS1: + VectorCopy( ent->pos1, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + break; + + case MOVER_POS2: + VectorCopy( ent->pos2, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + break; + + case MOVER_1TO2: + VectorCopy( ent->pos1, ent->s.pos.trBase ); + VectorSubtract( ent->pos2, ent->pos1, delta ); + f = 1000.0 / ent->s.pos.trDuration; + VectorScale( delta, f, ent->s.pos.trDelta ); + ent->s.pos.trType = TR_LINEAR_STOP; + break; + + case MOVER_2TO1: + VectorCopy( ent->pos2, ent->s.pos.trBase ); + VectorSubtract( ent->pos1, ent->pos2, delta ); + f = 1000.0 / ent->s.pos.trDuration; + VectorScale( delta, f, ent->s.pos.trDelta ); + ent->s.pos.trType = TR_LINEAR_STOP; + break; + + case ROTATOR_POS1: + VectorCopy( ent->pos1, ent->s.apos.trBase ); + ent->s.apos.trType = TR_STATIONARY; + break; + + case ROTATOR_POS2: + VectorCopy( ent->pos2, ent->s.apos.trBase ); + ent->s.apos.trType = TR_STATIONARY; + break; + + case ROTATOR_1TO2: + VectorCopy( ent->pos1, ent->s.apos.trBase ); + VectorSubtract( ent->pos2, ent->pos1, delta ); + f = 1000.0 / ent->s.apos.trDuration; + VectorScale( delta, f, ent->s.apos.trDelta ); + ent->s.apos.trType = TR_LINEAR_STOP; + break; + + case ROTATOR_2TO1: + VectorCopy( ent->pos2, ent->s.apos.trBase ); + VectorSubtract( ent->pos1, ent->pos2, delta ); + f = 1000.0 / ent->s.apos.trDuration; + VectorScale( delta, f, ent->s.apos.trDelta ); + ent->s.apos.trType = TR_LINEAR_STOP; + break; + + case MODEL_POS1: + break; + + case MODEL_POS2: + break; + + default: + break; + } + + if( moverState >= MOVER_POS1 && moverState <= MOVER_2TO1 ) + BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin ); + + if( moverState >= ROTATOR_POS1 && moverState <= ROTATOR_2TO1 ) + BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles ); + + trap_LinkEntity( ent ); +} + +/* +================ +MatchTeam + +All entities in a mover team will move from pos1 to pos2 +in the same amount of time +================ +*/ +void MatchTeam( gentity_t *teamLeader, int moverState, int time ) +{ + gentity_t *slave; + + for( slave = teamLeader; slave; slave = slave->teamchain ) + SetMoverState( slave, moverState, time ); +} + + + +/* +================ +ReturnToPos1 +================ +*/ +void ReturnToPos1( gentity_t *ent ) +{ + MatchTeam( ent, MOVER_2TO1, level.time ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // starting sound + if( ent->sound2to1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); +} + + +/* +================ +ReturnToApos1 +================ +*/ +void ReturnToApos1( gentity_t *ent ) +{ + MatchTeam( ent, ROTATOR_2TO1, level.time ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // starting sound + if( ent->sound2to1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); +} + + +/* +================ +Think_ClosedModelDoor +================ +*/ +void Think_ClosedModelDoor( gentity_t *ent ) +{ + // play sound + if( ent->soundPos1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); + + // close areaportals + if( ent->teammaster == ent || !ent->teammaster ) + trap_AdjustAreaPortalState( ent, qfalse ); + + ent->moverState = MODEL_POS1; +} + + +/* +================ +Think_CloseModelDoor +================ +*/ +void Think_CloseModelDoor( gentity_t *ent ) +{ + int entityList[ MAX_GENTITIES ]; + int numEntities, i; + gentity_t *clipBrush = ent->clipBrush; + gentity_t *check; + qboolean canClose = qtrue; + + numEntities = trap_EntitiesInBox( clipBrush->r.absmin, clipBrush->r.absmax, entityList, MAX_GENTITIES ); + + //set brush solid + trap_LinkEntity( ent->clipBrush ); + + //see if any solid entities are inside the door + for( i = 0; i < numEntities; i++ ) + { + check = &g_entities[ entityList[ i ] ]; + + //only test items and players + if( check->s.eType != ET_BUILDABLE && check->s.eType != ET_CORPSE && + check->s.eType != ET_PLAYER && !check->physicsObject ) + continue; + + //test is this entity collides with this door + if( G_TestEntityPosition( check ) ) + canClose = qfalse; + } + + //something is blocking this door + if( !canClose ) + { + //set brush non-solid + trap_UnlinkEntity( ent->clipBrush ); + + ent->nextthink = level.time + ent->wait; + return; + } + + //toggle door state + ent->s.legsAnim = qfalse; + + // play sound + if( ent->sound2to1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + + ent->moverState = MODEL_2TO1; + + ent->think = Think_ClosedModelDoor; + ent->nextthink = level.time + ent->speed; +} + + +/* +================ +Think_OpenModelDoor +================ +*/ +void Think_OpenModelDoor( gentity_t *ent ) +{ + //set brush non-solid + trap_UnlinkEntity( ent->clipBrush ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // starting sound + if( ent->soundPos2 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); + + ent->moverState = MODEL_POS2; + + // return to pos1 after a delay + ent->think = Think_CloseModelDoor; + ent->nextthink = level.time + ent->wait; + + // fire targets + if( !ent->activator ) + ent->activator = ent; + + G_UseTargets( ent, ent->activator ); +} + + +/* +================ +Reached_BinaryMover +================ +*/ +void Reached_BinaryMover( gentity_t *ent ) +{ + // stop the looping sound + ent->s.loopSound = ent->soundLoop; + + if( ent->moverState == MOVER_1TO2 ) + { + // reached pos2 + SetMoverState( ent, MOVER_POS2, level.time ); + + // play sound + if( ent->soundPos2 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); + + // return to pos1 after a delay + ent->think = ReturnToPos1; + ent->nextthink = level.time + ent->wait; + + // fire targets + if( !ent->activator ) + ent->activator = ent; + + G_UseTargets( ent, ent->activator ); + } + else if( ent->moverState == MOVER_2TO1 ) + { + // reached pos1 + SetMoverState( ent, MOVER_POS1, level.time ); + + // play sound + if( ent->soundPos1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); + + // close areaportals + if( ent->teammaster == ent || !ent->teammaster ) + trap_AdjustAreaPortalState( ent, qfalse ); + } + else if( ent->moverState == ROTATOR_1TO2 ) + { + // reached pos2 + SetMoverState( ent, ROTATOR_POS2, level.time ); + + // play sound + if( ent->soundPos2 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); + + // return to apos1 after a delay + ent->think = ReturnToApos1; + ent->nextthink = level.time + ent->wait; + + // fire targets + if( !ent->activator ) + ent->activator = ent; + + G_UseTargets( ent, ent->activator ); + } + else if( ent->moverState == ROTATOR_2TO1 ) + { + // reached pos1 + SetMoverState( ent, ROTATOR_POS1, level.time ); + + // play sound + if( ent->soundPos1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); + + // close areaportals + if( ent->teammaster == ent || !ent->teammaster ) + trap_AdjustAreaPortalState( ent, qfalse ); + } + else + G_Error( "Reached_BinaryMover: bad moverState" ); +} + + +/* +================ +Use_BinaryMover +================ +*/ +void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + int total; + int partial; + + // if this is a non-client-usable door return + if( ent->targetname && other && other->client ) + return; + + // only the master should be used + if( ent->flags & FL_TEAMSLAVE ) + { + Use_BinaryMover( ent->teammaster, other, activator ); + return; + } + + ent->activator = activator; + + if( ent->moverState == MOVER_POS1 ) + { + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + MatchTeam( ent, MOVER_1TO2, level.time + 50 ); + + // starting sound + if( ent->sound1to2 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // open areaportal + if( ent->teammaster == ent || !ent->teammaster ) + trap_AdjustAreaPortalState( ent, qtrue ); + } + else if( ent->moverState == MOVER_POS2 ) + { + // if all the way up, just delay before coming down + ent->nextthink = level.time + ent->wait; + } + else if( ent->moverState == MOVER_2TO1 ) + { + // only partway down before reversing + total = ent->s.pos.trDuration; + partial = level.time - ent->s.pos.trTime; + + if( partial > total ) + partial = total; + + MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) ); + + if( ent->sound1to2 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + } + else if( ent->moverState == MOVER_1TO2 ) + { + // only partway up before reversing + total = ent->s.pos.trDuration; + partial = level.time - ent->s.pos.trTime; + + if( partial > total ) + partial = total; + + MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) ); + + if( ent->sound2to1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + } + else if( ent->moverState == ROTATOR_POS1 ) + { + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + MatchTeam( ent, ROTATOR_1TO2, level.time + 50 ); + + // starting sound + if( ent->sound1to2 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // open areaportal + if( ent->teammaster == ent || !ent->teammaster ) + trap_AdjustAreaPortalState( ent, qtrue ); + } + else if( ent->moverState == ROTATOR_POS2 ) + { + // if all the way up, just delay before coming down + ent->nextthink = level.time + ent->wait; + } + else if( ent->moverState == ROTATOR_2TO1 ) + { + // only partway down before reversing + total = ent->s.apos.trDuration; + partial = level.time - ent->s.apos.trTime; + + if( partial > total ) + partial = total; + + MatchTeam( ent, ROTATOR_1TO2, level.time - ( total - partial ) ); + + if( ent->sound1to2 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + } + else if( ent->moverState == ROTATOR_1TO2 ) + { + // only partway up before reversing + total = ent->s.apos.trDuration; + partial = level.time - ent->s.apos.trTime; + + if( partial > total ) + partial = total; + + MatchTeam( ent, ROTATOR_2TO1, level.time - ( total - partial ) ); + + if( ent->sound2to1 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + } + else if( ent->moverState == MODEL_POS1 ) + { + //toggle door state + ent->s.legsAnim = qtrue; + + ent->think = Think_OpenModelDoor; + ent->nextthink = level.time + ent->speed; + + // starting sound + if( ent->sound1to2 ) + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // open areaportal + if( ent->teammaster == ent || !ent->teammaster ) + trap_AdjustAreaPortalState( ent, qtrue ); + + ent->moverState = MODEL_1TO2; + } + else if( ent->moverState == MODEL_POS2 ) + { + // if all the way up, just delay before coming down + ent->nextthink = level.time + ent->wait; + } +} + + + +/* +================ +InitMover + +"pos1", "pos2", and "speed" should be set before calling, +so the movement delta can be calculated +================ +*/ +void InitMover( gentity_t *ent ) +{ + vec3_t move; + float distance; + float light; + vec3_t color; + qboolean lightSet, colorSet; + char *sound; + + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if( ent->model2 ) + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + + // if the "loopsound" key is set, use a constant looping sound when moving + if( G_SpawnString( "noise", "100", &sound ) ) + ent->s.loopSound = G_SoundIndex( sound ); + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + + if( lightSet || colorSet ) + { + int r, g, b, i; + + r = color[ 0 ] * 255; + if( r > 255 ) + r = 255; + + g = color[ 1 ] * 255; + if( g > 255 ) + g = 255; + + b = color[ 2 ] * 255; + if( b > 255 ) + b = 255; + + i = light / 4; + if( i > 255 ) + i = 255; + + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + + ent->use = Use_BinaryMover; + ent->reached = Reached_BinaryMover; + + ent->moverState = MOVER_POS1; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + VectorCopy( ent->pos1, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.pos.trBase ); + + // calculate time to reach second position from speed + VectorSubtract( ent->pos2, ent->pos1, move ); + distance = VectorLength( move ); + if( !ent->speed ) + ent->speed = 100; + + VectorScale( move, ent->speed, ent->s.pos.trDelta ); + ent->s.pos.trDuration = distance * 1000 / ent->speed; + + if( ent->s.pos.trDuration <= 0 ) + ent->s.pos.trDuration = 1; +} + + +/* +================ +InitRotator + +"pos1", "pos2", and "speed" should be set before calling, +so the movement delta can be calculated +================ +*/ +void InitRotator( gentity_t *ent ) +{ + vec3_t move; + float angle; + float light; + vec3_t color; + qboolean lightSet, colorSet; + char *sound; + + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if( ent->model2 ) + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + + // if the "loopsound" key is set, use a constant looping sound when moving + if( G_SpawnString( "noise", "100", &sound ) ) + ent->s.loopSound = G_SoundIndex( sound ); + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + + if( lightSet || colorSet ) + { + int r, g, b, i; + + r = color[ 0 ] * 255; + + if( r > 255 ) + r = 255; + + g = color[ 1 ] * 255; + + if( g > 255 ) + g = 255; + + b = color[ 2 ] * 255; + + if( b > 255 ) + b = 255; + + i = light / 4; + + if( i > 255 ) + i = 255; + + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + + ent->use = Use_BinaryMover; + ent->reached = Reached_BinaryMover; + + ent->moverState = ROTATOR_POS1; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + VectorCopy( ent->pos1, ent->r.currentAngles ); + trap_LinkEntity( ent ); + + ent->s.apos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.apos.trBase ); + + // calculate time to reach second position from speed + VectorSubtract( ent->pos2, ent->pos1, move ); + angle = VectorLength( move ); + + if( !ent->speed ) + ent->speed = 120; + + VectorScale( move, ent->speed, ent->s.apos.trDelta ); + ent->s.apos.trDuration = angle * 1000 / ent->speed; + + if( ent->s.apos.trDuration <= 0 ) + ent->s.apos.trDuration = 1; +} + + +/* +=============================================================================== + +DOOR + +A use can be triggered either by a touch function, by being shot, or by being +targeted by another entity. + +=============================================================================== +*/ + +/* +================ +Blocked_Door +================ +*/ +void Blocked_Door( gentity_t *ent, gentity_t *other ) +{ + // remove anything other than a client or buildable + if( !other->client && other->s.eType != ET_BUILDABLE ) + { + G_FreeEntity( other ); + return; + } + + if( ent->damage ) + G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH ); + + if( ent->spawnflags & 4 ) + return; // crushers don't reverse + + // reverse direction + Use_BinaryMover( ent, ent, other ); +} + +/* +================ +Touch_DoorTriggerSpectator +================ +*/ +static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + int i, axis; + vec3_t origin, dir, angles; + + axis = ent->count; + VectorClear( dir ); + + if( fabs( other->s.origin[ axis ] - ent->r.absmax[ axis ] ) < + fabs( other->s.origin[ axis ] - ent->r.absmin[ axis ] ) ) + { + origin[ axis ] = ent->r.absmin[ axis ] - 20; + dir[ axis ] = -1; + } + else + { + origin[ axis ] = ent->r.absmax[ axis ] + 20; + dir[ axis ] = 1; + } + + for( i = 0; i < 3; i++ ) + { + if( i == axis ) + continue; + + origin[ i ] = ( ent->r.absmin[ i ] + ent->r.absmax[ i ] ) * 0.5; + } + + vectoangles( dir, angles ); + TeleportPlayer( other, origin, angles ); +} + + +/* +================ +manualDoorTriggerSpectator + +This effectively creates a temporary door auto trigger so manually +triggers doors can be skipped by spectators +================ +*/ +static void manualDoorTriggerSpectator( gentity_t *door, gentity_t *player ) +{ + gentity_t *other; + gentity_t triggerHull; + int best, i; + vec3_t mins, maxs; + + //don't skip a door that is already open + if( door->moverState == MOVER_1TO2 || + door->moverState == MOVER_POS2 || + door->moverState == ROTATOR_1TO2 || + door->moverState == ROTATOR_POS2 || + door->moverState == MODEL_1TO2 || + door->moverState == MODEL_POS2 ) + return; + + // find the bounds of everything on the team + VectorCopy( door->r.absmin, mins ); + VectorCopy( door->r.absmax, maxs ); + + for( other = door->teamchain; other; other = other->teamchain ) + { + AddPointToBounds( other->r.absmin, mins, maxs ); + AddPointToBounds( other->r.absmax, mins, maxs ); + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for( i = 1; i < 3; i++ ) + { + if( maxs[ i ] - mins[ i ] < maxs[ best ] - mins[ best ] ) + best = i; + } + + maxs[ best ] += 60; + mins[ best ] -= 60; + + VectorCopy( mins, triggerHull.r.absmin ); + VectorCopy( maxs, triggerHull.r.absmax ); + triggerHull.count = best; + + Touch_DoorTriggerSpectator( &triggerHull, player, NULL ); +} + +/* +================ +manualTriggerSpectator + +Trip to skip the closest door targetted by trigger +================ +*/ +void manualTriggerSpectator( gentity_t *trigger, gentity_t *player ) +{ + gentity_t *t = NULL; + gentity_t *targets[ MAX_GENTITIES ]; + int i = 0, j; + float minDistance = (float)INFINITE; + + //restrict this hack to trigger_multiple only for now + if( strcmp( trigger->classname, "trigger_multiple" ) ) + return; + + if( !trigger->target ) + return; + + //create a list of door entities this trigger targets + while( ( t = G_Find( t, FOFS( targetname ), trigger->target ) ) != NULL ) + { + if( !strcmp( t->classname, "func_door" ) ) + targets[ i++ ] = t; + else if( t == trigger ) + G_Printf( "WARNING: Entity used itself.\n" ); + + if( !trigger->inuse ) + { + G_Printf( "triggerity was removed while using targets\n" ); + return; + } + } + + //if more than 0 targets + if( i > 0 ) + { + gentity_t *closest = NULL; + + //pick the closest door + for( j = 0; j < i; j++ ) + { + float d = Distance( player->r.currentOrigin, targets[ j ]->r.currentOrigin ); + + if( d < minDistance ) + { + minDistance = d; + closest = targets[ j ]; + } + } + + //try and skip the door + manualDoorTriggerSpectator( closest, player ); + } +} + + +/* +================ +Touch_DoorTrigger +================ +*/ +void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + //buildables don't trigger movers + if( other->s.eType == ET_BUILDABLE ) + return; + + if( other->client && other->client->sess.spectatorState != SPECTATOR_NOT ) + { + // if the door is not open and not opening + if( ent->parent->moverState != MOVER_1TO2 && + ent->parent->moverState != MOVER_POS2 && + ent->parent->moverState != ROTATOR_1TO2 && + ent->parent->moverState != ROTATOR_POS2 ) + Touch_DoorTriggerSpectator( ent, other, trace ); + } + else if( ent->parent->moverState != MOVER_1TO2 && + ent->parent->moverState != ROTATOR_1TO2 && + ent->parent->moverState != ROTATOR_2TO1 ) + { + Use_BinaryMover( ent->parent, ent, other ); + } +} + + +/* +====================== +Think_SpawnNewDoorTrigger + +All of the parts of a door have been spawned, so create +a trigger that encloses all of them +====================== +*/ +void Think_SpawnNewDoorTrigger( gentity_t *ent ) +{ + gentity_t *other; + vec3_t mins, maxs; + int i, best; + + // find the bounds of everything on the team + VectorCopy( ent->r.absmin, mins ); + VectorCopy( ent->r.absmax, maxs ); + + for( other = ent->teamchain; other; other=other->teamchain ) + { + AddPointToBounds( other->r.absmin, mins, maxs ); + AddPointToBounds( other->r.absmax, mins, maxs ); + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for( i = 1; i < 3; i++ ) + { + if( maxs[ i ] - mins[ i ] < maxs[ best ] - mins[ best ] ) + best = i; + } + + maxs[ best ] += 60; + mins[ best ] -= 60; + + // create a trigger with this size + other = G_Spawn( ); + other->classname = "door_trigger"; + VectorCopy( mins, other->r.mins ); + VectorCopy( maxs, other->r.maxs ); + other->parent = ent; + other->r.contents = CONTENTS_TRIGGER; + other->touch = Touch_DoorTrigger; + // remember the thinnest axis + other->count = best; + trap_LinkEntity( other ); + + if( ent->moverState < MODEL_POS1 ) + MatchTeam( ent, ent->moverState, level.time ); +} + +void Think_MatchTeam( gentity_t *ent ) +{ + MatchTeam( ent, ent->moverState, level.time ); +} + + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"model2" .md3 model to also draw +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +"health" if set, the door must be shot open +*/ +void SP_func_door( gentity_t *ent ) +{ + vec3_t abs_movedir; + float distance; + vec3_t size; + float lip; + char *s; + + G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s ); + ent->sound2to1 = G_SoundIndex( s ); + G_SpawnString( "sound1to2", "sound/movers/doors/dr1_strt.wav", &s ); + ent->sound1to2 = G_SoundIndex( s ); + + G_SpawnString( "soundPos2", "sound/movers/doors/dr1_end.wav", &s ); + ent->soundPos2 = G_SoundIndex( s ); + G_SpawnString( "soundPos1", "sound/movers/doors/dr1_end.wav", &s ); + ent->soundPos1 = G_SoundIndex( s ); + + ent->blocked = Blocked_Door; + + // default speed of 400 + if( !ent->speed ) + ent->speed = 400; + + // default wait of 2 seconds + if( !ent->wait ) + ent->wait = 2; + + ent->wait *= 1000; + + // default lip of 8 units + G_SpawnFloat( "lip", "8", &lip ); + + // default damage of 2 points + G_SpawnInt( "dmg", "2", &ent->damage ); + + // first position at start + VectorCopy( ent->s.origin, ent->pos1 ); + + // calculate second position + trap_SetBrushModel( ent, ent->model ); + G_SetMovedir( ent->s.angles, ent->movedir ); + abs_movedir[ 0 ] = fabs( ent->movedir[ 0 ] ); + abs_movedir[ 1 ] = fabs( ent->movedir[ 1 ] ); + abs_movedir[ 2 ] = fabs( ent->movedir[ 2 ] ); + VectorSubtract( ent->r.maxs, ent->r.mins, size ); + distance = DotProduct( abs_movedir, size ) - lip; + VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 ); + + // if "start_open", reverse position 1 and 2 + if( ent->spawnflags & 1 ) + { + vec3_t temp; + + VectorCopy( ent->pos2, temp ); + VectorCopy( ent->s.origin, ent->pos2 ); + VectorCopy( temp, ent->pos1 ); + } + + InitMover( ent ); + + ent->nextthink = level.time + FRAMETIME; + + if( !( ent->flags & FL_TEAMSLAVE ) ) + { + int health; + + G_SpawnInt( "health", "0", &health ); + if( health ) + ent->takedamage = qtrue; + + if( ent->targetname || health ) + { + // non touch/shoot doors + ent->think = Think_MatchTeam; + } + else + ent->think = Think_SpawnNewDoorTrigger; + } +} + +/*QUAKED func_door_rotating (0 .5 .8) START_OPEN CRUSHER REVERSE TOGGLE X_AXIS Y_AXIS + * This is the rotating door... just as the name suggests it's a door that rotates + * START_OPEN the door to moves to its destination when spawned, and operate in reverse. + * REVERSE if you want the door to open in the other direction, use this switch. + * TOGGLE wait in both the start and end states for a trigger event. + * X_AXIS open on the X-axis instead of the Z-axis + * Y_AXIS open on the Y-axis instead of the Z-axis + * + * You need to have an origin brush as part of this entity. The center of that brush will be + * the point around which it is rotated. It will rotate around the Z axis by default. You can + * check either the X_AXIS or Y_AXIS box to change that. + * + * "model2" .md3 model to also draw + * "distance" how many degrees the door will open + * "speed" how fast the door will open (degrees/second) + * "color" constantLight color + * "light" constantLight radius + * */ + +void SP_func_door_rotating( gentity_t *ent ) +{ + char *s; + + G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s ); + ent->sound2to1 = G_SoundIndex( s ); + G_SpawnString( "sound1to2", "sound/movers/doors/dr1_strt.wav", &s ); + ent->sound1to2 = G_SoundIndex( s ); + + G_SpawnString( "soundPos2", "sound/movers/doors/dr1_end.wav", &s ); + ent->soundPos2 = G_SoundIndex( s ); + G_SpawnString( "soundPos1", "sound/movers/doors/dr1_end.wav", &s ); + ent->soundPos1 = G_SoundIndex( s ); + + ent->blocked = Blocked_Door; + + //default speed of 120 + if( !ent->speed ) + ent->speed = 120; + + // if speed is negative, positize it and add reverse flag + if( ent->speed < 0 ) + { + ent->speed *= -1; + ent->spawnflags |= 8; + } + + // default of 2 seconds + if( !ent->wait ) + ent->wait = 2; + + ent->wait *= 1000; + + // set the axis of rotation + VectorClear( ent->movedir ); + VectorClear( ent->s.angles ); + + if( ent->spawnflags & 32 ) + ent->movedir[ 2 ] = 1.0; + else if( ent->spawnflags & 64 ) + ent->movedir[ 0 ] = 1.0; + else + ent->movedir[ 1 ] = 1.0; + + // reverse direction if necessary + if( ent->spawnflags & 8 ) + VectorNegate ( ent->movedir, ent->movedir ); + + // default distance of 90 degrees. This is something the mapper should not + // leave out, so we'll tell him if he does. + if( !ent->rotatorAngle ) + { + G_Printf( "%s at %s with no rotatorAngle set.\n", + ent->classname, vtos( ent->s.origin ) ); + + ent->rotatorAngle = 90.0; + } + + VectorCopy( ent->s.angles, ent->pos1 ); + trap_SetBrushModel( ent, ent->model ); + VectorMA( ent->pos1, ent->rotatorAngle, ent->movedir, ent->pos2 ); + + // if "start_open", reverse position 1 and 2 + if( ent->spawnflags & 1 ) + { + vec3_t temp; + + VectorCopy( ent->pos2, temp ); + VectorCopy( ent->s.angles, ent->pos2 ); + VectorCopy( temp, ent->pos1 ); + VectorNegate( ent->movedir, ent->movedir ); + } + + // set origin + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + InitRotator( ent ); + + ent->nextthink = level.time + FRAMETIME; + + if( !( ent->flags & FL_TEAMSLAVE ) ) + { + int health; + + G_SpawnInt( "health", "0", &health ); + + if( health ) + ent->takedamage = qtrue; + + if( ent->targetname || health ) + { + // non touch/shoot doors + ent->think = Think_MatchTeam; + } + else + ent->think = Think_SpawnNewDoorTrigger; + } +} + +/*QUAKED func_door_model (0 .5 .8) ? START_OPEN +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"model2" .md3 model to also draw +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"color" constantLight color +"light" constantLight radius +"health" if set, the door must be shot open +*/ +void SP_func_door_model( gentity_t *ent ) +{ + char *s; + float light; + vec3_t color; + qboolean lightSet, colorSet; + char *sound; + gentity_t *clipBrush; + + G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s ); + ent->sound2to1 = G_SoundIndex( s ); + G_SpawnString( "sound1to2", "sound/movers/doors/dr1_strt.wav", &s ); + ent->sound1to2 = G_SoundIndex( s ); + + G_SpawnString( "soundPos2", "sound/movers/doors/dr1_end.wav", &s ); + ent->soundPos2 = G_SoundIndex( s ); + G_SpawnString( "soundPos1", "sound/movers/doors/dr1_end.wav", &s ); + ent->soundPos1 = G_SoundIndex( s ); + + //default speed of 100ms + if( !ent->speed ) + ent->speed = 200; + + //default wait of 2 seconds + if( ent->wait <= 0 ) + ent->wait = 2; + + ent->wait *= 1000; + + //brush model + clipBrush = ent->clipBrush = G_Spawn( ); + clipBrush->model = ent->model; + trap_SetBrushModel( clipBrush, clipBrush->model ); + clipBrush->s.eType = ET_INVISIBLE; + trap_LinkEntity( clipBrush ); + + //copy the bounds back from the clipBrush so the + //triggers can be made + VectorCopy( clipBrush->r.absmin, ent->r.absmin ); + VectorCopy( clipBrush->r.absmax, ent->r.absmax ); + VectorCopy( clipBrush->r.mins, ent->r.mins ); + VectorCopy( clipBrush->r.maxs, ent->r.maxs ); + + G_SpawnVector( "modelOrigin", "0 0 0", ent->s.origin ); + + G_SpawnVector( "scale", "1 1 1", ent->s.origin2 ); + + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if( !ent->model2 ) + G_Printf( S_COLOR_YELLOW "WARNING: func_door_model %d spawned with no model2 key\n", ent->s.number ); + else + ent->s.modelindex = G_ModelIndex( ent->model2 ); + + // if the "loopsound" key is set, use a constant looping sound when moving + if( G_SpawnString( "noise", "100", &sound ) ) + ent->s.loopSound = G_SoundIndex( sound ); + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + + if( lightSet || colorSet ) + { + int r, g, b, i; + + r = color[ 0 ] * 255; + if( r > 255 ) + r = 255; + + g = color[ 1 ] * 255; + if( g > 255 ) + g = 255; + + b = color[ 2 ] * 255; + if( b > 255 ) + b = 255; + + i = light / 4; + if( i > 255 ) + i = 255; + + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + ent->use = Use_BinaryMover; + + ent->moverState = MODEL_POS1; + ent->s.eType = ET_MODELDOOR; + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + ent->s.pos.trTime = 0; + ent->s.pos.trDuration = 0; + VectorClear( ent->s.pos.trDelta ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + ent->s.apos.trType = TR_STATIONARY; + ent->s.apos.trTime = 0; + ent->s.apos.trDuration = 0; + VectorClear( ent->s.apos.trDelta ); + + ent->s.misc = (int)ent->animation[ 0 ]; //first frame + ent->s.weapon = abs( (int)ent->animation[ 1 ] ); //number of frames + + //must be at least one frame -- mapper has forgotten animation key + if( ent->s.weapon == 0 ) + ent->s.weapon = 1; + + ent->s.torsoAnim = ent->s.weapon * ( 1000.0f / ent->speed ); //framerate + + trap_LinkEntity( ent ); + + if( !( ent->flags & FL_TEAMSLAVE ) ) + { + int health; + + G_SpawnInt( "health", "0", &health ); + if( health ) + ent->takedamage = qtrue; + + if( !( ent->targetname || health ) ) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_SpawnNewDoorTrigger; + } + } +} + +/* +=============================================================================== + +PLAT + +=============================================================================== +*/ + +/* +============== +Touch_Plat + +Don't allow decent if a living player is on it +=============== +*/ +void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + // DONT_WAIT + if( ent->spawnflags & 1 ) + return; + + if( !other->client || other->client->ps.stats[ STAT_HEALTH ] <= 0 ) + return; + + // delay return-to-pos1 by one second + if( ent->moverState == MOVER_POS2 ) + ent->nextthink = level.time + 1000; +} + +/* +============== +Touch_PlatCenterTrigger + +If the plat is at the bottom position, start it going up +=============== +*/ +void Touch_PlatCenterTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + if( !other->client ) + return; + + if( ent->parent->moverState == MOVER_POS1 ) + Use_BinaryMover( ent->parent, ent, other ); +} + + +/* +================ +SpawnPlatTrigger + +Spawn a trigger in the middle of the plat's low position +Elevator cars require that the trigger extend through the entire low position, +not just sit on top of it. +================ +*/ +void SpawnPlatTrigger( gentity_t *ent ) +{ + gentity_t *trigger; + vec3_t tmin, tmax; + + // the middle trigger will be a thin trigger just + // above the starting position + trigger = G_Spawn( ); + trigger->classname = "plat_trigger"; + trigger->touch = Touch_PlatCenterTrigger; + trigger->r.contents = CONTENTS_TRIGGER; + trigger->parent = ent; + + tmin[ 0 ] = ent->pos1[ 0 ] + ent->r.mins[ 0 ] + 33; + tmin[ 1 ] = ent->pos1[ 1 ] + ent->r.mins[ 1 ] + 33; + tmin[ 2 ] = ent->pos1[ 2 ] + ent->r.mins[ 2 ]; + + tmax[ 0 ] = ent->pos1[ 0 ] + ent->r.maxs[ 0 ] - 33; + tmax[ 1 ] = ent->pos1[ 1 ] + ent->r.maxs[ 1 ] - 33; + tmax[ 2 ] = ent->pos1[ 2 ] + ent->r.maxs[ 2 ] + 8; + + if( tmax[ 0 ] <= tmin[ 0 ] ) + { + tmin[ 0 ] = ent->pos1[ 0 ] + ( ent->r.mins[ 0 ] + ent->r.maxs[ 0 ] ) * 0.5; + tmax[ 0 ] = tmin[ 0 ] + 1; + } + + if( tmax[ 1 ] <= tmin[ 1 ] ) + { + tmin[ 1 ] = ent->pos1[ 1 ] + ( ent->r.mins[ 1 ] + ent->r.maxs[ 1 ] ) * 0.5; + tmax[ 1 ] = tmin[ 1 ] + 1; + } + + VectorCopy( tmin, trigger->r.mins ); + VectorCopy( tmax, trigger->r.maxs ); + + trap_LinkEntity( trigger ); +} + + +/*QUAKED func_plat (0 .5 .8) ? +Plats are always drawn in the extended position so they will light correctly. + +"lip" default 8, protrusion above rest position +"height" total height of movement, defaults to model height +"speed" overrides default 200. +"dmg" overrides default 2 +"model2" .md3 model to also draw +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_plat( gentity_t *ent ) +{ + float lip, height; + char *s; + + G_SpawnString( "sound2to1", "sound/movers/plats/pt1_strt.wav", &s ); + ent->sound2to1 = G_SoundIndex( s ); + G_SpawnString( "sound1to2", "sound/movers/plats/pt1_strt.wav", &s ); + ent->sound1to2 = G_SoundIndex( s ); + + G_SpawnString( "soundPos2", "sound/movers/plats/pt1_end.wav", &s ); + ent->soundPos2 = G_SoundIndex( s ); + G_SpawnString( "soundPos1", "sound/movers/plats/pt1_end.wav", &s ); + ent->soundPos1 = G_SoundIndex( s ); + + VectorClear( ent->s.angles ); + + G_SpawnFloat( "speed", "200", &ent->speed ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "wait", "1", &ent->wait ); + G_SpawnFloat( "lip", "8", &lip ); + + ent->wait = 1000; + + // create second position + trap_SetBrushModel( ent, ent->model ); + + if( !G_SpawnFloat( "height", "0", &height ) ) + height = ( ent->r.maxs[ 2 ] - ent->r.mins[ 2 ] ) - lip; + + // pos1 is the rest (bottom) position, pos2 is the top + VectorCopy( ent->s.origin, ent->pos2 ); + VectorCopy( ent->pos2, ent->pos1 ); + ent->pos1[ 2 ] -= height; + + InitMover( ent ); + + // touch function keeps the plat from returning while + // a live player is standing on it + ent->touch = Touch_Plat; + + ent->blocked = Blocked_Door; + + ent->parent = ent; // so it can be treated as a door + + // spawn the trigger if one hasn't been custom made + if( !ent->targetname ) + SpawnPlatTrigger( ent ); +} + + +/* +=============================================================================== + +BUTTON + +=============================================================================== +*/ + +/* +============== +Touch_Button + +=============== +*/ +void Touch_Button( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + if( !other->client ) + return; + + if( ent->moverState == MOVER_POS1 ) + Use_BinaryMover( ent, other, other ); +} + + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"model2" .md3 model to also draw +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_button( gentity_t *ent ) +{ + vec3_t abs_movedir; + float distance; + vec3_t size; + float lip; + char *s; + + G_SpawnString( "sound1to2", "sound/movers/switches/button1.wav", &s ); + ent->sound1to2 = G_SoundIndex( s ); + + if( !ent->speed ) + ent->speed = 40; + + if( !ent->wait ) + ent->wait = 1; + + ent->wait *= 1000; + + // first position + VectorCopy( ent->s.origin, ent->pos1 ); + + // calculate second position + trap_SetBrushModel( ent, ent->model ); + + G_SpawnFloat( "lip", "4", &lip ); + + G_SetMovedir( ent->s.angles, ent->movedir ); + abs_movedir[ 0 ] = fabs( ent->movedir[ 0 ] ); + abs_movedir[ 1 ] = fabs( ent->movedir[ 1 ] ); + abs_movedir[ 2 ] = fabs( ent->movedir[ 2 ] ); + VectorSubtract( ent->r.maxs, ent->r.mins, size ); + distance = abs_movedir[ 0 ] * size[ 0 ] + abs_movedir[ 1 ] * size[ 1 ] + abs_movedir[ 2 ] * size[ 2 ] - lip; + VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 ); + + if( ent->health ) + { + // shootable button + ent->takedamage = qtrue; + } + else + { + // touchable button + ent->touch = Touch_Button; + } + + InitMover( ent ); +} + + + +/* +=============================================================================== + +TRAIN + +=============================================================================== +*/ + + +#define TRAIN_START_OFF 1 +#define TRAIN_BLOCK_STOPS 2 + +/* +=============== +Think_BeginMoving + +The wait time at a corner has completed, so start moving again +=============== +*/ +void Think_BeginMoving( gentity_t *ent ) +{ + ent->s.pos.trTime = level.time; + ent->s.pos.trType = TR_LINEAR_STOP; +} + +/* +=============== +Reached_Train +=============== +*/ +void Reached_Train( gentity_t *ent ) +{ + gentity_t *next; + float speed; + vec3_t move; + float length; + + // copy the apropriate values + next = ent->nextTrain; + if( !next || !next->nextTrain ) + return; // just stop + + // fire all other targets + G_UseTargets( next, NULL ); + + // set the new trajectory + ent->nextTrain = next->nextTrain; + VectorCopy( next->s.origin, ent->pos1 ); + VectorCopy( next->nextTrain->s.origin, ent->pos2 ); + + // if the path_corner has a speed, use that + if( next->speed ) + { + speed = next->speed; + } + else + { + // otherwise use the train's speed + speed = ent->speed; + } + + if( speed < 1 ) + speed = 1; + + ent->lastSpeed = speed; + + // calculate duration + VectorSubtract( ent->pos2, ent->pos1, move ); + length = VectorLength( move ); + + ent->s.pos.trDuration = length * 1000 / speed; + + // Be sure to send to clients after any fast move case + ent->r.svFlags &= ~SVF_NOCLIENT; + + // Fast move case + if( ent->s.pos.trDuration < 1 ) + { + // As trDuration is used later in a division, we need to avoid that case now + ent->s.pos.trDuration = 1; + + // Don't send entity to clients so it becomes really invisible + ent->r.svFlags |= SVF_NOCLIENT; + } + + // looping sound + ent->s.loopSound = next->soundLoop; + + // start it going + SetMoverState( ent, MOVER_1TO2, level.time ); + + if( ent->spawnflags & TRAIN_START_OFF ) + { + ent->s.pos.trType = TR_STATIONARY; + return; + } + + // if there is a "wait" value on the target, don't start moving yet + if( next->wait ) + { + ent->nextthink = level.time + next->wait * 1000; + ent->think = Think_BeginMoving; + ent->s.pos.trType = TR_STATIONARY; + } +} + +/* +================ +Start_Train +================ +*/ +void Start_Train( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + vec3_t move; + + //recalculate duration as the mover is highly + //unlikely to be right on a path_corner + VectorSubtract( ent->pos2, ent->pos1, move ); + ent->s.pos.trDuration = VectorLength( move ) * 1000 / ent->lastSpeed; + SetMoverState( ent, MOVER_1TO2, level.time ); + + ent->spawnflags &= ~TRAIN_START_OFF; +} + +/* +================ +Stop_Train +================ +*/ +void Stop_Train( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + vec3_t origin; + + //get current origin + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + VectorCopy( origin, ent->pos1 ); + SetMoverState( ent, MOVER_POS1, level.time ); + + ent->spawnflags |= TRAIN_START_OFF; +} + +/* +================ +Use_Train +================ +*/ +void Use_Train( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + if( ent->spawnflags & TRAIN_START_OFF ) + { + //train is currently not moving so start it + Start_Train( ent, other, activator ); + } + else + { + //train is moving so stop it + Stop_Train( ent, other, activator ); + } +} + +/* +=============== +Think_SetupTrainTargets + +Link all the corners together +=============== +*/ +void Think_SetupTrainTargets( gentity_t *ent ) +{ + gentity_t *path, *next, *start; + + ent->nextTrain = G_Find( NULL, FOFS( targetname ), ent->target ); + + if( !ent->nextTrain ) + { + G_Printf( "func_train at %s with an unfound target\n", + vtos( ent->r.absmin ) ); + return; + } + + start = NULL; + for( path = ent->nextTrain; path != start; path = next ) + { + if( !start ) + start = path; + + if( !path->target ) + { + G_Printf( "Train corner at %s without a target\n", + vtos( path->s.origin ) ); + return; + } + + // find a path_corner among the targets + // there may also be other targets that get fired when the corner + // is reached + next = NULL; + do + { + next = G_Find( next, FOFS( targetname ), path->target ); + + if( !next ) + { + G_Printf( "Train corner at %s without a target path_corner\n", + vtos( path->s.origin ) ); + return; + } + } while( strcmp( next->classname, "path_corner" ) ); + + path->nextTrain = next; + } + + // start the train moving from the first corner + Reached_Train( ent ); +} + + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) +Train path corners. +Target: next path corner and other targets to fire +"speed" speed to move to the next corner +"wait" seconds to wait before behining move to next corner +*/ +void SP_path_corner( gentity_t *self ) +{ + if( !self->targetname ) + { + G_Printf( "path_corner with no targetname at %s\n", vtos( self->s.origin ) ); + G_FreeEntity( self ); + return; + } + // path corners don't need to be linked in +} + +/* +================ +Blocked_Train +================ +*/ +void Blocked_Train( gentity_t *self, gentity_t *other ) +{ + if( self->spawnflags & TRAIN_BLOCK_STOPS ) + Stop_Train( self, other, other ); + else + { + if( !other->client ) + { + //whatever is blocking the train isn't a client + + //KILL!!1!!! + G_Damage( other, self, self, NULL, NULL, 10000, 0, MOD_CRUSH ); + + //buildables need to be handled differently since even when + //dealth fatal amounts of damage they won't instantly become non-solid + if( other->s.eType == ET_BUILDABLE && other->spawned ) + { + vec3_t dir; + gentity_t *tent; + + if( other->buildableTeam == TEAM_ALIENS ) + { + VectorCopy( other->s.origin2, dir ); + tent = G_TempEntity( other->s.origin, EV_ALIEN_BUILDABLE_EXPLOSION ); + tent->s.eventParm = DirToByte( dir ); + } + else if( other->buildableTeam == TEAM_HUMANS ) + { + VectorSet( dir, 0.0f, 0.0f, 1.0f ); + tent = G_TempEntity( other->s.origin, EV_HUMAN_BUILDABLE_EXPLOSION ); + tent->s.eventParm = DirToByte( dir ); + } + } + + //if it's still around free it + if( other ) + G_FreeEntity( other ); + + return; + } + + G_Damage( other, self, self, NULL, NULL, 10000, 0, MOD_CRUSH ); + } +} + + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +A train is a mover that moves between path_corner target points. +Trains MUST HAVE AN ORIGIN BRUSH. +The train spawns at the first target it is pointing at. +"model2" .md3 model to also draw +"speed" default 100 +"dmg" default 2 +"noise" looping sound to play when the train is in motion +"target" next path corner +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_train( gentity_t *self ) +{ + VectorClear( self->s.angles ); + + if( self->spawnflags & TRAIN_BLOCK_STOPS ) + self->damage = 0; + else if( !self->damage ) + self->damage = 2; + + if( !self->speed ) + self->speed = 100; + + if( !self->target ) + { + G_Printf( "func_train without a target at %s\n", vtos( self->r.absmin ) ); + G_FreeEntity( self ); + return; + } + + trap_SetBrushModel( self, self->model ); + InitMover( self ); + + self->reached = Reached_Train; + self->use = Use_Train; + self->blocked = Blocked_Train; + + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = Think_SetupTrainTargets; +} + +/* +=============================================================================== + +STATIC + +=============================================================================== +*/ + + +/*QUAKED func_static (0 .5 .8) ? +A bmodel that just sits there, doing nothing. Can be used for conditional walls and models. +"model2" .md3 model to also draw +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_static( gentity_t *ent ) +{ + trap_SetBrushModel( ent, ent->model ); + InitMover( ent ); + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); +} + + +/* +=============================================================================== + +ROTATING + +=============================================================================== +*/ + + +/*QUAKED func_rotating (0 .5 .8) ? START_ON - X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"model2" .md3 model to also draw +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_rotating( gentity_t *ent ) +{ + if( !ent->speed ) + ent->speed = 100; + + // set the axis of rotation + ent->s.apos.trType = TR_LINEAR; + + if( ent->spawnflags & 4 ) + ent->s.apos.trDelta[ 2 ] = ent->speed; + else if( ent->spawnflags & 8 ) + ent->s.apos.trDelta[ 0 ] = ent->speed; + else + ent->s.apos.trDelta[ 1 ] = ent->speed; + + if( !ent->damage ) + ent->damage = 2; + + trap_SetBrushModel( ent, ent->model ); + InitMover( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + VectorCopy( ent->s.apos.trBase, ent->r.currentAngles ); + + trap_LinkEntity( ent ); +} + + +/* +=============================================================================== + +BOBBING + +=============================================================================== +*/ + + +/*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS +Normally bobs on the Z axis +"model2" .md3 model to also draw +"height" amplitude of bob (32 default) +"speed" seconds to complete a bob cycle (4 default) +"phase" the 0.0 to 1.0 offset in the cycle to start at +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_bobbing( gentity_t *ent ) +{ + float height; + float phase; + + G_SpawnFloat( "speed", "4", &ent->speed ); + G_SpawnFloat( "height", "32", &height ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "phase", "0", &phase ); + + trap_SetBrushModel( ent, ent->model ); + InitMover( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + + ent->s.pos.trDuration = ent->speed * 1000; + ent->s.pos.trTime = ent->s.pos.trDuration * phase; + ent->s.pos.trType = TR_SINE; + + // set the axis of bobbing + if( ent->spawnflags & 1 ) + ent->s.pos.trDelta[ 0 ] = height; + else if( ent->spawnflags & 2 ) + ent->s.pos.trDelta[ 1 ] = height; + else + ent->s.pos.trDelta[ 2 ] = height; +} + +/* +=============================================================================== + +PENDULUM + +=============================================================================== +*/ + + +/*QUAKED func_pendulum (0 .5 .8) ? +You need to have an origin brush as part of this entity. +Pendulums always swing north / south on unrotated models. Add an angles field to the model to allow rotation in other directions. +Pendulum frequency is a physical constant based on the length of the beam and gravity. +"model2" .md3 model to also draw +"speed" the number of degrees each way the pendulum swings, (30 default) +"phase" the 0.0 to 1.0 offset in the cycle to start at +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_pendulum( gentity_t *ent ) +{ + float freq; + float length; + float phase; + float speed; + + G_SpawnFloat( "speed", "30", &speed ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "phase", "0", &phase ); + + trap_SetBrushModel( ent, ent->model ); + + // find pendulum length + length = fabs( ent->r.mins[ 2 ] ); + + if( length < 8 ) + length = 8; + + freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) ); + + ent->s.pos.trDuration = ( 1000 / freq ); + + InitMover( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + + ent->s.apos.trDuration = 1000 / freq; + ent->s.apos.trTime = ent->s.apos.trDuration * phase; + ent->s.apos.trType = TR_SINE; + ent->s.apos.trDelta[ 2 ] = speed; +} diff --git a/src/game/g_namelog.c b/src/game/g_namelog.c new file mode 100644 index 0000000..6613684 --- /dev/null +++ b/src/game/g_namelog.c @@ -0,0 +1,127 @@ +/* +=========================================================================== +Copyright (C) 2010 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +void G_namelog_cleanup( void ) +{ + namelog_t *namelog, *n; + + for( namelog = level.namelogs; namelog; namelog = n ) + { + n = namelog->next; + BG_Free( namelog ); + } +} + +void G_namelog_connect( gclient_t *client ) +{ + namelog_t *n, *p = NULL; + int i; + char *newname; + + for( n = level.namelogs; n; p = n, n = n->next ) + { + if( n->slot != -1 ) + continue; + if( !Q_stricmp( client->pers.guid, n->guid ) ) + break; + } + if( !n ) + { + n = BG_Alloc( sizeof( namelog_t ) ); + strcpy( n->guid, client->pers.guid ); + if( p ) + { + p->next = n; + n->id = p->id + 1; + } + else + { + level.namelogs = n; + n->id = MAX_CLIENTS; + } + } + client->pers.namelog = n; + n->slot = client - level.clients; + n->banned = qfalse; + + newname = n->name[ n->nameOffset ]; + // If they're muted, copy in their last known name - this will stop people + // reconnecting to get around the name change protection. + if( n->muted && G_admin_name_check( &g_entities[ n->slot ], + newname, NULL, 0 ) ) + Q_strncpyz( client->pers.netname, newname, MAX_NAME_LENGTH ); + + for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ ) + if( !strcmp( n->ip[ i ].str, client->pers.ip.str ) ) + return; + if( i == MAX_NAMELOG_ADDRS ) + i--; + memcpy( &n->ip[ i ], &client->pers.ip, sizeof( n->ip[ i ] ) ); +} + +void G_namelog_disconnect( gclient_t *client ) +{ + if( client->pers.namelog == NULL ) + return; + client->pers.namelog->slot = -1; + client->pers.namelog = NULL; +} + +void G_namelog_update_score( gclient_t *client ) +{ + namelog_t *n = client->pers.namelog; + if( n == NULL ) + return; + + n->team = client->pers.teamSelection; + n->score = client->ps.persistant[ PERS_SCORE ]; + n->credits = client->pers.credit; +} + +void G_namelog_update_name( gclient_t *client ) +{ + char n1[ MAX_NAME_LENGTH ], n2[ MAX_NAME_LENGTH ]; + namelog_t *n = client->pers.namelog; + + if( n->name[ n->nameOffset ][ 0 ] ) + { + G_SanitiseString( client->pers.netname, n1, sizeof( n1 ) ); + G_SanitiseString( n->name[ n->nameOffset ], + n2, sizeof( n2 ) ); + if( strcmp( n1, n2 ) != 0 ) + n->nameOffset = ( n->nameOffset + 1 ) % MAX_NAMELOG_NAMES; + } + strcpy( n->name[ n->nameOffset ], client->pers.netname ); +} + +void G_namelog_restore( gclient_t *client ) +{ + namelog_t *n = client->pers.namelog; + + G_ChangeTeam( g_entities + n->slot, n->team ); + client->ps.persistant[ PERS_SCORE ] = n->score; + client->ps.persistant[ PERS_CREDIT ] = 0; + G_AddCreditToClient( client, n->credits, qfalse ); +} + diff --git a/src/game/g_physics.c b/src/game/g_physics.c new file mode 100644 index 0000000..840f5c4 --- /dev/null +++ b/src/game/g_physics.c @@ -0,0 +1,169 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +/* +================ +G_Bounce + +================ +*/ +static void G_Bounce( gentity_t *ent, trace_t *trace ) +{ + vec3_t velocity; + float dot; + int hitTime; + float minNormal; + qboolean invert = qfalse; + + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); + + if( ent->s.eType == ET_BUILDABLE ) + { + minNormal = BG_Buildable( ent->s.modelindex, NULL )->minNormal; + invert = BG_Buildable( ent->s.modelindex, NULL )->invertNormal; + } + else + minNormal = 0.707f; + + // cut the velocity to keep from bouncing forever + if( ent->s.eType == ET_BUILDABLE ) // buildable should never bounce + VectorScale( ent->s.pos.trDelta, 0.0f, ent->s.pos.trDelta ); + else if( ( trace->plane.normal[ 2 ] >= minNormal || + ( invert && trace->plane.normal[ 2 ] <= -minNormal ) ) && + trace->entityNum == ENTITYNUM_WORLD ) + VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta ); + else + VectorScale( ent->s.pos.trDelta, 0.3f, ent->s.pos.trDelta ); + + if( VectorLength( ent->s.pos.trDelta ) < 10 ) + { + VectorMA( trace->endpos, 0.5f, trace->plane.normal, trace->endpos ); // make sure it is off ground + G_SetOrigin( ent, trace->endpos ); + ent->s.groundEntityNum = trace->entityNum; + VectorCopy( trace->plane.normal, ent->s.origin2 ); + VectorSet( ent->s.pos.trDelta, 0.0f, 0.0f, 0.0f ); + return; + } + + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin); + ent->s.pos.trTime = level.time; +} + +#define PHYSICS_TIME 50 + +/* +================ +G_Physics + +================ +*/ +void G_Physics( gentity_t *ent, int msec ) +{ + vec3_t origin; + trace_t tr; + int contents; + int mask; + + // if groundentity has been set to -1, it may have been pushed off an edge + if( ent->s.groundEntityNum == -1 ) + { + if( ent->s.eType == ET_BUILDABLE ) + { + if( ent->s.pos.trType != BG_Buildable( ent->s.modelindex, NULL )->traj ) + { + ent->s.pos.trType = BG_Buildable( ent->s.modelindex, NULL )->traj; + ent->s.pos.trTime = level.time; + } + } + else if( ent->s.pos.trType != TR_GRAVITY ) + { + ent->s.pos.trType = TR_GRAVITY; + ent->s.pos.trTime = level.time; + } + } + + // trace a line from the previous position to the current position + if( ent->clipmask ) + mask = ent->clipmask; + else + mask = MASK_PLAYERSOLID; + + if( ent->s.pos.trType == TR_STATIONARY ) + { + // check think function + G_RunThink( ent ); + + //check floor infrequently + if( ent->nextPhysicsTime < level.time ) + { + VectorCopy( ent->r.currentOrigin, origin ); + + VectorMA( origin, -2.0f, ent->s.origin2, origin ); + + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, mask ); + + if( tr.fraction == 1.0f ) + ent->s.groundEntityNum = -1; + + ent->nextPhysicsTime = level.time + PHYSICS_TIME; + } + + return; + } + + // get current position + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, mask ); + + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + if( tr.startsolid ) + tr.fraction = 0; + + trap_LinkEntity( ent ); // FIXME: avoid this for stationary? + + // check think function + G_RunThink( ent ); + + if( tr.fraction == 1.0f ) + return; + + // if it is in a nodrop volume, remove it + contents = trap_PointContents( ent->r.currentOrigin, -1 ); + if( contents & CONTENTS_NODROP ) + { + G_FreeEntity( ent ); + return; + } + + G_Bounce( ent, &tr ); +} + diff --git a/src/game/g_public.h b/src/game/g_public.h new file mode 100644 index 0000000..312a5e5 --- /dev/null +++ b/src/game/g_public.h @@ -0,0 +1,265 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// g_public.h -- game module information visible to server + +#define GAME_API_VERSION 9 + +// entity->svFlags +// the server does not know how to interpret most of the values +// in entityStates (level eType), so the game must explicitly flag +// special server behaviors +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects + +#define SVF_CLIENTMASK 0x00000002 + +#define SVF_BROADCAST 0x00000020 // send to all connected clients +#define SVF_PORTAL 0x00000040 // merge a second pvs at origin2 into snapshots +#define SVF_USE_CURRENT_ORIGIN 0x00000080 // entity->r.currentOrigin instead of entity->s.origin + // for link position (missiles and movers) +#define SVF_SINGLECLIENT 0x00000100 // only send to a single client (entityShared_t->singleClient) +#define SVF_NOSERVERINFO 0x00000200 // don't send CS_SERVERINFO updates to this client + // so that it can be updated for ping tools without + // lagging clients +#define SVF_CAPSULE 0x00000400 // use capsule for collision detection instead of bbox +#define SVF_NOTSINGLECLIENT 0x00000800 // send entity to everyone but one client + // (entityShared_t->singleClient) + +//=============================================================== + + +typedef struct { + entityState_t s; // communicated by server to clients + + qboolean linked; // qfalse if not in any good cluster + int linkcount; + + int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc + int singleClient; // only send to this client when SVF_SINGLECLIENT is set + + qboolean bmodel; // if false, assume an explicit mins / maxs bounding box + // only set by trap_SetBrushModel + vec3_t mins, maxs; + int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc + // a non-solid entity should set to 0 + + vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation + + // currentOrigin will be used for all collision detection and world linking. + // it will not necessarily be the same as the trajectory evaluation for the current + // time, because each entity must be moved one at a time after time is advanced + // to avoid simultanious collision issues + vec3_t currentOrigin; + vec3_t currentAngles; + + // when a trace call is made and passEntityNum != ENTITYNUM_NONE, + // an ent will be excluded from testing if: + // ent->s.number == passEntityNum (don't interact with self) + // ent->s.ownerNum = passEntityNum (don't interact with your own missiles) + // entity[ent->s.ownerNum].ownerNum = passEntityNum (don't interact with other missiles from owner) + int ownerNum; +} entityShared_t; + + + +// the server looks at a sharedEntity, which is the start of the game's gentity_t structure +typedef struct { + entityState_t s; // communicated by server to clients + entityShared_t r; // shared by both the server system and game +} sharedEntity_t; + + + +//=============================================================== + +// +// system traps provided by the main engine +// +typedef enum { + //============== general Quake services ================== + + G_PRINT, // ( const char *string ); + // print message on the local console + + G_ERROR, // ( const char *string ); + // abort the game + + G_MILLISECONDS, // ( void ); + // get current time for profiling reasons + // this should NOT be used for any game related tasks, + // because it is not journaled + + // console variable interaction + G_CVAR_REGISTER, // ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); + G_CVAR_UPDATE, // ( vmCvar_t *vmCvar ); + G_CVAR_SET, // ( const char *var_name, const char *value ); + G_CVAR_VARIABLE_INTEGER_VALUE, // ( const char *var_name ); + + G_CVAR_VARIABLE_STRING_BUFFER, // ( const char *var_name, char *buffer, int bufsize ); + + G_ARGC, // ( void ); + // ClientCommand and ServerCommand parameter access + + G_ARGV, // ( int n, char *buffer, int bufferLength ); + + G_FS_FOPEN_FILE, // ( const char *qpath, fileHandle_t *file, fsMode_t mode ); + G_FS_READ, // ( void *buffer, int len, fileHandle_t f ); + G_FS_WRITE, // ( const void *buffer, int len, fileHandle_t f ); + G_FS_FCLOSE_FILE, // ( fileHandle_t f ); + + G_SEND_CONSOLE_COMMAND, // ( const char *text ); + // add commands to the console as if they were typed in + // for map changing, etc + + + //=========== server specific functionality ============= + + G_LOCATE_GAME_DATA, // ( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, + // playerState_t *clients, int sizeofGameClient ); + // the game needs to let the server system know where and how big the gentities + // are, so it can look at them directly without going through an interface + + G_DROP_CLIENT, // ( int clientNum, const char *reason ); + // kick a client off the server with a message + + G_SEND_SERVER_COMMAND, // ( int clientNum, const char *fmt, ... ); + // reliably sends a command string to be interpreted by the given + // client. If clientNum is -1, it will be sent to all clients + + G_SET_CONFIGSTRING, // ( int num, const char *string ); + // config strings hold all the index strings, and various other information + // that is reliably communicated to all clients + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + // All confgstrings are cleared at each level start. + + G_GET_CONFIGSTRING, // ( int num, char *buffer, int bufferSize ); + + G_SET_CONFIGSTRING_RESTRICTIONS, // ( int num, const clientList* clientList ); + + G_GET_USERINFO, // ( int num, char *buffer, int bufferSize ); + // userinfo strings are maintained by the server system, so they + // are persistant across level loads, while all other game visible + // data is completely reset + + G_SET_USERINFO, // ( int num, const char *buffer ); + + G_GET_SERVERINFO, // ( char *buffer, int bufferSize ); + // the serverinfo info string has all the cvars visible to server browsers + + G_SET_BRUSH_MODEL, // ( gentity_t *ent, const char *name ); + // sets mins and maxs based on the brushmodel name + + G_TRACE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); + // collision detection against all linked entities + + G_POINT_CONTENTS, // ( const vec3_t point, int passEntityNum ); + // point contents against all linked entities + + G_IN_PVS, // ( const vec3_t p1, const vec3_t p2 ); + + G_IN_PVS_IGNORE_PORTALS, // ( const vec3_t p1, const vec3_t p2 ); + + G_ADJUST_AREA_PORTAL_STATE, // ( gentity_t *ent, qboolean open ); + + G_AREAS_CONNECTED, // ( int area1, int area2 ); + + G_LINKENTITY, // ( gentity_t *ent ); + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + + G_UNLINKENTITY, // ( gentity_t *ent ); + // call before removing an interactive entity + + G_ENTITIES_IN_BOX, // ( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount ); + // EntitiesInBox will return brush models based on their bounding box, + // so exact determination must still be done with EntityContact + + G_ENTITY_CONTACT, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); + // perform an exact check against inline brush models of non-square shape + + G_GET_USERCMD, // ( int clientNum, usercmd_t *cmd ) + + G_GET_ENTITY_TOKEN, // qboolean ( char *buffer, int bufferSize ) + // Retrieves the next string token from the entity spawn text, returning + // false when all tokens have been parsed. + // This should only be done at GAME_INIT time. + + G_FS_GETFILELIST, + G_REAL_TIME, + G_SNAPVECTOR, + + G_TRACECAPSULE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); + G_ENTITY_CONTACTCAPSULE, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); + + // 1.32 + G_FS_SEEK, + + G_PARSE_ADD_GLOBAL_DEFINE, + G_PARSE_LOAD_SOURCE, + G_PARSE_FREE_SOURCE, + G_PARSE_READ_TOKEN, + G_PARSE_SOURCE_FILE_AND_LINE, + + G_SEND_GAMESTAT, + + G_ADDCOMMAND, + G_REMOVECOMMAND +} gameImport_t; + + +// +// functions exported by the game subsystem +// +typedef enum { + GAME_INIT, // ( int levelTime, int randomSeed, int restart ); + // init and shutdown will be called every single level + // The game should call G_GET_ENTITY_TOKEN to parse through all the + // entity configuration text and spawn gentities. + + GAME_SHUTDOWN, // (void); + + GAME_CLIENT_CONNECT, // ( int clientNum, qboolean firstTime ); + // return NULL if the client is allowed to connect, otherwise return + // a text string with the reason for denial + + GAME_CLIENT_BEGIN, // ( int clientNum ); + + GAME_CLIENT_USERINFO_CHANGED, // ( int clientNum ); + + GAME_CLIENT_DISCONNECT, // ( int clientNum ); + + GAME_CLIENT_COMMAND, // ( int clientNum ); + + GAME_CLIENT_THINK, // ( int clientNum ); + + GAME_RUN_FRAME, // ( int levelTime ); + + GAME_CONSOLE_COMMAND // ( void ); + // ConsoleCommand will be called when a command has been issued + // that is not recognized as a builtin function. + // The game can issue trap_argc() / trap_argv() commands to get the command + // and parameters. Return qfalse if the game doesn't recognize it as a command. +} gameExport_t; + diff --git a/src/game/g_session.c b/src/game/g_session.c new file mode 100644 index 0000000..181c739 --- /dev/null +++ b/src/game/g_session.c @@ -0,0 +1,152 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + + +/* +======================================================================= + + SESSION DATA + +Session data is the only data that stays persistant across level loads +and tournament restarts. +======================================================================= +*/ + +/* +================ +G_WriteClientSessionData + +Called on game shutdown +================ +*/ +void G_WriteClientSessionData( gclient_t *client ) +{ + const char *s; + const char *var; + + s = va( "%i %i %i %i %s", + client->sess.spectatorTime, + client->sess.spectatorState, + client->sess.spectatorClient, + client->sess.restartTeam, + Com_ClientListString( &client->sess.ignoreList ) + ); + + var = va( "session%i", client - level.clients ); + + trap_Cvar_Set( var, s ); +} + +/* +================ +G_ReadSessionData + +Called on a reconnect +================ +*/ +void G_ReadSessionData( gclient_t *client ) +{ + char s[ MAX_STRING_CHARS ]; + const char *var; + int spectatorState; + int restartTeam; + char ignorelist[ 17 ]; + + var = va( "session%i", client - level.clients ); + trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + sscanf( s, "%i %i %i %i %16s", + &client->sess.spectatorTime, + &spectatorState, + &client->sess.spectatorClient, + &restartTeam, + ignorelist + ); + + client->sess.spectatorState = (spectatorState_t)spectatorState; + client->sess.restartTeam = (team_t)restartTeam; + Com_ClientListParse( &client->sess.ignoreList, ignorelist ); +} + + +/* +================ +G_InitSessionData + +Called on a first-time connect +================ +*/ +void G_InitSessionData( gclient_t *client, char *userinfo ) +{ + clientSession_t *sess; + const char *value; + + sess = &client->sess; + + // initial team determination + value = Info_ValueForKey( userinfo, "team" ); + if( value[ 0 ] == 's' ) + { + // a willing spectator, not a waiting-in-line + sess->spectatorState = SPECTATOR_FREE; + } + else + { + if( g_maxGameClients.integer > 0 && + level.numNonSpectatorClients >= g_maxGameClients.integer ) + sess->spectatorState = SPECTATOR_FREE; + else + sess->spectatorState = SPECTATOR_NOT; + } + + sess->restartTeam = TEAM_NONE; + sess->spectatorState = SPECTATOR_FREE; + sess->spectatorTime = level.time; + sess->spectatorClient = -1; + memset( &sess->ignoreList, 0, sizeof( sess->ignoreList ) ); + + G_WriteClientSessionData( client ); +} + + +/* +================== +G_WriteSessionData + +================== +*/ +void G_WriteSessionData( void ) +{ + int i; + + //FIXME: What's this for? + trap_Cvar_Set( "session", va( "%i", 0 ) ); + + for( i = 0 ; i < level.maxclients ; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + G_WriteClientSessionData( &level.clients[ i ] ); + } +} diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c new file mode 100644 index 0000000..1ebc04d --- /dev/null +++ b/src/game/g_spawn.c @@ -0,0 +1,665 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +qboolean G_SpawnString( const char *key, const char *defaultString, char **out ) +{ + int i; + + if( !level.spawning ) + { + *out = (char *)defaultString; +// G_Error( "G_SpawnString() called while not spawning" ); + return qfalse; + } + + for( i = 0; i < level.numSpawnVars; i++ ) + { + if( !Q_stricmp( key, level.spawnVars[ i ][ 0 ] ) ) + { + *out = level.spawnVars[ i ][ 1 ]; + return qtrue; + } + } + + *out = (char *)defaultString; + return qfalse; +} + +qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out ) +{ + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + *out = atof( s ); + return present; +} + +qboolean G_SpawnInt( const char *key, const char *defaultString, int *out ) +{ + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + *out = atoi( s ); + return present; +} + +qboolean G_SpawnVector( const char *key, const char *defaultString, float *out ) +{ + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + sscanf( s, "%f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ] ); + return present; +} + +qboolean G_SpawnVector4( const char *key, const char *defaultString, float *out ) +{ + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + sscanf( s, "%f %f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ], &out[ 3 ] ); + return present; +} + + + +// +// fields are needed for spawning from the entity string +// +typedef enum +{ + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_VECTOR4, + F_ANGLEHACK, + F_ENTITY, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + +field_t fields[ ] = +{ + {"acceleration", FOFS(acceleration), F_VECTOR}, + {"alpha", FOFS(pos1), F_VECTOR}, + {"angle", FOFS(s.angles), F_ANGLEHACK}, + {"angles", FOFS(s.angles), F_VECTOR}, + {"animation", FOFS(animation), F_VECTOR4}, + {"bounce", FOFS(physicsBounce), F_FLOAT}, + {"classname", FOFS(classname), F_LSTRING}, + {"count", FOFS(count), F_INT}, + {"dmg", FOFS(damage), F_INT}, + {"health", FOFS(health), F_INT}, + {"light", 0, F_IGNORE}, + {"message", FOFS(message), F_LSTRING}, + {"model", FOFS(model), F_LSTRING}, + {"model2", FOFS(model2), F_LSTRING}, + {"origin", FOFS(s.origin), F_VECTOR}, + {"radius", FOFS(pos2), F_VECTOR}, + {"random", FOFS(random), F_FLOAT}, + {"rotatorAngle", FOFS(rotatorAngle), F_FLOAT}, + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"target", FOFS(target), F_LSTRING}, + {"targetname", FOFS(targetname), F_LSTRING}, + {"targetShaderName", FOFS(targetShaderName), F_LSTRING}, + {"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING}, + {"team", FOFS(team), F_LSTRING}, + {"wait", FOFS(wait), F_FLOAT} +}; + + +typedef struct +{ + char *name; + void (*spawn)(gentity_t *ent); +} spawn_t; + +void SP_info_player_start( gentity_t *ent ); +void SP_info_player_deathmatch( gentity_t *ent ); +void SP_info_player_intermission( gentity_t *ent ); + +void SP_info_alien_intermission( gentity_t *ent ); +void SP_info_human_intermission( gentity_t *ent ); + +void SP_info_firstplace( gentity_t *ent ); +void SP_info_secondplace( gentity_t *ent ); +void SP_info_thirdplace( gentity_t *ent ); +void SP_info_podium( gentity_t *ent ); + +void SP_func_plat( gentity_t *ent ); +void SP_func_static( gentity_t *ent ); +void SP_func_rotating( gentity_t *ent ); +void SP_func_bobbing( gentity_t *ent ); +void SP_func_pendulum( gentity_t *ent ); +void SP_func_button( gentity_t *ent ); +void SP_func_door( gentity_t *ent ); +void SP_func_door_rotating( gentity_t *ent ); +void SP_func_door_model( gentity_t *ent ); +void SP_func_train( gentity_t *ent ); +void SP_func_timer( gentity_t *self); + +void SP_trigger_always( gentity_t *ent ); +void SP_trigger_multiple( gentity_t *ent ); +void SP_trigger_push( gentity_t *ent ); +void SP_trigger_teleport( gentity_t *ent ); +void SP_trigger_hurt( gentity_t *ent ); +void SP_trigger_stage( gentity_t *ent ); +void SP_trigger_win( gentity_t *ent ); +void SP_trigger_buildable( gentity_t *ent ); +void SP_trigger_class( gentity_t *ent ); +void SP_trigger_equipment( gentity_t *ent ); +void SP_trigger_gravity( gentity_t *ent ); +void SP_trigger_heal( gentity_t *ent ); +void SP_trigger_ammo( gentity_t *ent ); + +void SP_target_delay( gentity_t *ent ); +void SP_target_speaker( gentity_t *ent ); +void SP_target_print( gentity_t *ent ); +void SP_target_character( gentity_t *ent ); +void SP_target_score( gentity_t *ent ); +void SP_target_teleporter( gentity_t *ent ); +void SP_target_relay( gentity_t *ent ); +void SP_target_kill( gentity_t *ent ); +void SP_target_position( gentity_t *ent ); +void SP_target_location( gentity_t *ent ); +void SP_target_push( gentity_t *ent ); +void SP_target_rumble( gentity_t *ent ); +void SP_target_alien_win( gentity_t *ent ); +void SP_target_human_win( gentity_t *ent ); +void SP_target_hurt( gentity_t *ent ); + +void SP_light( gentity_t *self ); +void SP_info_null( gentity_t *self ); +void SP_info_notnull( gentity_t *self ); +void SP_info_camp( gentity_t *self ); +void SP_path_corner( gentity_t *self ); + +void SP_misc_teleporter_dest( gentity_t *self ); +void SP_misc_model( gentity_t *ent ); +void SP_misc_portal_camera( gentity_t *ent ); +void SP_misc_portal_surface( gentity_t *ent ); + +void SP_shooter_rocket( gentity_t *ent ); +void SP_shooter_plasma( gentity_t *ent ); +void SP_shooter_grenade( gentity_t *ent ); + +void SP_misc_particle_system( gentity_t *ent ); +void SP_misc_anim_model( gentity_t *ent ); +void SP_misc_light_flare( gentity_t *ent ); + +spawn_t spawns[ ] = +{ + { "func_bobbing", SP_func_bobbing }, + { "func_button", SP_func_button }, + { "func_door", SP_func_door }, + { "func_door_model", SP_func_door_model }, + { "func_door_rotating", SP_func_door_rotating }, + { "func_group", SP_info_null }, + { "func_pendulum", SP_func_pendulum }, + { "func_plat", SP_func_plat }, + { "func_rotating", SP_func_rotating }, + { "func_static", SP_func_static }, + { "func_timer", SP_func_timer }, // rename trigger_timer? + { "func_train", SP_func_train }, + + // info entities don't do anything at all, but provide positional + // information for things controlled by other processes + { "info_alien_intermission", SP_info_alien_intermission }, + { "info_human_intermission", SP_info_human_intermission }, + { "info_notnull", SP_info_notnull }, // use target_position instead + { "info_null", SP_info_null }, + { "info_player_deathmatch", SP_info_player_deathmatch }, + { "info_player_intermission", SP_info_player_intermission }, + { "info_player_start", SP_info_player_start }, + { "light", SP_light }, + { "misc_anim_model", SP_misc_anim_model }, + { "misc_light_flare", SP_misc_light_flare }, + { "misc_model", SP_misc_model }, + { "misc_particle_system", SP_misc_particle_system }, + { "misc_portal_camera", SP_misc_portal_camera }, + { "misc_portal_surface", SP_misc_portal_surface }, + { "misc_teleporter_dest", SP_misc_teleporter_dest }, + { "path_corner", SP_path_corner }, + + // targets perform no action by themselves, but must be triggered + // by another entity + { "target_alien_win", SP_target_alien_win }, + { "target_delay", SP_target_delay }, + { "target_human_win", SP_target_human_win }, + { "target_hurt", SP_target_hurt }, + { "target_kill", SP_target_kill }, + { "target_location", SP_target_location }, + { "target_position", SP_target_position }, + { "target_print", SP_target_print }, + { "target_push", SP_target_push }, + { "target_relay", SP_target_relay }, + { "target_rumble", SP_target_rumble }, + { "target_score", SP_target_score }, + { "target_speaker", SP_target_speaker }, + { "target_teleporter", SP_target_teleporter }, + + // Triggers are brush objects that cause an effect when contacted + // by a living player, usually involving firing targets. + // While almost everything could be done with + // a single trigger class and different targets, triggered effects + // could not be client side predicted (push and teleport). + { "trigger_always", SP_trigger_always }, + { "trigger_ammo", SP_trigger_ammo }, + { "trigger_buildable", SP_trigger_buildable }, + { "trigger_class", SP_trigger_class }, + { "trigger_equipment", SP_trigger_equipment }, + { "trigger_gravity", SP_trigger_gravity }, + { "trigger_heal", SP_trigger_heal }, + { "trigger_hurt", SP_trigger_hurt }, + { "trigger_multiple", SP_trigger_multiple }, + { "trigger_push", SP_trigger_push }, + { "trigger_stage", SP_trigger_stage }, + { "trigger_teleport", SP_trigger_teleport }, + { "trigger_win", SP_trigger_win } +}; + +/* +=============== +G_CallSpawn + +Finds the spawn function for the entity and calls it, +returning qfalse if not found +=============== +*/ +qboolean G_CallSpawn( gentity_t *ent ) +{ + spawn_t *s; + buildable_t buildable; + + if( !ent->classname ) + { + G_Printf( "G_CallSpawn: NULL classname\n" ); + return qfalse; + } + + //check buildable spawn functions + buildable = BG_BuildableByEntityName( ent->classname )->number; + if( buildable != BA_NONE ) + { + // don't spawn built-in buildings if we are using a custom layout + if( level.layout[ 0 ] && Q_stricmp( level.layout, "*BUILTIN*" ) ) + return qfalse; + + if( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN ) + { + ent->s.angles[ YAW ] += 180.0f; + AngleNormalize360( ent->s.angles[ YAW ] ); + } + + G_SpawnBuildable( ent, buildable ); + return qtrue; + } + + // check normal spawn functions + s = bsearch( ent->classname, spawns, sizeof( spawns ) / sizeof( spawn_t ), + sizeof( spawn_t ), cmdcmp ); + if( s ) + { + // found it + s->spawn( ent ); + return qtrue; + } + + G_Printf( "%s doesn't have a spawn function\n", ent->classname ); + return qfalse; +} + +/* +============= +G_NewString + +Builds a copy of the string, translating \n to real linefeeds +so message texts can be multi-line +============= +*/ +char *G_NewString( const char *string ) +{ + char *newb, *new_p; + int i,l; + + l = strlen( string ) + 1; + + newb = BG_Alloc( l ); + + new_p = newb; + + // turn \n into a real linefeed + for( i = 0 ; i < l ; i++ ) + { + if( string[ i ] == '\\' && i < l - 1 ) + { + i++; + if( string[ i ] == 'n' ) + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[ i ]; + } + + return newb; +} + + + + +/* +=============== +G_ParseField + +Takes a key/value pair and sets the binary values +in a gentity +=============== +*/ +void G_ParseField( const char *key, const char *value, gentity_t *ent ) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + vec4_t vec4; + + f = bsearch( key, fields, sizeof( fields ) / sizeof( field_t ), + sizeof( field_t ), cmdcmp ); + if( !f ) + return; + b = (byte *)ent; + + switch( f->type ) + { + case F_LSTRING: + *(char **)( b + f->ofs ) = G_NewString( value ); + break; + + case F_VECTOR: + sscanf( value, "%f %f %f", &vec[ 0 ], &vec[ 1 ], &vec[ 2 ] ); + + ( (float *)( b + f->ofs ) )[ 0 ] = vec[ 0 ]; + ( (float *)( b + f->ofs ) )[ 1 ] = vec[ 1 ]; + ( (float *)( b + f->ofs ) )[ 2 ] = vec[ 2 ]; + break; + + case F_VECTOR4: + sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] ); + + ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ]; + ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ]; + ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ]; + ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ]; + break; + + case F_INT: + *(int *)( b + f->ofs ) = atoi( value ); + break; + + case F_FLOAT: + *(float *)( b + f->ofs ) = atof( value ); + break; + + case F_ANGLEHACK: + v = atof( value ); + ( (float *)( b + f->ofs ) )[ 0 ] = 0; + ( (float *)( b + f->ofs ) )[ 1 ] = v; + ( (float *)( b + f->ofs ) )[ 2 ] = 0; + break; + + default: + case F_IGNORE: + break; + } +} + + + + +/* +=================== +G_SpawnGEntityFromSpawnVars + +Spawn an entity and fill in all of the level fields from +level.spawnVars[], then call the class specfic spawn function +=================== +*/ +void G_SpawnGEntityFromSpawnVars( void ) +{ + int i; + gentity_t *ent; + + // get the next free entity + ent = G_Spawn( ); + + for( i = 0 ; i < level.numSpawnVars ; i++ ) + G_ParseField( level.spawnVars[ i ][ 0 ], level.spawnVars[ i ][ 1 ], ent ); + + G_SpawnInt( "notq3a", "0", &i ); + + if( i ) + { + G_FreeEntity( ent ); + return; + } + + // move editor origin to pos + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + + // if we didn't get a classname, don't bother spawning anything + if( !G_CallSpawn( ent ) ) + G_FreeEntity( ent ); +} + + + +/* +==================== +G_AddSpawnVarToken +==================== +*/ +char *G_AddSpawnVarToken( const char *string ) +{ + int l; + char *dest; + + l = strlen( string ); + if( level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) + G_Error( "G_AddSpawnVarToken: MAX_SPAWN_CHARS" ); + + dest = level.spawnVarChars + level.numSpawnVarChars; + memcpy( dest, string, l + 1 ); + + level.numSpawnVarChars += l + 1; + + return dest; +} + +/* +==================== +G_ParseSpawnVars + +Parses a brace bounded set of key / value pairs out of the +level's entity strings into level.spawnVars[] + +This does not actually spawn an entity. +==================== +*/ +qboolean G_ParseSpawnVars( void ) +{ + char keyname[ MAX_TOKEN_CHARS ]; + char com_token[ MAX_TOKEN_CHARS ]; + + level.numSpawnVars = 0; + level.numSpawnVarChars = 0; + + // parse the opening brace + if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) + { + // end of spawn string + return qfalse; + } + + if( com_token[ 0 ] != '{' ) + G_Error( "G_ParseSpawnVars: found %s when expecting {", com_token ); + + // go through all the key / value pairs + while( 1 ) + { + // parse key + if( !trap_GetEntityToken( keyname, sizeof( keyname ) ) ) + G_Error( "G_ParseSpawnVars: EOF without closing brace" ); + + if( keyname[0] == '}' ) + break; + + // parse value + if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) + G_Error( "G_ParseSpawnVars: EOF without closing brace" ); + + if( com_token[0] == '}' ) + G_Error( "G_ParseSpawnVars: closing brace without data" ); + + if( level.numSpawnVars == MAX_SPAWN_VARS ) + G_Error( "G_ParseSpawnVars: MAX_SPAWN_VARS" ); + + level.spawnVars[ level.numSpawnVars ][ 0 ] = G_AddSpawnVarToken( keyname ); + level.spawnVars[ level.numSpawnVars ][ 1 ] = G_AddSpawnVarToken( com_token ); + level.numSpawnVars++; + } + + return qtrue; +} + + + +/*QUAKED worldspawn (0 0 0) ? + +Every map should have exactly one worldspawn. +"music" music wav file +"gravity" 800 is default gravity +"message" Text to print during connection process +*/ +void SP_worldspawn( void ) +{ + char *s; + + G_SpawnString( "classname", "", &s ); + + if( Q_stricmp( s, "worldspawn" ) ) + G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" ); + + // make some data visible to connecting client + trap_SetConfigstring( CS_GAME_VERSION, GAME_VERSION ); + + trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) ); + + G_SpawnString( "music", "", &s ); + trap_SetConfigstring( CS_MUSIC, s ); + + G_SpawnString( "message", "", &s ); + trap_SetConfigstring( CS_MESSAGE, s ); // map specific message + + trap_SetConfigstring( CS_MOTD, g_motd.string ); // message of the day + + if( G_SpawnString( "gravity", "", &s ) ) + trap_Cvar_Set( "g_gravity", s ); + + if( G_SpawnString( "humanMaxStage", "", &s ) ) + trap_Cvar_Set( "g_humanMaxStage", s ); + + if( G_SpawnString( "alienMaxStage", "", &s ) ) + trap_Cvar_Set( "g_alienMaxStage", s ); + + G_SpawnString( "disabledEquipment", "", &s ); + trap_Cvar_Set( "g_disabledEquipment", s ); + + G_SpawnString( "disabledClasses", "", &s ); + trap_Cvar_Set( "g_disabledClasses", s ); + + G_SpawnString( "disabledBuildables", "", &s ); + trap_Cvar_Set( "g_disabledBuildables", s ); + + g_entities[ ENTITYNUM_WORLD ].s.number = ENTITYNUM_WORLD; + g_entities[ ENTITYNUM_WORLD ].classname = "worldspawn"; + + if( g_restarted.integer ) + trap_Cvar_Set( "g_restarted", "0" ); + + // see if we want a warmup time + trap_SetConfigstring( CS_WARMUP, "-1" ); + if( g_doWarmup.integer ) + { + level.warmupTime = level.time - level.startTime + ( g_warmup.integer * 1000 ); + trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) ); + G_LogPrintf( "Warmup: %i\n", g_warmup.integer ); + } + +} + + +/* +============== +G_SpawnEntitiesFromString + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +void G_SpawnEntitiesFromString( void ) +{ + level.numSpawnVars = 0; + + // the worldspawn is not an actual entity, but it still + // has a "spawn" function to perform any global setup + // needed by a level (setting configstrings or cvars, etc) + if( !G_ParseSpawnVars( ) ) + G_Error( "SpawnEntities: no entities" ); + + SP_worldspawn( ); + + // parse ents + while( G_ParseSpawnVars( ) ) + G_SpawnGEntityFromSpawnVars( ); +} + diff --git a/src/game/g_svcmds.c b/src/game/g_svcmds.c new file mode 100644 index 0000000..16cb6b8 --- /dev/null +++ b/src/game/g_svcmds.c @@ -0,0 +1,639 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// this file holds commands that can be executed by the server console, but not remote clients + +#include "g_local.h" + +/* +=================== +Svcmd_EntityList_f +=================== +*/ +void Svcmd_EntityList_f( void ) +{ + int e; + gentity_t *check; + + check = g_entities; + + for( e = 0; e < level.num_entities; e++, check++ ) + { + if( !check->inuse ) + continue; + + G_Printf( "%3i:", e ); + + switch( check->s.eType ) + { + case ET_GENERAL: + G_Printf( "ET_GENERAL " ); + break; + case ET_PLAYER: + G_Printf( "ET_PLAYER " ); + break; + case ET_ITEM: + G_Printf( "ET_ITEM " ); + break; + case ET_BUILDABLE: + G_Printf( "ET_BUILDABLE " ); + break; + case ET_LOCATION: + G_Printf( "ET_LOCATION " ); + break; + case ET_MISSILE: + G_Printf( "ET_MISSILE " ); + break; + case ET_MOVER: + G_Printf( "ET_MOVER " ); + break; + case ET_BEAM: + G_Printf( "ET_BEAM " ); + break; + case ET_PORTAL: + G_Printf( "ET_PORTAL " ); + break; + case ET_SPEAKER: + G_Printf( "ET_SPEAKER " ); + break; + case ET_PUSH_TRIGGER: + G_Printf( "ET_PUSH_TRIGGER " ); + break; + case ET_TELEPORT_TRIGGER: + G_Printf( "ET_TELEPORT_TRIGGER " ); + break; + case ET_INVISIBLE: + G_Printf( "ET_INVISIBLE " ); + break; + case ET_GRAPPLE: + G_Printf( "ET_GRAPPLE " ); + break; + case ET_CORPSE: + G_Printf( "ET_CORPSE " ); + break; + case ET_PARTICLE_SYSTEM: + G_Printf( "ET_PARTICLE_SYSTEM " ); + break; + case ET_ANIMMAPOBJ: + G_Printf( "ET_ANIMMAPOBJ " ); + break; + case ET_MODELDOOR: + G_Printf( "ET_MODELDOOR " ); + break; + case ET_LIGHTFLARE: + G_Printf( "ET_LIGHTFLARE " ); + break; + case ET_LEV2_ZAP_CHAIN: + G_Printf( "ET_LEV2_ZAP_CHAIN " ); + break; + default: + G_Printf( "%-3i ", check->s.eType ); + break; + } + + if( check->classname ) + G_Printf( "%s", check->classname ); + + G_Printf( "\n" ); + } +} + +static gclient_t *ClientForString( char *s ) +{ + int idnum; + char err[ MAX_STRING_CHARS ]; + + idnum = G_ClientNumberFromString( s, err, sizeof( err ) ); + if( idnum == -1 ) + { + G_Printf( "%s", err ); + return NULL; + } + + return &level.clients[ idnum ]; +} + +static void Svcmd_Status_f( void ) +{ + int i; + gclient_t *cl; + char userinfo[ MAX_INFO_STRING ]; + + G_Printf( "slot score ping address rate name\n" ); + G_Printf( "---- ----- ---- ------- ---- ----\n" ); + for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ ) + { + if( cl->pers.connected == CON_DISCONNECTED ) + continue; + + G_Printf( "%-4d ", i ); + G_Printf( "%-5d ", cl->ps.persistant[ PERS_SCORE ] ); + + if( cl->pers.connected == CON_CONNECTING ) + G_Printf( "CNCT " ); + else + G_Printf( "%-4d ", cl->ps.ping ); + + trap_GetUserinfo( i, userinfo, sizeof( userinfo ) ); + G_Printf( "%-21s ", Info_ValueForKey( userinfo, "ip" ) ); + G_Printf( "%-8d ", Info_ValueForKey( userinfo, "rate" ) ); + G_Printf( "%s\n", cl->pers.netname ); // Info_ValueForKey( userinfo, "name" ) + } +} + +/* +=================== +Svcmd_ForceTeam_f + +forceteam +=================== +*/ +static void Svcmd_ForceTeam_f( void ) +{ + gclient_t *cl; + char str[ MAX_TOKEN_CHARS ]; + team_t team; + + if( trap_Argc( ) != 3 ) + { + G_Printf( "usage: forceteam \n" ); + return; + } + + trap_Argv( 1, str, sizeof( str ) ); + cl = ClientForString( str ); + + if( !cl ) + return; + + trap_Argv( 2, str, sizeof( str ) ); + team = G_TeamFromString( str ); + if( team == NUM_TEAMS ) + { + G_Printf( "forceteam: invalid team \"%s\"\n", str ); + return; + } + G_ChangeTeam( &g_entities[ cl - level.clients ], team ); +} + +/* +=================== +Svcmd_LayoutSave_f + +layoutsave +=================== +*/ +static void Svcmd_LayoutSave_f( void ) +{ + char str[ MAX_QPATH ]; + char str2[ MAX_QPATH - 4 ]; + char *s; + int i = 0; + + if( trap_Argc( ) != 2 ) + { + G_Printf( "usage: layoutsave \n" ); + return; + } + trap_Argv( 1, str, sizeof( str ) ); + + // sanitize name + s = &str[ 0 ]; + while( *s && i < sizeof( str2 ) - 1 ) + { + if( isalnum( *s ) || *s == '-' || *s == '_' ) + { + str2[ i++ ] = *s; + str2[ i ] = '\0'; + } + s++; + } + + if( !str2[ 0 ] ) + { + G_Printf( "layoutsave: invalid name \"%s\"\n", str ); + return; + } + + G_LayoutSave( str2 ); +} + +char *ConcatArgs( int start ); + +/* +=================== +Svcmd_LayoutLoad_f + +layoutload [ [ [ ...\n" ); + return; + } + + s = ConcatArgs( 1 ); + Q_strncpyz( layouts, s, sizeof( layouts ) ); + trap_Cvar_Set( "g_layouts", layouts ); + trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); + level.restarted = qtrue; +} + +static void Svcmd_AdmitDefeat_f( void ) +{ + int team; + char teamNum[ 2 ]; + + if( trap_Argc( ) != 2 ) + { + G_Printf("admitdefeat: must provide a team\n"); + return; + } + trap_Argv( 1, teamNum, sizeof( teamNum ) ); + team = G_TeamFromString( teamNum ); + if( team == TEAM_ALIENS ) + { + G_TeamCommand( TEAM_ALIENS, "cp \"Hivemind Link Broken\" 1"); + trap_SendServerCommand( -1, "print \"Alien team has admitted defeat\n\"" ); + } + else if( team == TEAM_HUMANS ) + { + G_TeamCommand( TEAM_HUMANS, "cp \"Life Support Terminated\" 1"); + trap_SendServerCommand( -1, "print \"Human team has admitted defeat\n\"" ); + } + else + { + G_Printf("admitdefeat: invalid team\n"); + return; + } + level.surrenderTeam = team; + G_BaseSelfDestruct( team ); +} + +static void Svcmd_TeamWin_f( void ) +{ + // this is largely made redundant by admitdefeat + char cmd[ 6 ]; + trap_Argv( 0, cmd, sizeof( cmd ) ); + + switch( G_TeamFromString( cmd ) ) + { + case TEAM_ALIENS: + G_BaseSelfDestruct( TEAM_HUMANS ); + break; + + case TEAM_HUMANS: + G_BaseSelfDestruct( TEAM_ALIENS ); + break; + + default: + return; + } +} + +static void Svcmd_Evacuation_f( void ) +{ + trap_SendServerCommand( -1, "print \"Evacuation ordered\n\"" ); + level.lastWin = TEAM_NONE; + trap_SetConfigstring( CS_WINNER, "Evacuation" ); + LogExit( "Evacuation." ); +} + +static void Svcmd_MapRotation_f( void ) +{ + char rotationName[ MAX_QPATH ]; + + if( trap_Argc( ) != 2 ) + { + G_Printf( "usage: maprotation \n" ); + return; + } + + G_ClearRotationStack( ); + + trap_Argv( 1, rotationName, sizeof( rotationName ) ); + if( !G_StartMapRotation( rotationName, qfalse, qtrue, qfalse, 0 ) ) + G_Printf( "maprotation: invalid map rotation \"%s\"\n", rotationName ); +} + +static void Svcmd_TeamMessage_f( void ) +{ + char teamNum[ 2 ]; + team_t team; + + if( trap_Argc( ) < 3 ) + { + G_Printf( "usage: say_team \n" ); + return; + } + + trap_Argv( 1, teamNum, sizeof( teamNum ) ); + team = G_TeamFromString( teamNum ); + + if( team == NUM_TEAMS ) + { + G_Printf( "say_team: invalid team \"%s\"\n", teamNum ); + return; + } + + G_TeamCommand( team, va( "chat -1 %d \"%s\"", SAY_TEAM, ConcatArgs( 2 ) ) ); + G_LogPrintf( "SayTeam: -1 \"console\": %s\n", ConcatArgs( 2 ) ); +} + +static void Svcmd_CenterPrint_f( void ) +{ + if( trap_Argc( ) < 2 ) + { + G_Printf( "usage: cp \n" ); + return; + } + + trap_SendServerCommand( -1, va( "cp \"%s\"", ConcatArgs( 1 ) ) ); +} + +static void Svcmd_EjectClient_f( void ) +{ + char *reason, name[ MAX_STRING_CHARS ]; + + if( trap_Argc( ) < 2 ) + { + G_Printf( "usage: eject \n" ); + return; + } + + trap_Argv( 1, name, sizeof( name ) ); + reason = ConcatArgs( 2 ); + + if( atoi( name ) == -1 ) + { + int i; + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + if( level.clients[ i ].pers.localClient ) + continue; + trap_DropClient( i, reason ); + } + } + else + { + gclient_t *cl = ClientForString( name ); + if( !cl ) + return; + if( cl->pers.localClient ) + { + G_Printf( "eject: cannot eject local clients\n" ); + return; + } + trap_DropClient( cl-level.clients, reason ); + } +} + +static void Svcmd_DumpUser_f( void ) +{ + char name[ MAX_STRING_CHARS ], userinfo[ MAX_INFO_STRING ]; + char key[ BIG_INFO_KEY ], value[ BIG_INFO_VALUE ]; + const char *info; + gclient_t *cl; + + if( trap_Argc( ) != 2 ) + { + G_Printf( "usage: dumpuser \n" ); + return; + } + + trap_Argv( 1, name, sizeof( name ) ); + cl = ClientForString( name ); + if( !cl ) + return; + + trap_GetUserinfo( cl-level.clients, userinfo, sizeof( userinfo ) ); + info = &userinfo[ 0 ]; + G_Printf( "userinfo\n--------\n" ); + //Info_Print( userinfo ); + while( 1 ) + { + Info_NextPair( &info, key, value ); + if( !*info ) + return; + + G_Printf( "%-20s%s\n", key, value ); + } +} + +static void Svcmd_Pr_f( void ) +{ + char targ[ 4 ]; + int cl; + + if( trap_Argc( ) < 3 ) + { + G_Printf( "usage: \n" ); + return; + } + + trap_Argv( 1, targ, sizeof( targ ) ); + cl = atoi( targ ); + + if( cl >= MAX_CLIENTS || cl < -1 ) + { + G_Printf( "invalid clientnum %d\n", cl ); + return; + } + + trap_SendServerCommand( cl, va( "print \"%s\n\"", ConcatArgs( 2 ) ) ); +} + +static void Svcmd_PrintQueue_f( void ) +{ + char team[ MAX_STRING_CHARS ]; + + if( trap_Argc() != 2 ) + { + G_Printf( "usage: printqueue \n" ); + return; + } + + trap_Argv( 1, team, sizeof( team ) ); + + switch( G_TeamFromString( team ) ) + { + case TEAM_ALIENS: + G_PrintSpawnQueue( &level.alienSpawnQueue ); + break; + + case TEAM_HUMANS: + G_PrintSpawnQueue( &level.humanSpawnQueue ); + break; + + default: + G_Printf( "unknown team\n" ); + } +} + +// dumb wrapper for "a", "m", "chat", and "say" +static void Svcmd_MessageWrapper( void ) +{ + char cmd[ 5 ]; + trap_Argv( 0, cmd, sizeof( cmd ) ); + + if( !Q_stricmp( cmd, "a" ) ) + Cmd_AdminMessage_f( NULL ); + else if( !Q_stricmp( cmd, "m" ) ) + Cmd_PrivateMessage_f( NULL ); + else if( !Q_stricmp( cmd, "say" ) ) + G_Say( NULL, SAY_ALL, ConcatArgs( 1 ) ); + else if( !Q_stricmp( cmd, "chat" ) ) + G_Say( NULL, SAY_RAW, ConcatArgs( 1 ) ); +} + +static void Svcmd_ListMapsWrapper( void ) +{ + Cmd_ListMaps_f( NULL ); +} + +static void Svcmd_SuddenDeath_f( void ) +{ + char secs[ 5 ]; + int offset; + trap_Argv( 1, secs, sizeof( secs ) ); + offset = atoi( secs ); + + level.suddenDeathBeginTime = level.time - level.startTime + offset * 1000; + trap_SendServerCommand( -1, + va( "cp \"Sudden Death will begin in %d second%s\"", + offset, offset == 1 ? "" : "s" ) ); +} + +static void Svcmd_G_AdvanceMapRotation_f( void ) +{ + G_AdvanceMapRotation( 0 ); +} + +struct svcmd +{ + char *cmd; + qboolean dedicated; + void ( *function )( void ); +} svcmds[ ] = { + { "a", qtrue, Svcmd_MessageWrapper }, + { "admitDefeat", qfalse, Svcmd_AdmitDefeat_f }, + { "advanceMapRotation", qfalse, Svcmd_G_AdvanceMapRotation_f }, + { "alienWin", qfalse, Svcmd_TeamWin_f }, + { "chat", qtrue, Svcmd_MessageWrapper }, + { "cp", qtrue, Svcmd_CenterPrint_f }, + { "dumpuser", qfalse, Svcmd_DumpUser_f }, + { "eject", qfalse, Svcmd_EjectClient_f }, + { "entityList", qfalse, Svcmd_EntityList_f }, + { "evacuation", qfalse, Svcmd_Evacuation_f }, + { "forceTeam", qfalse, Svcmd_ForceTeam_f }, + { "game_memory", qfalse, BG_MemoryInfo }, + { "humanWin", qfalse, Svcmd_TeamWin_f }, + { "layoutLoad", qfalse, Svcmd_LayoutLoad_f }, + { "layoutSave", qfalse, Svcmd_LayoutSave_f }, + { "listmaps", qtrue, Svcmd_ListMapsWrapper }, + { "loadcensors", qfalse, G_LoadCensors }, + { "m", qtrue, Svcmd_MessageWrapper }, + { "mapRotation", qfalse, Svcmd_MapRotation_f }, + { "pr", qfalse, Svcmd_Pr_f }, + { "printqueue", qfalse, Svcmd_PrintQueue_f }, + { "say", qtrue, Svcmd_MessageWrapper }, + { "say_team", qtrue, Svcmd_TeamMessage_f }, + { "status", qfalse, Svcmd_Status_f }, + { "stopMapRotation", qfalse, G_StopMapRotation }, + { "suddendeath", qfalse, Svcmd_SuddenDeath_f } +}; + +/* +================= +ConsoleCommand + +================= +*/ +qboolean ConsoleCommand( void ) +{ + char cmd[ MAX_TOKEN_CHARS ]; + struct svcmd *command; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + + command = bsearch( cmd, svcmds, sizeof( svcmds ) / sizeof( struct svcmd ), + sizeof( struct svcmd ), cmdcmp ); + + if( !command ) + { + // see if this is an admin command + if( G_admin_cmd_check( NULL ) ) + return qtrue; + + if( g_dedicated.integer ) + G_Printf( "unknown command: %s\n", cmd ); + + return qfalse; + } + + if( command->dedicated && !g_dedicated.integer ) + return qfalse; + + command->function( ); + return qtrue; +} + +void G_RegisterCommands( void ) +{ + int i; + + for( i = 0; i < sizeof( svcmds ) / sizeof( svcmds[ 0 ] ); i++ ) + { + if( svcmds[ i ].dedicated && !g_dedicated.integer ) + continue; + trap_AddCommand( svcmds[ i ].cmd ); + } + + G_admin_register_cmds( ); +} + +void G_UnregisterCommands( void ) +{ + int i; + + for( i = 0; i < sizeof( svcmds ) / sizeof( svcmds[ 0 ] ); i++ ) + { + if( svcmds[ i ].dedicated && !g_dedicated.integer ) + continue; + trap_RemoveCommand( svcmds[ i ].cmd ); + } + + G_admin_unregister_cmds( ); +} diff --git a/src/game/g_syscalls.asm b/src/game/g_syscalls.asm new file mode 100644 index 0000000..f39b138 --- /dev/null +++ b/src/game/g_syscalls.asm @@ -0,0 +1,68 @@ +code + +equ trap_Print -1 +equ trap_Error -2 +equ trap_Milliseconds -3 +equ trap_Cvar_Register -4 +equ trap_Cvar_Update -5 +equ trap_Cvar_Set -6 +equ trap_Cvar_VariableIntegerValue -7 +equ trap_Cvar_VariableStringBuffer -8 +equ trap_Argc -9 +equ trap_Argv -10 +equ trap_FS_FOpenFile -11 +equ trap_FS_Read -12 +equ trap_FS_Write -13 +equ trap_FS_FCloseFile -14 +equ trap_SendConsoleCommand -15 +equ trap_LocateGameData -16 +equ trap_DropClient -17 +equ trap_SendServerCommand -18 +equ trap_SetConfigstring -19 +equ trap_GetConfigstring -20 +equ trap_SetConfigstringRestrictions -21 +equ trap_GetUserinfo -22 +equ trap_SetUserinfo -23 +equ trap_GetServerinfo -24 +equ trap_SetBrushModel -25 +equ trap_Trace -26 +equ trap_PointContents -27 +equ trap_InPVS -28 +equ trap_InPVSIgnorePortals -29 +equ trap_AdjustAreaPortalState -30 +equ trap_AreasConnected -31 +equ trap_LinkEntity -32 +equ trap_UnlinkEntity -33 +equ trap_EntitiesInBox -34 +equ trap_EntityContact -35 +equ trap_GetUsercmd -36 +equ trap_GetEntityToken -37 +equ trap_FS_GetFileList -38 +equ trap_RealTime -39 +equ trap_SnapVector -40 +equ trap_TraceCapsule -41 +equ trap_EntityContactCapsule -42 +equ trap_FS_Seek -43 + +equ trap_Parse_AddGlobalDefine -44 +equ trap_Parse_LoadSource -45 +equ trap_Parse_FreeSource -46 +equ trap_Parse_ReadToken -47 +equ trap_Parse_SourceFileAndLine -48 + +equ trap_SendGameStat -49 + +equ trap_AddCommand -50 +equ trap_RemoveCommand -51 + +equ memset -101 +equ memcpy -102 +equ strncpy -103 +equ sin -104 +equ cos -105 +equ atan2 -106 +equ sqrt -107 +equ floor -111 +equ ceil -112 +equ testPrintInt -113 +equ testPrintFloat -114 diff --git a/src/game/g_syscalls.c b/src/game/g_syscalls.c new file mode 100644 index 0000000..38e5203 --- /dev/null +++ b/src/game/g_syscalls.c @@ -0,0 +1,299 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +// this file is only included when building a dll +// g_syscalls.asm is included instead when building a qvm + +static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; + + +Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) +{ + syscall = syscallptr; +} + +int PASSFLOAT( float x ) +{ + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_Print( const char *fmt ) +{ + syscall( G_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) +{ + syscall( G_ERROR, fmt ); +} + +int trap_Milliseconds( void ) +{ + return syscall( G_MILLISECONDS ); +} +int trap_Argc( void ) +{ + return syscall( G_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) +{ + syscall( G_ARGV, n, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) +{ + return syscall( G_FS_FOPEN_FILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) +{ + syscall( G_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) +{ + syscall( G_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) +{ + syscall( G_FS_FCLOSE_FILE, f ); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) +{ + return syscall( G_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +void trap_SendConsoleCommand( int exec_when, const char *text ) +{ + syscall( G_SEND_CONSOLE_COMMAND, exec_when, text ); +} + +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) +{ + syscall( G_CVAR_REGISTER, cvar, var_name, value, flags ); +} + +void trap_Cvar_Update( vmCvar_t *cvar ) +{ + syscall( G_CVAR_UPDATE, cvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) +{ + syscall( G_CVAR_SET, var_name, value ); +} + +int trap_Cvar_VariableIntegerValue( const char *var_name ) +{ + return syscall( G_CVAR_VARIABLE_INTEGER_VALUE, var_name ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) +{ + syscall( G_CVAR_VARIABLE_STRING_BUFFER, var_name, buffer, bufsize ); +} + + +void trap_LocateGameData( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *clients, int sizeofGClient ) +{ + syscall( G_LOCATE_GAME_DATA, gEnts, numGEntities, sizeofGEntity_t, clients, sizeofGClient ); +} + +void trap_DropClient( int clientNum, const char *reason ) +{ + syscall( G_DROP_CLIENT, clientNum, reason ); +} + +void trap_SendServerCommand( int clientNum, const char *text ) +{ + syscall( G_SEND_SERVER_COMMAND, clientNum, text ); +} + +void trap_SetConfigstring( int num, const char *string ) +{ + syscall( G_SET_CONFIGSTRING, num, string ); +} + +void trap_GetConfigstring( int num, char *buffer, int bufferSize ) +{ + syscall( G_GET_CONFIGSTRING, num, buffer, bufferSize ); +} + +void trap_SetConfigstringRestrictions( int num, const clientList_t *clientList ) +{ + syscall( G_SET_CONFIGSTRING_RESTRICTIONS, num, clientList ); +} + +void trap_GetUserinfo( int num, char *buffer, int bufferSize ) +{ + syscall( G_GET_USERINFO, num, buffer, bufferSize ); +} + +void trap_SetUserinfo( int num, const char *buffer ) +{ + syscall( G_SET_USERINFO, num, buffer ); +} + +void trap_GetServerinfo( char *buffer, int bufferSize ) +{ + syscall( G_GET_SERVERINFO, buffer, bufferSize ); +} + +void trap_SetBrushModel( gentity_t *ent, const char *name ) +{ + syscall( G_SET_BRUSH_MODEL, ent, name ); +} + +void trap_Trace( trace_t *results, const vec3_t start, const vec3_t mins, + const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ) +{ + syscall( G_TRACE, results, start, mins, maxs, end, passEntityNum, contentmask ); +} + +void trap_TraceCapsule( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ) +{ + syscall( G_TRACECAPSULE, results, start, mins, maxs, end, passEntityNum, contentmask ); +} + +int trap_PointContents( const vec3_t point, int passEntityNum ) +{ + return syscall( G_POINT_CONTENTS, point, passEntityNum ); +} + + +qboolean trap_InPVS( const vec3_t p1, const vec3_t p2 ) +{ + return syscall( G_IN_PVS, p1, p2 ); +} + +qboolean trap_InPVSIgnorePortals( const vec3_t p1, const vec3_t p2 ) +{ + return syscall( G_IN_PVS_IGNORE_PORTALS, p1, p2 ); +} + +void trap_AdjustAreaPortalState( gentity_t *ent, qboolean open ) +{ + syscall( G_ADJUST_AREA_PORTAL_STATE, ent, open ); +} + +qboolean trap_AreasConnected( int area1, int area2 ) +{ + return syscall( G_AREAS_CONNECTED, area1, area2 ); +} + +void trap_LinkEntity( gentity_t *ent ) +{ + syscall( G_LINKENTITY, ent ); +} + +void trap_UnlinkEntity( gentity_t *ent ) +{ + syscall( G_UNLINKENTITY, ent ); +} + + +int trap_EntitiesInBox( const vec3_t mins, const vec3_t maxs, int *list, int maxcount ) +{ + return syscall( G_ENTITIES_IN_BOX, mins, maxs, list, maxcount ); +} + +qboolean trap_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ) +{ + return syscall( G_ENTITY_CONTACT, mins, maxs, ent ); +} + +qboolean trap_EntityContactCapsule( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ) +{ + return syscall( G_ENTITY_CONTACTCAPSULE, mins, maxs, ent ); +} + +void trap_GetUsercmd( int clientNum, usercmd_t *cmd ) +{ + syscall( G_GET_USERCMD, clientNum, cmd ); +} + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ) +{ + return syscall( G_GET_ENTITY_TOKEN, buffer, bufferSize ); +} + +int trap_RealTime( qtime_t *qtime ) +{ + return syscall( G_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) +{ + syscall( G_SNAPVECTOR, v ); + return; +} + +void trap_SendGameStat( const char *data ) +{ + syscall( G_SEND_GAMESTAT, data ); + return; +} + +int trap_Parse_AddGlobalDefine( char *define ) +{ + return syscall( G_PARSE_ADD_GLOBAL_DEFINE, define ); +} + +int trap_Parse_LoadSource( const char *filename ) +{ + return syscall( G_PARSE_LOAD_SOURCE, filename ); +} + +int trap_Parse_FreeSource( int handle ) +{ + return syscall( G_PARSE_FREE_SOURCE, handle ); +} + +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ) +{ + return syscall( G_PARSE_READ_TOKEN, handle, pc_token ); +} + +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) +{ + return syscall( G_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_AddCommand( const char *cmdName ) +{ + syscall( G_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) +{ + syscall( G_REMOVECOMMAND, cmdName ); +} + diff --git a/src/game/g_target.c b/src/game/g_target.c new file mode 100644 index 0000000..3157c8f --- /dev/null +++ b/src/game/g_target.c @@ -0,0 +1,478 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +//========================================================== + +/*QUAKED target_delay (1 0 0) (-8 -8 -8) (8 8 8) +"wait" seconds to pause before firing targets. +"random" delay variance, total delay = delay +/- random seconds +*/ +void Think_Target_Delay( gentity_t *ent ) +{ + G_UseTargets( ent, ent->activator ); +} + +void Use_Target_Delay( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + ent->nextthink = level.time + ( ent->wait + ent->random * crandom( ) ) * 1000; + ent->think = Think_Target_Delay; + ent->activator = activator; +} + +void SP_target_delay( gentity_t *ent ) +{ + // check delay for backwards compatability + if( !G_SpawnFloat( "delay", "0", &ent->wait ) ) + G_SpawnFloat( "wait", "1", &ent->wait ); + + if( !ent->wait ) + ent->wait = 1; + + ent->use = Use_Target_Delay; +} + + +//========================================================== + +/*QUAKED target_score (1 0 0) (-8 -8 -8) (8 8 8) +"count" number of points to add, default 1 + +The activator is given this many points. +*/ +void Use_Target_Score( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + if( !activator ) + return; + + AddScore( activator, ent->count ); +} + +void SP_target_score( gentity_t *ent ) +{ + if( !ent->count ) + ent->count = 1; + + ent->use = Use_Target_Score; +} + + +//========================================================== + +/*QUAKED target_print (1 0 0) (-8 -8 -8) (8 8 8) humanteam alienteam private +"message" text to print +If "private", only the activator gets the message. If no checks, all clients get the message. +*/ +void Use_Target_Print( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + if( ent->spawnflags & 4 ) + { + if( activator && activator->client ) + trap_SendServerCommand( activator-g_entities, va( "cp \"%s\"", ent->message ) ); + return; + } + + if( ent->spawnflags & 3 ) + { + if( ent->spawnflags & 1 ) + G_TeamCommand( TEAM_HUMANS, va( "cp \"%s\"", ent->message ) ); + if( ent->spawnflags & 2 ) + G_TeamCommand( TEAM_ALIENS, va( "cp \"%s\"", ent->message ) ); + + return; + } + + trap_SendServerCommand( -1, va("cp \"%s\"", ent->message ) ); +} + +void SP_target_print( gentity_t *ent ) +{ + ent->use = Use_Target_Print; +} + + +//========================================================== + + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off global activator +"noise" wav file to play + +A global sound will play full volume throughout the level. +Activator sounds will play on the player that activated the target. +Global and activator sounds can't be combined with looping. +Normal sounds play each time the target is used. +Looped sounds will be toggled by use functions. +Multiple identical looping sounds will just increase volume without any speed cost. +"wait" : Seconds between auto triggerings, 0 = don't auto trigger +"random" wait variance, default is 0 +*/ +void Use_Target_Speaker( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + if( ent->spawnflags & 3 ) + { // looping sound toggles + if( ent->s.loopSound ) + ent->s.loopSound = 0; // turn it off + else + ent->s.loopSound = ent->noise_index; // start it + } + else + { + // normal sound + if( ent->spawnflags & 8 && activator ) + G_AddEvent( activator, EV_GENERAL_SOUND, ent->noise_index ); + else if( ent->spawnflags & 4 ) + G_AddEvent( ent, EV_GLOBAL_SOUND, ent->noise_index ); + else + G_AddEvent( ent, EV_GENERAL_SOUND, ent->noise_index ); + } +} + +void SP_target_speaker( gentity_t *ent ) +{ + char buffer[ MAX_QPATH ]; + char *s; + + G_SpawnFloat( "wait", "0", &ent->wait ); + G_SpawnFloat( "random", "0", &ent->random ); + + if( !G_SpawnString( "noise", "NOSOUND", &s ) ) + G_Error( "target_speaker without a noise key at %s", vtos( ent->s.origin ) ); + + // force all client reletive sounds to be "activator" speakers that + // play on the entity that activates it + if( s[ 0 ] == '*' ) + ent->spawnflags |= 8; + + if( !strstr( s, ".wav" ) ) + Com_sprintf( buffer, sizeof( buffer ), "%s.wav", s ); + else + Q_strncpyz( buffer, s, sizeof( buffer ) ); + + ent->noise_index = G_SoundIndex( buffer ); + + // a repeating speaker can be done completely client side + ent->s.eType = ET_SPEAKER; + ent->s.eventParm = ent->noise_index; + ent->s.frame = ent->wait * 10; + ent->s.clientNum = ent->random * 10; + + + // check for prestarted looping sound + if( ent->spawnflags & 1 ) + ent->s.loopSound = ent->noise_index; + + ent->use = Use_Target_Speaker; + + if( ent->spawnflags & 4 ) + ent->r.svFlags |= SVF_BROADCAST; + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + trap_LinkEntity( ent ); +} + +//========================================================== + +void target_teleporter_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + gentity_t *dest; + + if( !activator || !activator->client ) + return; + + dest = G_PickTarget( self->target ); + + if( !dest ) + { + G_Printf( "Couldn't find teleporter destination\n" ); + return; + } + + TeleportPlayer( activator, dest->s.origin, dest->s.angles ); +} + +/*QUAKED target_teleporter (1 0 0) (-8 -8 -8) (8 8 8) +The activator will be teleported away. +*/ +void SP_target_teleporter( gentity_t *self ) +{ + if( !self->targetname ) + G_Printf( "untargeted %s at %s\n", self->classname, vtos( self->s.origin ) ); + + self->use = target_teleporter_use; +} + +//========================================================== + + +/*QUAKED target_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM +This doesn't perform any actions except fire its targets. +The activator can be forced to be from a certain team. +if RANDOM is checked, only one of the targets will be fired, not all of them +*/ +void target_relay_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( ( self->spawnflags & 1 ) && activator && activator->client && + activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return; + + if( ( self->spawnflags & 2 ) && activator && activator->client && + activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + return; + + if( self->spawnflags & 4 ) + { + gentity_t *ent; + + ent = G_PickTarget( self->target ); + if( ent && ent->use ) + ent->use( ent, self, activator ); + + return; + } + + G_UseTargets( self, activator ); +} + +void SP_target_relay( gentity_t *self ) +{ + self->use = target_relay_use; +} + + +//========================================================== + +/*QUAKED target_kill (.5 .5 .5) (-8 -8 -8) (8 8 8) +Kills the activator. +*/ +void target_kill_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( !activator ) + return; + + G_Damage( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG ); +} + +void SP_target_kill( gentity_t *self ) +{ + self->use = target_kill_use; +} + +/*QUAKED target_position (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for in-game calculation, like jumppad targets. +*/ +void SP_target_position( gentity_t *self ) +{ + G_SetOrigin( self, self->s.origin ); +} + +/*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8) +Set "message" to the name of this location. +Set "count" to 0-7 for color. +0:white 1:red 2:green 3:yellow 4:blue 5:cyan 6:magenta 7:white + +Closest target_location in sight used for the location, if none +in site, closest in distance +*/ +void SP_target_location( gentity_t *self ) +{ + static int n = 1; + char *message; + self->s.eType = ET_LOCATION; + self->r.svFlags = SVF_BROADCAST; + trap_LinkEntity( self ); // make the server send them to the clients + if( n == MAX_LOCATIONS ) + { + G_Printf( S_COLOR_YELLOW "too many target_locations\n" ); + return; + } + if( self->count ) + { + if( self->count < 0 ) + self->count = 0; + + if( self->count > 7 ) + self->count = 7; + + message = va( "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, self->count + '0', + self->message); + } + else + message = self->message; + trap_SetConfigstring( CS_LOCATIONS + n, message ); + self->nextTrain = level.locationHead; + self->s.generic1 = n; // use for location marking + level.locationHead = self; + n++; + + G_SetOrigin( self, self->s.origin ); +} + + +/* +=============== +target_rumble_think +=============== +*/ +void target_rumble_think( gentity_t *self ) +{ + int i; + gentity_t *ent; + + if( self->last_move_time < level.time ) + self->last_move_time = level.time + 0.5; + + for( i = 0, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( !ent->client ) + continue; + + if( ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) + continue; + + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + ent->client->ps.velocity[ 0 ] += crandom( ) * 150; + ent->client->ps.velocity[ 1 ] += crandom( ) * 150; + ent->client->ps.velocity[ 2 ] = self->speed; + } + + if( level.time < self->timestamp ) + self->nextthink = level.time + FRAMETIME; +} + +/* +=============== +target_rumble_use +=============== +*/ +void target_rumble_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + self->timestamp = level.time + ( self->count * FRAMETIME ); + self->nextthink = level.time + FRAMETIME; + self->activator = activator; + self->last_move_time = 0; +} + +/* +=============== +SP_target_rumble +=============== +*/ +void SP_target_rumble( gentity_t *self ) +{ + if( !self->targetname ) + { + G_Printf( S_COLOR_YELLOW "WARNING: untargeted %s at %s\n", self->classname, + vtos( self->s.origin ) ); + } + + if( !self->count ) + self->count = 10; + + if( !self->speed ) + self->speed = 100; + + self->think = target_rumble_think; + self->use = target_rumble_use; +} + +/* +=============== +target_alien_win_use +=============== +*/ +void target_alien_win_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( !level.uncondHumanWin ) + level.uncondAlienWin = qtrue; +} + +/* +=============== +SP_target_alien_win +=============== +*/ +void SP_target_alien_win( gentity_t *self ) +{ + self->use = target_alien_win_use; +} + +/* +=============== +target_human_win_use +=============== +*/ +void target_human_win_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( !level.uncondAlienWin ) + level.uncondHumanWin = qtrue; +} + +/* +=============== +SP_target_human_win +=============== +*/ +void SP_target_human_win( gentity_t *self ) +{ + self->use = target_human_win_use; +} + +/* +=============== +target_hurt_use +=============== +*/ +void target_hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + // hurt the activator + if( !activator || !activator->takedamage ) + return; + + G_Damage( activator, self, self, NULL, NULL, self->damage, 0, MOD_TRIGGER_HURT ); +} + +/* +=============== +SP_target_hurt +=============== +*/ +void SP_target_hurt( gentity_t *self ) +{ + if( !self->targetname ) + { + G_Printf( S_COLOR_YELLOW "WARNING: untargeted %s at %s\n", self->classname, + vtos( self->s.origin ) ); + } + + if( !self->damage ) + self->damage = 5; + + self->use = target_hurt_use; +} diff --git a/src/game/g_team.c b/src/game/g_team.c new file mode 100644 index 0000000..fd15aa7 --- /dev/null +++ b/src/game/g_team.c @@ -0,0 +1,473 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + +// NULL for everyone +void QDECL PrintMsg( gentity_t *ent, const char *fmt, ... ) +{ + char msg[ 1024 ]; + va_list argptr; + char *p; + + va_start( argptr,fmt ); + + if( Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ) > sizeof( msg ) ) + G_Error ( "PrintMsg overrun" ); + + va_end( argptr ); + + // double quotes are bad + while( ( p = strchr( msg, '"' ) ) != NULL ) + *p = '\''; + + trap_SendServerCommand( ( ( ent == NULL ) ? -1 : ent-g_entities ), va( "print \"%s\"", msg ) ); +} + +/* +================ +G_TeamFromString + +Return the team referenced by a string +================ +*/ +team_t G_TeamFromString( char *str ) +{ + switch( tolower( *str ) ) + { + case '0': case 's': return TEAM_NONE; + case '1': case 'a': return TEAM_ALIENS; + case '2': case 'h': return TEAM_HUMANS; + default: return NUM_TEAMS; + } +} + +/* +================ +G_TeamCommand + +Broadcasts a command to only a specific team +================ +*/ +void G_TeamCommand( team_t team, char *cmd ) +{ + int i; + + for( i = 0 ; i < level.maxclients ; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + { + if( level.clients[ i ].pers.teamSelection == team || + ( level.clients[ i ].pers.teamSelection == TEAM_NONE && + G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) + trap_SendServerCommand( i, cmd ); + } + } +} + +/* +============== +OnSameTeam +============== +*/ +qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ) +{ + if( !ent1->client || !ent2->client ) + return qfalse; + + if( ent1->client->pers.teamSelection == ent2->client->pers.teamSelection ) + return qtrue; + + return qfalse; +} + +/* +================== +G_ClientListForTeam +================== +*/ +static clientList_t G_ClientListForTeam( team_t team ) +{ + int i; + clientList_t clientList; + + Com_Memset( &clientList, 0, sizeof( clientList_t ) ); + + for( i = 0; i < g_maxclients.integer; i++ ) + { + gentity_t *ent = g_entities + i; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + + if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == team ) ) + Com_ClientListAdd( &clientList, ent->client->ps.clientNum ); + } + + return clientList; +} + +/* +================== +G_UpdateTeamConfigStrings +================== +*/ +void G_UpdateTeamConfigStrings( void ) +{ + clientList_t alienTeam = G_ClientListForTeam( TEAM_ALIENS ); + clientList_t humanTeam = G_ClientListForTeam( TEAM_HUMANS ); + + if( level.intermissiontime ) + { + // No restrictions once the game has ended + Com_Memset( &alienTeam, 0, sizeof( clientList_t ) ); + Com_Memset( &humanTeam, 0, sizeof( clientList_t ) ); + } + + trap_SetConfigstringRestrictions( CS_VOTE_TIME + TEAM_ALIENS, &humanTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_STRING + TEAM_ALIENS, &humanTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_YES + TEAM_ALIENS, &humanTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_NO + TEAM_ALIENS, &humanTeam ); + + trap_SetConfigstringRestrictions( CS_VOTE_TIME + TEAM_HUMANS, &alienTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_STRING + TEAM_HUMANS, &alienTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_YES + TEAM_HUMANS, &alienTeam ); + trap_SetConfigstringRestrictions( CS_VOTE_NO + TEAM_HUMANS, &alienTeam ); + + trap_SetConfigstringRestrictions( CS_ALIEN_STAGES, &humanTeam ); + trap_SetConfigstringRestrictions( CS_HUMAN_STAGES, &alienTeam ); +} + +/* +================== +G_LeaveTeam +================== +*/ +void G_LeaveTeam( gentity_t *self ) +{ + team_t team = self->client->pers.teamSelection; + gentity_t *ent; + int i; + + if( team == TEAM_ALIENS ) + G_RemoveFromSpawnQueue( &level.alienSpawnQueue, self->client->ps.clientNum ); + else if( team == TEAM_HUMANS ) + G_RemoveFromSpawnQueue( &level.humanSpawnQueue, self->client->ps.clientNum ); + else + { + if( self->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( self ); + return; + } + + // stop any following clients + G_StopFromFollowing( self ); + + G_Vote( self, team, qfalse ); + self->suicideTime = 0; + + for( i = 0; i < level.num_entities; i++ ) + { + ent = &g_entities[ i ]; + if( !ent->inuse ) + continue; + + if( ent->client && ent->client->pers.connected == CON_CONNECTED ) + { + // cure poison + if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONED && + ent->client->lastPoisonClient == self ) + ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + + // clear impregnatedBy for everyone impregnated by this player + if( ent->client->isImpregnated && ent->client->impregnatedBy == self->s.number ) + ent->client->impregnatedBy = -2; + } + else if( ent->s.eType == ET_MISSILE && ent->r.ownerNum == self->s.number ) + G_FreeEntity( ent ); + } + + // cut all relevant zap beams + G_ClearPlayerZapEffects( self ); + + G_namelog_update_score( self->client ); +} + +/* +================= +G_ChangeTeam +================= +*/ +void G_ChangeTeam( gentity_t *ent, team_t newTeam ) +{ + team_t oldTeam = ent->client->pers.teamSelection; + + if( oldTeam == newTeam ) + return; + + G_LeaveTeam( ent ); + ent->client->pers.teamChangeTime = level.time; + ent->client->pers.teamSelection = newTeam; + ent->client->pers.classSelection = PCL_NONE; + ClientSpawn( ent, NULL, NULL, NULL ); + + if( oldTeam == TEAM_HUMANS && newTeam == TEAM_ALIENS ) + { + // Convert from human to alien credits + ent->client->pers.credit = + (int)( ent->client->pers.credit * + ALIEN_MAX_CREDITS / HUMAN_MAX_CREDITS + 0.5f ); + } + else if( oldTeam == TEAM_ALIENS && newTeam == TEAM_HUMANS ) + { + // Convert from alien to human credits + ent->client->pers.credit = + (int)( ent->client->pers.credit * + HUMAN_MAX_CREDITS / ALIEN_MAX_CREDITS + 0.5f ); + } + + // Copy credits to ps for the client + ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit; + + ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + + G_UpdateTeamConfigStrings( ); + + G_LogPrintf( "ChangeTeam: %d %s: %s" S_COLOR_WHITE " switched teams\n", + ent - g_entities, BG_TeamName( newTeam ), ent->client->pers.netname ); + + G_namelog_update_score( ent->client ); + TeamplayInfoMessage( ent ); +} + +/* +=========== +Team_GetLocation + +Report a location for the player. Uses placed nearby target_location entities +============ +*/ +gentity_t *Team_GetLocation( gentity_t *ent ) +{ + gentity_t *eloc, *best; + float bestlen, len; + + best = NULL; + bestlen = 3.0f * 8192.0f * 8192.0f; + + for( eloc = level.locationHead; eloc; eloc = eloc->nextTrain ) + { + len = DistanceSquared( ent->r.currentOrigin, eloc->r.currentOrigin ); + + if( len > bestlen ) + continue; + + if( !trap_InPVS( ent->r.currentOrigin, eloc->r.currentOrigin ) ) + continue; + + bestlen = len; + best = eloc; + } + + return best; +} + + +/*---------------------------------------------------------------------------*/ + +/* +================== +TeamplayInfoMessage + +Format: + clientNum location health weapon upgrade + +================== +*/ +void TeamplayInfoMessage( gentity_t *ent ) +{ + char entry[ 19 ], string[ 1143 ]; + int i, j; + int team, stringlength; + int sent = 0; + gentity_t *player; + gclient_t *cl; + upgrade_t upgrade = UP_NONE; + int curWeaponClass = WP_NONE ; // sends weapon for humans, class for aliens + char *tmp; + + if( !g_allowTeamOverlay.integer ) + return; + + if( !ent->client->pers.teamInfo ) + return; + + if( ent->client->pers.teamSelection == TEAM_NONE ) + { + if( ent->client->sess.spectatorState == SPECTATOR_FREE || + ent->client->sess.spectatorClient < 0 ) + return; + team = g_entities[ ent->client->sess.spectatorClient ].client-> + pers.teamSelection; + } + else + team = ent->client->pers.teamSelection; + + string[ 0 ] = '\0'; + stringlength = 0; + + for( i = 0; i < MAX_CLIENTS; i++) + { + player = g_entities + i ; + cl = player->client; + + if( ent == player || !cl || team != cl->pers.teamSelection || + !player->inuse ) + continue; + + if( cl->sess.spectatorState != SPECTATOR_NOT ) + { + curWeaponClass = WP_NONE; + upgrade = UP_NONE; + } + else if ( cl->pers.teamSelection == TEAM_HUMANS ) + { + curWeaponClass = cl->ps.weapon; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) + upgrade = UP_BATTLESUIT; + else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) + upgrade = UP_JETPACK; + else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) ) + upgrade = UP_BATTPACK; + else if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, cl->ps.stats ) ) + upgrade = UP_HELMET_MK1; + else if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, cl->ps.stats ) ) + upgrade = UP_HELMET_MK2; + else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) + upgrade = UP_LIGHTARMOUR; + else + upgrade = UP_NONE; + } + else if( cl->pers.teamSelection == TEAM_ALIENS ) + { + curWeaponClass = cl->ps.stats[ STAT_CLASS ]; + upgrade = UP_NONE; + } + + tmp = va( "%i %i %i %i", + player->client->pers.location, + player->client->ps.stats[ STAT_HEALTH ] < 1 ? 0 : + player->client->ps.stats[ STAT_HEALTH ], + curWeaponClass, + upgrade ); + + if( !strcmp( ent->client->pers.cinfo[ i ], tmp ) ) + continue; + + Q_strncpyz( ent->client->pers.cinfo[ i ], tmp, + sizeof( ent->client->pers.cinfo[ i ] ) ); + + Com_sprintf( entry, sizeof( entry ), " %i %s", i, tmp ); + + j = strlen( entry ); + + if( stringlength + j > sizeof( string ) ) + break; + + strcpy( string + stringlength, entry ); + stringlength += j; + sent++; + } + + if( !sent ) + return; + + trap_SendServerCommand( ent - g_entities, va( "tinfo%s", string ) ); +} + +void CheckTeamStatus( void ) +{ + int i; + gentity_t *loc, *ent; + + if( level.time - level.lastTeamLocationTime > TEAM_LOCATION_UPDATE_TIME ) + { + level.lastTeamLocationTime = level.time; + + for( i = 0; i < g_maxclients.integer; i++ ) + { + ent = g_entities + i; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + + if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS || + ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) ) + { + + loc = Team_GetLocation( ent ); + + if( loc ) + ent->client->pers.location = loc->s.generic1; + else + ent->client->pers.location = 0; + } + } + + for( i = 0; i < g_maxclients.integer; i++ ) + { + ent = g_entities + i; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + + if( ent->inuse ) + TeamplayInfoMessage( ent ); + } + } + + // Warn on imbalanced teams + if( g_teamImbalanceWarnings.integer && !level.intermissiontime && + ( level.time - level.lastTeamImbalancedTime > + ( g_teamImbalanceWarnings.integer * 1000 ) ) && + level.numTeamImbalanceWarnings < 3 && !level.restarted ) + { + level.lastTeamImbalancedTime = level.time; + if( level.numAlienSpawns > 0 && + level.numHumanClients - level.numAlienClients > 2 ) + { + trap_SendServerCommand( -1, "print \"Teams are imbalanced. " + "Humans have more players.\n\""); + level.numTeamImbalanceWarnings++; + } + else if( level.numHumanSpawns > 0 && + level.numAlienClients - level.numHumanClients > 2 ) + { + trap_SendServerCommand ( -1, "print \"Teams are imbalanced. " + "Aliens have more players.\n\""); + level.numTeamImbalanceWarnings++; + } + else + { + level.numTeamImbalanceWarnings = 0; + } + } +} diff --git a/src/game/g_trigger.c b/src/game/g_trigger.c new file mode 100644 index 0000000..65bf1bd --- /dev/null +++ b/src/game/g_trigger.c @@ -0,0 +1,1148 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "g_local.h" + + +void InitTrigger( gentity_t *self ) +{ + if( !VectorCompare( self->s.angles, vec3_origin ) ) + G_SetMovedir( self->s.angles, self->movedir ); + + trap_SetBrushModel( self, self->model ); + self->r.contents = CONTENTS_TRIGGER; // replaces the -1 from trap_SetBrushModel + self->r.svFlags = SVF_NOCLIENT; +} + + +// the wait time has passed, so set back up for another activation +void multi_wait( gentity_t *ent ) +{ + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger( gentity_t *ent, gentity_t *activator ) +{ + ent->activator = activator; + if( ent->nextthink ) + return; // can't retrigger until the wait is over + + if( activator && activator->client ) + { + if( ( ent->spawnflags & 1 ) && + activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return; + + if( ( ent->spawnflags & 2 ) && + activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + return; + } + + G_UseTargets( ent, ent->activator ); + + if( ent->wait > 0 ) + { + ent->think = multi_wait; + ent->nextthink = level.time + ( ent->wait + ent->random * crandom( ) ) * 1000; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = 0; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEntity; + } +} + +void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + multi_trigger( ent, activator ); +} + +void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + if( !other->client && other->s.eType != ET_BUILDABLE ) + return; + + multi_trigger( self, other ); +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? +"wait" : Seconds between triggerings, 0.5 default, -1 = one time only. +"random" wait variance, default is 0 +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +*/ +void SP_trigger_multiple( gentity_t *ent ) +{ + G_SpawnFloat( "wait", "0.5", &ent->wait ); + G_SpawnFloat( "random", "0", &ent->random ); + + if( ent->random >= ent->wait && ent->wait >= 0 ) + { + ent->random = ent->wait - FRAMETIME; + G_Printf( "trigger_multiple has random >= wait\n" ); + } + + ent->touch = Touch_Multi; + ent->use = Use_Multi; + + InitTrigger( ent ); + trap_LinkEntity( ent ); +} + + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +void trigger_always_think( gentity_t *ent ) +{ + G_UseTargets( ent, ent ); + G_FreeEntity( ent ); +} + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always( gentity_t *ent ) +{ + // we must have some delay to make sure our use targets are present + ent->nextthink = level.time + 300; + ent->think = trigger_always_think; +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +void trigger_push_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + if( !other->client ) + return; +} + + +/* +================= +AimAtTarget + +Calculate origin2 so the target apogee will be hit +================= +*/ +void AimAtTarget( gentity_t *self ) +{ + gentity_t *ent; + vec3_t origin; + float height, gravity, time, forward; + float dist; + + VectorAdd( self->r.absmin, self->r.absmax, origin ); + VectorScale( origin, 0.5, origin ); + + ent = G_PickTarget( self->target ); + + if( !ent ) + { + G_FreeEntity( self ); + return; + } + + height = ent->s.origin[ 2 ] - origin[ 2 ]; + gravity = g_gravity.value; + time = sqrt( height / ( 0.5 * gravity ) ); + + if( !time ) + { + G_FreeEntity( self ); + return; + } + + // set s.origin2 to the push velocity + VectorSubtract( ent->s.origin, origin, self->s.origin2 ); + self->s.origin2[ 2 ] = 0; + dist = VectorNormalize( self->s.origin2 ); + + forward = dist / time; + VectorScale( self->s.origin2, forward, self->s.origin2 ); + + self->s.origin2[ 2 ] = time * gravity; +} + + +/*QUAKED trigger_push (.5 .5 .5) ? +Must point at a target_position, which will be the apex of the leap. +This will be client side predicted, unlike target_push +*/ +void SP_trigger_push( gentity_t *self ) +{ + InitTrigger( self ); + + // unlike other triggers, we need to send this one to the client + self->r.svFlags &= ~SVF_NOCLIENT; + + self->s.eType = ET_PUSH_TRIGGER; + self->touch = trigger_push_touch; + self->think = AimAtTarget; + self->nextthink = level.time + FRAMETIME; + trap_LinkEntity( self ); +} + + +void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( !activator || !activator->client ) + return; + + if( activator->client->ps.pm_type != PM_NORMAL ) + return; + + VectorCopy( self->s.origin2, activator->client->ps.velocity ); + +} + +/*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) +Pushes the activator in the direction.of angle, or towards a target apex. +"speed" defaults to 1000 +*/ +void SP_target_push( gentity_t *self ) +{ + if( !self->speed ) + self->speed = 1000; + + G_SetMovedir( self->s.angles, self->s.origin2 ); + VectorScale( self->s.origin2, self->speed, self->s.origin2 ); + + if( self->target ) + { + VectorCopy( self->s.origin, self->r.absmin ); + VectorCopy( self->s.origin, self->r.absmax ); + self->think = AimAtTarget; + self->nextthink = level.time + FRAMETIME; + } + + self->use = Use_target_push; +} + +/* +============================================================================== + +trigger_teleport + +============================================================================== +*/ + +void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + gentity_t *dest; + + if( self->s.eFlags & EF_NODRAW ) + return; + + if( !other->client ) + return; + + if( other->client->ps.pm_type == PM_DEAD ) + return; + + // Spectators only? + if( ( self->spawnflags & 1 ) && + other->client->sess.spectatorState == SPECTATOR_NOT ) + return; + + + dest = G_PickTarget( self->target ); + + if( !dest ) + { + G_Printf( "Couldn't find teleporter destination\n" ); + return; + } + + TeleportPlayer( other, dest->s.origin, dest->s.angles ); +} + +/* +=============== +trigger_teleport_use +=============== +*/ +void trigger_teleporter_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + ent->s.eFlags ^= EF_NODRAW; +} + + +/*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR SPAWN_DISABLED +Allows client side prediction of teleportation events. +Must point at a target_position, which will be the teleport destination. + +If spectator is set, only spectators can use this teleport +Spectator teleporters are not normally placed in the editor, but are created +automatically near doors to allow spectators to move through them +*/ +void SP_trigger_teleport( gentity_t *self ) +{ + InitTrigger( self ); + + // unlike other triggers, we need to send this one to the client + // unless is a spectator trigger + if( self->spawnflags & 1 ) + self->r.svFlags |= SVF_NOCLIENT; + else + self->r.svFlags &= ~SVF_NOCLIENT; + + // SPAWN_DISABLED + if( self->spawnflags & 2 ) + self->s.eFlags |= EF_NODRAW; + + self->s.eType = ET_TELEPORT_TRIGGER; + self->touch = trigger_teleporter_touch; + self->use = trigger_teleporter_use; + + trap_LinkEntity( self ); +} + + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW +Any entity that touches this will be hurt. +It does dmg points of damage each server frame +Targeting the trigger will toggle its on / off state. + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ +void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->r.linked ) + trap_UnlinkEntity( self ); + else + trap_LinkEntity( self ); +} + +void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + int dflags; + + if( !other->takedamage ) + return; + + if( self->timestamp > level.time ) + return; + + if( self->spawnflags & 16 ) + self->timestamp = level.time + 1000; + else + self->timestamp = level.time + FRAMETIME; + + // play sound + if( !( self->spawnflags & 4 ) ) + G_Sound( other, CHAN_AUTO, self->noise_index ); + + if( self->spawnflags & 8 ) + dflags = DAMAGE_NO_PROTECTION; + else + dflags = 0; + + G_Damage( other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT ); +} + +void SP_trigger_hurt( gentity_t *self ) +{ + InitTrigger( self ); + + self->noise_index = G_SoundIndex( "sound/misc/electro.wav" ); + self->touch = hurt_touch; + + if( self->damage <= 0 ) + self->damage = 5; + + self->r.contents = CONTENTS_TRIGGER; + + if( self->spawnflags & 2 ) + self->use = hurt_use; + + // link in to the world if starting active + if( !( self->spawnflags & 1 ) ) + trap_LinkEntity( self ); +} + + +/* +============================================================================== + +timer + +============================================================================== +*/ + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +This should be renamed trigger_timer... +Repeatedly fires its targets. +Can be turned on or off by using. + +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +*/ +void func_timer_think( gentity_t *self ) +{ + G_UseTargets( self, self->activator ); + // set time before next firing + self->nextthink = level.time + 1000 * ( self->wait + crandom( ) * self->random ); +} + +void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + self->activator = activator; + + // if on, turn it off + if( self->nextthink ) + { + self->nextthink = 0; + return; + } + + // turn it on + func_timer_think( self ); +} + +void SP_func_timer( gentity_t *self ) +{ + G_SpawnFloat( "random", "1", &self->random ); + G_SpawnFloat( "wait", "1", &self->wait ); + + self->use = func_timer_use; + self->think = func_timer_think; + + if( self->random >= self->wait ) + { + self->random = self->wait - FRAMETIME; + G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) ); + } + + if( self->spawnflags & 1 ) + { + self->nextthink = level.time + FRAMETIME; + self->activator = self; + } + + self->r.svFlags = SVF_NOCLIENT; +} + + +/* +=============== +G_Checktrigger_stages + +Called when stages change +=============== +*/ +void G_Checktrigger_stages( team_t team, stage_t stage ) +{ + int i; + gentity_t *ent; + + for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( !Q_stricmp( ent->classname, "trigger_stage" ) ) + { + if( team == ent->stageTeam && stage == ent->stageStage ) + ent->use( ent, ent, ent ); + } + } +} + + +/* +=============== +trigger_stage_use +=============== +*/ +void trigger_stage_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + G_UseTargets( self, self ); +} + +void SP_trigger_stage( gentity_t *self ) +{ + G_SpawnInt( "team", "0", (int *)&self->stageTeam ); + G_SpawnInt( "stage", "0", (int *)&self->stageStage ); + + self->use = trigger_stage_use; + + self->r.svFlags = SVF_NOCLIENT; +} + + +/* +=============== +trigger_win +=============== +*/ +void trigger_win( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + G_UseTargets( self, self ); +} + +void SP_trigger_win( gentity_t *self ) +{ + G_SpawnInt( "team", "0", (int *)&self->stageTeam ); + + self->use = trigger_win; + + self->r.svFlags = SVF_NOCLIENT; +} + + +/* +=============== +trigger_buildable_match +=============== +*/ +qboolean trigger_buildable_match( gentity_t *self, gentity_t *activator ) +{ + int i = 0; + + if( !activator ) + return qfalse; + + //if there is no buildable list every buildable triggers + if( self->bTriggers[ i ] == BA_NONE ) + return qtrue; + else + { + //otherwise check against the list + for( i = 0; self->bTriggers[ i ] != BA_NONE; i++ ) + { + if( activator->s.modelindex == self->bTriggers[ i ] ) + return qtrue; + } + } + + return qfalse; +} + +/* +=============== +trigger_buildable_trigger +=============== +*/ +void trigger_buildable_trigger( gentity_t *self, gentity_t *activator ) +{ + self->activator = activator; + + if( self->s.eFlags & EF_NODRAW ) + return; + + if( self->nextthink ) + return; // can't retrigger until the wait is over + + if( self->s.eFlags & EF_DEAD ) + { + if( !trigger_buildable_match( self, activator ) ) + G_UseTargets( self, activator ); + } + else + { + if( trigger_buildable_match( self, activator ) ) + G_UseTargets( self, activator ); + } + + if( self->wait > 0 ) + { + self->think = multi_wait; + self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + self->touch = 0; + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + } +} + +/* +=============== +trigger_buildable_touch +=============== +*/ +void trigger_buildable_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + //only triggered by buildables + if( !other || other->s.eType != ET_BUILDABLE ) + return; + + trigger_buildable_trigger( ent, other ); +} + +/* +=============== +trigger_buildable_use +=============== +*/ +void trigger_buildable_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + ent->s.eFlags ^= EF_NODRAW; +} + +/* +=============== +SP_trigger_buildable +=============== +*/ +void SP_trigger_buildable( gentity_t *self ) +{ + char *buffer; + + G_SpawnFloat( "wait", "0.5", &self->wait ); + G_SpawnFloat( "random", "0", &self->random ); + + if( self->random >= self->wait && self->wait >= 0 ) + { + self->random = self->wait - FRAMETIME; + G_Printf( S_COLOR_YELLOW "WARNING: trigger_buildable has random >= wait\n" ); + } + + G_SpawnString( "buildables", "", &buffer ); + + BG_ParseCSVBuildableList( buffer, self->bTriggers, BA_NUM_BUILDABLES ); + + self->touch = trigger_buildable_touch; + self->use = trigger_buildable_use; + + // SPAWN_DISABLED + if( self->spawnflags & 1 ) + self->s.eFlags |= EF_NODRAW; + + // NEGATE + if( self->spawnflags & 2 ) + self->s.eFlags |= EF_DEAD; + + InitTrigger( self ); + trap_LinkEntity( self ); +} + + +/* +=============== +trigger_class_match +=============== +*/ +qboolean trigger_class_match( gentity_t *self, gentity_t *activator ) +{ + int i = 0; + + if( !activator ) + return qfalse; + + //if there is no class list every class triggers (stupid case) + if( self->cTriggers[ i ] == PCL_NONE ) + return qtrue; + else + { + //otherwise check against the list + for( i = 0; self->cTriggers[ i ] != PCL_NONE; i++ ) + { + if( activator->client->ps.stats[ STAT_CLASS ] == self->cTriggers[ i ] ) + return qtrue; + } + } + + return qfalse; +} + +/* +=============== +trigger_class_trigger +=============== +*/ +void trigger_class_trigger( gentity_t *self, gentity_t *activator ) +{ + //sanity check + if( !activator || !activator->client ) + return; + + if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + return; + + if( self->s.eFlags & EF_NODRAW ) + return; + + self->activator = activator; + if( self->nextthink ) + return; // can't retrigger until the wait is over + + if( self->s.eFlags & EF_DEAD ) + { + if( !trigger_class_match( self, activator ) ) + G_UseTargets( self, activator ); + } + else + { + if( trigger_class_match( self, activator ) ) + G_UseTargets( self, activator ); + } + + if( self->wait > 0 ) + { + self->think = multi_wait; + self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + self->touch = 0; + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + } +} + +/* +=============== +trigger_class_touch +=============== +*/ +void trigger_class_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + //only triggered by clients + if( !other->client ) + return; + + trigger_class_trigger( ent, other ); +} + +/* +=============== +trigger_class_use +=============== +*/ +void trigger_class_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + ent->s.eFlags ^= EF_NODRAW; +} + +/* +=============== +SP_trigger_class +=============== +*/ +void SP_trigger_class( gentity_t *self ) +{ + char *buffer; + + G_SpawnFloat( "wait", "0.5", &self->wait ); + G_SpawnFloat( "random", "0", &self->random ); + + if( self->random >= self->wait && self->wait >= 0 ) + { + self->random = self->wait - FRAMETIME; + G_Printf( S_COLOR_YELLOW "WARNING: trigger_class has random >= wait\n" ); + } + + G_SpawnString( "classes", "", &buffer ); + + BG_ParseCSVClassList( buffer, self->cTriggers, PCL_NUM_CLASSES ); + + self->touch = trigger_class_touch; + self->use = trigger_class_use; + + // SPAWN_DISABLED + if( self->spawnflags & 1 ) + self->s.eFlags |= EF_NODRAW; + + // NEGATE + if( self->spawnflags & 2 ) + self->s.eFlags |= EF_DEAD; + + InitTrigger( self ); + trap_LinkEntity( self ); +} + + +/* +=============== +trigger_equipment_match +=============== +*/ +qboolean trigger_equipment_match( gentity_t *self, gentity_t *activator ) +{ + int i = 0; + + if( !activator ) + return qfalse; + + //if there is no equipment list all equipment triggers (stupid case) + if( self->wTriggers[ i ] == WP_NONE && self->uTriggers[ i ] == UP_NONE ) + return qtrue; + else + { + //otherwise check against the lists + for( i = 0; self->wTriggers[ i ] != WP_NONE; i++ ) + { + if( BG_InventoryContainsWeapon( self->wTriggers[ i ], activator->client->ps.stats ) ) + return qtrue; + } + + for( i = 0; self->uTriggers[ i ] != UP_NONE; i++ ) + { + if( BG_InventoryContainsUpgrade( self->uTriggers[ i ], activator->client->ps.stats ) ) + return qtrue; + } + } + + return qfalse; +} + +/* +=============== +trigger_equipment_trigger +=============== +*/ +void trigger_equipment_trigger( gentity_t *self, gentity_t *activator ) +{ + //sanity check + if( !activator || !activator->client ) + return; + + if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return; + + if( self->s.eFlags & EF_NODRAW ) + return; + + self->activator = activator; + if( self->nextthink ) + return; // can't retrigger until the wait is over + + if( self->s.eFlags & EF_DEAD ) + { + if( !trigger_equipment_match( self, activator ) ) + G_UseTargets( self, activator ); + } + else + { + if( trigger_equipment_match( self, activator ) ) + G_UseTargets( self, activator ); + } + + if( self->wait > 0 ) + { + self->think = multi_wait; + self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + self->touch = 0; + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + } +} + +/* +=============== +trigger_equipment_touch +=============== +*/ +void trigger_equipment_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + //only triggered by clients + if( !other->client ) + return; + + trigger_equipment_trigger( ent, other ); +} + +/* +=============== +trigger_equipment_use +=============== +*/ +void trigger_equipment_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + ent->s.eFlags ^= EF_NODRAW; +} + +/* +=============== +SP_trigger_equipment +=============== +*/ +void SP_trigger_equipment( gentity_t *self ) +{ + char *buffer; + + G_SpawnFloat( "wait", "0.5", &self->wait ); + G_SpawnFloat( "random", "0", &self->random ); + + if( self->random >= self->wait && self->wait >= 0 ) + { + self->random = self->wait - FRAMETIME; + G_Printf( S_COLOR_YELLOW "WARNING: trigger_equipment has random >= wait\n" ); + } + + G_SpawnString( "equipment", "", &buffer ); + + BG_ParseCSVEquipmentList( buffer, self->wTriggers, WP_NUM_WEAPONS, + self->uTriggers, UP_NUM_UPGRADES ); + + self->touch = trigger_equipment_touch; + self->use = trigger_equipment_use; + + // SPAWN_DISABLED + if( self->spawnflags & 1 ) + self->s.eFlags |= EF_NODRAW; + + // NEGATE + if( self->spawnflags & 2 ) + self->s.eFlags |= EF_DEAD; + + InitTrigger( self ); + trap_LinkEntity( self ); +} + + +/* +=============== +trigger_gravity_touch +=============== +*/ +void trigger_gravity_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + //only triggered by clients + if( !other->client ) + return; + + other->client->ps.gravity = ent->triggerGravity; +} + +/* +=============== +trigger_gravity_use +=============== +*/ +void trigger_gravity_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + if( ent->r.linked ) + trap_UnlinkEntity( ent ); + else + trap_LinkEntity( ent ); +} + + +/* +=============== +SP_trigger_gravity +=============== +*/ +void SP_trigger_gravity( gentity_t *self ) +{ + G_SpawnInt( "gravity", "800", &self->triggerGravity ); + + self->touch = trigger_gravity_touch; + self->use = trigger_gravity_use; + + InitTrigger( self ); + trap_LinkEntity( self ); +} + + +/* +=============== +trigger_heal_use +=============== +*/ +void trigger_heal_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->r.linked ) + trap_UnlinkEntity( self ); + else + trap_LinkEntity( self ); +} + +/* +=============== +trigger_heal_touch +=============== +*/ +void trigger_heal_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + int max; + + if( !other->client ) + return; + + if( self->timestamp > level.time ) + return; + + if( self->spawnflags & 2 ) + self->timestamp = level.time + 1000; + else + self->timestamp = level.time + FRAMETIME; + + max = other->client->ps.stats[ STAT_MAX_HEALTH ]; + + other->health += self->damage; + + if( other->health > max ) + other->health = max; + + other->client->ps.stats[ STAT_HEALTH ] = other->health; +} + +/* +=============== +SP_trigger_heal +=============== +*/ +void SP_trigger_heal( gentity_t *self ) +{ + G_SpawnInt( "heal", "5", &self->damage ); + + if( self->damage <= 0 ) + { + self->damage = 1; + G_Printf( S_COLOR_YELLOW "WARNING: trigger_heal with negative damage key\n" ); + } + + self->touch = trigger_heal_touch; + self->use = trigger_heal_use; + + InitTrigger( self ); + + // link in to the world if starting active + if( !( self->spawnflags & 1 ) ) + trap_LinkEntity( self ); +} + + +/* +=============== +trigger_ammo_touch +=============== +*/ +void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + int maxClips, maxAmmo; + weapon_t weapon; + + if( !other->client ) + return; + + if( other->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) + return; + + if( self->timestamp > level.time ) + return; + + if( other->client->ps.weaponstate != WEAPON_READY ) + return; + + weapon = BG_PrimaryWeapon( other->client->ps.stats ); + if( BG_Weapon( weapon )->usesEnergy && self->spawnflags & 2 ) + return; + + if( !BG_Weapon( weapon )->usesEnergy && self->spawnflags & 4 ) + return; + + if( self->spawnflags & 1 ) + self->timestamp = level.time + 1000; + else + self->timestamp = level.time + FRAMETIME; + + maxAmmo = BG_Weapon( weapon )->maxAmmo; + maxClips = BG_Weapon( weapon )->maxClips; + + if( ( other->client->ps.ammo + self->damage ) > maxAmmo ) + { + if( other->client->ps.clips < maxClips ) + { + other->client->ps.clips++; + other->client->ps.ammo = 1; + } + else + other->client->ps.ammo = maxAmmo; + } + else + other->client->ps.ammo += self->damage; +} + +/* +=============== +SP_trigger_ammo +=============== +*/ +void SP_trigger_ammo( gentity_t *self ) +{ + G_SpawnInt( "ammo", "1", &self->damage ); + + if( self->damage <= 0 ) + { + self->damage = 1; + G_Printf( S_COLOR_YELLOW "WARNING: trigger_ammo with negative ammo key\n" ); + } + + self->touch = trigger_ammo_touch; + + InitTrigger( self ); + trap_LinkEntity( self ); +} diff --git a/src/game/g_utils.c b/src/game/g_utils.c new file mode 100644 index 0000000..c0b6b0e --- /dev/null +++ b/src/game/g_utils.c @@ -0,0 +1,1032 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" + +typedef struct +{ + char oldShader[ MAX_QPATH ]; + char newShader[ MAX_QPATH ]; + float timeOffset; +} shaderRemap_t; + +#define MAX_SHADER_REMAPS 128 + +int remapCount = 0; +shaderRemap_t remappedShaders[ MAX_SHADER_REMAPS ]; + +void AddRemap( const char *oldShader, const char *newShader, float timeOffset ) +{ + int i; + + for( i = 0; i < remapCount; i++ ) + { + if( Q_stricmp( oldShader, remappedShaders[ i ].oldShader ) == 0 ) + { + // found it, just update this one + strcpy( remappedShaders[ i ].newShader,newShader ); + remappedShaders[ i ].timeOffset = timeOffset; + return; + } + } + + if( remapCount < MAX_SHADER_REMAPS ) + { + strcpy( remappedShaders[ remapCount ].newShader,newShader ); + strcpy( remappedShaders[ remapCount ].oldShader,oldShader ); + remappedShaders[ remapCount ].timeOffset = timeOffset; + remapCount++; + } +} + +const char *BuildShaderStateConfig( void ) +{ + static char buff[ MAX_STRING_CHARS * 4 ]; + char out[ ( MAX_QPATH * 2 ) + 5 ]; + int i; + + memset( buff, 0, MAX_STRING_CHARS ); + + for( i = 0; i < remapCount; i++ ) + { + Com_sprintf( out, ( MAX_QPATH * 2 ) + 5, "%s=%s:%5.2f@", remappedShaders[ i ].oldShader, + remappedShaders[ i ].newShader, remappedShaders[ i ].timeOffset ); + Q_strcat( buff, sizeof( buff ), out ); + } + return buff; +} + + +/* +========================================================================= + +model / sound configstring indexes + +========================================================================= +*/ + +/* +================ +G_FindConfigstringIndex + +================ +*/ +int G_FindConfigstringIndex( char *name, int start, int max, qboolean create ) +{ + int i; + char s[ MAX_STRING_CHARS ]; + + if( !name || !name[ 0 ] ) + return 0; + + for( i = 1; i < max; i++ ) + { + trap_GetConfigstring( start + i, s, sizeof( s ) ); + if( !s[ 0 ] ) + break; + + if( !strcmp( s, name ) ) + return i; + } + + if( !create ) + return 0; + + if( i == max ) + G_Error( "G_FindConfigstringIndex: overflow" ); + + trap_SetConfigstring( start + i, name ); + + return i; +} + +int G_ParticleSystemIndex( char *name ) +{ + return G_FindConfigstringIndex( name, CS_PARTICLE_SYSTEMS, MAX_GAME_PARTICLE_SYSTEMS, qtrue ); +} + +int G_ShaderIndex( char *name ) +{ + return G_FindConfigstringIndex( name, CS_SHADERS, MAX_GAME_SHADERS, qtrue ); +} + +int G_ModelIndex( char *name ) +{ + return G_FindConfigstringIndex( name, CS_MODELS, MAX_MODELS, qtrue ); +} + +int G_SoundIndex( char *name ) +{ + return G_FindConfigstringIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue ); +} + +//===================================================================== + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +gentity_t *G_Find( gentity_t *from, int fieldofs, const char *match ) +{ + char *s; + + if( !from ) + from = g_entities; + else + from++; + + for( ; from < &g_entities[ level.num_entities ]; from++ ) + { + if( !from->inuse ) + continue; + s = *(char **)( (byte *)from + fieldofs ); + + if( !s ) + continue; + + if( !Q_stricmp( s, match ) ) + return from; + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Selects a random entity from among the targets +============= +*/ +#define MAXCHOICES 32 + +gentity_t *G_PickTarget( char *targetname ) +{ + gentity_t *ent = NULL; + int num_choices = 0; + gentity_t *choice[ MAXCHOICES ]; + + if( !targetname ) + { + G_Printf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while( 1 ) + { + ent = G_Find( ent, FOFS( targetname ), targetname ); + + if( !ent ) + break; + + choice[ num_choices++ ] = ent; + + if( num_choices == MAXCHOICES ) + break; + } + + if( !num_choices ) + { + G_Printf( "G_PickTarget: target %s not found\n", targetname ); + return NULL; + } + + return choice[ rand( ) / ( RAND_MAX / num_choices + 1 ) ]; +} + + +/* +============================== +G_UseTargets + +"activator" should be set to the entity that initiated the firing. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets( gentity_t *ent, gentity_t *activator ) +{ + gentity_t *t; + + if( !ent ) + return; + + if( ent->targetShaderName && ent->targetShaderNewName ) + { + float f = level.time * 0.001; + AddRemap( ent->targetShaderName, ent->targetShaderNewName, f ); + trap_SetConfigstring( CS_SHADERSTATE, BuildShaderStateConfig( ) ); + } + + if( !ent->target ) + return; + + t = NULL; + while( ( t = G_Find( t, FOFS( targetname ), ent->target ) ) != NULL ) + { + if( t == ent ) + G_Printf( "WARNING: Entity used itself.\n" ); + else + { + if( t->use ) + t->use( t, ent, activator ); + } + + if( !ent->inuse ) + { + G_Printf( "entity was removed while using targets\n" ); + return; + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv( float x, float y, float z ) +{ + static int index; + static vec3_t vecs[ 8 ]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[ index ]; + index = ( index + 1 ) & 7; + + v[ 0 ] = x; + v[ 1 ] = y; + v[ 2 ] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos( const vec3_t v ) +{ + static int index; + static char str[ 8 ][ 32 ]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[ index ]; + index = ( index + 1 ) & 7; + + Com_sprintf( s, 32, "(%i %i %i)", (int)v[ 0 ], (int)v[ 1 ], (int)v[ 2 ] ); + + return s; +} + + +/* +=============== +G_SetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void G_SetMovedir( vec3_t angles, vec3_t movedir ) +{ + static vec3_t VEC_UP = { 0, -1, 0 }; + static vec3_t MOVEDIR_UP = { 0, 0, 1 }; + static vec3_t VEC_DOWN = { 0, -2, 0 }; + static vec3_t MOVEDIR_DOWN = { 0, 0, -1 }; + + if( VectorCompare( angles, VEC_UP ) ) + VectorCopy( MOVEDIR_UP, movedir ); + else if( VectorCompare( angles, VEC_DOWN ) ) + VectorCopy( MOVEDIR_DOWN, movedir ); + else + AngleVectors( angles, movedir, NULL, NULL ); + + VectorClear( angles ); +} + + +float vectoyaw( const vec3_t vec ) +{ + float yaw; + + if( vec[ YAW ] == 0 && vec[ PITCH ] == 0 ) + { + yaw = 0; + } + else + { + if( vec[ PITCH ] ) + yaw = ( atan2( vec[ YAW ], vec[ PITCH ] ) * 180 / M_PI ); + else if( vec[ YAW ] > 0 ) + yaw = 90; + else + yaw = 270; + + if( yaw < 0 ) + yaw += 360; + } + + return yaw; +} + + +void G_InitGentity( gentity_t *e ) +{ + e->inuse = qtrue; + e->classname = "noclass"; + e->s.number = e - g_entities; + e->r.ownerNum = ENTITYNUM_NONE; +} + +/* +================= +G_Spawn + +Either finds a free entity, or allocates a new one. + + The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will +never be used by anything else. + +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. +================= +*/ +gentity_t *G_Spawn( void ) +{ + int i, force; + gentity_t *e; + + e = NULL; // shut up warning + i = 0; // shut up warning + + for( force = 0; force < 2; force++ ) + { + // if we go through all entities and can't find one to free, + // override the normal minimum times before use + e = &g_entities[ MAX_CLIENTS ]; + + for( i = MAX_CLIENTS; i < level.num_entities; i++, e++ ) + { + if( e->inuse ) + continue; + + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 ) + continue; + + // reuse this slot + G_InitGentity( e ); + return e; + } + + if( i != MAX_GENTITIES ) + break; + } + + if( i == ENTITYNUM_MAX_NORMAL ) + { + for( i = 0; i < MAX_GENTITIES; i++ ) + G_Printf( "%4i: %s\n", i, g_entities[ i ].classname ); + + G_Error( "G_Spawn: no free entities" ); + } + + // open up a new slot + level.num_entities++; + + // let the server system know that there are more entities + trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), + &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) ); + + G_InitGentity( e ); + return e; +} + + +/* +================= +G_EntitiesFree +================= +*/ +qboolean G_EntitiesFree( void ) +{ + int i; + gentity_t *e; + + e = &g_entities[ MAX_CLIENTS ]; + + for( i = MAX_CLIENTS; i < level.num_entities; i++, e++ ) + { + if( e->inuse ) + continue; + + // slot available + return qtrue; + } + + return qfalse; +} + + +/* +================= +G_FreeEntity + +Marks the entity as free +================= +*/ +void G_FreeEntity( gentity_t *ent ) +{ + trap_UnlinkEntity( ent ); // unlink from world + + if( ent->neverFree ) + return; + + memset( ent, 0, sizeof( *ent ) ); + ent->classname = "freent"; + ent->freetime = level.time; + ent->inuse = qfalse; +} + +/* +================= +G_TempEntity + +Spawns an event entity that will be auto-removed +The origin will be snapped to save net bandwidth, so care +must be taken if the origin is right on a surface (snap towards start vector first) +================= +*/ +gentity_t *G_TempEntity( vec3_t origin, int event ) +{ + gentity_t *e; + vec3_t snapped; + + e = G_Spawn( ); + e->s.eType = ET_EVENTS + event; + + e->classname = "tempEntity"; + e->eventTime = level.time; + e->freeAfterEvent = qtrue; + + VectorCopy( origin, snapped ); + SnapVector( snapped ); // save network bandwidth + G_SetOrigin( e, snapped ); + + // find cluster for PVS + trap_LinkEntity( e ); + + return e; +} + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +G_KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +void G_KillBox( gentity_t *ent ) +{ + int i, num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); + VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if( !hit->client ) + continue; + + // impossible to telefrag self + if( ent == hit ) + continue; + + // nail it + G_Damage( hit, ent, ent, NULL, NULL, + 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG ); + } + +} + +//============================================================================== + +/* +=============== +G_AddPredictableEvent + +Use for non-pmove events that would also be predicted on the +client side: jumppads and item pickups +Adds an event+parm and twiddles the event counter +=============== +*/ +void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) +{ + if( !ent->client ) + return; + + BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps ); +} + + +/* +=============== +G_AddEvent + +Adds an event+parm and twiddles the event counter +=============== +*/ +void G_AddEvent( gentity_t *ent, int event, int eventParm ) +{ + int bits; + + if( !event ) + { + G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number ); + return; + } + + // eventParm is converted to uint8_t (0 - 255) in msg.c + if( eventParm & ~0xFF ) + { + G_Printf( S_COLOR_YELLOW "WARNING: G_AddEvent( %s ) has eventParm %d, " + "which will overflow\n", BG_EventName( event ), eventParm ); + } + + // clients need to add the event in playerState_t instead of entityState_t + if( ent->client ) + { + bits = ent->client->ps.externalEvent & EV_EVENT_BITS; + bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS; + ent->client->ps.externalEvent = event | bits; + ent->client->ps.externalEventParm = eventParm; + ent->client->ps.externalEventTime = level.time; + } + else + { + bits = ent->s.event & EV_EVENT_BITS; + bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS; + ent->s.event = event | bits; + ent->s.eventParm = eventParm; + } + + ent->eventTime = level.time; +} + + +/* +=============== +G_BroadcastEvent + +Sends an event to every client +=============== +*/ +void G_BroadcastEvent( int event, int eventParm ) +{ + gentity_t *ent; + + ent = G_TempEntity( vec3_origin, event ); + ent->s.eventParm = eventParm; + ent->r.svFlags = SVF_BROADCAST; // send to everyone +} + + +/* +============= +G_Sound +============= +*/ +void G_Sound( gentity_t *ent, int channel, int soundIndex ) +{ + gentity_t *te; + + te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND ); + te->s.eventParm = soundIndex; +} + + +/* +============= +G_ClientIsLagging +============= +*/ +qboolean G_ClientIsLagging( gclient_t *client ) +{ + if( client ) + { + if( client->ps.ping >= 999 ) + return qtrue; + else + return qfalse; + } + + return qfalse; //is a non-existant client lagging? woooo zen +} + +//============================================================================== + + +/* +================ +G_SetOrigin + +Sets the pos trajectory for a fixed position +================ +*/ +void G_SetOrigin( gentity_t *ent, vec3_t origin ) +{ + VectorCopy( origin, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + ent->s.pos.trTime = 0; + ent->s.pos.trDuration = 0; + VectorClear( ent->s.pos.trDelta ); + + VectorCopy( origin, ent->r.currentOrigin ); + VectorCopy( origin, ent->s.origin ); +} + +// from quakestyle.telefragged.com +// (NOBODY): Code helper function +// +gentity_t *G_FindRadius( gentity_t *from, vec3_t org, float rad ) +{ + vec3_t eorg; + int j; + + if( !from ) + from = g_entities; + else + from++; + + for( ; from < &g_entities[ level.num_entities ]; from++ ) + { + if( !from->inuse ) + continue; + + for( j = 0; j < 3; j++ ) + eorg[ j ] = org[ j ] - ( from->r.currentOrigin[ j ] + ( from->r.mins[ j ] + from->r.maxs[ j ] ) * 0.5 ); + + if( VectorLength( eorg ) > rad ) + continue; + + return from; + } + + return NULL; +} + +/* +=============== +G_Visible + +Test for a LOS between two entities +=============== +*/ +qboolean G_Visible( gentity_t *ent1, gentity_t *ent2, int contents ) +{ + trace_t trace; + + trap_Trace( &trace, ent1->s.pos.trBase, NULL, NULL, ent2->s.pos.trBase, + ent1->s.number, contents ); + + return trace.fraction >= 1.0f || trace.entityNum == ent2 - g_entities; +} + +/* +=============== +G_ClosestEnt + +Test a list of entities for the closest to a particular point +=============== +*/ +gentity_t *G_ClosestEnt( vec3_t origin, gentity_t **entities, int numEntities ) +{ + int i; + float nd, d; + gentity_t *closestEnt; + + if( numEntities <= 0 ) + return NULL; + + closestEnt = entities[ 0 ]; + d = DistanceSquared( origin, closestEnt->s.origin ); + + for( i = 1; i < numEntities; i++ ) + { + gentity_t *ent = entities[ i ]; + + nd = DistanceSquared( origin, ent->s.origin ); + if( nd < d ) + { + d = nd; + closestEnt = ent; + } + } + + return closestEnt; +} + +/* +=============== +G_TriggerMenu + +Trigger a menu on some client +=============== +*/ +void G_TriggerMenu( int clientNum, dynMenu_t menu ) +{ + char buffer[ 32 ]; + + Com_sprintf( buffer, sizeof( buffer ), "servermenu %d", menu ); + trap_SendServerCommand( clientNum, buffer ); +} + +/* +=============== +G_TriggerMenuArgs + +Trigger a menu on some client and passes an argument +=============== +*/ +void G_TriggerMenuArgs( int clientNum, dynMenu_t menu, int arg ) +{ + char buffer[ 64 ]; + + Com_sprintf( buffer, sizeof( buffer ), "servermenu %d %d", menu, arg ); + trap_SendServerCommand( clientNum, buffer ); +} + +/* +=============== +G_CloseMenus + +Close all open menus on some client +=============== +*/ +void G_CloseMenus( int clientNum ) +{ + char buffer[ 32 ]; + + Com_sprintf( buffer, 32, "serverclosemenus" ); + trap_SendServerCommand( clientNum, buffer ); +} + + +/* +=============== +G_AddressParse + +Make an IP address more usable +=============== +*/ +static const char *addr4parse( const char *str, addr_t *addr ) +{ + int i; + int octet = 0; + int num = 0; + memset( addr, 0, sizeof( addr_t ) ); + addr->type = IPv4; + for( i = 0; octet < 4; i++ ) + { + if( isdigit( str[ i ] ) ) + num = num * 10 + str[ i ] - '0'; + else + { + if( num < 0 || num > 255 ) + return NULL; + addr->addr[ octet ] = (byte)num; + octet++; + if( str[ i ] != '.' || str[ i + 1 ] == '.' ) + break; + num = 0; + } + } + if( octet < 1 ) + return NULL; + return str + i; +} + +static const char *addr6parse( const char *str, addr_t *addr ) +{ + int i; + qboolean seen = qfalse; + /* keep track of the parts before and after the :: + it's either this or even uglier hacks */ + byte a[ ADDRLEN ], b[ ADDRLEN ]; + size_t before = 0, after = 0; + int num = 0; + /* 8 hexadectets unless :: is present */ + for( i = 0; before + after <= 8; i++ ) + { + //num = num << 4 | str[ i ] - '0'; + if( isdigit( str[ i ] ) ) + num = num * 16 + str[ i ] - '0'; + else if( str[ i ] >= 'A' && str[ i ] <= 'F' ) + num = num * 16 + 10 + str[ i ] - 'A'; + else if( str[ i ] >= 'a' && str[ i ] <= 'f' ) + num = num * 16 + 10 + str[ i ] - 'a'; + else + { + if( num < 0 || num > 65535 ) + return NULL; + if( i == 0 ) + { + // + } + else if( seen ) // :: has been seen already + { + b[ after * 2 ] = num >> 8; + b[ after * 2 + 1 ] = num & 0xff; + after++; + } + else + { + a[ before * 2 ] = num >> 8; + a[ before * 2 + 1 ] = num & 0xff; + before++; + } + if( !str[ i ] ) + break; + if( str[ i ] != ':' || before + after == 8 ) + break; + if( str[ i + 1 ] == ':' ) + { + // ::: or multiple :: + if( seen || str[ i + 2 ] == ':' ) + break; + seen = qtrue; + i++; + } + else if( i == 0 ) // starts with : but not :: + return NULL; + num = 0; + } + } + if( seen ) + { + // there have to be fewer than 8 hexadectets when :: is present + if( before + after == 8 ) + return NULL; + } + else if( before + after < 8 ) // require exactly 8 hexadectets + return NULL; + memset( addr, 0, sizeof( addr_t ) ); + addr->type = IPv6; + if( before ) + memcpy( addr->addr, a, before * 2 ); + if( after ) + memcpy( addr->addr + ADDRLEN - 2 * after, b, after * 2 ); + return str + i; +} + +qboolean G_AddressParse( const char *str, addr_t *addr ) +{ + const char *p; + int max; + if( strchr( str, ':' ) ) + { + p = addr6parse( str, addr ); + max = 128; + } + else if( strchr( str, '.' ) ) + { + p = addr4parse( str, addr ); + max = 32; + } + else + return qfalse; + Q_strncpyz( addr->str, str, sizeof( addr->str ) ); + if( !p ) + return qfalse; + if( *p == '/' ) + { + addr->mask = atoi( p + 1 ); + if( addr->mask < 1 || addr->mask > max ) + addr->mask = max; + } + else + { + if( *p ) + return qfalse; + addr->mask = max; + } + return qtrue; +} + +/* +=============== +G_AddressCompare + +Based largely on NET_CompareBaseAdrMask from ioq3 revision 1557 +=============== +*/ +qboolean G_AddressCompare( const addr_t *a, const addr_t *b ) +{ + int i, netmask; + if( a->type != b->type ) + return qfalse; + netmask = a->mask; + if( a->type == IPv4 ) + { + if( netmask < 1 || netmask > 32 ) + netmask = 32; + } + else if( a->type == IPv6 ) + { + if( netmask < 1 || netmask > 128 ) + netmask = 128; + } + for( i = 0; netmask > 7; i++, netmask -= 8 ) + if( a->addr[ i ] != b->addr[ i ] ) + return qfalse; + if( netmask ) + { + netmask = ( ( 1 << netmask ) - 1 ) << ( 8 - netmask ); + return ( a->addr[ i ] & netmask ) == ( b->addr[ i ] & netmask ); + } + return qtrue; +} + +/* +=============== +G_RecalcBuildTimer + +recalc client's STAT_MISC +=============== +*/ + +void G_RecalcBuildTimer( gclient_t *client ) +{ + if(BG_InventoryContainsWeapon(WP_ABUILD,client->ps.stats)|| + BG_InventoryContainsWeapon(WP_ABUILD2,client->ps.stats)|| + BG_InventoryContainsWeapon(WP_HBUILD,client->ps.stats)) + client->ps.stats[STAT_MISC]=MIN(client->buildTimer,MAXIMUM_BUILD_TIME); +} diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c new file mode 100644 index 0000000..63e80be --- /dev/null +++ b/src/game/g_weapon.c @@ -0,0 +1,1647 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// g_weapon.c +// perform the server side effects of a weapon firing + +#include "g_local.h" + +static vec3_t forward, right, up; +static vec3_t muzzle; + +/* +================ +G_ForceWeaponChange +================ +*/ +void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon ) +{ + playerState_t *ps = &ent->client->ps; + + // stop a reload in progress + if( ps->weaponstate == WEAPON_RELOADING ) + { + ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_RAISE; + ps->weaponTime = 250; + ps->weaponstate = WEAPON_READY; + } + + if( weapon == WP_NONE || + !BG_InventoryContainsWeapon( weapon, ps->stats ) ) + { + // switch to the first non blaster weapon + ps->persistant[ PERS_NEWWEAPON ] = + BG_PrimaryWeapon( ent->client->ps.stats ); + } + else + ps->persistant[ PERS_NEWWEAPON ] = weapon; + + // force this here to prevent flamer effect from continuing + ps->generic1 = WPM_NOTFIRING; + + // The PMove will do an animated drop, raise, and set the new weapon + ps->pm_flags |= PMF_WEAPON_SWITCH; +} + +/* +================= +G_GiveClientMaxAmmo +================= +*/ +void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo ) +{ + int i, maxAmmo, maxClips; + qboolean restoredAmmo = qfalse, restoredEnergy = qfalse; + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + qboolean energyWeapon; + + energyWeapon = BG_Weapon( i )->usesEnergy; + if( !BG_InventoryContainsWeapon( i, ent->client->ps.stats ) || + BG_Weapon( i )->infiniteAmmo || + BG_WeaponIsFull( i, ent->client->ps.stats, + ent->client->ps.ammo, ent->client->ps.clips ) || + ( buyingEnergyAmmo && !energyWeapon ) ) + continue; + + maxAmmo = BG_Weapon( i )->maxAmmo; + maxClips = BG_Weapon( i )->maxClips; + + // Apply battery pack modifier + if( energyWeapon && + BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) + { + maxAmmo *= BATTPACK_MODIFIER; + restoredEnergy = qtrue; + } + + ent->client->ps.ammo = maxAmmo; + ent->client->ps.clips = maxClips; + + restoredAmmo = qtrue; + } + + if( restoredAmmo ) + G_ForceWeaponChange( ent, ent->client->ps.weapon ); + + if( restoredEnergy ) + G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); +} + +/* +================ +G_BounceProjectile +================ +*/ +void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout ) +{ + vec3_t v, newv; + float dot; + + VectorSubtract( impact, start, v ); + dot = DotProduct( v, dir ); + VectorMA( v, -2 * dot, dir, newv ); + + VectorNormalize(newv); + VectorMA(impact, 8192, newv, endout); +} + +/* +================ +G_WideTrace + +Trace a bounding box against entities, but not the world +Also check there is a line of sight between the start and end point +================ +*/ +static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, + float width, float height, gentity_t **target ) +{ + vec3_t mins, maxs; + vec3_t end; + + VectorSet( mins, -width, -width, -height ); + VectorSet( maxs, width, width, width ); + + *target = NULL; + + if( !ent->client ) + return; + + G_UnlaggedOn( ent, muzzle, range + width ); + + VectorMA( muzzle, range, forward, end ); + + // Trace against entities + trap_Trace( tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_BODY ); + if( tr->entityNum != ENTITYNUM_NONE ) + *target = &g_entities[ tr->entityNum ]; + + // Set range to the trace length plus the width, so that the end of the + // LOS trace is close to the exterior of the target's bounding box + range = Distance( muzzle, tr->endpos ) + width; + VectorMA( muzzle, range, forward, end ); + + // Trace for line of sight against the world + trap_Trace( tr, muzzle, NULL, NULL, end, ent->s.number, CONTENTS_SOLID ); + if( tr->entityNum != ENTITYNUM_NONE ) + *target = &g_entities[ tr->entityNum ]; + + G_UnlaggedOff( ); +} + +/* +====================== +SnapVectorTowards +SnapVectorNormal + +Round a vector to integers for more efficient network +transmission, but make sure that it rounds towards a given point +rather than blindly truncating. This prevents it from truncating +into a wall. +====================== +*/ +void SnapVectorTowards( vec3_t v, vec3_t to ) +{ + int i; + + for( i = 0 ; i < 3 ; i++ ) + { + if( v[ i ] >= 0 ) + v[ i ] = (int)( v[ i ] + ( to[ i ] <= v[ i ] ? 0 : 1 ) ); + else + v[ i ] = (int)( v[ i ] + ( to[ i ] <= v[ i ] ? -1 : 0 ) ); + } +} + +void SnapVectorNormal( vec3_t v, vec3_t normal ) +{ + int i; + + for( i = 0 ; i < 3 ; i++ ) + { + if( v[ i ] >= 0 ) + v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? 0 : 1 ) ); + else + v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? -1 : 0 ) ); + } +} + +/* +=============== +BloodSpurt + +Generates a blood spurt event for traces with accurate end points +=============== +*/ +static void BloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr ) +{ + gentity_t *tent; + + if( !attacker->client ) + return; + + if( victim->health <= 0 ) + return; + + tent = G_TempEntity( tr->endpos, EV_MISSILE_HIT ); + tent->s.otherEntityNum = victim->s.number; + tent->s.eventParm = DirToByte( tr->plane.normal ); + tent->s.weapon = attacker->s.weapon; + tent->s.generic1 = attacker->s.generic1; // weaponMode +} + +/* +=============== +WideBloodSpurt + +Calculates the position of a blood spurt for wide traces and generates an event +=============== +*/ +static void WideBloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr ) +{ + gentity_t *tent; + vec3_t normal, origin; + float mag, radius; + + if( !attacker->client ) + return; + + if( victim->health <= 0 ) + return; + + if( tr ) + VectorSubtract( tr->endpos, victim->s.origin, normal ); + else + VectorSubtract( attacker->client->ps.origin, + victim->s.origin, normal ); + + // Normalize the horizontal components of the vector difference to the + // "radius" of the bounding box + mag = sqrt( normal[ 0 ] * normal[ 0 ] + normal[ 1 ] * normal[ 1 ] ); + radius = victim->r.maxs[ 0 ] * 1.21f; + if( mag > radius ) + { + normal[ 0 ] = normal[ 0 ] / mag * radius; + normal[ 1 ] = normal[ 1 ] / mag * radius; + } + + // Clamp origin to be within bounding box vertically + if( normal[ 2 ] > victim->r.maxs[ 2 ] ) + normal[ 2 ] = victim->r.maxs[ 2 ]; + if( normal[ 2 ] < victim->r.mins[ 2 ] ) + normal[ 2 ] = victim->r.mins[ 2 ]; + + VectorAdd( victim->s.origin, normal, origin ); + VectorNegate( normal, normal ); + VectorNormalize( normal ); + + // Create the blood spurt effect entity + tent = G_TempEntity( origin, EV_MISSILE_HIT ); + tent->s.eventParm = DirToByte( normal ); + tent->s.otherEntityNum = victim->s.number; + tent->s.weapon = attacker->s.weapon; + tent->s.generic1 = attacker->s.generic1; // weaponMode +} + +/* +=============== +meleeAttack +=============== +*/ +void meleeAttack( gentity_t *ent, float range, float width, float height, + int damage, meansOfDeath_t mod ) +{ + trace_t tr; + gentity_t *traceEnt; + + G_WideTrace( &tr, ent, range, width, height, &traceEnt ); + if( traceEnt == NULL || !traceEnt->takedamage ) + return; + + WideBloodSpurt( ent, traceEnt, &tr ); + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod ); +} + +/* +====================================================================== + +MACHINEGUN + +====================================================================== +*/ + +void bulletFire( gentity_t *ent, float spread, int damage, int mod ) +{ + trace_t tr; + vec3_t end; + float r; + float u; + gentity_t *tent; + gentity_t *traceEnt; + + r = random( ) * M_PI * 2.0f; + u = sin( r ) * crandom( ) * spread * 16; + r = cos( r ) * crandom( ) * spread * 16; + VectorMA( muzzle, 8192 * 16, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + // don't use unlagged if this is not a client (e.g. turret) + if( ent->client ) + { + G_UnlaggedOn( ent, muzzle, 8192 * 16 ); + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); + } + else + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzle ); + + // send bullet impact + if( traceEnt->takedamage && + (traceEnt->s.eType == ET_PLAYER || + traceEnt->s.eType == ET_BUILDABLE ) ) + { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + } + else + { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL ); + tent->s.eventParm = DirToByte( tr.plane.normal ); + } + tent->s.otherEntityNum = ent->s.number; + + if( traceEnt->takedamage ) + { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + damage, 0, mod ); + } +} + +/* +====================================================================== + +SHOTGUN + +====================================================================== +*/ + +// this should match CG_ShotgunPattern +void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) +{ + int i; + float r, u; + vec3_t end; + vec3_t forward, right, up; + trace_t tr; + gentity_t *traceEnt; + + // derive the right and up vectors from the forward vector, because + // the client won't have any other information + VectorNormalize2( origin2, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + // generate the "random" spread pattern + for( i = 0; i < SHOTGUN_PELLETS; i++ ) + { + r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; + u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; + VectorMA( origin, SHOTGUN_RANGE, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + trap_Trace( &tr, origin, NULL, NULL, end, ent->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + + // send bullet impact + if( !( tr.surfaceFlags & SURF_NOIMPACT ) ) + { + if( traceEnt->takedamage ) + G_Damage( traceEnt, ent, ent, forward, tr.endpos, SHOTGUN_DMG, 0, MOD_SHOTGUN ); + } + } +} + + +void shotgunFire( gentity_t *ent ) +{ + gentity_t *tent; + + // send shotgun blast + tent = G_TempEntity( muzzle, EV_SHOTGUN ); + VectorScale( forward, 4096, tent->s.origin2 ); + SnapVector( tent->s.origin2 ); + tent->s.eventParm = rand() / ( RAND_MAX / 0x100 + 1 ); // seed for spread pattern + tent->s.otherEntityNum = ent->s.number; + G_UnlaggedOn( ent, muzzle, SHOTGUN_RANGE ); + ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent ); + G_UnlaggedOff(); +} + +/* +====================================================================== + +MASS DRIVER + +====================================================================== +*/ + +void massDriverFire( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + + VectorMA( muzzle, 8192.0f * 16.0f, forward, end ); + + G_UnlaggedOn( ent, muzzle, 8192.0f * 16.0f ); + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); + + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzle ); + + // send impact + if( traceEnt->takedamage && + (traceEnt->s.eType == ET_BUILDABLE || + traceEnt->s.eType == ET_PLAYER ) ) + { + BloodSpurt( ent, traceEnt, &tr ); + } + else + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS ); + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + if( traceEnt->takedamage ) + { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + MDRIVER_DMG, 0, MOD_MDRIVER ); + } +} + +/* +====================================================================== + +LOCKBLOB + +====================================================================== +*/ + +void lockBlobLauncherFire( gentity_t *ent ) +{ + fire_lockblob( ent, muzzle, forward ); +} + +/* +====================================================================== + +HIVE + +====================================================================== +*/ + +void hiveFire( gentity_t *ent ) +{ + vec3_t origin; + + // Fire from the hive tip, not the center + VectorMA( muzzle, ent->r.maxs[ 2 ], ent->s.origin2, origin ); + + fire_hive( ent, origin, forward ); +} + +/* +====================================================================== + +BLASTER PISTOL + +====================================================================== +*/ + +void blasterFire( gentity_t *ent ) +{ + fire_blaster( ent, muzzle, forward ); +} + +/* +====================================================================== + +PULSE RIFLE + +====================================================================== +*/ + +void pulseRifleFire( gentity_t *ent ) +{ + fire_pulseRifle( ent, muzzle, forward ); +} + +/* +====================================================================== + +FLAME THROWER + +====================================================================== +*/ + +void flamerFire( gentity_t *ent ) +{ + vec3_t origin; + + // Correct muzzle so that the missile does not start in the ceiling + VectorMA( muzzle, -7.0f, up, origin ); + + // Correct muzzle so that the missile fires from the player's hand + VectorMA( origin, 4.5f, right, origin ); + + fire_flamer( ent, origin, forward ); +} + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +void throwGrenade( gentity_t *ent ) +{ + launch_grenade( ent, muzzle, forward ); +} + +/* +====================================================================== + +LAS GUN + +====================================================================== +*/ + +/* +=============== +lasGunFire +=============== +*/ +void lasGunFire( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + + VectorMA( muzzle, 8192 * 16, forward, end ); + + G_UnlaggedOn( ent, muzzle, 8192 * 16 ); + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); + + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzle ); + + // send impact + if( traceEnt->takedamage && + (traceEnt->s.eType == ET_BUILDABLE || + traceEnt->s.eType == ET_PLAYER ) ) + { + BloodSpurt( ent, traceEnt, &tr ); + } + else + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS ); + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + if( traceEnt->takedamage ) + G_Damage( traceEnt, ent, ent, forward, tr.endpos, LASGUN_DAMAGE, 0, MOD_LASGUN ); +} + +/* +====================================================================== + +PAIN SAW + +====================================================================== +*/ + +void painSawFire( gentity_t *ent ) +{ + trace_t tr; + vec3_t temp; + gentity_t *tent, *traceEnt; + + G_WideTrace( &tr, ent, PAINSAW_RANGE, PAINSAW_WIDTH, PAINSAW_HEIGHT, + &traceEnt ); + if( !traceEnt || !traceEnt->takedamage ) + return; + + // hack to line up particle system with weapon model + tr.endpos[ 2 ] -= 5.0f; + + // send blood impact + if( traceEnt->s.eType == ET_PLAYER || traceEnt->s.eType == ET_BUILDABLE ) + { + BloodSpurt( ent, traceEnt, &tr ); + } + else + { + VectorCopy( tr.endpos, temp ); + tent = G_TempEntity( temp, EV_MISSILE_MISS ); + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW ); +} + +/* +====================================================================== + +LUCIFER CANNON + +====================================================================== +*/ + +/* +=============== +LCChargeFire +=============== +*/ +void LCChargeFire( gentity_t *ent, qboolean secondary ) +{ + if( secondary && ent->client->ps.stats[ STAT_MISC ] <= 0 ) + fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE, + LCANNON_SECONDARY_RADIUS, LCANNON_SECONDARY_SPEED ); + else + fire_luciferCannon( ent, muzzle, forward, + ent->client->ps.stats[ STAT_MISC ] * + LCANNON_DAMAGE / LCANNON_CHARGE_TIME_MAX, + LCANNON_RADIUS, LCANNON_SPEED ); + + ent->client->ps.stats[ STAT_MISC ] = 0; +} + +/* +====================================================================== + +TESLA GENERATOR + +====================================================================== +*/ + + +void teslaFire( gentity_t *self ) +{ + trace_t tr; + vec3_t origin, target; + gentity_t *tent; + + if( !self->enemy ) + return; + + // Move the muzzle from the entity origin up a bit to fire over turrets + VectorMA( muzzle, self->r.maxs[ 2 ], self->s.origin2, origin ); + + // Don't aim for the center, aim at the top of the bounding box + VectorCopy( self->enemy->s.origin, target ); + target[ 2 ] += self->enemy->r.maxs[ 2 ]; + + // Trace to the target entity + trap_Trace( &tr, origin, NULL, NULL, target, self->s.number, MASK_SHOT ); + if( tr.entityNum != self->enemy->s.number ) + return; + + // Client side firing effect + self->s.eFlags |= EF_FIRING; + + // Deal damage + if( self->enemy->takedamage ) + { + vec3_t dir; + + VectorSubtract( target, origin, dir ); + G_Damage( self->enemy, self, self, dir, tr.endpos, + TESLAGEN_DMG, 0, MOD_TESLAGEN ); + } + + // Send tesla zap trail + tent = G_TempEntity( tr.endpos, EV_TESLATRAIL ); + tent->s.generic1 = self->s.number; // src + tent->s.clientNum = self->enemy->s.number; // dest +} + + +/* +====================================================================== + +BUILD GUN + +====================================================================== +*/ +void CheckCkitRepair( gentity_t *ent ) +{ + vec3_t viewOrigin, forward, end; + trace_t tr; + gentity_t *traceEnt; + int bHealth; + + if( ent->client->ps.weaponTime > 0 || + ent->client->buildTimer > 0 ) + return; + + BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); + AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( viewOrigin, 100, forward, end ); + + trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + + if( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->health > 0 && + traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS ) + { + if(BG_Buildable(traceEnt->s.modelindex,NULL)->cuboid) + if(!BG_CuboidAttributes(traceEnt->s.modelindex)->repairable) + return; + + bHealth = BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->health; + + if( traceEnt->health < bHealth ) + { + traceEnt->health += HBUILD_HEALRATE; + if( traceEnt->health >= bHealth ) + { + traceEnt->health = bHealth; + G_AddEvent( ent, EV_BUILD_REPAIRED, 0 ); + } + else + G_AddEvent( ent, EV_BUILD_REPAIR, 0 ); + + ent->client->ps.weaponTime += BG_Weapon( ent->client->ps.weapon )->repeatRate1; + } + } +} + +/* +=============== +cancelBuildFire +=============== +*/ +void cancelBuildFire( gentity_t *ent ) +{ + // Cancel ghost buildable + if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) + { + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + return; + } + + if( ent->client->ps.weapon == WP_ABUILD || + ent->client->ps.weapon == WP_ABUILD2 ) + meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH, + ABUILDER_CLAW_WIDTH, ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW ); +} + +/* +=============== +buildFire +=============== +*/ +void buildFire( gentity_t *ent, dynMenu_t menu ) +{ + buildable_t buildable = ( ent->client->ps.stats[ STAT_BUILDABLE ] + & ~SB_VALID_TOGGLEBIT ); + + if( buildable > BA_NONE ) + { + if( ent->client->buildTimer > 0 ) + { + G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); + return; + } + + if( G_BuildIfValid( ent, buildable, ent->client->cuboidSelection ) ) + { + if( !g_cheats.integer && !g_instantBuild.integer ) + { + ent->client->buildTimer += BG_Buildable( buildable, ent->client->cuboidSelection )->buildTime; + G_RecalcBuildTimer(ent->client); + } + + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + } + + return; + } + + G_TriggerMenu( ent->client->ps.clientNum, menu ); +} + +void slowBlobFire( gentity_t *ent ) +{ + fire_slowBlob( ent, muzzle, forward ); +} + + +/* +====================================================================== + +LEVEL0 + +====================================================================== +*/ + +/* +=============== +CheckVenomAttack +=============== +*/ +qboolean CheckVenomAttack( gentity_t *ent ) +{ + trace_t tr; + gentity_t *traceEnt; + int damage = LEVEL0_BITE_DMG; + + if( ent->client->ps.weaponTime ) + return qfalse; + + // Calculate muzzle point + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + G_WideTrace( &tr, ent, LEVEL0_BITE_RANGE, LEVEL0_BITE_WIDTH, + LEVEL0_BITE_WIDTH, &traceEnt ); + + if( traceEnt == NULL ) + return qfalse; + + if( !traceEnt->takedamage ) + return qfalse; + + if( traceEnt->health <= 0 ) + return qfalse; + + // only allow bites to work against buildings as they are constructing + if( traceEnt->s.eType == ET_BUILDABLE ) + { + if( traceEnt->spawned ) + return qfalse; + + if( traceEnt->buildableTeam == TEAM_ALIENS ) + return qfalse; + } + + if( traceEnt->client ) + { + if( traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + return qfalse; + if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 ) + return qfalse; + } + + // send blood impact + WideBloodSpurt( ent, traceEnt, &tr ); + + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE ); + ent->client->ps.weaponTime += LEVEL0_BITE_REPEAT; + return qtrue; +} + +/* +====================================================================== + +LEVEL1 + +====================================================================== +*/ + +/* +=============== +CheckGrabAttack +=============== +*/ +void CheckGrabAttack( gentity_t *ent ) +{ + trace_t tr; + vec3_t end, dir; + gentity_t *traceEnt; + + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + if( ent->client->ps.weapon == WP_ALEVEL1 ) + VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end ); + else if( ent->client->ps.weapon == WP_ALEVEL1_UPG ) + VectorMA( muzzle, LEVEL1_GRAB_U_RANGE, forward, end ); + + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + if( !traceEnt->takedamage ) + return; + + if( traceEnt->client ) + { + if( traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + return; + + if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 ) + return; + + if( !( traceEnt->client->ps.stats[ STAT_STATE ] & SS_GRABBED ) ) + { + AngleVectors( traceEnt->client->ps.viewangles, dir, NULL, NULL ); + traceEnt->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir ); + + //event for client side grab effect + G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 ); + } + + traceEnt->client->ps.stats[ STAT_STATE ] |= SS_GRABBED; + + if( ent->client->ps.weapon == WP_ALEVEL1 ) + traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_TIME; + else if( ent->client->ps.weapon == WP_ALEVEL1_UPG ) + traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_U_TIME; + } +} + +/* +=============== +poisonCloud +=============== +*/ +void poisonCloud( gentity_t *ent ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE }; + vec3_t mins, maxs; + int i, num; + gentity_t *humanPlayer; + trace_t tr; + + VectorAdd( ent->client->ps.origin, range, maxs ); + VectorSubtract( ent->client->ps.origin, range, mins ); + + G_UnlaggedOn( ent, ent->client->ps.origin, LEVEL1_PCLOUD_RANGE ); + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + humanPlayer = &g_entities[ entityList[ i ] ]; + + if( humanPlayer->client && + humanPlayer->client->pers.teamSelection == TEAM_HUMANS ) + { + trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->s.origin, + humanPlayer->s.number, CONTENTS_SOLID ); + + //can't see target from here + if( tr.entityNum == ENTITYNUM_WORLD ) + continue; + + humanPlayer->client->ps.eFlags |= EF_POISONCLOUDED; + humanPlayer->client->lastPoisonCloudedTime = level.time; + + trap_SendServerCommand( humanPlayer->client->ps.clientNum, + "poisoncloud" ); + } + } + G_UnlaggedOff( ); +} + + +/* +====================================================================== + +LEVEL2 + +====================================================================== +*/ +#define MAX_ZAPS MAX_CLIENTS + +static zap_t zaps[ MAX_ZAPS ]; + +/* +=============== +G_FindZapChainTargets +=============== +*/ +static void G_FindZapChainTargets( zap_t *zap ) +{ + gentity_t *ent = zap->targets[ 0 ]; // the source + int entityList[ MAX_GENTITIES ]; + vec3_t range = { LEVEL2_AREAZAP_CHAIN_RANGE, + LEVEL2_AREAZAP_CHAIN_RANGE, + LEVEL2_AREAZAP_CHAIN_RANGE }; + vec3_t mins, maxs; + int i, num; + gentity_t *enemy; + trace_t tr; + float distance; + + VectorAdd( ent->s.origin, range, maxs ); + VectorSubtract( ent->s.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for( i = 0; i < num; i++ ) + { + enemy = &g_entities[ entityList[ i ] ]; + // don't chain to self; noclippers can be listed, don't chain to them either + if( enemy == ent || ( enemy->client && enemy->client->noclip ) ) + continue; + + distance = Distance( ent->s.origin, enemy->s.origin ); + + if((enemy->client&&enemy->client->ps.stats[STAT_TEAM]==TEAM_HUMANS) || + (enemy->s.eType==ET_BUILDABLE&&BG_Buildable(enemy->s.modelindex,NULL)->team==TEAM_HUMANS && + (!BG_Buildable(enemy->s.modelindex,NULL)->cuboid||BG_CuboidAttributes(enemy->s.modelindex)->zappable)) && + enemy->health>0 && distance <= LEVEL2_AREAZAP_CHAIN_RANGE) + { + + // world-LOS check: trace against the world, ignoring other BODY entities + trap_Trace( &tr, ent->s.origin, NULL, NULL, + enemy->s.origin, ent->s.number, CONTENTS_SOLID ); + + if( tr.entityNum == ENTITYNUM_NONE ) + { + zap->targets[ zap->numTargets ] = enemy; + zap->distances[ zap->numTargets ] = distance; + if( ++zap->numTargets >= LEVEL2_AREAZAP_MAX_TARGETS ) + return; + } + } + } +} + +/* +=============== +G_UpdateZapEffect +=============== +*/ +static void G_UpdateZapEffect( zap_t *zap ) +{ + int i; + int entityNums[ LEVEL2_AREAZAP_MAX_TARGETS + 1 ]; + + entityNums[ 0 ] = zap->creator->s.number; + + for( i = 0; i < zap->numTargets; i++ ) + entityNums[ i + 1 ] = zap->targets[ i ]->s.number; + + BG_PackEntityNumbers( &zap->effectChannel->s, + entityNums, zap->numTargets + 1 ); + + VectorCopy( zap->creator->s.origin, zap->effectChannel->r.currentOrigin ); + trap_LinkEntity( zap->effectChannel ); +} + +/* +=============== +G_CreateNewZap +=============== +*/ +static void G_CreateNewZap( gentity_t *creator, gentity_t *target ) +{ + int i; + zap_t *zap; + + for( i = 0; i < MAX_ZAPS; i++ ) + { + zap = &zaps[ i ]; + if( zap->used ) + continue; + + zap->used = qtrue; + zap->timeToLive = LEVEL2_AREAZAP_TIME; + + zap->creator = creator; + zap->targets[ 0 ] = target; + zap->numTargets = 1; + + // the zap chains only through living entities + if( target->health > 0 ) + { + G_Damage( target, creator, creator, forward, + target->s.origin, LEVEL2_AREAZAP_DMG, + DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, + MOD_LEVEL2_ZAP ); + + G_FindZapChainTargets( zap ); + + for( i = 1; i < zap->numTargets; i++ ) + { + G_Damage( zap->targets[ i ], target, zap->creator, forward, target->s.origin, + LEVEL2_AREAZAP_DMG * ( 1 - pow( (zap->distances[ i ] / + LEVEL2_AREAZAP_CHAIN_RANGE ), LEVEL2_AREAZAP_CHAIN_FALLOFF ) ) + 1, + DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, + MOD_LEVEL2_ZAP ); + } + } + + zap->effectChannel = G_Spawn( ); + zap->effectChannel->s.eType = ET_LEV2_ZAP_CHAIN; + zap->effectChannel->classname = "lev2zapchain"; + G_UpdateZapEffect( zap ); + + return; + } +} + + +/* +=============== +G_UpdateZaps +=============== +*/ +void G_UpdateZaps( int msec ) +{ + int i, j; + zap_t *zap; + + for( i = 0; i < MAX_ZAPS; i++ ) + { + zap = &zaps[ i ]; + if( !zap->used ) + continue; + + zap->timeToLive -= msec; + + // first, the disappearance of players is handled immediately in G_ClearPlayerZapEffects() + + // the deconstruction or gibbing of a directly targeted buildable destroys the whole zap effect + if( zap->timeToLive <= 0 || !zap->targets[ 0 ]->inuse ) + { + G_FreeEntity( zap->effectChannel ); + zap->used = qfalse; + continue; + } + + // the deconstruction or gibbing of chained buildables destroy the appropriate beams + for( j = 1; j < zap->numTargets; j++ ) + { + if( !zap->targets[ j ]->inuse ) + zap->targets[ j-- ] = zap->targets[ --zap->numTargets ]; + } + + G_UpdateZapEffect( zap ); + } +} + +/* +=============== +G_ClearPlayerZapEffects + +called from G_LeaveTeam() and TeleportPlayer() +=============== +*/ +void G_ClearPlayerZapEffects( gentity_t *player ) +{ + int i, j; + zap_t *zap; + + for( i = 0; i < MAX_ZAPS; i++ ) + { + zap = &zaps[ i ]; + if( !zap->used ) + continue; + + // the disappearance of the creator or the first target destroys the whole zap effect + if( zap->creator == player || zap->targets[ 0 ] == player ) + { + G_FreeEntity( zap->effectChannel ); + zap->used = qfalse; + continue; + } + + // the disappearance of chained players destroy the appropriate beams + for( j = 1; j < zap->numTargets; j++ ) + { + if( zap->targets[ j ] == player ) + zap->targets[ j-- ] = zap->targets[ --zap->numTargets ]; + } + } +} + +/* +=============== +areaZapFire +=============== +*/ +void areaZapFire( gentity_t *ent ) +{ + trace_t tr; + gentity_t *traceEnt; + + G_WideTrace( &tr, ent, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, &traceEnt ); + + if( traceEnt == NULL ) + return; + + if( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || + ( traceEnt->s.eType == ET_BUILDABLE && + BG_Buildable( traceEnt->s.modelindex, NULL )->team == TEAM_HUMANS ) && + (!BG_Buildable(traceEnt->s.modelindex,NULL)->cuboid||BG_CuboidAttributes(traceEnt->s.modelindex)->zappable)) + { + G_CreateNewZap( ent, traceEnt ); + } +} + + +/* +====================================================================== + +LEVEL3 + +====================================================================== +*/ + +/* +=============== +CheckPounceAttack +=============== +*/ +qboolean CheckPounceAttack( gentity_t *ent ) +{ + trace_t tr; + gentity_t *traceEnt; + int damage, timeMax, pounceRange, payload; + + if( ent->client->pmext.pouncePayload <= 0 ) + return qfalse; + + // In case the goon lands on his target, he get's one shot after landing + payload = ent->client->pmext.pouncePayload; + if( !( ent->client->ps.pm_flags & PMF_CHARGE ) ) + ent->client->pmext.pouncePayload = 0; + + // Calculate muzzle point + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + // Trace from muzzle to see what we hit + pounceRange = ent->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_RANGE : + LEVEL3_POUNCE_UPG_RANGE; + G_WideTrace( &tr, ent, pounceRange, LEVEL3_POUNCE_WIDTH, + LEVEL3_POUNCE_WIDTH, &traceEnt ); + if( traceEnt == NULL ) + return qfalse; + + // Send blood impact + if( traceEnt->takedamage ) + WideBloodSpurt( ent, traceEnt, &tr ); + + if( !traceEnt->takedamage ) + return qfalse; + + // Deal damage + timeMax = ent->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME : + LEVEL3_POUNCE_TIME_UPG; + damage = payload * LEVEL3_POUNCE_DMG / timeMax; + ent->client->pmext.pouncePayload = 0; + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, + DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE ); + + return qtrue; +} + +void bounceBallFire( gentity_t *ent ) +{ + fire_bounceBall( ent, muzzle, forward ); +} + + +/* +====================================================================== + +LEVEL4 + +====================================================================== +*/ + +/* +=============== +G_ChargeAttack +=============== +*/ +void G_ChargeAttack( gentity_t *ent, gentity_t *victim ) +{ + int damage; + int i; + vec3_t forward; + + if( ent->client->ps.stats[ STAT_MISC ] <= 0 || + !( ent->client->ps.stats[ STAT_STATE ] & SS_CHARGING ) || + ent->client->ps.weaponTime ) + return; + + VectorSubtract( victim->s.origin, ent->s.origin, forward ); + VectorNormalize( forward ); + + if( !victim->takedamage ) + return; + + // For buildables, track the last MAX_TRAMPLE_BUILDABLES_TRACKED buildables + // hit, and do not do damage if the current buildable is in that list + // in order to prevent dancing over stuff to kill it very quickly + if( !victim->client ) + { + for( i = 0; i < MAX_TRAMPLE_BUILDABLES_TRACKED; i++ ) + { + if( ent->client->trampleBuildablesHit[ i ] == victim - g_entities ) + return; + } + + ent->client->trampleBuildablesHit[ + ent->client->trampleBuildablesHitPos++ % MAX_TRAMPLE_BUILDABLES_TRACKED ] = + victim - g_entities; + } + + WideBloodSpurt( ent, victim, NULL ); + + damage = LEVEL4_TRAMPLE_DMG * ent->client->ps.stats[ STAT_MISC ] / + LEVEL4_TRAMPLE_DURATION; + + G_Damage( victim, ent, ent, forward, victim->s.origin, damage, + DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_TRAMPLE ); + + ent->client->ps.weaponTime += LEVEL4_TRAMPLE_REPEAT; +} + +/* +=============== +G_CrushAttack + +Should only be called if there was an impact between a tyrant and another player +=============== +*/ +void G_CrushAttack( gentity_t *ent, gentity_t *victim ) +{ + vec3_t dir; + float jump; + int damage; + + if( !victim->takedamage || + ent->client->ps.origin[ 2 ] + ent->r.mins[ 2 ] < + victim->s.origin[ 2 ] + victim->r.maxs[ 2 ] || + ( victim->client && + victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) ) + return; + + // Deal velocity based damage to target + jump = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->jumpMagnitude; + damage = ( ent->client->pmext.fallVelocity + jump ) * + -LEVEL4_CRUSH_DAMAGE_PER_V; + + if( damage < 0 ) + damage = 0; + + // Players also get damaged periodically + if( victim->client && + ent->client->lastCrushTime + LEVEL4_CRUSH_REPEAT < level.time ) + { + ent->client->lastCrushTime = level.time; + damage += LEVEL4_CRUSH_DAMAGE; + } + + if( damage < 1 ) + return; + + // Crush the victim over a period of time + VectorSubtract( victim->s.origin, ent->client->ps.origin, dir ); + G_Damage( victim, ent, ent, dir, victim->s.origin, damage, + DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_CRUSH ); +} + +void rantBombFire( gentity_t *ent ) +{ + fire_rantBomb( ent, muzzle, forward ); +} + +//====================================================================== + +/* +=============== +CalcMuzzlePoint + +set muzzle location relative to pivoting eye +=============== +*/ +void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) +{ + vec3_t normal; + + VectorCopy( ent->client->ps.origin, muzzlePoint ); + BG_GetClientNormal( &ent->client->ps, normal ); + VectorMA( muzzlePoint, ent->client->ps.viewheight, normal, muzzlePoint ); + VectorMA( muzzlePoint, 1, forward, muzzlePoint ); + // snap to integer coordinates for more efficient network bandwidth usage + SnapVector( muzzlePoint ); +} + +/* +=============== +FireWeapon3 +=============== +*/ +void FireWeapon3( gentity_t *ent ) +{ + if( ent->client ) + { + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + } + else + { + AngleVectors( ent->s.angles2, forward, right, up ); + VectorCopy( ent->s.pos.trBase, muzzle ); + } + + // fire the specific weapon + switch( ent->s.weapon ) + { + case WP_ALEVEL3_UPG: + bounceBallFire( ent ); + break; + + case WP_ALEVEL4: + rantBombFire( ent ); + break; + + case WP_ABUILD2: + slowBlobFire( ent ); + break; + + default: + break; + } +} + +/* +=============== +FireWeapon2 +=============== +*/ +void FireWeapon2( gentity_t *ent ) +{ + if( ent->client ) + { + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + } + else + { + AngleVectors( ent->s.angles2, forward, right, up ); + VectorCopy( ent->s.pos.trBase, muzzle ); + } + + // fire the specific weapon + switch( ent->s.weapon ) + { + case WP_ALEVEL1_UPG: + poisonCloud( ent ); + break; + + case WP_LUCIFER_CANNON: + LCChargeFire( ent, qtrue ); + break; + + case WP_ALEVEL2_UPG: + areaZapFire( ent ); + break; + + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + cancelBuildFire( ent ); + break; + default: + break; + } +} + +/* +=============== +FireWeapon +=============== +*/ +void FireWeapon( gentity_t *ent ) +{ + if( ent->client ) + { + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + } + else + { + AngleVectors( ent->turretAim, forward, right, up ); + VectorCopy( ent->s.pos.trBase, muzzle ); + } + + // fire the specific weapon + switch( ent->s.weapon ) + { + case WP_ALEVEL1: + meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH, + LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW ); + break; + case WP_ALEVEL1_UPG: + meleeAttack( ent, LEVEL1_CLAW_U_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH, + LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW ); + break; + case WP_ALEVEL3: + meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_WIDTH, + LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW ); + break; + case WP_ALEVEL3_UPG: + meleeAttack( ent, LEVEL3_CLAW_UPG_RANGE, LEVEL3_CLAW_WIDTH, + LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW ); + break; + case WP_ALEVEL2: + meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_WIDTH, + LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); + break; + case WP_ALEVEL2_UPG: + meleeAttack( ent, LEVEL2_CLAW_U_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_WIDTH, + LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); + break; + case WP_ALEVEL4: + meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, + LEVEL4_CLAW_HEIGHT, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW ); + break; + + case WP_BLASTER: + blasterFire( ent ); + break; + case WP_MACHINEGUN: + bulletFire( ent, RIFLE_SPREAD, RIFLE_DMG, MOD_MACHINEGUN ); + break; + case WP_SHOTGUN: + shotgunFire( ent ); + break; + case WP_CHAINGUN: + bulletFire( ent, CHAINGUN_SPREAD, CHAINGUN_DMG, MOD_CHAINGUN ); + break; + case WP_FLAMER: + flamerFire( ent ); + break; + case WP_PULSE_RIFLE: + pulseRifleFire( ent ); + break; + case WP_MASS_DRIVER: + massDriverFire( ent ); + break; + case WP_LUCIFER_CANNON: + LCChargeFire( ent, qfalse ); + break; + case WP_LAS_GUN: + lasGunFire( ent ); + break; + case WP_PAIN_SAW: + painSawFire( ent ); + break; + case WP_GRENADE: + throwGrenade( ent ); + break; + + case WP_LOCKBLOB_LAUNCHER: + lockBlobLauncherFire( ent ); + break; + case WP_HIVE: + hiveFire( ent ); + break; + case WP_TESLAGEN: + teslaFire( ent ); + break; + case WP_MGTURRET: + bulletFire( ent, MGTURRET_SPREAD, MGTURRET_DMG, MOD_MGTURRET ); + break; + + case WP_ABUILD: + case WP_ABUILD2: + buildFire( ent, MN_A_BUILD ); + break; + case WP_HBUILD: + buildFire( ent, MN_H_BUILD ); + break; + default: + break; + } +} + diff --git a/src/game/tremulous.h b/src/game/tremulous.h new file mode 100644 index 0000000..effa064 --- /dev/null +++ b/src/game/tremulous.h @@ -0,0 +1,718 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +/* + * ALIEN weapons + * + * _REPEAT - time in msec until the weapon can be used again + * _DMG - amount of damage the weapon does + * + * ALIEN_WDMG_MODIFIER - overall damage modifier for coarse tuning + * + */ + +#define ALIEN_WDMG_MODIFIER 1.0f +#define ADM(d) ((int)((float)d*ALIEN_WDMG_MODIFIER)) + +#define ABUILDER_BUILD_REPEAT 500 +#define ABUILDER_CLAW_DMG ADM(20) +#define ABUILDER_CLAW_RANGE 64.0f +#define ABUILDER_CLAW_WIDTH 4.0f +#define ABUILDER_CLAW_REPEAT 1000 +#define ABUILDER_CLAW_K_SCALE 1.0f +#define ABUILDER_BLOB_DMG ADM(4) +#define ABUILDER_BLOB_REPEAT 1000 +#define ABUILDER_BLOB_SPEED 800.0f +#define ABUILDER_BLOB_SPEED_MOD 0.5f +#define ABUILDER_BLOB_TIME 2000 + +#define LEVEL0_BITE_DMG ADM(36) +#define LEVEL0_BITE_RANGE 64.0f +#define LEVEL0_BITE_WIDTH 6.0f +#define LEVEL0_BITE_REPEAT 500 +#define LEVEL0_BITE_K_SCALE 1.0f + +#define LEVEL1_CLAW_DMG ADM(32) +#define LEVEL1_CLAW_RANGE 64.0f +#define LEVEL1_CLAW_U_RANGE LEVEL1_CLAW_RANGE + 3.0f +#define LEVEL1_CLAW_WIDTH 10.0f +#define LEVEL1_CLAW_REPEAT 600 +#define LEVEL1_CLAW_U_REPEAT 500 +#define LEVEL1_CLAW_K_SCALE 1.0f +#define LEVEL1_CLAW_U_K_SCALE 1.0f +#define LEVEL1_GRAB_RANGE 96.0f +#define LEVEL1_GRAB_U_RANGE LEVEL1_GRAB_RANGE + 3.0f +#define LEVEL1_GRAB_TIME 300 +#define LEVEL1_GRAB_U_TIME 300 +#define LEVEL1_PCLOUD_DMG ADM(4) +#define LEVEL1_PCLOUD_RANGE 120.0f +#define LEVEL1_PCLOUD_REPEAT 2000 +#define LEVEL1_PCLOUD_TIME 10000 +#define LEVEL1_REGEN_MOD 2.0f +#define LEVEL1_UPG_REGEN_MOD 3.0f +#define LEVEL1_REGEN_SCOREINC AVM(100) // score added for healing per 10s +#define LEVEL1_UPG_REGEN_SCOREINC AVM(200) + +#define LEVEL2_CLAW_DMG ADM(40) +#define LEVEL2_CLAW_RANGE 80.0f +#define LEVEL2_CLAW_U_RANGE LEVEL2_CLAW_RANGE + 2.0f +#define LEVEL2_CLAW_WIDTH 14.0f +#define LEVEL2_CLAW_REPEAT 500 +#define LEVEL2_CLAW_K_SCALE 1.0f +#define LEVEL2_CLAW_U_REPEAT 400 +#define LEVEL2_CLAW_U_K_SCALE 1.0f +#define LEVEL2_AREAZAP_DMG 6 //ADM(60) +#define LEVEL2_AREAZAP_RANGE 200.0f +#define LEVEL2_AREAZAP_CHAIN_RANGE 150.0f +#define LEVEL2_AREAZAP_CHAIN_FALLOFF 8.0f +#define LEVEL2_AREAZAP_WIDTH 15.0f +#define LEVEL2_AREAZAP_REPEAT 100 //1500 +#define LEVEL2_AREAZAP_TIME 100 //1000 +#define LEVEL2_AREAZAP_MAX_TARGETS 5 +#define LEVEL2_WALLJUMP_MAXSPEED 1000.0f + +#define LEVEL3_CLAW_DMG ADM(80) +#define LEVEL3_CLAW_RANGE 80.0f +#define LEVEL3_CLAW_UPG_RANGE LEVEL3_CLAW_RANGE + 3.0f +#define LEVEL3_CLAW_WIDTH 12.0f +#define LEVEL3_CLAW_REPEAT 900 +#define LEVEL3_CLAW_K_SCALE 1.0f +#define LEVEL3_CLAW_U_REPEAT 800 +#define LEVEL3_CLAW_U_K_SCALE 1.0f +#define LEVEL3_POUNCE_DMG ADM(75) //ADM(100) +#define LEVEL3_POUNCE_RANGE 48.0f +#define LEVEL3_POUNCE_UPG_RANGE LEVEL3_POUNCE_RANGE + 3.0f +#define LEVEL3_POUNCE_WIDTH 14.0f +#define LEVEL3_POUNCE_TIME 800 // msec for full Dragoon pounce +#define LEVEL3_POUNCE_TIME_UPG 800 // msec for full Adv. Dragoon pounce +#define LEVEL3_POUNCE_TIME_MIN 200 // msec before which pounce cancels +#define LEVEL3_POUNCE_REPEAT 400 // msec before a new pounce starts +#define LEVEL3_POUNCE_SPEED_MOD 0.75f // walking speed modifier for pounce charging +#define LEVEL3_POUNCE_JUMP_MAG 700 // Dragoon pounce jump power +#define LEVEL3_POUNCE_JUMP_MAG_UPG 800 // Adv. Dragoon pounce jump power +#define LEVEL3_BOUNCEBALL_DMG ADM(110) +#define LEVEL3_BOUNCEBALL_REPEAT 1200 +#define LEVEL3_BOUNCEBALL_SPEED 1000.0f +#define LEVEL3_BOUNCEBALL_RADIUS 75 +#define LEVEL3_BOUNCEBALL_REGEN 15000 // msec until new barb + +#define LEVEL4_CLAW_DMG ADM(100) +#define LEVEL4_CLAW_RANGE 100.0f +#define LEVEL4_CLAW_WIDTH 14.0f +#define LEVEL4_CLAW_HEIGHT 20.0f +#define LEVEL4_CLAW_REPEAT 800 +#define LEVEL4_CLAW_K_SCALE 1.0f + +#define LEVEL4_TRAMPLE_DMG ADM(111) +#define LEVEL4_TRAMPLE_SPEED 2.0f +#define LEVEL4_TRAMPLE_CHARGE_MIN 375 // minimum msec to start a charge +#define LEVEL4_TRAMPLE_CHARGE_MAX 1000 // msec to maximum charge stored +#define LEVEL4_TRAMPLE_CHARGE_TRIGGER 3000 // msec charge starts on its own +#define LEVEL4_TRAMPLE_DURATION 3000 // msec trample lasts on full charge +#define LEVEL4_TRAMPLE_STOP_PENALTY 1 // charge lost per msec when stopped +#define LEVEL4_TRAMPLE_REPEAT 100 // msec before a trample will rehit a player + +#define LEVEL4_CRUSH_DAMAGE_PER_V 0.5f // damage per falling velocity +#define LEVEL4_CRUSH_DAMAGE 120 // to players only +#define LEVEL4_CRUSH_REPEAT 500 // player damage repeat + +#define LEVEL4_BOMB_DMG 150 +#define LEVEL4_BOMB_RADIUS 400 +#define LEVEL4_BOMB_SPEED 500.0f +#define LEVEL4_BOMB_REGEN 120000 // minimum time between getting a bomb + +/* + * ALIEN classes + * + * _SPEED - fraction of Q3A run speed the class can move + * _REGEN - health per second regained + * + * ALIEN_HLTH_MODIFIER - overall health modifier for coarse tuning + * + */ + +#define ALIEN_HLTH_MODIFIER 1.0f +#define AHM(h) ((int)((float)h*ALIEN_HLTH_MODIFIER)) + +#define ALIEN_VALUE_MODIFIER 1.0f +#define AVM(h) ((int)((float)h*ALIEN_VALUE_MODIFIER)) + +#define ABUILDER_SPEED 0.9f +#define ABUILDER_VALUE AVM(240) +#define ABUILDER_HEALTH AHM(50) +#define ABUILDER_REGEN (0.04f * ABUILDER_HEALTH) +#define ABUILDER_COST 0 + +#define ABUILDER_UPG_SPEED 0.9f +#define ABUILDER_UPG_VALUE AVM(300) +#define ABUILDER_UPG_HEALTH AHM(75) +#define ABUILDER_UPG_REGEN (0.04f * ABUILDER_UPG_HEALTH) +#define ABUILDER_UPG_COST 0 + +#define LEVEL0_SPEED 1.4f +#define LEVEL0_VALUE AVM(180) +#define LEVEL0_HEALTH AHM(25) +#define LEVEL0_REGEN (0.05f * LEVEL0_HEALTH) +#define LEVEL0_COST 0 + +#define LEVEL1_SPEED 1.25f +#define LEVEL1_VALUE AVM(270) +#define LEVEL1_HEALTH AHM(60) +#define LEVEL1_REGEN (0.03f * LEVEL1_HEALTH) +#define LEVEL1_COST 1 + +#define LEVEL1_UPG_SPEED 1.25f +#define LEVEL1_UPG_VALUE AVM(330) +#define LEVEL1_UPG_HEALTH AHM(80) +#define LEVEL1_UPG_REGEN (0.03f * LEVEL1_UPG_HEALTH) +#define LEVEL1_UPG_COST 1 + +#define LEVEL2_SPEED 1.2f +#define LEVEL2_VALUE AVM(420) +#define LEVEL2_HEALTH AHM(150) +#define LEVEL2_REGEN (0.03f * LEVEL2_HEALTH) +#define LEVEL2_COST 1 + +#define LEVEL2_UPG_SPEED 1.2f +#define LEVEL2_UPG_VALUE AVM(540) +#define LEVEL2_UPG_HEALTH AHM(175) +#define LEVEL2_UPG_REGEN (0.03f * LEVEL2_UPG_HEALTH) +#define LEVEL2_UPG_COST 1 + +#define LEVEL3_SPEED 1.1f +#define LEVEL3_VALUE AVM(600) +#define LEVEL3_HEALTH AHM(200) +#define LEVEL3_REGEN (0.03f * LEVEL3_HEALTH) +#define LEVEL3_COST 1 + +#define LEVEL3_UPG_SPEED 1.1f +#define LEVEL3_UPG_VALUE AVM(720) +#define LEVEL3_UPG_HEALTH AHM(250) +#define LEVEL3_UPG_REGEN (0.03f * LEVEL3_UPG_HEALTH) +#define LEVEL3_UPG_COST 1 + +#define LEVEL4_SPEED 1.2f +#define LEVEL4_VALUE AVM(960) +#define LEVEL4_HEALTH AHM(350) +#define LEVEL4_REGEN (0.025f * LEVEL4_HEALTH) +#define LEVEL4_COST 2 + +/* + * ALIEN buildables + * + * _BP - build points required for this buildable + * _BT - build time required for this buildable + * _REGEN - the amount of health per second regained + * _SPLASHDAMGE - the amount of damage caused by this buildable when melting + * _SPLASHRADIUS - the radius around which it does this damage + * + * CREEP_BASESIZE - the maximum distance a buildable can be from an egg/overmind + * ALIEN_BHLTH_MODIFIER - overall health modifier for coarse tuning + * + */ + +#define ALIEN_BHLTH_MODIFIER 1.0f +#define ABHM(h) ((int)((float)h*ALIEN_BHLTH_MODIFIER)) +#define ALIEN_BVALUE_MODIFIER 90.0f +#define ABVM(h) ((int)((float)h*ALIEN_BVALUE_MODIFIER)) + +#define CREEP_BASESIZE 700 +#define CREEP_TIMEOUT 1000 +#define CREEP_MODIFIER 0.5f +#define CREEP_ARMOUR_MODIFIER 0.75f +#define CREEP_SCALEDOWN_TIME 3000 + +#define PCLOUD_MODIFIER 0.5f +#define PCLOUD_ARMOUR_MODIFIER 0.75f + +#define ASPAWN_BP 10 +#define ASPAWN_BT 10000 +#define ASPAWN_HEALTH ABHM(250) +#define ASPAWN_REGEN 8 +#define ASPAWN_SPLASHDAMAGE 50 +#define ASPAWN_SPLASHRADIUS 100 +#define ASPAWN_CREEPSIZE 120 +#define ASPAWN_VALUE ABVM(ASPAWN_BP) + +#define BARRICADE_BP 8 +#define BARRICADE_BT 15000 +#define BARRICADE_HEALTH ABHM(300) +#define BARRICADE_REGEN 14 +#define BARRICADE_SPLASHDAMAGE 50 +#define BARRICADE_SPLASHRADIUS 100 +#define BARRICADE_CREEPSIZE 120 +#define BARRICADE_SHRINKPROP 0.25f +#define BARRICADE_SHRINKTIMEOUT 500 +#define BARRICADE_VALUE ABVM(BARRICADE_BP) + +#define BOOSTER_BP 12 +#define BOOSTER_BT 10000 +#define BOOSTER_HEALTH ABHM(150) +#define BOOSTER_REGEN 8 +#define BOOSTER_SPLASHDAMAGE 50 +#define BOOSTER_SPLASHRADIUS 100 +#define BOOSTER_CREEPSIZE 120 +#define BOOSTER_REGEN_MOD 3.0f +#define BOOSTER_VALUE ABVM(BOOSTER_BP) +#define BOOST_TIME 20000 +#define BOOST_WARN_TIME 15000 + +#define ACIDTUBE_BP 8 +#define ACIDTUBE_BT 10000 +#define ACIDTUBE_HEALTH ABHM(125) +#define ACIDTUBE_REGEN 10 +#define ACIDTUBE_SPLASHDAMAGE 50 +#define ACIDTUBE_SPLASHRADIUS 100 +#define ACIDTUBE_CREEPSIZE 120 +#define ACIDTUBE_DAMAGE 8 +#define ACIDTUBE_RANGE 300.0f +#define ACIDTUBE_REPEAT 300 +#define ACIDTUBE_REPEAT_ANIM 2000 +#define ACIDTUBE_VALUE ABVM(ACIDTUBE_BP) + +#define HIVE_BP 12 +#define HIVE_BT 15000 +#define HIVE_HEALTH ABHM(125) +#define HIVE_REGEN 10 +#define HIVE_SPLASHDAMAGE 30 +#define HIVE_SPLASHRADIUS 200 +#define HIVE_CREEPSIZE 120 +#define HIVE_SENSE_RANGE 500.0f +#define HIVE_LIFETIME 3000 +#define HIVE_REPEAT 3000 +#define HIVE_K_SCALE 0.0f +#define HIVE_DMG 80 +#define HIVE_SPEED 320.0f +#define HIVE_DIR_CHANGE_PERIOD 500 +#define HIVE_VALUE ABVM(HIVE_BP) + +#define TRAPPER_BP 8 +#define TRAPPER_BT 7500 +#define TRAPPER_HEALTH ABHM(50) +#define TRAPPER_REGEN 6 +#define TRAPPER_SPLASHDAMAGE 15 +#define TRAPPER_SPLASHRADIUS 100 +#define TRAPPER_CREEPSIZE 30 +#define TRAPPER_RANGE 400 +#define TRAPPER_REPEAT 1000 +#define TRAPPER_VALUE ABVM(TRAPPER_BP) +#define LOCKBLOB_SPEED 650.0f +#define LOCKBLOB_LOCKTIME 5000 +#define LOCKBLOB_DOT 0.85f // max angle = acos( LOCKBLOB_DOT ) +#define LOCKBLOB_K_SCALE 1.0f + +#define OVERMIND_BP 0 +#define OVERMIND_BT 20000 +#define OVERMIND_HEALTH ABHM(750) +#define OVERMIND_REGEN 6 +#define OVERMIND_SPLASHDAMAGE 15 +#define OVERMIND_SPLASHRADIUS 300 +#define OVERMIND_CREEPSIZE 120 +#define OVERMIND_ATTACK_RANGE 150.0f +#define OVERMIND_ATTACK_REPEAT 1000 +#define OVERMIND_VALUE ABVM(30) + +/* + * ALIEN misc + * + * ALIENSENSE_RANGE - the distance alien sense is useful for + * + */ + +#define ALIENSENSE_RANGE 1000.0f +#define REGEN_BOOST_RANGE 200.0f + +#define ALIEN_POISON_TIME 10000 +#define ALIEN_POISON_DMG 5 +#define ALIEN_POISON_DIVIDER (1.0f/1.32f) //about 1.0/(time`th root of damage) + +#define ALIEN_SPAWN_REPEAT_TIME 10000 + +#define ALIEN_REGEN_DAMAGE_TIME 2000 //msec since damage that regen starts again +#define ALIEN_REGEN_NOCREEP_MOD (1.0f/3.0f) //regen off creep + +#define ALIEN_MAX_FRAGS 9 +#define ALIEN_MAX_CREDITS (ALIEN_MAX_FRAGS*ALIEN_CREDITS_PER_KILL) +#define ALIEN_CREDITS_PER_KILL 400 +#define ALIEN_TK_SUICIDE_PENALTY 350 + +/* + * HUMAN weapons + * + * _REPEAT - time between firings + * _RELOAD - time needed to reload + * _PRICE - amount in credits weapon costs + * + * HUMAN_WDMG_MODIFIER - overall damage modifier for coarse tuning + * + */ + +#define HUMAN_WDMG_MODIFIER 1.0f +#define HDM(d) ((int)((float)d*HUMAN_WDMG_MODIFIER)) + +#define BLASTER_REPEAT 600 +#define BLASTER_K_SCALE 1.0f +#define BLASTER_SPREAD 200 +#define BLASTER_SPEED 1400 +#define BLASTER_DMG HDM(10) +#define BLASTER_SIZE 5 + +#define RIFLE_CLIPSIZE 30 +#define RIFLE_MAXCLIPS 6 +#define RIFLE_REPEAT 90 +#define RIFLE_K_SCALE 1.0f +#define RIFLE_RELOAD 2000 +#define RIFLE_PRICE 0 +#define RIFLE_SPREAD 200 +#define RIFLE_DMG HDM(5) + +#define PAINSAW_PRICE 100 +#define PAINSAW_REPEAT 75 +#define PAINSAW_K_SCALE 1.0f +#define PAINSAW_DAMAGE HDM(11) +#define PAINSAW_RANGE 64.0f +#define PAINSAW_WIDTH 0.0f +#define PAINSAW_HEIGHT 8.0f + +#define GRENADE_PRICE 200 +#define GRENADE_REPEAT 0 +#define GRENADE_K_SCALE 1.0f +#define GRENADE_DAMAGE HDM(310) +#define GRENADE_RANGE 192.0f +#define GRENADE_SPEED 400.0f + +#define SHOTGUN_PRICE 150 +#define SHOTGUN_SHELLS 8 +#define SHOTGUN_PELLETS 11 //used to sync server and client side +#define SHOTGUN_MAXCLIPS 3 +#define SHOTGUN_REPEAT 1000 +#define SHOTGUN_K_SCALE 1.0f +#define SHOTGUN_RELOAD 2000 +#define SHOTGUN_SPREAD 700 +#define SHOTGUN_DMG HDM(5) +#define SHOTGUN_RANGE (8192 * 12) + +#define LASGUN_PRICE 250 +#define LASGUN_AMMO 200 +#define LASGUN_REPEAT 200 +#define LASGUN_K_SCALE 1.0f +#define LASGUN_RELOAD 2000 +#define LASGUN_DAMAGE HDM(9) + +#define MDRIVER_PRICE 350 +#define MDRIVER_CLIPSIZE 5 +#define MDRIVER_MAXCLIPS 4 +#define MDRIVER_DMG HDM(40) +#define MDRIVER_REPEAT 1000 +#define MDRIVER_K_SCALE 1.0f +#define MDRIVER_RELOAD 2000 + +#define CHAINGUN_PRICE 400 +#define CHAINGUN_BULLETS 300 +#define CHAINGUN_REPEAT 80 +#define CHAINGUN_K_SCALE 1.0f +#define CHAINGUN_SPREAD 900 +#define CHAINGUN_DMG HDM(6) + +#define FLAMER_PRICE 400 +#define FLAMER_GAS 200 +#define FLAMER_REPEAT 200 +#define FLAMER_K_SCALE 0.0f +#define FLAMER_DMG HDM(20) +#define FLAMER_SPLASHDAMAGE HDM(10) +#define FLAMER_RADIUS 50 // splash radius +#define FLAMER_SIZE 15 // missile bounding box +#define FLAMER_LIFETIME 700.0f +#define FLAMER_SPEED 500.0f +#define FLAMER_LAG 0.65f // the amount of player velocity that is added to the fireball + +#define PRIFLE_PRICE 450 +#define PRIFLE_CLIPS 40 +#define PRIFLE_MAXCLIPS 5 +#define PRIFLE_REPEAT 100 +#define PRIFLE_K_SCALE 1.0f +#define PRIFLE_RELOAD 2000 +#define PRIFLE_DMG HDM(9) +#define PRIFLE_SPEED 1200 +#define PRIFLE_SIZE 5 + +#define LCANNON_PRICE 600 +#define LCANNON_AMMO 90 +#define LCANNON_K_SCALE 1.0f +#define LCANNON_REPEAT 500 +#define LCANNON_RELOAD 0 +#define LCANNON_DAMAGE HDM(265) +#define LCANNON_RADIUS 150 +#define LCANNON_SIZE 5 // missile bounding box radius +#define LCANNON_SECONDARY_DAMAGE HDM(27) +#define LCANNON_SECONDARY_RADIUS 75 +#define LCANNON_SECONDARY_SPEED 350 +#define LCANNON_SECONDARY_RELOAD 2000 +#define LCANNON_SECONDARY_REPEAT 666 +#define LCANNON_SPEED 350 +#define LCANNON_TOTAL_CHARGE 255 +#define LCANNON_CHARGE_TIME_MIN 50 +#define LCANNON_CHARGE_TIME_MAX 2000 +#define LCANNON_CHARGE_TIME_WARN 1500 +#define LCANNON_CHARGE_AMMO 10 + +#define HBUILD_PRICE 0 +#define HBUILD_REPEAT 1000 +#define HBUILD_HEALRATE 18 + +/* + * HUMAN upgrades + */ + +#define LIGHTARMOUR_PRICE 70 +#define LIGHTARMOUR_POISON_PROTECTION 1 +#define LIGHTARMOUR_PCLOUD_PROTECTION 1000 + +#define HELMET_RANGE 1000.0f +#define HELMET_MK1_PRICE 90 +#define HELMET_MK1_POISON_PROTECTION 0 +#define HELMET_MK1_PCLOUD_PROTECTION 500 +#define HELMET_MK2_PRICE HELMET_MK1_PRICE +#define HELMET_MK2_POISON_PROTECTION 1 +#define HELMET_MK2_PCLOUD_PROTECTION 1000 + +#define MEDKIT_PRICE 0 + +#define BATTPACK_PRICE 100 +#define BATTPACK_MODIFIER 1.5f //modifier for extra energy storage available + +#define JETPACK_PRICE 120 +#define JETPACK_FLOAT_SPEED 128.0f //up movement speed +#define JETPACK_SINK_SPEED 192.0f //down movement speed +#define JETPACK_DISABLE_TIME 1000 //time to disable the jetpack when player damaged +#define JETPACK_DISABLE_CHANCE 0.3f +#define JETPACK_FUEL_FULL 6000 //can't exceed 32767 +#define JETPACK_FUEL_LOW 1500 +#define JETPACK_FUEL_USAGE 10 //every 100ms +#define JETPACK_FUEL_JUMP 85 + +#define BSUIT_PRICE 400 +#define BSUIT_POISON_PROTECTION 3 +#define BSUIT_PCLOUD_PROTECTION 3000 + +#define MGCLIP_PRICE 0 + +#define CGAMMO_PRICE 0 + +#define GAS_PRICE 0 + +#define MEDKIT_POISON_IMMUNITY_TIME 15000 +#define MEDKIT_STARTUP_TIME 4000 +#define MEDKIT_STARTUP_SPEED 5 + +#define BIORES_PRICE 300 +#define BIORES_EQUATION (1-hp)*sqrt(hp)*5+0.2 //health regenerated per second, note: hp is a floating-point value 0 <= hp <= 1 +#define BIORES_POISON_MODIFIER 0.3f + +/* + * HUMAN buildables + * + * _BP - build points required for this buildable + * _BT - build time required for this buildable + * _SPLASHDAMGE - the amount of damage caused by this buildable when it blows up + * _SPLASHRADIUS - the radius around which it does this damage + * + * REACTOR_BASESIZE - the maximum distance a buildable can be from an reactor + * REPEATER_BASESIZE - the maximum distance a buildable can be from a repeater + * HUMAN_BHLTH_MODIFIER - overall health modifier for coarse tuning + * + */ + +#define HUMAN_BHLTH_MODIFIER 1.0f +#define HBHM(h) ((int)((float)h*HUMAN_BHLTH_MODIFIER)) +#define HUMAN_BVALUE_MODIFIER 240.0f +#define HBVM(h) ((int)((float)h*(float)HUMAN_BVALUE_MODIFIER)) // remember these are measured in credits not frags (c.f. ALIEN_CREDITS_PER_KILL) + +#define REACTOR_BASESIZE 1000 +#define REPEATER_BASESIZE 500 +#define HUMAN_DETONATION_DELAY 5000 + +#define HSPAWN_BP 10 +#define HSPAWN_BT 15000 +#define HSPAWN_HEALTH HBHM(310) +#define HSPAWN_SPLASHDAMAGE 50 +#define HSPAWN_SPLASHRADIUS 100 +#define HSPAWN_VALUE HBVM(HSPAWN_BP) + +#define MEDISTAT_BP 8 +#define MEDISTAT_BT 15000 +#define MEDISTAT_HEALTH HBHM(190) +#define MEDISTAT_SPLASHDAMAGE 50 +#define MEDISTAT_SPLASHRADIUS 100 +#define MEDISTAT_VALUE HBVM(MEDISTAT_BP) + +#define MGTURRET_BP 8 +#define MGTURRET_BT 15000 +#define MGTURRET_HEALTH HBHM(190) +#define MGTURRET_SPLASHDAMAGE 100 +#define MGTURRET_SPLASHRADIUS 100 +#define MGTURRET_ANGULARSPEED 12 +#define MGTURRET_ACCURACY_TO_FIRE 0 +#define MGTURRET_VERTICALCAP 30 // +/- maximum pitch +#define MGTURRET_REPEAT 150 +#define MGTURRET_K_SCALE 1.0f +#define MGTURRET_RANGE 400.0f +#define MGTURRET_SPREAD 200 +#define MGTURRET_DMG HDM(8) +#define MGTURRET_SPINUP_TIME 750 // time between target sighted and fire +#define MGTURRET_VALUE HBVM(MGTURRET_BP) + +#define TESLAGEN_BP 10 +#define TESLAGEN_BT 20000 +#define TESLAGEN_HEALTH HBHM(220) +#define TESLAGEN_SPLASHDAMAGE 50 +#define TESLAGEN_SPLASHRADIUS 100 +#define TESLAGEN_REPEAT 250 +#define TESLAGEN_K_SCALE 1.0f //4.0f +#define TESLAGEN_RANGE 200 +#define TESLAGEN_DMG HDM(10) +#define TESLAGEN_VALUE HBVM(TESLAGEN_BP) + +#define DC_BP 8 +#define DC_BT 15000 +#define DC_HEALTH HBHM(190) +#define DC_SPLASHDAMAGE 50 +#define DC_SPLASHRADIUS 100 +#define DC_ATTACK_PERIOD 10000 // how often to spam "under attack" +#define DC_HEALRATE 4 +#define DC_RANGE 1000 +#define DC_VALUE HBVM(DC_BP) + +#define ARMOURY_BP 10 +#define ARMOURY_BT 15000 +#define ARMOURY_HEALTH HBHM(420) +#define ARMOURY_SPLASHDAMAGE 50 +#define ARMOURY_SPLASHRADIUS 100 +#define ARMOURY_VALUE HBVM(ARMOURY_BP) + +#define REACTOR_BP 0 +#define REACTOR_BT 30000 +#define REACTOR_HEALTH HBHM(930) +#define REACTOR_SPLASHDAMAGE 200 +#define REACTOR_SPLASHRADIUS 300 +#define REACTOR_ATTACK_RANGE 100.0f +#define REACTOR_ATTACK_REPEAT 1000 +#define REACTOR_ATTACK_DAMAGE 40 +#define REACTOR_ATTACK_DCC_REPEAT 1000 +#define REACTOR_ATTACK_DCC_RANGE 150.0f +#define REACTOR_ATTACK_DCC_DAMAGE 40 +#define REACTOR_VALUE HBVM(30) + +#define REPEATER_BP 4 +#define REPEATER_BT 15000 +#define REPEATER_HEALTH HBHM(250) +#define REPEATER_SPLASHDAMAGE 50 +#define REPEATER_SPLASHRADIUS 100 +#define REPEATER_VALUE HBVM(REPEATER_BP) + +/* + * HUMAN misc + */ + +#define HUMAN_SPRINT_MODIFIER 1.2f +#define HUMAN_JOG_MODIFIER 1.0f +#define HUMAN_BACK_MODIFIER 0.8f +#define HUMAN_SIDE_MODIFIER 0.9f +#define HUMAN_LAND_FRICTION 3.0f + +#define STAMINA_STOP_RESTORE 30 +#define STAMINA_WALK_RESTORE 15 +#define STAMINA_MEDISTAT_RESTORE 30 // stacked on STOP or WALK +#define STAMINA_SPRINT_TAKE 6 +#define STAMINA_JUMP_TAKE 250 +#define STAMINA_DODGE_TAKE 250 +#define STAMINA_MAX 1000 +#define STAMINA_BREATHING_LEVEL 0 +#define STAMINA_SLOW_LEVEL -500 +#define STAMINA_BLACKOUT_LEVEL -800 + +#define HUMAN_SPAWN_REPEAT_TIME 10000 +#define HUMAN_REGEN_DAMAGE_TIME 2000 //msec since damage before dcc repairs + +#define HUMAN_MAX_CREDITS 2000 +#define HUMAN_TK_SUICIDE_PENALTY 150 + +#define HUMAN_BUILDER_SCOREINC 50 // builders receive this many points every 10 seconds +#define ALIEN_BUILDER_SCOREINC AVM(100) // builders receive this many points every 10 seconds + +#define HUMAN_BUILDABLE_INACTIVE_TIME 90000 + +/* + * Misc + */ + +#define MIN_FALL_DISTANCE 30.0f //the fall distance at which fall damage kicks in +#define MAX_FALL_DISTANCE 120.0f //the fall distance at which maximum damage is dealt +#define AVG_FALL_DISTANCE ((MIN_FALL_DISTANCE+MAX_FALL_DISTANCE)/2.0f) + +#define DEFAULT_FREEKILL_PERIOD "120" //seconds +#define FREEKILL_ALIEN ALIEN_CREDITS_PER_KILL +#define FREEKILL_HUMAN LEVEL0_VALUE + +#define DEFAULT_ALIEN_BUILDPOINTS "150" +#define DEFAULT_ALIEN_QUEUE_TIME "12000" +#define DEFAULT_ALIEN_STAGE2_THRESH "12000" +#define DEFAULT_ALIEN_STAGE3_THRESH "24000" +#define DEFAULT_ALIEN_MAX_STAGE "2" +#define DEFAULT_HUMAN_BUILDPOINTS "100" +#define DEFAULT_HUMAN_QUEUE_TIME "8000" +#define DEFAULT_HUMAN_REPEATER_BUILDPOINTS "20" +#define DEFAULT_HUMAN_REPEATER_QUEUE_TIME "2000" +#define DEFAULT_HUMAN_REPEATER_MAX_ZONES "500" +#define DEFAULT_HUMAN_STAGE2_THRESH "6000" +#define DEFAULT_HUMAN_STAGE3_THRESH "12000" +#define DEFAULT_HUMAN_MAX_STAGE "2" + +#define DAMAGE_FRACTION_FOR_KILL 0.5f //how much damage players (versus structures) need to + //do to increment the stage kill counters + +#define MAXIMUM_BUILD_TIME 20000 // used for pie timer + +/* + * Cuboids + */ + +#define CUBOID_MINVOLUME 150 +//everything else is on the bottom of bg_misc.c + +/* + * Impregnation + */ + +#define ALIEN_IMPREGNATION_CHANCE_NAKED 0.60f +#define ALIEN_IMPREGNATION_CHANCE_ARMORED 0.15f +#define ALIEN_IMPREGNATION_REWARD ALIEN_CREDITS_PER_KILL //reward for the granger than impregnated (awarded when someone kills the human by spawning) +#define ALIEN_IMPREGNATION_REWARD_SCORE 1500 //15 points + +#define ALIEN_IMPLANT_MATURING_TIME_MIN 8000 //time before maturing can occur +#define ALIEN_IMPLANT_MATURING_CHANCE 0.05f //chance of maturing (every second) + +#define ALIEN_HATCHING_VELOCITY 250.0f +#define ALIEN_HATCHING_MAX_BATTLESUIT_HEALTH 80 // alien dies if tried to spawn from a battlesuit at least this healthy +#define ALIEN_FAILED_HATCH_DAMAGE 65 // shouldn't be higher than the value above diff --git a/src/jpeg-8c/README b/src/jpeg-8c/README new file mode 100644 index 0000000..451265d --- /dev/null +++ b/src/jpeg-8c/README @@ -0,0 +1,326 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 8c of 16-Jan-2011 +==================================== + +This distribution contains the eighth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +This software is the work of Tom Lane, Guido Vollbeding, Philip Gladstone, +Bill Allombert, Jim Boucher, Lee Crocker, Bob Friesenhahn, Ben Jackson, +Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, Ge' Weijers, +and other members of the Independent JPEG Group. + +IJG is not affiliated with the official ISO JPEG standards committee. + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +ACKNOWLEDGMENTS Special thanks. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.txt How to configure and install the IJG software. + usage.txt Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.txt). + wizard.txt Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.txt How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.txt Overview of the JPEG library's internal structure. + filelist.txt Road map of IJG files. + coderules.txt Coding style rules --- please read if you contribute code. + +Please read at least the files install.txt and usage.txt. Some information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image encoding, decoding, +and transcoding. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +We have made no provision for supporting the hierarchical or lossless +processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. + +We have also included "jpegtran", a utility for lossless transcoding between +different JPEG processes, and "rdjpgcom" and "wrjpgcom", two simple +applications for inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-2011, Thomas G. Lane, Guido Vollbeding. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, +sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. +ansi2knr.c is NOT covered by the above copyright and conditions, but instead +by the usual distribution terms of the Free Software Foundation; principally, +that you must include source code if you redistribute it. (See the file +ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part +of any program generated from the IJG code, this does not limit you more than +the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltmain.sh). Another support script, install-sh, is copyright by X Consortium +but is also freely distributable. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent, GIF reading support has +been removed altogether, and the GIF writer has been simplified to produce +"uncompressed GIFs". This technique does not use the LZW algorithm; the +resulting GIF files are larger than usual, but are readable by all standard +GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at http://www.ijg.org/files/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best currently available description of JPEG is the textbook "JPEG Still +Image Data Compression Standard" by William B. Pennebaker and Joan L. +Mitchell, published by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. +Price US$59.95, 638 pp. The book includes the complete text of the ISO JPEG +standards (DIS 10918-1 and draft DIS 10918-2). +Although this is by far the most detailed and comprehensive exposition of +JPEG publicly available, we point out that it is still missing an explanation +of the most essential properties and algorithms of the underlying DCT +technology. +If you think that you know about DCT-based JPEG after reading this book, +then you are in delusion. The real fundamentals and corresponding potential +of DCT-based JPEG are not publicly known so far, and that is the reason for +all the mistaken developments taking place in the image coding domain. + +The original JPEG standard is divided into two parts, Part 1 being the actual +specification, while Part 2 covers compliance testing methods. Part 1 is +titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. +IJG JPEG 8 introduces an implementation of the JPEG SmartScale extension +which is specified in a contributed document at ITU and ISO with title "ITU-T +JPEG-Plus Proposal for Extending ITU-T T.81 for Advanced Image Coding", April +2006, Geneva, Switzerland. The latest version of the document is Revision 3. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. JFIF 1.02 has been adopted as an Ecma International Technical Report +and thus received a formal publication status. It is available as a free +download in PDF format from +http://www.ecma-international.org/publications/techreports/E-TR-098.htm. +A PostScript version of the JFIF document is available at +http://www.ijg.org/files/jfif.ps.gz. There is also a plain text version at +http://www.ijg.org/files/jfif.txt.gz, but it is missing the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from +http://www.ijg.org/files/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is www.ijg.org. +The most recent released version can always be found there in +directory "files". This particular version will be archived as +http://www.ijg.org/files/jpegsrc.v8c.tar.gz, and in Windows-compatible +"zip" archive format as http://www.ijg.org/files/jpegsr8c.zip. + +The JPEG FAQ (Frequently Asked Questions) article is a source of some +general information about JPEG. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +ACKNOWLEDGMENTS +=============== + +Thank to Juergen Bruder for providing me with a copy of the common DCT +algorithm article, only to find out that I had come to the same result +in a more direct and comprehensible way with a more generative approach. + +Thank to Istvan Sebestyen and Joan L. Mitchell for inviting me to the +ITU JPEG (Study Group 16) meeting in Geneva, Switzerland. + +Thank to Thomas Wiegand and Gary Sullivan for inviting me to the +Joint Video Team (MPEG & ITU) meeting in Geneva, Switzerland. + +Thank to John Korejwa and Massimo Ballerini for inviting me to +fruitful consultations in Boston, MA and Milan, Italy. + +Thank to Hendrik Elstner, Roland Fassauer, Simone Zuck, Guenther +Maier-Gerber, Walter Stoeber, Fred Schmitz, and Norbert Braunagel +for corresponding business development. + +Thank to Nico Zschach and Dirk Stelling of the technical support team +at the Digital Images company in Halle for providing me with extra +equipment for configuration tests. + +Thank to Richard F. Lyon (then of Foveon Inc.) for fruitful +communication about JPEG configuration in Sigma Photo Pro software. + +Thank to Andrew Finkenstadt for hosting the ijg.org site. + +Last but not least special thank to Thomas G. Lane for the original +design and development of this singular software package. + + +FILE FORMAT WARS +================ + +The ISO JPEG standards committee actually promotes different formats like +"JPEG 2000" or "JPEG XR" which are incompatible with original DCT-based +JPEG and which are based on faulty technologies. IJG therefore does not +and will not support such momentary mistakes (see REFERENCES). +We have little or no sympathy for the promotion of these formats. Indeed, +one of the original reasons for developing this free software was to help +force convergence on common, interoperable format standards for JPEG files. +Don't use an incompatible file format! +(In any case, our decoder will remain capable of reading existing JPEG +image files indefinitely.) + + +TO DO +===== + +Version 8 is the first release of a new generation JPEG standard +to overcome the limitations of the original JPEG specification. +More features are being prepared for coming releases... + +Please send bug reports, offers of help, etc. to jpeg-info@uc.ag. diff --git a/src/jpeg-8c/jaricom.c b/src/jpeg-8c/jaricom.c new file mode 100644 index 0000000..f43e2ea --- /dev/null +++ b/src/jpeg-8c/jaricom.c @@ -0,0 +1,153 @@ +/* + * jaricom.c + * + * Developed 1997-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains probability estimation tables for common use in + * arithmetic entropy encoding and decoding routines. + * + * This data represents Table D.2 in the JPEG spec (ISO/IEC IS 10918-1 + * and CCITT Recommendation ITU-T T.81) and Table 24 in the JBIG spec + * (ISO/IEC IS 11544 and CCITT Recommendation ITU-T T.82). + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +/* The following #define specifies the packing of the four components + * into the compact INT32 representation. + * Note that this formula must match the actual arithmetic encoder + * and decoder implementation. The implementation has to be changed + * if this formula is changed. + * The current organization is leaned on Markus Kuhn's JBIG + * implementation (jbig_tab.c). + */ + +#define V(i,a,b,c,d) (((INT32)a << 16) | ((INT32)c << 8) | ((INT32)d << 7) | b) + +const INT32 jpeg_aritab[113+1] = { +/* + * Index, Qe_Value, Next_Index_LPS, Next_Index_MPS, Switch_MPS + */ + V( 0, 0x5a1d, 1, 1, 1 ), + V( 1, 0x2586, 14, 2, 0 ), + V( 2, 0x1114, 16, 3, 0 ), + V( 3, 0x080b, 18, 4, 0 ), + V( 4, 0x03d8, 20, 5, 0 ), + V( 5, 0x01da, 23, 6, 0 ), + V( 6, 0x00e5, 25, 7, 0 ), + V( 7, 0x006f, 28, 8, 0 ), + V( 8, 0x0036, 30, 9, 0 ), + V( 9, 0x001a, 33, 10, 0 ), + V( 10, 0x000d, 35, 11, 0 ), + V( 11, 0x0006, 9, 12, 0 ), + V( 12, 0x0003, 10, 13, 0 ), + V( 13, 0x0001, 12, 13, 0 ), + V( 14, 0x5a7f, 15, 15, 1 ), + V( 15, 0x3f25, 36, 16, 0 ), + V( 16, 0x2cf2, 38, 17, 0 ), + V( 17, 0x207c, 39, 18, 0 ), + V( 18, 0x17b9, 40, 19, 0 ), + V( 19, 0x1182, 42, 20, 0 ), + V( 20, 0x0cef, 43, 21, 0 ), + V( 21, 0x09a1, 45, 22, 0 ), + V( 22, 0x072f, 46, 23, 0 ), + V( 23, 0x055c, 48, 24, 0 ), + V( 24, 0x0406, 49, 25, 0 ), + V( 25, 0x0303, 51, 26, 0 ), + V( 26, 0x0240, 52, 27, 0 ), + V( 27, 0x01b1, 54, 28, 0 ), + V( 28, 0x0144, 56, 29, 0 ), + V( 29, 0x00f5, 57, 30, 0 ), + V( 30, 0x00b7, 59, 31, 0 ), + V( 31, 0x008a, 60, 32, 0 ), + V( 32, 0x0068, 62, 33, 0 ), + V( 33, 0x004e, 63, 34, 0 ), + V( 34, 0x003b, 32, 35, 0 ), + V( 35, 0x002c, 33, 9, 0 ), + V( 36, 0x5ae1, 37, 37, 1 ), + V( 37, 0x484c, 64, 38, 0 ), + V( 38, 0x3a0d, 65, 39, 0 ), + V( 39, 0x2ef1, 67, 40, 0 ), + V( 40, 0x261f, 68, 41, 0 ), + V( 41, 0x1f33, 69, 42, 0 ), + V( 42, 0x19a8, 70, 43, 0 ), + V( 43, 0x1518, 72, 44, 0 ), + V( 44, 0x1177, 73, 45, 0 ), + V( 45, 0x0e74, 74, 46, 0 ), + V( 46, 0x0bfb, 75, 47, 0 ), + V( 47, 0x09f8, 77, 48, 0 ), + V( 48, 0x0861, 78, 49, 0 ), + V( 49, 0x0706, 79, 50, 0 ), + V( 50, 0x05cd, 48, 51, 0 ), + V( 51, 0x04de, 50, 52, 0 ), + V( 52, 0x040f, 50, 53, 0 ), + V( 53, 0x0363, 51, 54, 0 ), + V( 54, 0x02d4, 52, 55, 0 ), + V( 55, 0x025c, 53, 56, 0 ), + V( 56, 0x01f8, 54, 57, 0 ), + V( 57, 0x01a4, 55, 58, 0 ), + V( 58, 0x0160, 56, 59, 0 ), + V( 59, 0x0125, 57, 60, 0 ), + V( 60, 0x00f6, 58, 61, 0 ), + V( 61, 0x00cb, 59, 62, 0 ), + V( 62, 0x00ab, 61, 63, 0 ), + V( 63, 0x008f, 61, 32, 0 ), + V( 64, 0x5b12, 65, 65, 1 ), + V( 65, 0x4d04, 80, 66, 0 ), + V( 66, 0x412c, 81, 67, 0 ), + V( 67, 0x37d8, 82, 68, 0 ), + V( 68, 0x2fe8, 83, 69, 0 ), + V( 69, 0x293c, 84, 70, 0 ), + V( 70, 0x2379, 86, 71, 0 ), + V( 71, 0x1edf, 87, 72, 0 ), + V( 72, 0x1aa9, 87, 73, 0 ), + V( 73, 0x174e, 72, 74, 0 ), + V( 74, 0x1424, 72, 75, 0 ), + V( 75, 0x119c, 74, 76, 0 ), + V( 76, 0x0f6b, 74, 77, 0 ), + V( 77, 0x0d51, 75, 78, 0 ), + V( 78, 0x0bb6, 77, 79, 0 ), + V( 79, 0x0a40, 77, 48, 0 ), + V( 80, 0x5832, 80, 81, 1 ), + V( 81, 0x4d1c, 88, 82, 0 ), + V( 82, 0x438e, 89, 83, 0 ), + V( 83, 0x3bdd, 90, 84, 0 ), + V( 84, 0x34ee, 91, 85, 0 ), + V( 85, 0x2eae, 92, 86, 0 ), + V( 86, 0x299a, 93, 87, 0 ), + V( 87, 0x2516, 86, 71, 0 ), + V( 88, 0x5570, 88, 89, 1 ), + V( 89, 0x4ca9, 95, 90, 0 ), + V( 90, 0x44d9, 96, 91, 0 ), + V( 91, 0x3e22, 97, 92, 0 ), + V( 92, 0x3824, 99, 93, 0 ), + V( 93, 0x32b4, 99, 94, 0 ), + V( 94, 0x2e17, 93, 86, 0 ), + V( 95, 0x56a8, 95, 96, 1 ), + V( 96, 0x4f46, 101, 97, 0 ), + V( 97, 0x47e5, 102, 98, 0 ), + V( 98, 0x41cf, 103, 99, 0 ), + V( 99, 0x3c3d, 104, 100, 0 ), + V( 100, 0x375e, 99, 93, 0 ), + V( 101, 0x5231, 105, 102, 0 ), + V( 102, 0x4c0f, 106, 103, 0 ), + V( 103, 0x4639, 107, 104, 0 ), + V( 104, 0x415e, 103, 99, 0 ), + V( 105, 0x5627, 105, 106, 1 ), + V( 106, 0x50e7, 108, 107, 0 ), + V( 107, 0x4b85, 109, 103, 0 ), + V( 108, 0x5597, 110, 109, 0 ), + V( 109, 0x504f, 111, 107, 0 ), + V( 110, 0x5a10, 110, 111, 1 ), + V( 111, 0x5522, 112, 109, 0 ), + V( 112, 0x59eb, 112, 111, 1 ), +/* + * This last entry is used for fixed probability estimate of 0.5 + * as recommended in Section 10.3 Table 5 of ITU-T Rec. T.851. + */ + V( 113, 0x5a1d, 113, 113, 0 ) +}; diff --git a/src/jpeg-8c/jcapimin.c b/src/jpeg-8c/jcapimin.c new file mode 100644 index 0000000..639ce86 --- /dev/null +++ b/src/jpeg-8c/jcapimin.c @@ -0,0 +1,288 @@ +/* + * jcapimin.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * Modified 2003-2010 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-compression case or the transcoding-only + * case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jcapistd.c. But also see jcparam.c for + * parameter-setup helper routines, jcomapi.c for routines shared by + * compression and decompression, and jctrans.c for the transcoding case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG compression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL(void) +jpeg_CreateCompress (j_compress_ptr cinfo, int version, size_t structsize) +{ + int i; + + /* Guard against version mismatches between library and caller. */ + cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */ + if (version != JPEG_LIB_VERSION) + ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); + if (structsize != SIZEOF(struct jpeg_compress_struct)) + ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, + (int) SIZEOF(struct jpeg_compress_struct), (int) structsize); + + /* For debugging purposes, we zero the whole master structure. + * But the application has already set the err pointer, and may have set + * client_data, so we have to save and restore those fields. + * Note: if application hasn't set client_data, tools like Purify may + * complain here. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + void * client_data = cinfo->client_data; /* ignore Purify complaint here */ + MEMZERO(cinfo, SIZEOF(struct jpeg_compress_struct)); + cinfo->err = err; + cinfo->client_data = client_data; + } + cinfo->is_decompressor = FALSE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->dest = NULL; + + cinfo->comp_info = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + cinfo->quant_tbl_ptrs[i] = NULL; + cinfo->q_scale_factor[i] = 100; + } + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Must do it here for emit_dqt in case jpeg_write_tables is used */ + cinfo->block_size = DCTSIZE; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + + cinfo->script_space = NULL; + + cinfo->input_gamma = 1.0; /* in case application forgets */ + + /* OK, I'm ready */ + cinfo->global_state = CSTATE_START; +} + + +/* + * Destruction of a JPEG compression object + */ + +GLOBAL(void) +jpeg_destroy_compress (j_compress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG compression operation, + * but don't destroy the object itself. + */ + +GLOBAL(void) +jpeg_abort_compress (j_compress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Forcibly suppress or un-suppress all quantization and Huffman tables. + * Marks all currently defined tables as already written (if suppress) + * or not written (if !suppress). This will control whether they get emitted + * by a subsequent jpeg_start_compress call. + * + * This routine is exported for use by applications that want to produce + * abbreviated JPEG datastreams. It logically belongs in jcparam.c, but + * since it is called by jpeg_start_compress, we put it here --- otherwise + * jcparam.o would be linked whether the application used it or not. + */ + +GLOBAL(void) +jpeg_suppress_tables (j_compress_ptr cinfo, boolean suppress) +{ + int i; + JQUANT_TBL * qtbl; + JHUFF_TBL * htbl; + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if ((qtbl = cinfo->quant_tbl_ptrs[i]) != NULL) + qtbl->sent_table = suppress; + } + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if ((htbl = cinfo->dc_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + if ((htbl = cinfo->ac_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + } +} + + +/* + * Finish JPEG compression. + * + * If a multipass operating mode was selected, this may do a great deal of + * work including most of the actual output. + */ + +GLOBAL(void) +jpeg_finish_compress (j_compress_ptr cinfo) +{ + JDIMENSION iMCU_row; + + if (cinfo->global_state == CSTATE_SCANNING || + cinfo->global_state == CSTATE_RAW_OK) { + /* Terminate first pass */ + if (cinfo->next_scanline < cinfo->image_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_pass) (cinfo); + } else if (cinfo->global_state != CSTATE_WRCOEFS) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any remaining passes */ + while (! cinfo->master->is_last_pass) { + (*cinfo->master->prepare_for_pass) (cinfo); + for (iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++) { + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) iMCU_row; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* We bypass the main controller and invoke coef controller directly; + * all work is being done from the coefficient buffer. + */ + if (! (*cinfo->coef->compress_data) (cinfo, (JSAMPIMAGE) NULL)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } + (*cinfo->master->finish_pass) (cinfo); + } + /* Write EOI, do final cleanup */ + (*cinfo->marker->write_file_trailer) (cinfo); + (*cinfo->dest->term_destination) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); +} + + +/* + * Write a special marker. + * This is only recommended for writing COM or APPn markers. + * Must be called after jpeg_start_compress() and before + * first call to jpeg_write_scanlines() or jpeg_write_raw_data(). + */ + +GLOBAL(void) +jpeg_write_marker (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen) +{ + JMETHOD(void, write_marker_byte, (j_compress_ptr info, int val)); + + if (cinfo->next_scanline != 0 || + (cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS)) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + (*cinfo->marker->write_marker_header) (cinfo, marker, datalen); + write_marker_byte = cinfo->marker->write_marker_byte; /* copy for speed */ + while (datalen--) { + (*write_marker_byte) (cinfo, *dataptr); + dataptr++; + } +} + +/* Same, but piecemeal. */ + +GLOBAL(void) +jpeg_write_m_header (j_compress_ptr cinfo, int marker, unsigned int datalen) +{ + if (cinfo->next_scanline != 0 || + (cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS)) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + (*cinfo->marker->write_marker_header) (cinfo, marker, datalen); +} + +GLOBAL(void) +jpeg_write_m_byte (j_compress_ptr cinfo, int val) +{ + (*cinfo->marker->write_marker_byte) (cinfo, val); +} + + +/* + * Alternate compression function: just write an abbreviated table file. + * Before calling this, all parameters and a data destination must be set up. + * + * To produce a pair of files containing abbreviated tables and abbreviated + * image data, one would proceed as follows: + * + * initialize JPEG object + * set JPEG parameters + * set destination to table file + * jpeg_write_tables(cinfo); + * set destination to image file + * jpeg_start_compress(cinfo, FALSE); + * write data... + * jpeg_finish_compress(cinfo); + * + * jpeg_write_tables has the side effect of marking all tables written + * (same as jpeg_suppress_tables(..., TRUE)). Thus a subsequent start_compress + * will not re-emit the tables unless it is passed write_all_tables=TRUE. + */ + +GLOBAL(void) +jpeg_write_tables (j_compress_ptr cinfo) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Initialize the marker writer ... bit of a crock to do it here. */ + jinit_marker_writer(cinfo); + /* Write them tables! */ + (*cinfo->marker->write_tables_only) (cinfo); + /* And clean up. */ + (*cinfo->dest->term_destination) (cinfo); + /* + * In library releases up through v6a, we called jpeg_abort() here to free + * any working memory allocated by the destination manager and marker + * writer. Some applications had a problem with that: they allocated space + * of their own from the library memory manager, and didn't want it to go + * away during write_tables. So now we do nothing. This will cause a + * memory leak if an app calls write_tables repeatedly without doing a full + * compression cycle or otherwise resetting the JPEG object. However, that + * seems less bad than unexpectedly freeing memory in the normal case. + * An app that prefers the old behavior can call jpeg_abort for itself after + * each call to jpeg_write_tables(). + */ +} diff --git a/src/jpeg-8c/jcapistd.c b/src/jpeg-8c/jcapistd.c new file mode 100644 index 0000000..c0320b1 --- /dev/null +++ b/src/jpeg-8c/jcapistd.c @@ -0,0 +1,161 @@ +/* + * jcapistd.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-compression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_compress, it will end up linking in the entire compressor. + * We thus must separate this file from jcapimin.c to avoid linking the + * whole compression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object. Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default. This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images. Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL(void) +jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (write_all_tables) + jpeg_suppress_tables(cinfo, FALSE); /* mark all tables to be written */ + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + jinit_compress_master(cinfo); + /* Set up for the first pass */ + (*cinfo->master->prepare_for_pass) (cinfo); + /* Ready for application to drive first pass through jpeg_write_scanlines + * or jpeg_write_raw_data. + */ + cinfo->next_scanline = 0; + cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error. However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL(JDIMENSION) +jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines) +{ + JDIMENSION row_ctr, rows_left; + + if (cinfo->global_state != CSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_scanlines. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_scanlines. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Ignore any extra scanlines at bottom of image. */ + rows_left = cinfo->image_height - cinfo->next_scanline; + if (num_lines > rows_left) + num_lines = rows_left; + + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines); + cinfo->next_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to write raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL(JDIMENSION) +jpeg_write_raw_data (j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != CSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_raw_data. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_raw_data. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Verify that at least one iMCU row has been passed. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * DCTSIZE; + if (num_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Directly compress the row. */ + if (! (*cinfo->coef->compress_data) (cinfo, data)) { + /* If compressor did not consume the whole row, suspend processing. */ + return 0; + } + + /* OK, we processed one iMCU row. */ + cinfo->next_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} diff --git a/src/jpeg-8c/jcarith.c b/src/jpeg-8c/jcarith.c new file mode 100644 index 0000000..0b7ea55 --- /dev/null +++ b/src/jpeg-8c/jcarith.c @@ -0,0 +1,934 @@ +/* + * jcarith.c + * + * Developed 1997-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains portable arithmetic entropy encoding routines for JPEG + * (implementing the ISO/IEC IS 10918-1 and CCITT Recommendation ITU-T T.81). + * + * Both sequential and progressive modes are supported in this single module. + * + * Suspension is not currently supported in this module. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Expanded entropy encoder object for arithmetic encoding. */ + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + INT32 c; /* C register, base of coding interval, layout as in sec. D.1.3 */ + INT32 a; /* A register, normalized size of coding interval */ + INT32 sc; /* counter for stacked 0xFF values which might overflow */ + INT32 zc; /* counter for pending 0x00 output values which might * + * be discarded at the end ("Pacman" termination) */ + int ct; /* bit shift counter, determines when next byte will be written */ + int buffer; /* buffer for most recent output byte != 0xFF */ + + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ + int dc_context[MAX_COMPS_IN_SCAN]; /* context index for DC conditioning */ + + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to statistics areas (these workspaces have image lifespan) */ + unsigned char * dc_stats[NUM_ARITH_TBLS]; + unsigned char * ac_stats[NUM_ARITH_TBLS]; + + /* Statistics bin for coding with fixed probability 0.5 */ + unsigned char fixed_bin[4]; +} arith_entropy_encoder; + +typedef arith_entropy_encoder * arith_entropy_ptr; + +/* The following two definitions specify the allocation chunk size + * for the statistics area. + * According to sections F.1.4.4.1.3 and F.1.4.4.2, we need at least + * 49 statistics bins for DC, and 245 statistics bins for AC coding. + * + * We use a compact representation with 1 byte per statistics bin, + * thus the numbers directly represent byte sizes. + * This 1 byte per statistics bin contains the meaning of the MPS + * (more probable symbol) in the highest bit (mask 0x80), and the + * index into the probability estimation state machine table + * in the lower bits (mask 0x7F). + */ + +#define DC_STAT_BINS 64 +#define AC_STAT_BINS 256 + +/* NOTE: Uncomment the following #define if you want to use the + * given formula for calculating the AC conditioning parameter Kx + * for spectral selection progressive coding in section G.1.3.2 + * of the spec (Kx = Kmin + SRL (8 + Se - Kmin) 4). + * Although the spec and P&M authors claim that this "has proven + * to give good results for 8 bit precision samples", I'm not + * convinced yet that this is really beneficial. + * Early tests gave only very marginal compression enhancements + * (a few - around 5 or so - bytes even for very large files), + * which would turn out rather negative if we'd suppress the + * DAC (Define Arithmetic Conditioning) marker segments for + * the default parameters in the future. + * Note that currently the marker writing module emits 12-byte + * DAC segments for a full-component scan in a color image. + * This is not worth worrying about IMHO. However, since the + * spec defines the default values to be used if the tables + * are omitted (unlike Huffman tables, which are required + * anyway), one might optimize this behaviour in the future, + * and then it would be disadvantageous to use custom tables if + * they don't provide sufficient gain to exceed the DAC size. + * + * On the other hand, I'd consider it as a reasonable result + * that the conditioning has no significant influence on the + * compression performance. This means that the basic + * statistical model is already rather stable. + * + * Thus, at the moment, we use the default conditioning values + * anyway, and do not use the custom formula. + * +#define CALCULATE_SPECTRAL_CONDITIONING + */ + +/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32. + * We assume that int right shift is unsigned if INT32 right shift is, + * which should be safe. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS int ishift_temp; +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~0) << (16-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +LOCAL(void) +emit_byte (int val, j_compress_ptr cinfo) +/* Write next output byte; we do not support suspension in this module. */ +{ + struct jpeg_destination_mgr * dest = cinfo->dest; + + *dest->next_output_byte++ = (JOCTET) val; + if (--dest->free_in_buffer == 0) + if (! (*dest->empty_output_buffer) (cinfo)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); +} + + +/* + * Finish up at the end of an arithmetic-compressed scan. + */ + +METHODDEF(void) +finish_pass (j_compress_ptr cinfo) +{ + arith_entropy_ptr e = (arith_entropy_ptr) cinfo->entropy; + INT32 temp; + + /* Section D.1.8: Termination of encoding */ + + /* Find the e->c in the coding interval with the largest + * number of trailing zero bits */ + if ((temp = (e->a - 1 + e->c) & 0xFFFF0000L) < e->c) + e->c = temp + 0x8000L; + else + e->c = temp; + /* Send remaining bytes to output */ + e->c <<= e->ct; + if (e->c & 0xF8000000L) { + /* One final overflow has to be handled */ + if (e->buffer >= 0) { + if (e->zc) + do emit_byte(0x00, cinfo); + while (--e->zc); + emit_byte(e->buffer + 1, cinfo); + if (e->buffer + 1 == 0xFF) + emit_byte(0x00, cinfo); + } + e->zc += e->sc; /* carry-over converts stacked 0xFF bytes to 0x00 */ + e->sc = 0; + } else { + if (e->buffer == 0) + ++e->zc; + else if (e->buffer >= 0) { + if (e->zc) + do emit_byte(0x00, cinfo); + while (--e->zc); + emit_byte(e->buffer, cinfo); + } + if (e->sc) { + if (e->zc) + do emit_byte(0x00, cinfo); + while (--e->zc); + do { + emit_byte(0xFF, cinfo); + emit_byte(0x00, cinfo); + } while (--e->sc); + } + } + /* Output final bytes only if they are not 0x00 */ + if (e->c & 0x7FFF800L) { + if (e->zc) /* output final pending zero bytes */ + do emit_byte(0x00, cinfo); + while (--e->zc); + emit_byte((e->c >> 19) & 0xFF, cinfo); + if (((e->c >> 19) & 0xFF) == 0xFF) + emit_byte(0x00, cinfo); + if (e->c & 0x7F800L) { + emit_byte((e->c >> 11) & 0xFF, cinfo); + if (((e->c >> 11) & 0xFF) == 0xFF) + emit_byte(0x00, cinfo); + } + } +} + + +/* + * The core arithmetic encoding routine (common in JPEG and JBIG). + * This needs to go as fast as possible. + * Machine-dependent optimization facilities + * are not utilized in this portable implementation. + * However, this code should be fairly efficient and + * may be a good base for further optimizations anyway. + * + * Parameter 'val' to be encoded may be 0 or 1 (binary decision). + * + * Note: I've added full "Pacman" termination support to the + * byte output routines, which is equivalent to the optional + * Discard_final_zeros procedure (Figure D.15) in the spec. + * Thus, we always produce the shortest possible output + * stream compliant to the spec (no trailing zero bytes, + * except for FF stuffing). + * + * I've also introduced a new scheme for accessing + * the probability estimation state machine table, + * derived from Markus Kuhn's JBIG implementation. + */ + +LOCAL(void) +arith_encode (j_compress_ptr cinfo, unsigned char *st, int val) +{ + register arith_entropy_ptr e = (arith_entropy_ptr) cinfo->entropy; + register unsigned char nl, nm; + register INT32 qe, temp; + register int sv; + + /* Fetch values from our compact representation of Table D.2: + * Qe values and probability estimation state machine + */ + sv = *st; + qe = jpeg_aritab[sv & 0x7F]; /* => Qe_Value */ + nl = qe & 0xFF; qe >>= 8; /* Next_Index_LPS + Switch_MPS */ + nm = qe & 0xFF; qe >>= 8; /* Next_Index_MPS */ + + /* Encode & estimation procedures per sections D.1.4 & D.1.5 */ + e->a -= qe; + if (val != (sv >> 7)) { + /* Encode the less probable symbol */ + if (e->a >= qe) { + /* If the interval size (qe) for the less probable symbol (LPS) + * is larger than the interval size for the MPS, then exchange + * the two symbols for coding efficiency, otherwise code the LPS + * as usual: */ + e->c += e->a; + e->a = qe; + } + *st = (sv & 0x80) ^ nl; /* Estimate_after_LPS */ + } else { + /* Encode the more probable symbol */ + if (e->a >= 0x8000L) + return; /* A >= 0x8000 -> ready, no renormalization required */ + if (e->a < qe) { + /* If the interval size (qe) for the less probable symbol (LPS) + * is larger than the interval size for the MPS, then exchange + * the two symbols for coding efficiency: */ + e->c += e->a; + e->a = qe; + } + *st = (sv & 0x80) ^ nm; /* Estimate_after_MPS */ + } + + /* Renormalization & data output per section D.1.6 */ + do { + e->a <<= 1; + e->c <<= 1; + if (--e->ct == 0) { + /* Another byte is ready for output */ + temp = e->c >> 19; + if (temp > 0xFF) { + /* Handle overflow over all stacked 0xFF bytes */ + if (e->buffer >= 0) { + if (e->zc) + do emit_byte(0x00, cinfo); + while (--e->zc); + emit_byte(e->buffer + 1, cinfo); + if (e->buffer + 1 == 0xFF) + emit_byte(0x00, cinfo); + } + e->zc += e->sc; /* carry-over converts stacked 0xFF bytes to 0x00 */ + e->sc = 0; + /* Note: The 3 spacer bits in the C register guarantee + * that the new buffer byte can't be 0xFF here + * (see page 160 in the P&M JPEG book). */ + e->buffer = temp & 0xFF; /* new output byte, might overflow later */ + } else if (temp == 0xFF) { + ++e->sc; /* stack 0xFF byte (which might overflow later) */ + } else { + /* Output all stacked 0xFF bytes, they will not overflow any more */ + if (e->buffer == 0) + ++e->zc; + else if (e->buffer >= 0) { + if (e->zc) + do emit_byte(0x00, cinfo); + while (--e->zc); + emit_byte(e->buffer, cinfo); + } + if (e->sc) { + if (e->zc) + do emit_byte(0x00, cinfo); + while (--e->zc); + do { + emit_byte(0xFF, cinfo); + emit_byte(0x00, cinfo); + } while (--e->sc); + } + e->buffer = temp & 0xFF; /* new output byte (can still overflow) */ + } + e->c &= 0x7FFFFL; + e->ct += 8; + } + } while (e->a < 0x8000L); +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL(void) +emit_restart (j_compress_ptr cinfo, int restart_num) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + int ci; + jpeg_component_info * compptr; + + finish_pass(cinfo); + + emit_byte(0xFF, cinfo); + emit_byte(JPEG_RST0 + restart_num, cinfo); + + /* Re-initialize statistics areas */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* DC needs no table for refinement scan */ + if (cinfo->Ss == 0 && cinfo->Ah == 0) { + MEMZERO(entropy->dc_stats[compptr->dc_tbl_no], DC_STAT_BINS); + /* Reset DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + entropy->dc_context[ci] = 0; + } + /* AC needs no table when not present */ + if (cinfo->Se) { + MEMZERO(entropy->ac_stats[compptr->ac_tbl_no], AC_STAT_BINS); + } + } + + /* Reset arithmetic encoding variables */ + entropy->c = 0; + entropy->a = 0x10000L; + entropy->sc = 0; + entropy->zc = 0; + entropy->ct = 11; + entropy->buffer = -1; /* empty */ +} + + +/* + * MCU encoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +encode_mcu_DC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + JBLOCKROW block; + unsigned char *st; + int blkn, ci, tbl; + int v, v2, m; + ISHIFT_TEMPS + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + emit_restart(cinfo, entropy->next_restart_num); + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + tbl = cinfo->cur_comp_info[ci]->dc_tbl_no; + + /* Compute the DC value after the required point transform by Al. + * This is simply an arithmetic right shift. + */ + m = IRIGHT_SHIFT((int) ((*block)[0]), cinfo->Al); + + /* Sections F.1.4.1 & F.1.4.4.1: Encoding of DC coefficients */ + + /* Table F.4: Point to statistics bin S0 for DC coefficient coding */ + st = entropy->dc_stats[tbl] + entropy->dc_context[ci]; + + /* Figure F.4: Encode_DC_DIFF */ + if ((v = m - entropy->last_dc_val[ci]) == 0) { + arith_encode(cinfo, st, 0); + entropy->dc_context[ci] = 0; /* zero diff category */ + } else { + entropy->last_dc_val[ci] = m; + arith_encode(cinfo, st, 1); + /* Figure F.6: Encoding nonzero value v */ + /* Figure F.7: Encoding the sign of v */ + if (v > 0) { + arith_encode(cinfo, st + 1, 0); /* Table F.4: SS = S0 + 1 */ + st += 2; /* Table F.4: SP = S0 + 2 */ + entropy->dc_context[ci] = 4; /* small positive diff category */ + } else { + v = -v; + arith_encode(cinfo, st + 1, 1); /* Table F.4: SS = S0 + 1 */ + st += 3; /* Table F.4: SN = S0 + 3 */ + entropy->dc_context[ci] = 8; /* small negative diff category */ + } + /* Figure F.8: Encoding the magnitude category of v */ + m = 0; + if (v -= 1) { + arith_encode(cinfo, st, 1); + m = 1; + v2 = v; + st = entropy->dc_stats[tbl] + 20; /* Table F.4: X1 = 20 */ + while (v2 >>= 1) { + arith_encode(cinfo, st, 1); + m <<= 1; + st += 1; + } + } + arith_encode(cinfo, st, 0); + /* Section F.1.4.4.1.2: Establish dc_context conditioning category */ + if (m < (int) ((1L << cinfo->arith_dc_L[tbl]) >> 1)) + entropy->dc_context[ci] = 0; /* zero diff category */ + else if (m > (int) ((1L << cinfo->arith_dc_U[tbl]) >> 1)) + entropy->dc_context[ci] += 8; /* large diff category */ + /* Figure F.9: Encoding the magnitude bit pattern of v */ + st += 14; + while (m >>= 1) + arith_encode(cinfo, st, (m & v) ? 1 : 0); + } + } + + return TRUE; +} + + +/* + * MCU encoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +encode_mcu_AC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + JBLOCKROW block; + unsigned char *st; + int tbl, k, ke; + int v, v2, m; + const int * natural_order; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + emit_restart(cinfo, entropy->next_restart_num); + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + natural_order = cinfo->natural_order; + + /* Encode the MCU data block */ + block = MCU_data[0]; + tbl = cinfo->cur_comp_info[0]->ac_tbl_no; + + /* Sections F.1.4.2 & F.1.4.4.2: Encoding of AC coefficients */ + + /* Establish EOB (end-of-block) index */ + for (ke = cinfo->Se; ke > 0; ke--) + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if ((v = (*block)[natural_order[ke]]) >= 0) { + if (v >>= cinfo->Al) break; + } else { + v = -v; + if (v >>= cinfo->Al) break; + } + + /* Figure F.5: Encode_AC_Coefficients */ + for (k = cinfo->Ss; k <= ke; k++) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + arith_encode(cinfo, st, 0); /* EOB decision */ + for (;;) { + if ((v = (*block)[natural_order[k]]) >= 0) { + if (v >>= cinfo->Al) { + arith_encode(cinfo, st + 1, 1); + arith_encode(cinfo, entropy->fixed_bin, 0); + break; + } + } else { + v = -v; + if (v >>= cinfo->Al) { + arith_encode(cinfo, st + 1, 1); + arith_encode(cinfo, entropy->fixed_bin, 1); + break; + } + } + arith_encode(cinfo, st + 1, 0); st += 3; k++; + } + st += 2; + /* Figure F.8: Encoding the magnitude category of v */ + m = 0; + if (v -= 1) { + arith_encode(cinfo, st, 1); + m = 1; + v2 = v; + if (v2 >>= 1) { + arith_encode(cinfo, st, 1); + m <<= 1; + st = entropy->ac_stats[tbl] + + (k <= cinfo->arith_ac_K[tbl] ? 189 : 217); + while (v2 >>= 1) { + arith_encode(cinfo, st, 1); + m <<= 1; + st += 1; + } + } + } + arith_encode(cinfo, st, 0); + /* Figure F.9: Encoding the magnitude bit pattern of v */ + st += 14; + while (m >>= 1) + arith_encode(cinfo, st, (m & v) ? 1 : 0); + } + /* Encode EOB decision only if k <= cinfo->Se */ + if (k <= cinfo->Se) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + arith_encode(cinfo, st, 1); + } + + return TRUE; +} + + +/* + * MCU encoding for DC successive approximation refinement scan. + */ + +METHODDEF(boolean) +encode_mcu_DC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + unsigned char *st; + int Al, blkn; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + emit_restart(cinfo, entropy->next_restart_num); + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + st = entropy->fixed_bin; /* use fixed probability estimation */ + Al = cinfo->Al; + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + /* We simply emit the Al'th bit of the DC coefficient value. */ + arith_encode(cinfo, st, (MCU_data[blkn][0][0] >> Al) & 1); + } + + return TRUE; +} + + +/* + * MCU encoding for AC successive approximation refinement scan. + */ + +METHODDEF(boolean) +encode_mcu_AC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + JBLOCKROW block; + unsigned char *st; + int tbl, k, ke, kex; + int v; + const int * natural_order; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + emit_restart(cinfo, entropy->next_restart_num); + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + natural_order = cinfo->natural_order; + + /* Encode the MCU data block */ + block = MCU_data[0]; + tbl = cinfo->cur_comp_info[0]->ac_tbl_no; + + /* Section G.1.3.3: Encoding of AC coefficients */ + + /* Establish EOB (end-of-block) index */ + for (ke = cinfo->Se; ke > 0; ke--) + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if ((v = (*block)[natural_order[ke]]) >= 0) { + if (v >>= cinfo->Al) break; + } else { + v = -v; + if (v >>= cinfo->Al) break; + } + + /* Establish EOBx (previous stage end-of-block) index */ + for (kex = ke; kex > 0; kex--) + if ((v = (*block)[natural_order[kex]]) >= 0) { + if (v >>= cinfo->Ah) break; + } else { + v = -v; + if (v >>= cinfo->Ah) break; + } + + /* Figure G.10: Encode_AC_Coefficients_SA */ + for (k = cinfo->Ss; k <= ke; k++) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + if (k > kex) + arith_encode(cinfo, st, 0); /* EOB decision */ + for (;;) { + if ((v = (*block)[natural_order[k]]) >= 0) { + if (v >>= cinfo->Al) { + if (v >> 1) /* previously nonzero coef */ + arith_encode(cinfo, st + 2, (v & 1)); + else { /* newly nonzero coef */ + arith_encode(cinfo, st + 1, 1); + arith_encode(cinfo, entropy->fixed_bin, 0); + } + break; + } + } else { + v = -v; + if (v >>= cinfo->Al) { + if (v >> 1) /* previously nonzero coef */ + arith_encode(cinfo, st + 2, (v & 1)); + else { /* newly nonzero coef */ + arith_encode(cinfo, st + 1, 1); + arith_encode(cinfo, entropy->fixed_bin, 1); + } + break; + } + } + arith_encode(cinfo, st + 1, 0); st += 3; k++; + } + } + /* Encode EOB decision only if k <= cinfo->Se */ + if (k <= cinfo->Se) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + arith_encode(cinfo, st, 1); + } + + return TRUE; +} + + +/* + * Encode and output one MCU's worth of arithmetic-compressed coefficients. + */ + +METHODDEF(boolean) +encode_mcu (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + jpeg_component_info * compptr; + JBLOCKROW block; + unsigned char *st; + int blkn, ci, tbl, k, ke; + int v, v2, m; + const int * natural_order; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + emit_restart(cinfo, entropy->next_restart_num); + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + natural_order = cinfo->natural_order; + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Sections F.1.4.1 & F.1.4.4.1: Encoding of DC coefficients */ + + tbl = compptr->dc_tbl_no; + + /* Table F.4: Point to statistics bin S0 for DC coefficient coding */ + st = entropy->dc_stats[tbl] + entropy->dc_context[ci]; + + /* Figure F.4: Encode_DC_DIFF */ + if ((v = (*block)[0] - entropy->last_dc_val[ci]) == 0) { + arith_encode(cinfo, st, 0); + entropy->dc_context[ci] = 0; /* zero diff category */ + } else { + entropy->last_dc_val[ci] = (*block)[0]; + arith_encode(cinfo, st, 1); + /* Figure F.6: Encoding nonzero value v */ + /* Figure F.7: Encoding the sign of v */ + if (v > 0) { + arith_encode(cinfo, st + 1, 0); /* Table F.4: SS = S0 + 1 */ + st += 2; /* Table F.4: SP = S0 + 2 */ + entropy->dc_context[ci] = 4; /* small positive diff category */ + } else { + v = -v; + arith_encode(cinfo, st + 1, 1); /* Table F.4: SS = S0 + 1 */ + st += 3; /* Table F.4: SN = S0 + 3 */ + entropy->dc_context[ci] = 8; /* small negative diff category */ + } + /* Figure F.8: Encoding the magnitude category of v */ + m = 0; + if (v -= 1) { + arith_encode(cinfo, st, 1); + m = 1; + v2 = v; + st = entropy->dc_stats[tbl] + 20; /* Table F.4: X1 = 20 */ + while (v2 >>= 1) { + arith_encode(cinfo, st, 1); + m <<= 1; + st += 1; + } + } + arith_encode(cinfo, st, 0); + /* Section F.1.4.4.1.2: Establish dc_context conditioning category */ + if (m < (int) ((1L << cinfo->arith_dc_L[tbl]) >> 1)) + entropy->dc_context[ci] = 0; /* zero diff category */ + else if (m > (int) ((1L << cinfo->arith_dc_U[tbl]) >> 1)) + entropy->dc_context[ci] += 8; /* large diff category */ + /* Figure F.9: Encoding the magnitude bit pattern of v */ + st += 14; + while (m >>= 1) + arith_encode(cinfo, st, (m & v) ? 1 : 0); + } + + /* Sections F.1.4.2 & F.1.4.4.2: Encoding of AC coefficients */ + + tbl = compptr->ac_tbl_no; + + /* Establish EOB (end-of-block) index */ + for (ke = cinfo->lim_Se; ke > 0; ke--) + if ((*block)[natural_order[ke]]) break; + + /* Figure F.5: Encode_AC_Coefficients */ + for (k = 1; k <= ke; k++) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + arith_encode(cinfo, st, 0); /* EOB decision */ + while ((v = (*block)[natural_order[k]]) == 0) { + arith_encode(cinfo, st + 1, 0); st += 3; k++; + } + arith_encode(cinfo, st + 1, 1); + /* Figure F.6: Encoding nonzero value v */ + /* Figure F.7: Encoding the sign of v */ + if (v > 0) { + arith_encode(cinfo, entropy->fixed_bin, 0); + } else { + v = -v; + arith_encode(cinfo, entropy->fixed_bin, 1); + } + st += 2; + /* Figure F.8: Encoding the magnitude category of v */ + m = 0; + if (v -= 1) { + arith_encode(cinfo, st, 1); + m = 1; + v2 = v; + if (v2 >>= 1) { + arith_encode(cinfo, st, 1); + m <<= 1; + st = entropy->ac_stats[tbl] + + (k <= cinfo->arith_ac_K[tbl] ? 189 : 217); + while (v2 >>= 1) { + arith_encode(cinfo, st, 1); + m <<= 1; + st += 1; + } + } + } + arith_encode(cinfo, st, 0); + /* Figure F.9: Encoding the magnitude bit pattern of v */ + st += 14; + while (m >>= 1) + arith_encode(cinfo, st, (m & v) ? 1 : 0); + } + /* Encode EOB decision only if k <= cinfo->lim_Se */ + if (k <= cinfo->lim_Se) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + arith_encode(cinfo, st, 1); + } + } + + return TRUE; +} + + +/* + * Initialize for an arithmetic-compressed scan. + */ + +METHODDEF(void) +start_pass (j_compress_ptr cinfo, boolean gather_statistics) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + int ci, tbl; + jpeg_component_info * compptr; + + if (gather_statistics) + /* Make sure to avoid that in the master control logic! + * We are fully adaptive here and need no extra + * statistics gathering pass! + */ + ERREXIT(cinfo, JERR_NOT_COMPILED); + + /* We assume jcmaster.c already validated the progressive scan parameters. */ + + /* Select execution routines */ + if (cinfo->progressive_mode) { + if (cinfo->Ah == 0) { + if (cinfo->Ss == 0) + entropy->pub.encode_mcu = encode_mcu_DC_first; + else + entropy->pub.encode_mcu = encode_mcu_AC_first; + } else { + if (cinfo->Ss == 0) + entropy->pub.encode_mcu = encode_mcu_DC_refine; + else + entropy->pub.encode_mcu = encode_mcu_AC_refine; + } + } else + entropy->pub.encode_mcu = encode_mcu; + + /* Allocate & initialize requested statistics areas */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* DC needs no table for refinement scan */ + if (cinfo->Ss == 0 && cinfo->Ah == 0) { + tbl = compptr->dc_tbl_no; + if (tbl < 0 || tbl >= NUM_ARITH_TBLS) + ERREXIT1(cinfo, JERR_NO_ARITH_TABLE, tbl); + if (entropy->dc_stats[tbl] == NULL) + entropy->dc_stats[tbl] = (unsigned char *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, DC_STAT_BINS); + MEMZERO(entropy->dc_stats[tbl], DC_STAT_BINS); + /* Initialize DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + entropy->dc_context[ci] = 0; + } + /* AC needs no table when not present */ + if (cinfo->Se) { + tbl = compptr->ac_tbl_no; + if (tbl < 0 || tbl >= NUM_ARITH_TBLS) + ERREXIT1(cinfo, JERR_NO_ARITH_TABLE, tbl); + if (entropy->ac_stats[tbl] == NULL) + entropy->ac_stats[tbl] = (unsigned char *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, AC_STAT_BINS); + MEMZERO(entropy->ac_stats[tbl], AC_STAT_BINS); +#ifdef CALCULATE_SPECTRAL_CONDITIONING + if (cinfo->progressive_mode) + /* Section G.1.3.2: Set appropriate arithmetic conditioning value Kx */ + cinfo->arith_ac_K[tbl] = cinfo->Ss + ((8 + cinfo->Se - cinfo->Ss) >> 4); +#endif + } + } + + /* Initialize arithmetic encoding variables */ + entropy->c = 0; + entropy->a = 0x10000L; + entropy->sc = 0; + entropy->zc = 0; + entropy->ct = 11; + entropy->buffer = -1; /* empty */ + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* + * Module initialization routine for arithmetic entropy encoding. + */ + +GLOBAL(void) +jinit_arith_encoder (j_compress_ptr cinfo) +{ + arith_entropy_ptr entropy; + int i; + + entropy = (arith_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(arith_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass; + entropy->pub.finish_pass = finish_pass; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_ARITH_TBLS; i++) { + entropy->dc_stats[i] = NULL; + entropy->ac_stats[i] = NULL; + } + + /* Initialize index for fixed probability estimation */ + entropy->fixed_bin[0] = 113; +} diff --git a/src/jpeg-8c/jccoefct.c b/src/jpeg-8c/jccoefct.c new file mode 100644 index 0000000..d775313 --- /dev/null +++ b/src/jpeg-8c/jccoefct.c @@ -0,0 +1,453 @@ +/* + * jccoefct.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for compression. + * This controller is the top level of the JPEG compressor proper. + * The coefficient buffer lies between forward-DCT and entropy encoding steps. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* We use a full-image coefficient buffer when doing Huffman optimization, + * and also for writing multiple-scan JPEG files. In all cases, the DCT + * step is run during the first pass, and subsequent passes need only read + * the buffered coefficients. + */ +#ifdef ENTROPY_OPT_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#else +#ifdef C_MULTISCAN_FILES_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#endif +#endif + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* For single-pass compression, it's sufficient to buffer just one MCU + * (although this may prove a bit slow in practice). We allocate a + * workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each + * MCU constructed and sent. (On 80x86, the workspace is FAR even though + * it's not really very big; this is to keep the module interfaces unchanged + * when a large coefficient buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays. + */ + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +/* Forward declarations */ +METHODDEF(boolean) compress_data + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#ifdef FULL_COEF_BUFFER_SUPPORTED +METHODDEF(boolean) compress_first_pass + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +METHODDEF(boolean) compress_output + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#endif + + +LOCAL(void) +start_iMCU_row (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + coef->iMCU_row_num = 0; + start_iMCU_row(cinfo); + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (coef->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_data; + break; +#ifdef FULL_COEF_BUFFER_SUPPORTED + case JBUF_SAVE_AND_PASS: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_first_pass; + break; + case JBUF_CRANK_DEST: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_output; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data in the single-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf contains a plane for each component in image, + * which we index according to the component's SOF position. + */ + +METHODDEF(boolean) +compress_data (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, bi, ci, yindex, yoffset, blockcnt; + JDIMENSION ypos, xpos; + jpeg_component_info *compptr; + forward_DCT_ptr forward_DCT; + + /* Loop to write as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Determine where data comes from in input_buf and do the DCT thing. + * Each call on forward_DCT processes a horizontal row of DCT blocks + * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks + * sequentially. Dummy blocks at the right or bottom edge are filled in + * specially. The data in them does not matter for image reconstruction, + * so we fill them with values that will encode to the smallest amount of + * data, viz: all zeroes in the AC entries, DC entries equal to previous + * block's DC value. (Thanks to Thomas Kinsman for this idea.) + */ + blkn = 0; + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + forward_DCT = cinfo->fdct->forward_DCT[compptr->component_index]; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + xpos = MCU_col_num * compptr->MCU_sample_width; + ypos = yoffset * compptr->DCT_v_scaled_size; + /* ypos == (yoffset+yindex) * DCTSIZE */ + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + (*forward_DCT) (cinfo, compptr, + input_buf[compptr->component_index], + coef->MCU_buffer[blkn], + ypos, xpos, (JDIMENSION) blockcnt); + if (blockcnt < compptr->MCU_width) { + /* Create some dummy blocks at the right edge of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn + blockcnt], + (compptr->MCU_width - blockcnt) * SIZEOF(JBLOCK)); + for (bi = blockcnt; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn+bi-1][0][0]; + } + } + } else { + /* Create a row of dummy blocks at the bottom of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn], + compptr->MCU_width * SIZEOF(JBLOCK)); + for (bi = 0; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn-1][0][0]; + } + } + blkn += compptr->MCU_width; + ypos += compptr->DCT_v_scaled_size; + } + } + /* Try to write the MCU. In event of a suspension failure, we will + * re-DCT the MCU on restart (a bit inefficient, could be fixed...) + */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + + +#ifdef FULL_COEF_BUFFER_SUPPORTED + +/* + * Process some data in the first pass of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * This amount of data is read from the source buffer, DCT'd and quantized, + * and saved into the virtual arrays. We also generate suitable dummy blocks + * as needed at the right and lower edges. (The dummy blocks are constructed + * in the virtual arrays, which have been padded appropriately.) This makes + * it possible for subsequent passes not to worry about real vs. dummy blocks. + * + * We must also emit the data to the entropy encoder. This is conveniently + * done by calling compress_output() after we've loaded the current strip + * of the virtual arrays. + * + * NB: input_buf contains a plane for each component in image. All + * components are DCT'd and loaded into the virtual arrays in this pass. + * However, it may be that only a subset of the components are emitted to + * the entropy encoder during this first pass; be careful about looking + * at the scan-dependent variables (MCU dimensions, etc). + */ + +METHODDEF(boolean) +compress_first_pass (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION blocks_across, MCUs_across, MCUindex; + int bi, ci, h_samp_factor, block_row, block_rows, ndummy; + JCOEF lastDC; + jpeg_component_info *compptr; + JBLOCKARRAY buffer; + JBLOCKROW thisblockrow, lastblockrow; + forward_DCT_ptr forward_DCT; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (coef->iMCU_row_num < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here, since may not be set! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + blocks_across = compptr->width_in_blocks; + h_samp_factor = compptr->h_samp_factor; + /* Count number of dummy blocks to be added at the right margin. */ + ndummy = (int) (blocks_across % h_samp_factor); + if (ndummy > 0) + ndummy = h_samp_factor - ndummy; + forward_DCT = cinfo->fdct->forward_DCT[ci]; + /* Perform DCT for all non-dummy blocks in this iMCU row. Each call + * on forward_DCT processes a complete horizontal row of DCT blocks. + */ + for (block_row = 0; block_row < block_rows; block_row++) { + thisblockrow = buffer[block_row]; + (*forward_DCT) (cinfo, compptr, input_buf[ci], thisblockrow, + (JDIMENSION) (block_row * compptr->DCT_v_scaled_size), + (JDIMENSION) 0, blocks_across); + if (ndummy > 0) { + /* Create dummy blocks at the right edge of the image. */ + thisblockrow += blocks_across; /* => first dummy block */ + jzero_far((void FAR *) thisblockrow, ndummy * SIZEOF(JBLOCK)); + lastDC = thisblockrow[-1][0]; + for (bi = 0; bi < ndummy; bi++) { + thisblockrow[bi][0] = lastDC; + } + } + } + /* If at end of image, create dummy block rows as needed. + * The tricky part here is that within each MCU, we want the DC values + * of the dummy blocks to match the last real block's DC value. + * This squeezes a few more bytes out of the resulting file... + */ + if (coef->iMCU_row_num == last_iMCU_row) { + blocks_across += ndummy; /* include lower right corner */ + MCUs_across = blocks_across / h_samp_factor; + for (block_row = block_rows; block_row < compptr->v_samp_factor; + block_row++) { + thisblockrow = buffer[block_row]; + lastblockrow = buffer[block_row-1]; + jzero_far((void FAR *) thisblockrow, + (size_t) (blocks_across * SIZEOF(JBLOCK))); + for (MCUindex = 0; MCUindex < MCUs_across; MCUindex++) { + lastDC = lastblockrow[h_samp_factor-1][0]; + for (bi = 0; bi < h_samp_factor; bi++) { + thisblockrow[bi][0] = lastDC; + } + thisblockrow += h_samp_factor; /* advance to next MCU in row */ + lastblockrow += h_samp_factor; + } + } + } + } + /* NB: compress_output will increment iMCU_row_num if successful. + * A suspension return will result in redoing all the work above next time. + */ + + /* Emit data to the entropy encoder, sharing code with subsequent passes */ + return compress_output(cinfo, input_buf); +} + + +/* + * Process some data in subsequent passes of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF(boolean) +compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. + * NB: during first pass, this is safe only because the buffers will + * already be aligned properly, so jmemmgr.c won't need to do any I/O. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + +#endif /* FULL_COEF_BUFFER_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL(void) +jinit_c_coef_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef FULL_COEF_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + int ci; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->whole_image[0] = NULL; /* flag for no virtual arrays */ + } +} diff --git a/src/jpeg-8c/jccolor.c b/src/jpeg-8c/jccolor.c new file mode 100644 index 0000000..0a8a4b5 --- /dev/null +++ b/src/jpeg-8c/jccolor.c @@ -0,0 +1,459 @@ +/* + * jccolor.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_converter pub; /* public fields */ + + /* Private state for RGB->YCC conversion */ + INT32 * rgb_ycc_tab; /* => table for RGB to YCbCr conversion */ +} my_color_converter; + +typedef my_color_converter * my_cconvert_ptr; + + +/**************** RGB -> YCbCr conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * Y = 0.29900 * R + 0.58700 * G + 0.11400 * B + * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTERJSAMPLE + * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTERJSAMPLE + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * Note: older versions of the IJG code used a zero offset of MAXJSAMPLE/2, + * rather than CENTERJSAMPLE, for Cb and Cr. This gave equal positive and + * negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0) + * were not represented exactly. Now we sacrifice exact representation of + * maximum red and maximum blue in order to get exact grayscales. + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times R,G,B for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included + * in the tables to save adding them separately in the inner loop. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define CBCR_OFFSET ((INT32) CENTERJSAMPLE << SCALEBITS) +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L< Y section */ +#define G_Y_OFF (1*(MAXJSAMPLE+1)) /* offset to G => Y section */ +#define B_Y_OFF (2*(MAXJSAMPLE+1)) /* etc. */ +#define R_CB_OFF (3*(MAXJSAMPLE+1)) +#define G_CB_OFF (4*(MAXJSAMPLE+1)) +#define B_CB_OFF (5*(MAXJSAMPLE+1)) +#define R_CR_OFF B_CB_OFF /* B=>Cb, R=>Cr are the same */ +#define G_CR_OFF (6*(MAXJSAMPLE+1)) +#define B_CR_OFF (7*(MAXJSAMPLE+1)) +#define TABLE_SIZE (8*(MAXJSAMPLE+1)) + + +/* + * Initialize for RGB->YCC colorspace conversion. + */ + +METHODDEF(void) +rgb_ycc_start (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + INT32 * rgb_ycc_tab; + INT32 i; + + /* Allocate and fill in the conversion tables. */ + cconvert->rgb_ycc_tab = rgb_ycc_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (TABLE_SIZE * SIZEOF(INT32))); + + for (i = 0; i <= MAXJSAMPLE; i++) { + rgb_ycc_tab[i+R_Y_OFF] = FIX(0.29900) * i; + rgb_ycc_tab[i+G_Y_OFF] = FIX(0.58700) * i; + rgb_ycc_tab[i+B_Y_OFF] = FIX(0.11400) * i + ONE_HALF; + rgb_ycc_tab[i+R_CB_OFF] = (-FIX(0.16874)) * i; + rgb_ycc_tab[i+G_CB_OFF] = (-FIX(0.33126)) * i; + /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr. + * This ensures that the maximum output will round to MAXJSAMPLE + * not MAXJSAMPLE+1, and thus that we don't have to range-limit. + */ + rgb_ycc_tab[i+B_CB_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +/* B=>Cb and R=>Cr tables are the same + rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +*/ + rgb_ycc_tab[i+G_CR_OFF] = (-FIX(0.41869)) * i; + rgb_ycc_tab[i+B_CR_OFF] = (-FIX(0.08131)) * i; + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * + * Note that we change from the application's interleaved-pixel format + * to our internal noninterleaved, one-plane-per-component format. + * The input buffer is therefore three times as wide as the output buffer. + * + * A starting row offset is provided only for the output buffer. The caller + * can easily adjust the passed input_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF(void) +rgb_ycc_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/**************** Cases other than RGB -> YCbCr **************/ + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles RGB->grayscale conversion, which is the same + * as the RGB->Y portion of RGB->YCbCr. + * We assume rgb_ycc_start has been called (we only use the Y tables). + */ + +METHODDEF(void) +rgb_gray_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* Y */ + outptr[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles Adobe-style CMYK->YCCK conversion, + * where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same + * conversion as above, while passing K (black) unchanged. + * We assume rgb_ycc_start has been called. + */ + +METHODDEF(void) +cmyk_ycck_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2, outptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + outptr3 = output_buf[3][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = MAXJSAMPLE - GETJSAMPLE(inptr[0]); + g = MAXJSAMPLE - GETJSAMPLE(inptr[1]); + b = MAXJSAMPLE - GETJSAMPLE(inptr[2]); + /* K passes through as-is */ + outptr3[col] = inptr[3]; /* don't need GETJSAMPLE here */ + inptr += 4; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles grayscale output with no conversion. + * The source can be either plain grayscale or YCbCr (since Y == gray). + */ + +METHODDEF(void) +grayscale_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + int instride = cinfo->input_components; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[0]; /* don't need GETJSAMPLE() here */ + inptr += instride; + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles multi-component colorspaces without conversion. + * We assume input_components == num_components. + */ + +METHODDEF(void) +null_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + register int ci; + int nc = cinfo->num_components; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + /* It seems fastest to make a separate pass for each component. */ + for (ci = 0; ci < nc; ci++) { + inptr = *input_buf; + outptr = output_buf[ci][output_row]; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */ + inptr += nc; + } + } + input_buf++; + output_row++; + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF(void) +null_method (j_compress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for input colorspace conversion. + */ + +GLOBAL(void) +jinit_color_converter (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_converter)); + cinfo->cconvert = (struct jpeg_color_converter *) cconvert; + /* set start_pass to null method until we find out differently */ + cconvert->pub.start_pass = null_method; + + /* Make sure input_components agrees with in_color_space */ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + if (cinfo->input_components != 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + if (cinfo->input_components != RGB_PIXELSIZE) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; +#endif /* else share code with YCbCr */ + + case JCS_YCbCr: + if (cinfo->input_components != 3) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->input_components != 4) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->input_components < 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + } + + /* Check num_components, set conversion method based on requested space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_GRAYSCALE) + cconvert->pub.color_convert = grayscale_convert; + else if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_gray_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = grayscale_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB && RGB_PIXELSIZE == 3) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_ycc_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = cmyk_ycck_convert; + } else if (cinfo->in_color_space == JCS_YCCK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: /* allow null conversion of JCS_UNKNOWN */ + if (cinfo->jpeg_color_space != cinfo->in_color_space || + cinfo->num_components != cinfo->input_components) + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + cconvert->pub.color_convert = null_convert; + break; + } +} diff --git a/src/jpeg-8c/jcdctmgr.c b/src/jpeg-8c/jcdctmgr.c new file mode 100644 index 0000000..0bbdbb6 --- /dev/null +++ b/src/jpeg-8c/jcdctmgr.c @@ -0,0 +1,482 @@ +/* + * jcdctmgr.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the forward-DCT management logic. + * This code selects a particular DCT implementation to be used, + * and it performs related housekeeping chores including coefficient + * quantization. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_forward_dct pub; /* public fields */ + + /* Pointer to the DCT routine actually in use */ + forward_DCT_method_ptr do_dct[MAX_COMPONENTS]; + + /* The actual post-DCT divisors --- not identical to the quant table + * entries, because of scaling (especially for an unnormalized DCT). + * Each table is given in normal array order. + */ + DCTELEM * divisors[NUM_QUANT_TBLS]; + +#ifdef DCT_FLOAT_SUPPORTED + /* Same as above for the floating-point case. */ + float_DCT_method_ptr do_float_dct[MAX_COMPONENTS]; + FAST_FLOAT * float_divisors[NUM_QUANT_TBLS]; +#endif +} my_fdct_controller; + +typedef my_fdct_controller * my_fdct_ptr; + + +/* The current scaled-DCT routines require ISLOW-style divisor tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef DCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Perform forward DCT on one or more blocks of a component. + * + * The input samples are taken from the sample_data[] array starting at + * position start_row/start_col, and moving to the right for any additional + * blocks. The quantized coefficients are returned in coef_blocks[]. + */ + +METHODDEF(void) +forward_DCT (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for integer DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + forward_DCT_method_ptr do_dct = fdct->do_dct[compptr->component_index]; + DCTELEM * divisors = fdct->divisors[compptr->quant_tbl_no]; + DCTELEM workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += compptr->DCT_h_scaled_size) { + /* Perform the DCT */ + (*do_dct) (workspace, sample_data, start_col); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register DCTELEM temp, qval; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + qval = divisors[i]; + temp = workspace[i]; + /* Divide the coefficient value by qval, ensuring proper rounding. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * + * In most files, at least half of the output values will be zero + * (at default quantization settings, more like three-quarters...) + * so we should ensure that this case is fast. On many machines, + * a comparison is enough cheaper than a divide to make a special test + * a win. Since both inputs will be nonnegative, we need only test + * for a < b to discover whether a/b is 0. + * If your machine's division is fast enough, define FAST_DIVIDE. + */ +#ifdef FAST_DIVIDE +#define DIVIDE_BY(a,b) a /= b +#else +#define DIVIDE_BY(a,b) if (a >= b) a /= b; else a = 0 +#endif + if (temp < 0) { + temp = -temp; + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + temp = -temp; + } else { + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + } + output_ptr[i] = (JCOEF) temp; + } + } + } +} + + +#ifdef DCT_FLOAT_SUPPORTED + +METHODDEF(void) +forward_DCT_float (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for floating-point DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + float_DCT_method_ptr do_dct = fdct->do_float_dct[compptr->component_index]; + FAST_FLOAT * divisors = fdct->float_divisors[compptr->quant_tbl_no]; + FAST_FLOAT workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += compptr->DCT_h_scaled_size) { + /* Perform the DCT */ + (*do_dct) (workspace, sample_data, start_col); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register FAST_FLOAT temp; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + /* Apply the quantization and scaling factor */ + temp = workspace[i] * divisors[i]; + /* Round to nearest integer. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * The maximum coefficient size is +-16K (for 12-bit data), so this + * code should work for either 16-bit or 32-bit ints. + */ + output_ptr[i] = (JCOEF) ((int) (temp + (FAST_FLOAT) 16384.5) - 16384); + } + } + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ + + +/* + * Initialize for a processing pass. + * Verify that all referenced Q-tables are present, and set up + * the divisor table for each one. + * In the current implementation, DCT of all components is done during + * the first pass, even if only some components will be output in the + * first scan. Hence all components should be examined here. + */ + +METHODDEF(void) +start_pass_fdctmgr (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + int ci, qtblno, i; + jpeg_component_info *compptr; + int method = 0; + JQUANT_TBL * qtbl; + DCTELEM * dtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Select the proper DCT routine for this component's scaling */ + switch ((compptr->DCT_h_scaled_size << 8) + compptr->DCT_v_scaled_size) { +#ifdef DCT_SCALING_SUPPORTED + case ((1 << 8) + 1): + fdct->do_dct[ci] = jpeg_fdct_1x1; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((2 << 8) + 2): + fdct->do_dct[ci] = jpeg_fdct_2x2; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((3 << 8) + 3): + fdct->do_dct[ci] = jpeg_fdct_3x3; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((4 << 8) + 4): + fdct->do_dct[ci] = jpeg_fdct_4x4; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((5 << 8) + 5): + fdct->do_dct[ci] = jpeg_fdct_5x5; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((6 << 8) + 6): + fdct->do_dct[ci] = jpeg_fdct_6x6; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((7 << 8) + 7): + fdct->do_dct[ci] = jpeg_fdct_7x7; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((9 << 8) + 9): + fdct->do_dct[ci] = jpeg_fdct_9x9; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((10 << 8) + 10): + fdct->do_dct[ci] = jpeg_fdct_10x10; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((11 << 8) + 11): + fdct->do_dct[ci] = jpeg_fdct_11x11; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((12 << 8) + 12): + fdct->do_dct[ci] = jpeg_fdct_12x12; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((13 << 8) + 13): + fdct->do_dct[ci] = jpeg_fdct_13x13; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((14 << 8) + 14): + fdct->do_dct[ci] = jpeg_fdct_14x14; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((15 << 8) + 15): + fdct->do_dct[ci] = jpeg_fdct_15x15; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((16 << 8) + 16): + fdct->do_dct[ci] = jpeg_fdct_16x16; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((16 << 8) + 8): + fdct->do_dct[ci] = jpeg_fdct_16x8; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((14 << 8) + 7): + fdct->do_dct[ci] = jpeg_fdct_14x7; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((12 << 8) + 6): + fdct->do_dct[ci] = jpeg_fdct_12x6; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((10 << 8) + 5): + fdct->do_dct[ci] = jpeg_fdct_10x5; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((8 << 8) + 4): + fdct->do_dct[ci] = jpeg_fdct_8x4; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((6 << 8) + 3): + fdct->do_dct[ci] = jpeg_fdct_6x3; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((4 << 8) + 2): + fdct->do_dct[ci] = jpeg_fdct_4x2; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((2 << 8) + 1): + fdct->do_dct[ci] = jpeg_fdct_2x1; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((8 << 8) + 16): + fdct->do_dct[ci] = jpeg_fdct_8x16; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((7 << 8) + 14): + fdct->do_dct[ci] = jpeg_fdct_7x14; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((6 << 8) + 12): + fdct->do_dct[ci] = jpeg_fdct_6x12; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((5 << 8) + 10): + fdct->do_dct[ci] = jpeg_fdct_5x10; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((4 << 8) + 8): + fdct->do_dct[ci] = jpeg_fdct_4x8; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((3 << 8) + 6): + fdct->do_dct[ci] = jpeg_fdct_3x6; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((2 << 8) + 4): + fdct->do_dct[ci] = jpeg_fdct_2x4; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; + case ((1 << 8) + 2): + fdct->do_dct[ci] = jpeg_fdct_1x2; + method = JDCT_ISLOW; /* jfdctint uses islow-style table */ + break; +#endif + case ((DCTSIZE << 8) + DCTSIZE): + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + fdct->do_dct[ci] = jpeg_fdct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + fdct->do_dct[ci] = jpeg_fdct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + fdct->do_float_dct[ci] = jpeg_fdct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + break; + default: + ERREXIT2(cinfo, JERR_BAD_DCTSIZE, + compptr->DCT_h_scaled_size, compptr->DCT_v_scaled_size); + break; + } + qtblno = compptr->quant_tbl_no; + /* Make sure specified quantization table is present */ + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + qtbl = cinfo->quant_tbl_ptrs[qtblno]; + /* Compute divisors for this quant table */ + /* We may do this more than once for same table, but it's not a big deal */ + switch (method) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + /* For LL&M IDCT method, divisors are equal to raw quantization + * coefficients multiplied by 8 (to counteract scaling). + */ + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = ((DCTELEM) qtbl->quantval[i]) << 3; + } + fdct->pub.forward_DCT[ci] = forward_DCT; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + */ +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = (DCTELEM) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i], + (INT32) aanscales[i]), + CONST_BITS-3); + } + } + fdct->pub.forward_DCT[ci] = forward_DCT; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + * What's actually stored is 1/divisor so that the inner loop can + * use a multiplication rather than a division. + */ + FAST_FLOAT * fdtbl; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + if (fdct->float_divisors[qtblno] == NULL) { + fdct->float_divisors[qtblno] = (FAST_FLOAT *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(FAST_FLOAT)); + } + fdtbl = fdct->float_divisors[qtblno]; + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fdtbl[i] = (FAST_FLOAT) + (1.0 / (((double) qtbl->quantval[i] * + aanscalefactor[row] * aanscalefactor[col] * 8.0))); + i++; + } + } + } + fdct->pub.forward_DCT[ci] = forward_DCT_float; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Initialize FDCT manager. + */ + +GLOBAL(void) +jinit_forward_dct (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct; + int i; + + fdct = (my_fdct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_fdct_controller)); + cinfo->fdct = (struct jpeg_forward_dct *) fdct; + fdct->pub.start_pass = start_pass_fdctmgr; + + /* Mark divisor tables unallocated */ + for (i = 0; i < NUM_QUANT_TBLS; i++) { + fdct->divisors[i] = NULL; +#ifdef DCT_FLOAT_SUPPORTED + fdct->float_divisors[i] = NULL; +#endif + } +} diff --git a/src/jpeg-8c/jchuff.c b/src/jpeg-8c/jchuff.c new file mode 100644 index 0000000..257d7aa --- /dev/null +++ b/src/jpeg-8c/jchuff.c @@ -0,0 +1,1576 @@ +/* + * jchuff.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 2006-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines. + * Both sequential and progressive modes are supported in this single module. + * + * Much of the complexity here has to do with supporting output suspension. + * If the data destination module demands suspension, we want to be able to + * back up to the start of the current MCU. To do this, we copy state + * variables into local working storage, and update them back to the + * permanent JPEG objects only upon successful completion of an MCU. + * + * We do not support output suspension for the progressive JPEG mode, since + * the library currently does not allow multiple-scan files to be written + * with output suspension. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* The legal range of a DCT coefficient is + * -1024 .. +1023 for 8-bit data; + * -16384 .. +16383 for 12-bit data. + * Hence the magnitude should always fit in 10 or 14 bits respectively. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MAX_COEF_BITS 10 +#else +#define MAX_COEF_BITS 14 +#endif + +/* Derived data constructed for each Huffman table */ + +typedef struct { + unsigned int ehufco[256]; /* code for each symbol */ + char ehufsi[256]; /* length of code for each symbol */ + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ +} c_derived_tbl; + + +/* Expanded entropy encoder object for Huffman encoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).put_buffer = (src).put_buffer, \ + (dest).put_bits = (src).put_bits, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + savable_state saved; /* Bit buffer & DC state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + c_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + c_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + + /* Statistics tables for optimization */ + long * dc_count_ptrs[NUM_HUFF_TBLS]; + long * ac_count_ptrs[NUM_HUFF_TBLS]; + + /* Following fields used only in progressive mode */ + + /* Mode flag: TRUE for optimization, FALSE for actual data output */ + boolean gather_statistics; + + /* next_output_byte/free_in_buffer are local copies of cinfo->dest fields. + */ + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + j_compress_ptr cinfo; /* link to cinfo (needed for dump_buffer) */ + + /* Coding status for AC components */ + int ac_tbl_no; /* the table number of the single component */ + unsigned int EOBRUN; /* run length of EOBs */ + unsigned int BE; /* # of buffered correction bits before MCU */ + char * bit_buffer; /* buffer for correction bits (1 per char) */ + /* packing correction bits tightly would save some space but cost time... */ +} huff_entropy_encoder; + +typedef huff_entropy_encoder * huff_entropy_ptr; + +/* Working state while writing an MCU (sequential mode). + * This struct contains all the fields that are needed by subroutines. + */ + +typedef struct { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + savable_state cur; /* Current bit buffer & DC state */ + j_compress_ptr cinfo; /* dump_buffer needs access to this */ +} working_state; + +/* MAX_CORR_BITS is the number of bits the AC refinement correction-bit + * buffer can hold. Larger sizes may slightly improve compression, but + * 1000 is already well into the realm of overkill. + * The minimum safe size is 64 bits. + */ + +#define MAX_CORR_BITS 1000 /* Max # of correction bits I can buffer */ + +/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32. + * We assume that int right shift is unsigned if INT32 right shift is, + * which should be safe. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS int ishift_temp; +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~0) << (16-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* + * Compute the derived values for a Huffman table. + * This routine also performs some validation checks on the table. + */ + +LOCAL(void) +jpeg_make_c_derived_tbl (j_compress_ptr cinfo, boolean isDC, int tblno, + c_derived_tbl ** pdtbl) +{ + JHUFF_TBL *htbl; + c_derived_tbl *dtbl; + int p, i, l, lastp, si, maxsymbol; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Note that huffsize[] and huffcode[] are filled in code-length order, + * paralleling the order of the symbols themselves in htbl->huffval[]. + */ + + /* Find the input Huffman table */ + if (tblno < 0 || tblno >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + htbl = + isDC ? cinfo->dc_huff_tbl_ptrs[tblno] : cinfo->ac_huff_tbl_ptrs[tblno]; + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (c_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(c_derived_tbl)); + dtbl = *pdtbl; + + /* Figure C.1: make table of Huffman code length for each symbol */ + + p = 0; + for (l = 1; l <= 16; l++) { + i = (int) htbl->bits[l]; + if (i < 0 || p + i > 256) /* protect against table overrun */ + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + while (i--) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + lastp = p; + + /* Figure C.2: generate the codes themselves */ + /* We also validate that the counts represent a legal Huffman code tree. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + /* code is now 1 more than the last code used for codelength si; but + * it must still fit in si bits, since no code is allowed to be all ones. + */ + if (((INT32) code) >= (((INT32) 1) << si)) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + code <<= 1; + si++; + } + + /* Figure C.3: generate encoding tables */ + /* These are code and size indexed by symbol value */ + + /* Set all codeless symbols to have code length 0; + * this lets us detect duplicate VAL entries here, and later + * allows emit_bits to detect any attempt to emit such symbols. + */ + MEMZERO(dtbl->ehufsi, SIZEOF(dtbl->ehufsi)); + + /* This is also a convenient place to check for out-of-range + * and duplicated VAL entries. We allow 0..255 for AC symbols + * but only 0..15 for DC. (We could constrain them further + * based on data depth and mode, but this seems enough.) + */ + maxsymbol = isDC ? 15 : 255; + + for (p = 0; p < lastp; p++) { + i = htbl->huffval[p]; + if (i < 0 || i > maxsymbol || dtbl->ehufsi[i]) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + dtbl->ehufco[i] = huffcode[p]; + dtbl->ehufsi[i] = huffsize[p]; + } +} + + +/* Outputting bytes to the file. + * NB: these must be called only when actually outputting, + * that is, entropy->gather_statistics == FALSE. + */ + +/* Emit a byte, taking 'action' if must suspend. */ +#define emit_byte_s(state,val,action) \ + { *(state)->next_output_byte++ = (JOCTET) (val); \ + if (--(state)->free_in_buffer == 0) \ + if (! dump_buffer_s(state)) \ + { action; } } + +/* Emit a byte */ +#define emit_byte_e(entropy,val) \ + { *(entropy)->next_output_byte++ = (JOCTET) (val); \ + if (--(entropy)->free_in_buffer == 0) \ + dump_buffer_e(entropy); } + + +LOCAL(boolean) +dump_buffer_s (working_state * state) +/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */ +{ + struct jpeg_destination_mgr * dest = state->cinfo->dest; + + if (! (*dest->empty_output_buffer) (state->cinfo)) + return FALSE; + /* After a successful buffer dump, must reset buffer pointers */ + state->next_output_byte = dest->next_output_byte; + state->free_in_buffer = dest->free_in_buffer; + return TRUE; +} + + +LOCAL(void) +dump_buffer_e (huff_entropy_ptr entropy) +/* Empty the output buffer; we do not support suspension in this case. */ +{ + struct jpeg_destination_mgr * dest = entropy->cinfo->dest; + + if (! (*dest->empty_output_buffer) (entropy->cinfo)) + ERREXIT(entropy->cinfo, JERR_CANT_SUSPEND); + /* After a successful buffer dump, must reset buffer pointers */ + entropy->next_output_byte = dest->next_output_byte; + entropy->free_in_buffer = dest->free_in_buffer; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL(boolean) +emit_bits_s (working_state * state, unsigned int code, int size) +/* Emit some bits; return TRUE if successful, FALSE if must suspend */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = state->cur.put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(state->cinfo, JERR_HUFF_MISSING_CODE); + + put_buffer &= (((INT32) 1)<cur.put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte_s(state, c, return FALSE); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte_s(state, 0, return FALSE); + } + put_buffer <<= 8; + put_bits -= 8; + } + + state->cur.put_buffer = put_buffer; /* update state variables */ + state->cur.put_bits = put_bits; + + return TRUE; +} + + +INLINE +LOCAL(void) +emit_bits_e (huff_entropy_ptr entropy, unsigned int code, int size) +/* Emit some bits, unless we are in gather mode */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = entropy->saved.put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE); + + if (entropy->gather_statistics) + return; /* do nothing if we're only getting stats */ + + put_buffer &= (((INT32) 1)<saved.put_buffer; + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte_e(entropy, c); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte_e(entropy, 0); + } + put_buffer <<= 8; + put_bits -= 8; + } + + entropy->saved.put_buffer = put_buffer; /* update variables */ + entropy->saved.put_bits = put_bits; +} + + +LOCAL(boolean) +flush_bits_s (working_state * state) +{ + if (! emit_bits_s(state, 0x7F, 7)) /* fill any partial byte with ones */ + return FALSE; + state->cur.put_buffer = 0; /* and reset bit-buffer to empty */ + state->cur.put_bits = 0; + return TRUE; +} + + +LOCAL(void) +flush_bits_e (huff_entropy_ptr entropy) +{ + emit_bits_e(entropy, 0x7F, 7); /* fill any partial byte with ones */ + entropy->saved.put_buffer = 0; /* and reset bit-buffer to empty */ + entropy->saved.put_bits = 0; +} + + +/* + * Emit (or just count) a Huffman symbol. + */ + +INLINE +LOCAL(void) +emit_dc_symbol (huff_entropy_ptr entropy, int tbl_no, int symbol) +{ + if (entropy->gather_statistics) + entropy->dc_count_ptrs[tbl_no][symbol]++; + else { + c_derived_tbl * tbl = entropy->dc_derived_tbls[tbl_no]; + emit_bits_e(entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol]); + } +} + + +INLINE +LOCAL(void) +emit_ac_symbol (huff_entropy_ptr entropy, int tbl_no, int symbol) +{ + if (entropy->gather_statistics) + entropy->ac_count_ptrs[tbl_no][symbol]++; + else { + c_derived_tbl * tbl = entropy->ac_derived_tbls[tbl_no]; + emit_bits_e(entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol]); + } +} + + +/* + * Emit bits from a correction bit buffer. + */ + +LOCAL(void) +emit_buffered_bits (huff_entropy_ptr entropy, char * bufstart, + unsigned int nbits) +{ + if (entropy->gather_statistics) + return; /* no real work */ + + while (nbits > 0) { + emit_bits_e(entropy, (unsigned int) (*bufstart), 1); + bufstart++; + nbits--; + } +} + + +/* + * Emit any pending EOBRUN symbol. + */ + +LOCAL(void) +emit_eobrun (huff_entropy_ptr entropy) +{ + register int temp, nbits; + + if (entropy->EOBRUN > 0) { /* if there is any pending EOBRUN */ + temp = entropy->EOBRUN; + nbits = 0; + while ((temp >>= 1)) + nbits++; + /* safety check: shouldn't happen given limited correction-bit buffer */ + if (nbits > 14) + ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE); + + emit_ac_symbol(entropy, entropy->ac_tbl_no, nbits << 4); + if (nbits) + emit_bits_e(entropy, entropy->EOBRUN, nbits); + + entropy->EOBRUN = 0; + + /* Emit any buffered correction bits */ + emit_buffered_bits(entropy, entropy->bit_buffer, entropy->BE); + entropy->BE = 0; + } +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL(boolean) +emit_restart_s (working_state * state, int restart_num) +{ + int ci; + + if (! flush_bits_s(state)) + return FALSE; + + emit_byte_s(state, 0xFF, return FALSE); + emit_byte_s(state, JPEG_RST0 + restart_num, return FALSE); + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < state->cinfo->comps_in_scan; ci++) + state->cur.last_dc_val[ci] = 0; + + /* The restart counter is not updated until we successfully write the MCU. */ + + return TRUE; +} + + +LOCAL(void) +emit_restart_e (huff_entropy_ptr entropy, int restart_num) +{ + int ci; + + emit_eobrun(entropy); + + if (! entropy->gather_statistics) { + flush_bits_e(entropy); + emit_byte_e(entropy, 0xFF); + emit_byte_e(entropy, JPEG_RST0 + restart_num); + } + + if (entropy->cinfo->Ss == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < entropy->cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + } else { + /* Re-initialize all AC-related fields to 0 */ + entropy->EOBRUN = 0; + entropy->BE = 0; + } +} + + +/* + * MCU encoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +encode_mcu_DC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + int blkn, ci; + int Al = cinfo->Al; + JBLOCKROW block; + jpeg_component_info * compptr; + ISHIFT_TEMPS + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart_e(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Compute the DC value after the required point transform by Al. + * This is simply an arithmetic right shift. + */ + temp2 = IRIGHT_SHIFT((int) ((*block)[0]), Al); + + /* DC differences are figured on the point-transformed values. */ + temp = temp2 - entropy->saved.last_dc_val[ci]; + entropy->saved.last_dc_val[ci] = temp2; + + /* Encode the DC coefficient difference per section G.1.2.1 */ + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count/emit the Huffman-coded symbol for the number of bits */ + emit_dc_symbol(entropy, compptr->dc_tbl_no, nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + emit_bits_e(entropy, (unsigned int) temp2, nbits); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +encode_mcu_AC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + register int r, k; + int Se, Al; + const int * natural_order; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart_e(entropy, entropy->next_restart_num); + + Se = cinfo->Se; + Al = cinfo->Al; + natural_order = cinfo->natural_order; + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */ + + r = 0; /* r = run length of zeros */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = (*block)[natural_order[k]]) == 0) { + r++; + continue; + } + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value; so the code is + * interwoven with finding the abs value (temp) and output bits (temp2). + */ + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ + temp2 = ~temp; + } else { + temp >>= Al; /* apply the point transform */ + temp2 = temp; + } + /* Watch out for case that nonzero coef is zero after point transform */ + if (temp == 0) { + r++; + continue; + } + + /* Emit any pending EOBRUN */ + if (entropy->EOBRUN > 0) + emit_eobrun(entropy); + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + emit_ac_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_ac_symbol(entropy, entropy->ac_tbl_no, (r << 4) + nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + emit_bits_e(entropy, (unsigned int) temp2, nbits); + + r = 0; /* reset zero run length */ + } + + if (r > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + if (entropy->EOBRUN == 0x7FFF) + emit_eobrun(entropy); /* force it out to avoid overflow */ + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF(boolean) +encode_mcu_DC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int temp; + int blkn; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart_e(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* We simply emit the Al'th bit of the DC coefficient value. */ + temp = (*block)[0]; + emit_bits_e(entropy, (unsigned int) (temp >> Al), 1); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC successive approximation refinement scan. + */ + +METHODDEF(boolean) +encode_mcu_AC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int temp; + register int r, k; + int EOB; + char *BR_buffer; + unsigned int BR; + int Se, Al; + const int * natural_order; + JBLOCKROW block; + int absvalues[DCTSIZE2]; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart_e(entropy, entropy->next_restart_num); + + Se = cinfo->Se; + Al = cinfo->Al; + natural_order = cinfo->natural_order; + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* It is convenient to make a pre-pass to determine the transformed + * coefficients' absolute values and the EOB position. + */ + EOB = 0; + for (k = cinfo->Ss; k <= Se; k++) { + temp = (*block)[natural_order[k]]; + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if (temp < 0) + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + absvalues[k] = temp; /* save abs value for main pass */ + if (temp == 1) + EOB = k; /* EOB = index of last newly-nonzero coef */ + } + + /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */ + + r = 0; /* r = run length of zeros */ + BR = 0; /* BR = count of buffered bits added now */ + BR_buffer = entropy->bit_buffer + entropy->BE; /* Append bits to buffer */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = absvalues[k]) == 0) { + r++; + continue; + } + + /* Emit any required ZRLs, but not if they can be folded into EOB */ + while (r > 15 && k <= EOB) { + /* emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + /* Emit ZRL */ + emit_ac_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + /* Emit buffered correction bits that must be associated with ZRL */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + } + + /* If the coef was previously nonzero, it only needs a correction bit. + * NOTE: a straight translation of the spec's figure G.7 would suggest + * that we also need to test r > 15. But if r > 15, we can only get here + * if k > EOB, which implies that this coefficient is not 1. + */ + if (temp > 1) { + /* The correction bit is the next bit of the absolute value. */ + BR_buffer[BR++] = (char) (temp & 1); + continue; + } + + /* Emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_ac_symbol(entropy, entropy->ac_tbl_no, (r << 4) + 1); + + /* Emit output bit for newly-nonzero coef */ + temp = ((*block)[natural_order[k]] < 0) ? 0 : 1; + emit_bits_e(entropy, (unsigned int) temp, 1); + + /* Emit buffered correction bits that must be associated with this code */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + r = 0; /* reset zero run length */ + } + + if (r > 0 || BR > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + entropy->BE += BR; /* concat my correction bits to older ones */ + /* We force out the EOB if we risk either: + * 1. overflow of the EOB counter; + * 2. overflow of the correction bit buffer during the next MCU. + */ + if (entropy->EOBRUN == 0x7FFF || entropy->BE > (MAX_CORR_BITS-DCTSIZE2+1)) + emit_eobrun(entropy); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* Encode a single block's worth of coefficients */ + +LOCAL(boolean) +encode_one_block (working_state * state, JCOEFPTR block, int last_dc_val, + c_derived_tbl *dctbl, c_derived_tbl *actbl) +{ + register int temp, temp2; + register int nbits; + register int k, r, i; + int Se = state->cinfo->lim_Se; + const int * natural_order = state->cinfo->natural_order; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = temp2 = block[0] - last_dc_val; + + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(state->cinfo, JERR_BAD_DCT_COEF); + + /* Emit the Huffman-coded symbol for the number of bits */ + if (! emit_bits_s(state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + if (! emit_bits_s(state, (unsigned int) temp2, nbits)) + return FALSE; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k <= Se; k++) { + if ((temp = block[natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + if (! emit_bits_s(state, actbl->ehufco[0xF0], actbl->ehufsi[0xF0])) + return FALSE; + r -= 16; + } + + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(state->cinfo, JERR_BAD_DCT_COEF); + + /* Emit Huffman symbol for run length / number of bits */ + i = (r << 4) + nbits; + if (! emit_bits_s(state, actbl->ehufco[i], actbl->ehufsi[i])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (! emit_bits_s(state, (unsigned int) temp2, nbits)) + return FALSE; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + if (! emit_bits_s(state, actbl->ehufco[0], actbl->ehufsi[0])) + return FALSE; + + return TRUE; +} + + +/* + * Encode and output one MCU's worth of Huffman-compressed coefficients. + */ + +METHODDEF(boolean) +encode_mcu_huff (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + int blkn, ci; + jpeg_component_info * compptr; + + /* Load up working state */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! emit_restart_s(&state, entropy->next_restart_num)) + return FALSE; + } + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + if (! encode_one_block(&state, + MCU_data[blkn][0], state.cur.last_dc_val[ci], + entropy->dc_derived_tbls[compptr->dc_tbl_no], + entropy->ac_derived_tbls[compptr->ac_tbl_no])) + return FALSE; + /* Update last_dc_val */ + state.cur.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + /* Completed MCU, so update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed scan. + */ + +METHODDEF(void) +finish_pass_huff (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + + if (cinfo->progressive_mode) { + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Flush out any buffered data */ + emit_eobrun(entropy); + flush_bits_e(entropy); + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + } else { + /* Load up working state ... flush_bits needs it */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Flush out the last data */ + if (! flush_bits_s(&state)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + + /* Update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); + } +} + + +/* + * Huffman coding optimization. + * + * We first scan the supplied data and count the number of uses of each symbol + * that is to be Huffman-coded. (This process MUST agree with the code above.) + * Then we build a Huffman coding tree for the observed counts. + * Symbols which are not needed at all for the particular image are not + * assigned any code, which saves space in the DHT marker as well as in + * the compressed data. + */ + + +/* Process a single block's worth of coefficients */ + +LOCAL(void) +htest_one_block (j_compress_ptr cinfo, JCOEFPTR block, int last_dc_val, + long dc_counts[], long ac_counts[]) +{ + register int temp; + register int nbits; + register int k, r; + int Se = cinfo->lim_Se; + const int * natural_order = cinfo->natural_order; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = block[0] - last_dc_val; + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count the Huffman symbol for the number of bits */ + dc_counts[nbits]++; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k <= Se; k++) { + if ((temp = block[natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + ac_counts[0xF0]++; + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count Huffman symbol for run length / number of bits */ + ac_counts[(r << 4) + nbits]++; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + ac_counts[0]++; +} + + +/* + * Trial-encode one MCU's worth of Huffman-compressed coefficients. + * No data is actually output, so no suspension return is possible. + */ + +METHODDEF(boolean) +encode_mcu_gather (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn, ci; + jpeg_component_info * compptr; + + /* Take care of restart intervals if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Update restart state */ + entropy->restarts_to_go = cinfo->restart_interval; + } + entropy->restarts_to_go--; + } + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + htest_one_block(cinfo, MCU_data[blkn][0], entropy->saved.last_dc_val[ci], + entropy->dc_count_ptrs[compptr->dc_tbl_no], + entropy->ac_count_ptrs[compptr->ac_tbl_no]); + entropy->saved.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + return TRUE; +} + + +/* + * Generate the best Huffman code table for the given counts, fill htbl. + * + * The JPEG standard requires that no symbol be assigned a codeword of all + * one bits (so that padding bits added at the end of a compressed segment + * can't look like a valid code). Because of the canonical ordering of + * codewords, this just means that there must be an unused slot in the + * longest codeword length category. Section K.2 of the JPEG spec suggests + * reserving such a slot by pretending that symbol 256 is a valid symbol + * with count 1. In theory that's not optimal; giving it count zero but + * including it in the symbol set anyway should give a better Huffman code. + * But the theoretically better code actually seems to come out worse in + * practice, because it produces more all-ones bytes (which incur stuffed + * zero bytes in the final file). In any case the difference is tiny. + * + * The JPEG standard requires Huffman codes to be no more than 16 bits long. + * If some symbols have a very small but nonzero probability, the Huffman tree + * must be adjusted to meet the code length restriction. We currently use + * the adjustment method suggested in JPEG section K.2. This method is *not* + * optimal; it may not choose the best possible limited-length code. But + * typically only very-low-frequency symbols will be given less-than-optimal + * lengths, so the code is almost optimal. Experimental comparisons against + * an optimal limited-length-code algorithm indicate that the difference is + * microscopic --- usually less than a hundredth of a percent of total size. + * So the extra complexity of an optimal algorithm doesn't seem worthwhile. + */ + +LOCAL(void) +jpeg_gen_optimal_table (j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[]) +{ +#define MAX_CLEN 32 /* assumed maximum initial code length */ + UINT8 bits[MAX_CLEN+1]; /* bits[k] = # of symbols with code length k */ + int codesize[257]; /* codesize[k] = code length of symbol k */ + int others[257]; /* next symbol in current branch of tree */ + int c1, c2; + int p, i, j; + long v; + + /* This algorithm is explained in section K.2 of the JPEG standard */ + + MEMZERO(bits, SIZEOF(bits)); + MEMZERO(codesize, SIZEOF(codesize)); + for (i = 0; i < 257; i++) + others[i] = -1; /* init links to empty */ + + freq[256] = 1; /* make sure 256 has a nonzero count */ + /* Including the pseudo-symbol 256 in the Huffman procedure guarantees + * that no real symbol is given code-value of all ones, because 256 + * will be placed last in the largest codeword category. + */ + + /* Huffman's basic algorithm to assign optimal code lengths to symbols */ + + for (;;) { + /* Find the smallest nonzero frequency, set c1 = its symbol */ + /* In case of ties, take the larger symbol number */ + c1 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v) { + v = freq[i]; + c1 = i; + } + } + + /* Find the next smallest nonzero frequency, set c2 = its symbol */ + /* In case of ties, take the larger symbol number */ + c2 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v && i != c1) { + v = freq[i]; + c2 = i; + } + } + + /* Done if we've merged everything into one frequency */ + if (c2 < 0) + break; + + /* Else merge the two counts/trees */ + freq[c1] += freq[c2]; + freq[c2] = 0; + + /* Increment the codesize of everything in c1's tree branch */ + codesize[c1]++; + while (others[c1] >= 0) { + c1 = others[c1]; + codesize[c1]++; + } + + others[c1] = c2; /* chain c2 onto c1's tree branch */ + + /* Increment the codesize of everything in c2's tree branch */ + codesize[c2]++; + while (others[c2] >= 0) { + c2 = others[c2]; + codesize[c2]++; + } + } + + /* Now count the number of symbols of each code length */ + for (i = 0; i <= 256; i++) { + if (codesize[i]) { + /* The JPEG standard seems to think that this can't happen, */ + /* but I'm paranoid... */ + if (codesize[i] > MAX_CLEN) + ERREXIT(cinfo, JERR_HUFF_CLEN_OVERFLOW); + + bits[codesize[i]]++; + } + } + + /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure + * Huffman procedure assigned any such lengths, we must adjust the coding. + * Here is what the JPEG spec says about how this next bit works: + * Since symbols are paired for the longest Huffman code, the symbols are + * removed from this length category two at a time. The prefix for the pair + * (which is one bit shorter) is allocated to one of the pair; then, + * skipping the BITS entry for that prefix length, a code word from the next + * shortest nonzero BITS entry is converted into a prefix for two code words + * one bit longer. + */ + + for (i = MAX_CLEN; i > 16; i--) { + while (bits[i] > 0) { + j = i - 2; /* find length of new prefix to be used */ + while (bits[j] == 0) + j--; + + bits[i] -= 2; /* remove two symbols */ + bits[i-1]++; /* one goes in this length */ + bits[j+1] += 2; /* two new symbols in this length */ + bits[j]--; /* symbol of this length is now a prefix */ + } + } + + /* Remove the count for the pseudo-symbol 256 from the largest codelength */ + while (bits[i] == 0) /* find largest codelength still in use */ + i--; + bits[i]--; + + /* Return final symbol counts (only for lengths 0..16) */ + MEMCOPY(htbl->bits, bits, SIZEOF(htbl->bits)); + + /* Return a list of the symbols sorted by code length */ + /* It's not real clear to me why we don't need to consider the codelength + * changes made above, but the JPEG spec seems to think this works. + */ + p = 0; + for (i = 1; i <= MAX_CLEN; i++) { + for (j = 0; j <= 255; j++) { + if (codesize[j] == i) { + htbl->huffval[p] = (UINT8) j; + p++; + } + } + } + + /* Set sent_table FALSE so updated table will be written to JPEG file. */ + htbl->sent_table = FALSE; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF(void) +finish_pass_gather (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, tbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did_dc[NUM_HUFF_TBLS]; + boolean did_ac[NUM_HUFF_TBLS]; + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + if (cinfo->progressive_mode) + /* Flush out buffered data (all we care about is counting the EOB symbol) */ + emit_eobrun(entropy); + + MEMZERO(did_dc, SIZEOF(did_dc)); + MEMZERO(did_ac, SIZEOF(did_ac)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* DC needs no table for refinement scan */ + if (cinfo->Ss == 0 && cinfo->Ah == 0) { + tbl = compptr->dc_tbl_no; + if (! did_dc[tbl]) { + htblptr = & cinfo->dc_huff_tbl_ptrs[tbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->dc_count_ptrs[tbl]); + did_dc[tbl] = TRUE; + } + } + /* AC needs no table when not present */ + if (cinfo->Se) { + tbl = compptr->ac_tbl_no; + if (! did_ac[tbl]) { + htblptr = & cinfo->ac_huff_tbl_ptrs[tbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->ac_count_ptrs[tbl]); + did_ac[tbl] = TRUE; + } + } + } +} + + +/* + * Initialize for a Huffman-compressed scan. + * If gather_statistics is TRUE, we do not output anything during the scan, + * just count the Huffman symbols used and generate Huffman code tables. + */ + +METHODDEF(void) +start_pass_huff (j_compress_ptr cinfo, boolean gather_statistics) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, tbl; + jpeg_component_info * compptr; + + if (gather_statistics) + entropy->pub.finish_pass = finish_pass_gather; + else + entropy->pub.finish_pass = finish_pass_huff; + + if (cinfo->progressive_mode) { + entropy->cinfo = cinfo; + entropy->gather_statistics = gather_statistics; + + /* We assume jcmaster.c already validated the scan parameters. */ + + /* Select execution routine */ + if (cinfo->Ah == 0) { + if (cinfo->Ss == 0) + entropy->pub.encode_mcu = encode_mcu_DC_first; + else + entropy->pub.encode_mcu = encode_mcu_AC_first; + } else { + if (cinfo->Ss == 0) + entropy->pub.encode_mcu = encode_mcu_DC_refine; + else { + entropy->pub.encode_mcu = encode_mcu_AC_refine; + /* AC refinement needs a correction bit buffer */ + if (entropy->bit_buffer == NULL) + entropy->bit_buffer = (char *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + MAX_CORR_BITS * SIZEOF(char)); + } + } + + /* Initialize AC stuff */ + entropy->ac_tbl_no = cinfo->cur_comp_info[0]->ac_tbl_no; + entropy->EOBRUN = 0; + entropy->BE = 0; + } else { + if (gather_statistics) + entropy->pub.encode_mcu = encode_mcu_gather; + else + entropy->pub.encode_mcu = encode_mcu_huff; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* DC needs no table for refinement scan */ + if (cinfo->Ss == 0 && cinfo->Ah == 0) { + tbl = compptr->dc_tbl_no; + if (gather_statistics) { + /* Check for invalid table index */ + /* (make_c_derived_tbl does this in the other path) */ + if (tbl < 0 || tbl >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tbl); + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->dc_count_ptrs[tbl] == NULL) + entropy->dc_count_ptrs[tbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->dc_count_ptrs[tbl], 257 * SIZEOF(long)); + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl(cinfo, TRUE, tbl, + & entropy->dc_derived_tbls[tbl]); + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + /* AC needs no table when not present */ + if (cinfo->Se) { + tbl = compptr->ac_tbl_no; + if (gather_statistics) { + if (tbl < 0 || tbl >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tbl); + if (entropy->ac_count_ptrs[tbl] == NULL) + entropy->ac_count_ptrs[tbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->ac_count_ptrs[tbl], 257 * SIZEOF(long)); + } else { + jpeg_make_c_derived_tbl(cinfo, FALSE, tbl, + & entropy->ac_derived_tbls[tbl]); + } + } + } + + /* Initialize bit buffer to empty */ + entropy->saved.put_buffer = 0; + entropy->saved.put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* + * Module initialization routine for Huffman entropy encoding. + */ + +GLOBAL(void) +jinit_huff_encoder (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_huff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + entropy->dc_count_ptrs[i] = entropy->ac_count_ptrs[i] = NULL; + } + + if (cinfo->progressive_mode) + entropy->bit_buffer = NULL; /* needed only in AC refinement scan */ +} diff --git a/src/jpeg-8c/jcinit.c b/src/jpeg-8c/jcinit.c new file mode 100644 index 0000000..0ba310f --- /dev/null +++ b/src/jpeg-8c/jcinit.c @@ -0,0 +1,65 @@ +/* + * jcinit.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains initialization logic for the JPEG compressor. + * This routine is in charge of selecting the modules to be executed and + * making an initialization call to each one. + * + * Logically, this code belongs in jcmaster.c. It's split out because + * linking this routine implies linking the entire compression library. + * For a transcoding-only application, we want to be able to use jcmaster.c + * without linking in the whole library. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Master selection of compression modules. + * This is done once at the start of processing an image. We determine + * which modules will be used and give them appropriate initialization calls. + */ + +GLOBAL(void) +jinit_compress_master (j_compress_ptr cinfo) +{ + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, FALSE /* full compression */); + + /* Preprocessing */ + if (! cinfo->raw_data_in) { + jinit_color_converter(cinfo); + jinit_downsampler(cinfo); + jinit_c_prep_controller(cinfo, FALSE /* never need full buffer here */); + } + /* Forward DCT */ + jinit_forward_dct(cinfo); + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) + jinit_arith_encoder(cinfo); + else { + jinit_huff_encoder(cinfo); + } + + /* Need a full-image coefficient buffer in any multi-pass mode. */ + jinit_c_coef_controller(cinfo, + (boolean) (cinfo->num_scans > 1 || cinfo->optimize_coding)); + jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} diff --git a/src/jpeg-8c/jcmainct.c b/src/jpeg-8c/jcmainct.c new file mode 100644 index 0000000..b9f525b --- /dev/null +++ b/src/jpeg-8c/jcmainct.c @@ -0,0 +1,293 @@ +/* + * jcmainct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for compression. + * The main buffer lies between the pre-processor and the JPEG + * compressor proper; it holds downsampled data in the JPEG colorspace. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Note: currently, there is no operating mode in which a full-image buffer + * is needed at this step. If there were, that mode could not be used with + * "raw data" input, since this module is bypassed in that case. However, + * we've left the code here for possible use in special applications. + */ +#undef FULL_MAIN_BUFFER_SUPPORTED + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_main_controller pub; /* public fields */ + + JDIMENSION cur_iMCU_row; /* number of current iMCU row */ + JDIMENSION rowgroup_ctr; /* counts row groups received in iMCU row */ + boolean suspended; /* remember if we suspended output */ + J_BUF_MODE pass_mode; /* current operating mode */ + + /* If using just a strip buffer, this points to the entire set of buffers + * (we allocate one for each component). In the full-image case, this + * points to the currently accessible strips of the virtual arrays. + */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* If using full-image storage, this array holds pointers to virtual-array + * control blocks for each component. Unused if not full-image storage. + */ + jvirt_sarray_ptr whole_image[MAX_COMPONENTS]; +#endif +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + + +/* Forward declarations */ +METHODDEF(void) process_data_simple_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#ifdef FULL_MAIN_BUFFER_SUPPORTED +METHODDEF(void) process_data_buffer_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_main (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + + /* Do nothing in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + main_ptr->cur_iMCU_row = 0; /* initialize counters */ + main_ptr->rowgroup_ctr = 0; + main_ptr->suspended = FALSE; + main_ptr->pass_mode = pass_mode; /* save mode for use by process_data */ + + switch (pass_mode) { + case JBUF_PASS_THRU: +#ifdef FULL_MAIN_BUFFER_SUPPORTED + if (main_ptr->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + main_ptr->pub.process_data = process_data_simple_main; + break; +#ifdef FULL_MAIN_BUFFER_SUPPORTED + case JBUF_SAVE_SOURCE: + case JBUF_CRANK_DEST: + case JBUF_SAVE_AND_PASS: + if (main_ptr->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + main_ptr->pub.process_data = process_data_buffer_main; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This routine handles the simple pass-through mode, + * where we have only a strip buffer. + */ + +METHODDEF(void) +process_data_simple_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + + while (main_ptr->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Read input data if we haven't filled the main buffer yet */ + if (main_ptr->rowgroup_ctr < (JDIMENSION) cinfo->min_DCT_v_scaled_size) + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main_ptr->buffer, &main_ptr->rowgroup_ctr, + (JDIMENSION) cinfo->min_DCT_v_scaled_size); + + /* If we don't have a full iMCU row buffered, return to application for + * more data. Note that preprocessor will always pad to fill the iMCU row + * at the bottom of the image. + */ + if (main_ptr->rowgroup_ctr != (JDIMENSION) cinfo->min_DCT_v_scaled_size) + return; + + /* Send the completed row to the compressor */ + if (! (*cinfo->coef->compress_data) (cinfo, main_ptr->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main_ptr->suspended) { + (*in_row_ctr)--; + main_ptr->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main_ptr->suspended) { + (*in_row_ctr)++; + main_ptr->suspended = FALSE; + } + main_ptr->rowgroup_ctr = 0; + main_ptr->cur_iMCU_row++; + } +} + + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + +/* + * Process some data. + * This routine handles all of the modes that use a full-size buffer. + */ + +METHODDEF(void) +process_data_buffer_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + int ci; + jpeg_component_info *compptr; + boolean writing = (main_ptr->pass_mode != JBUF_CRANK_DEST); + + while (main_ptr->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Realign the virtual buffers if at the start of an iMCU row. */ + if (main_ptr->rowgroup_ctr == 0) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main_ptr->buffer[ci] = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, main_ptr->whole_image[ci], + main_ptr->cur_iMCU_row * (compptr->v_samp_factor * DCTSIZE), + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE), writing); + } + /* In a read pass, pretend we just read some source data. */ + if (! writing) { + *in_row_ctr += cinfo->max_v_samp_factor * DCTSIZE; + main_ptr->rowgroup_ctr = DCTSIZE; + } + } + + /* If a write pass, read input data until the current iMCU row is full. */ + /* Note: preprocessor will pad if necessary to fill the last iMCU row. */ + if (writing) { + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main_ptr->buffer, &main_ptr->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + /* Return to application if we need more data to fill the iMCU row. */ + if (main_ptr->rowgroup_ctr < DCTSIZE) + return; + } + + /* Emit data, unless this is a sink-only pass. */ + if (main_ptr->pass_mode != JBUF_SAVE_SOURCE) { + if (! (*cinfo->coef->compress_data) (cinfo, main_ptr->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main_ptr->suspended) { + (*in_row_ctr)--; + main_ptr->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main_ptr->suspended) { + (*in_row_ctr)++; + main_ptr->suspended = FALSE; + } + } + + /* If get here, we are done with this iMCU row. Mark buffer empty. */ + main_ptr->rowgroup_ctr = 0; + main_ptr->cur_iMCU_row++; + } +} + +#endif /* FULL_MAIN_BUFFER_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL(void) +jinit_c_main_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr main_ptr; + int ci; + jpeg_component_info *compptr; + + main_ptr = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_c_main_controller *) main_ptr; + main_ptr->pub.start_pass = start_pass_main; + + /* We don't need to create a buffer in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + /* Create the buffer. It holds downsampled data, so each component + * may be of a different size. + */ + if (need_full_buffer) { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component */ + /* Note we pad the bottom to a multiple of the iMCU height */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main_ptr->whole_image[ci] = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + compptr->width_in_blocks * compptr->DCT_h_scaled_size, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor) * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * compptr->DCT_v_scaled_size)); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + main_ptr->whole_image[0] = NULL; /* flag for no virtual arrays */ +#endif + /* Allocate a strip buffer for each component */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main_ptr->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_h_scaled_size, + (JDIMENSION) (compptr->v_samp_factor * compptr->DCT_v_scaled_size)); + } + } +} diff --git a/src/jpeg-8c/jcmarker.c b/src/jpeg-8c/jcmarker.c new file mode 100644 index 0000000..606c19a --- /dev/null +++ b/src/jpeg-8c/jcmarker.c @@ -0,0 +1,682 @@ +/* + * jcmarker.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * Modified 2003-2010 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to write JPEG datastream markers. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* Private state */ + +typedef struct { + struct jpeg_marker_writer pub; /* public fields */ + + unsigned int last_restart_interval; /* last DRI value emitted; 0 after SOI */ +} my_marker_writer; + +typedef my_marker_writer * my_marker_ptr; + + +/* + * Basic output routines. + * + * Note that we do not support suspension while writing a marker. + * Therefore, an application using suspension must ensure that there is + * enough buffer space for the initial markers (typ. 600-700 bytes) before + * calling jpeg_start_compress, and enough space to write the trailing EOI + * (a few bytes) before calling jpeg_finish_compress. Multipass compression + * modes are not supported at all with suspension, so those two are the only + * points where markers will be written. + */ + +LOCAL(void) +emit_byte (j_compress_ptr cinfo, int val) +/* Emit a byte */ +{ + struct jpeg_destination_mgr * dest = cinfo->dest; + + *(dest->next_output_byte)++ = (JOCTET) val; + if (--dest->free_in_buffer == 0) { + if (! (*dest->empty_output_buffer) (cinfo)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } +} + + +LOCAL(void) +emit_marker (j_compress_ptr cinfo, JPEG_MARKER mark) +/* Emit a marker code */ +{ + emit_byte(cinfo, 0xFF); + emit_byte(cinfo, (int) mark); +} + + +LOCAL(void) +emit_2bytes (j_compress_ptr cinfo, int value) +/* Emit a 2-byte integer; these are always MSB first in JPEG files */ +{ + emit_byte(cinfo, (value >> 8) & 0xFF); + emit_byte(cinfo, value & 0xFF); +} + + +/* + * Routines to write specific marker types. + */ + +LOCAL(int) +emit_dqt (j_compress_ptr cinfo, int index) +/* Emit a DQT marker */ +/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */ +{ + JQUANT_TBL * qtbl = cinfo->quant_tbl_ptrs[index]; + int prec; + int i; + + if (qtbl == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, index); + + prec = 0; + for (i = 0; i <= cinfo->lim_Se; i++) { + if (qtbl->quantval[cinfo->natural_order[i]] > 255) + prec = 1; + } + + if (! qtbl->sent_table) { + emit_marker(cinfo, M_DQT); + + emit_2bytes(cinfo, + prec ? cinfo->lim_Se * 2 + 2 + 1 + 2 : cinfo->lim_Se + 1 + 1 + 2); + + emit_byte(cinfo, index + (prec<<4)); + + for (i = 0; i <= cinfo->lim_Se; i++) { + /* The table entries must be emitted in zigzag order. */ + unsigned int qval = qtbl->quantval[cinfo->natural_order[i]]; + if (prec) + emit_byte(cinfo, (int) (qval >> 8)); + emit_byte(cinfo, (int) (qval & 0xFF)); + } + + qtbl->sent_table = TRUE; + } + + return prec; +} + + +LOCAL(void) +emit_dht (j_compress_ptr cinfo, int index, boolean is_ac) +/* Emit a DHT marker */ +{ + JHUFF_TBL * htbl; + int length, i; + + if (is_ac) { + htbl = cinfo->ac_huff_tbl_ptrs[index]; + index += 0x10; /* output index has AC bit set */ + } else { + htbl = cinfo->dc_huff_tbl_ptrs[index]; + } + + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, index); + + if (! htbl->sent_table) { + emit_marker(cinfo, M_DHT); + + length = 0; + for (i = 1; i <= 16; i++) + length += htbl->bits[i]; + + emit_2bytes(cinfo, length + 2 + 1 + 16); + emit_byte(cinfo, index); + + for (i = 1; i <= 16; i++) + emit_byte(cinfo, htbl->bits[i]); + + for (i = 0; i < length; i++) + emit_byte(cinfo, htbl->huffval[i]); + + htbl->sent_table = TRUE; + } +} + + +LOCAL(void) +emit_dac (j_compress_ptr cinfo) +/* Emit a DAC marker */ +/* Since the useful info is so small, we want to emit all the tables in */ +/* one DAC marker. Therefore this routine does its own scan of the table. */ +{ +#ifdef C_ARITH_CODING_SUPPORTED + char dc_in_use[NUM_ARITH_TBLS]; + char ac_in_use[NUM_ARITH_TBLS]; + int length, i; + jpeg_component_info *compptr; + + for (i = 0; i < NUM_ARITH_TBLS; i++) + dc_in_use[i] = ac_in_use[i] = 0; + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + /* DC needs no table for refinement scan */ + if (cinfo->Ss == 0 && cinfo->Ah == 0) + dc_in_use[compptr->dc_tbl_no] = 1; + /* AC needs no table when not present */ + if (cinfo->Se) + ac_in_use[compptr->ac_tbl_no] = 1; + } + + length = 0; + for (i = 0; i < NUM_ARITH_TBLS; i++) + length += dc_in_use[i] + ac_in_use[i]; + + if (length) { + emit_marker(cinfo, M_DAC); + + emit_2bytes(cinfo, length*2 + 2); + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + if (dc_in_use[i]) { + emit_byte(cinfo, i); + emit_byte(cinfo, cinfo->arith_dc_L[i] + (cinfo->arith_dc_U[i]<<4)); + } + if (ac_in_use[i]) { + emit_byte(cinfo, i + 0x10); + emit_byte(cinfo, cinfo->arith_ac_K[i]); + } + } + } +#endif /* C_ARITH_CODING_SUPPORTED */ +} + + +LOCAL(void) +emit_dri (j_compress_ptr cinfo) +/* Emit a DRI marker */ +{ + emit_marker(cinfo, M_DRI); + + emit_2bytes(cinfo, 4); /* fixed length */ + + emit_2bytes(cinfo, (int) cinfo->restart_interval); +} + + +LOCAL(void) +emit_sof (j_compress_ptr cinfo, JPEG_MARKER code) +/* Emit a SOF marker */ +{ + int ci; + jpeg_component_info *compptr; + + emit_marker(cinfo, code); + + emit_2bytes(cinfo, 3 * cinfo->num_components + 2 + 5 + 1); /* length */ + + /* Make sure image isn't bigger than SOF field can handle */ + if ((long) cinfo->jpeg_height > 65535L || + (long) cinfo->jpeg_width > 65535L) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) 65535); + + emit_byte(cinfo, cinfo->data_precision); + emit_2bytes(cinfo, (int) cinfo->jpeg_height); + emit_2bytes(cinfo, (int) cinfo->jpeg_width); + + emit_byte(cinfo, cinfo->num_components); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + emit_byte(cinfo, compptr->component_id); + emit_byte(cinfo, (compptr->h_samp_factor << 4) + compptr->v_samp_factor); + emit_byte(cinfo, compptr->quant_tbl_no); + } +} + + +LOCAL(void) +emit_sos (j_compress_ptr cinfo) +/* Emit a SOS marker */ +{ + int i, td, ta; + jpeg_component_info *compptr; + + emit_marker(cinfo, M_SOS); + + emit_2bytes(cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3); /* length */ + + emit_byte(cinfo, cinfo->comps_in_scan); + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + emit_byte(cinfo, compptr->component_id); + + /* We emit 0 for unused field(s); this is recommended by the P&M text + * but does not seem to be specified in the standard. + */ + + /* DC needs no table for refinement scan */ + td = cinfo->Ss == 0 && cinfo->Ah == 0 ? compptr->dc_tbl_no : 0; + /* AC needs no table when not present */ + ta = cinfo->Se ? compptr->ac_tbl_no : 0; + + emit_byte(cinfo, (td << 4) + ta); + } + + emit_byte(cinfo, cinfo->Ss); + emit_byte(cinfo, cinfo->Se); + emit_byte(cinfo, (cinfo->Ah << 4) + cinfo->Al); +} + + +LOCAL(void) +emit_pseudo_sos (j_compress_ptr cinfo) +/* Emit a pseudo SOS marker */ +{ + emit_marker(cinfo, M_SOS); + + emit_2bytes(cinfo, 2 + 1 + 3); /* length */ + + emit_byte(cinfo, 0); /* Ns */ + + emit_byte(cinfo, 0); /* Ss */ + emit_byte(cinfo, cinfo->block_size * cinfo->block_size - 1); /* Se */ + emit_byte(cinfo, 0); /* Ah/Al */ +} + + +LOCAL(void) +emit_jfif_app0 (j_compress_ptr cinfo) +/* Emit a JFIF-compliant APP0 marker */ +{ + /* + * Length of APP0 block (2 bytes) + * Block ID (4 bytes - ASCII "JFIF") + * Zero byte (1 byte to terminate the ID string) + * Version Major, Minor (2 bytes - major first) + * Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm) + * Xdpu (2 bytes - dots per unit horizontal) + * Ydpu (2 bytes - dots per unit vertical) + * Thumbnail X size (1 byte) + * Thumbnail Y size (1 byte) + */ + + emit_marker(cinfo, M_APP0); + + emit_2bytes(cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); /* length */ + + emit_byte(cinfo, 0x4A); /* Identifier: ASCII "JFIF" */ + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0x49); + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0); + emit_byte(cinfo, cinfo->JFIF_major_version); /* Version fields */ + emit_byte(cinfo, cinfo->JFIF_minor_version); + emit_byte(cinfo, cinfo->density_unit); /* Pixel size information */ + emit_2bytes(cinfo, (int) cinfo->X_density); + emit_2bytes(cinfo, (int) cinfo->Y_density); + emit_byte(cinfo, 0); /* No thumbnail image */ + emit_byte(cinfo, 0); +} + + +LOCAL(void) +emit_adobe_app14 (j_compress_ptr cinfo) +/* Emit an Adobe APP14 marker */ +{ + /* + * Length of APP14 block (2 bytes) + * Block ID (5 bytes - ASCII "Adobe") + * Version Number (2 bytes - currently 100) + * Flags0 (2 bytes - currently 0) + * Flags1 (2 bytes - currently 0) + * Color transform (1 byte) + * + * Although Adobe TN 5116 mentions Version = 101, all the Adobe files + * now in circulation seem to use Version = 100, so that's what we write. + * + * We write the color transform byte as 1 if the JPEG color space is + * YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with + * whether the encoder performed a transformation, which is pretty useless. + */ + + emit_marker(cinfo, M_APP14); + + emit_2bytes(cinfo, 2 + 5 + 2 + 2 + 2 + 1); /* length */ + + emit_byte(cinfo, 0x41); /* Identifier: ASCII "Adobe" */ + emit_byte(cinfo, 0x64); + emit_byte(cinfo, 0x6F); + emit_byte(cinfo, 0x62); + emit_byte(cinfo, 0x65); + emit_2bytes(cinfo, 100); /* Version */ + emit_2bytes(cinfo, 0); /* Flags0 */ + emit_2bytes(cinfo, 0); /* Flags1 */ + switch (cinfo->jpeg_color_space) { + case JCS_YCbCr: + emit_byte(cinfo, 1); /* Color transform = 1 */ + break; + case JCS_YCCK: + emit_byte(cinfo, 2); /* Color transform = 2 */ + break; + default: + emit_byte(cinfo, 0); /* Color transform = 0 */ + break; + } +} + + +/* + * These routines allow writing an arbitrary marker with parameters. + * The only intended use is to emit COM or APPn markers after calling + * write_file_header and before calling write_frame_header. + * Other uses are not guaranteed to produce desirable results. + * Counting the parameter bytes properly is the caller's responsibility. + */ + +METHODDEF(void) +write_marker_header (j_compress_ptr cinfo, int marker, unsigned int datalen) +/* Emit an arbitrary marker header */ +{ + if (datalen > (unsigned int) 65533) /* safety check */ + ERREXIT(cinfo, JERR_BAD_LENGTH); + + emit_marker(cinfo, (JPEG_MARKER) marker); + + emit_2bytes(cinfo, (int) (datalen + 2)); /* total length */ +} + +METHODDEF(void) +write_marker_byte (j_compress_ptr cinfo, int val) +/* Emit one byte of marker parameters following write_marker_header */ +{ + emit_byte(cinfo, val); +} + + +/* + * Write datastream header. + * This consists of an SOI and optional APPn markers. + * We recommend use of the JFIF marker, but not the Adobe marker, + * when using YCbCr or grayscale data. The JFIF marker should NOT + * be used for any other JPEG colorspace. The Adobe marker is helpful + * to distinguish RGB, CMYK, and YCCK colorspaces. + * Note that an application can write additional header markers after + * jpeg_start_compress returns. + */ + +METHODDEF(void) +write_file_header (j_compress_ptr cinfo) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + + emit_marker(cinfo, M_SOI); /* first the SOI */ + + /* SOI is defined to reset restart interval to 0 */ + marker->last_restart_interval = 0; + + if (cinfo->write_JFIF_header) /* next an optional JFIF APP0 */ + emit_jfif_app0(cinfo); + if (cinfo->write_Adobe_marker) /* next an optional Adobe APP14 */ + emit_adobe_app14(cinfo); +} + + +/* + * Write frame header. + * This consists of DQT and SOFn markers, and a conditional pseudo SOS marker. + * Note that we do not emit the SOF until we have emitted the DQT(s). + * This avoids compatibility problems with incorrect implementations that + * try to error-check the quant table numbers as soon as they see the SOF. + */ + +METHODDEF(void) +write_frame_header (j_compress_ptr cinfo) +{ + int ci, prec; + boolean is_baseline; + jpeg_component_info *compptr; + + /* Emit DQT for each quantization table. + * Note that emit_dqt() suppresses any duplicate tables. + */ + prec = 0; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prec += emit_dqt(cinfo, compptr->quant_tbl_no); + } + /* now prec is nonzero iff there are any 16-bit quant tables. */ + + /* Check for a non-baseline specification. + * Note we assume that Huffman table numbers won't be changed later. + */ + if (cinfo->arith_code || cinfo->progressive_mode || + cinfo->data_precision != 8 || cinfo->block_size != DCTSIZE) { + is_baseline = FALSE; + } else { + is_baseline = TRUE; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->dc_tbl_no > 1 || compptr->ac_tbl_no > 1) + is_baseline = FALSE; + } + if (prec && is_baseline) { + is_baseline = FALSE; + /* If it's baseline except for quantizer size, warn the user */ + TRACEMS(cinfo, 0, JTRC_16BIT_TABLES); + } + } + + /* Emit the proper SOF marker */ + if (cinfo->arith_code) { + if (cinfo->progressive_mode) + emit_sof(cinfo, M_SOF10); /* SOF code for progressive arithmetic */ + else + emit_sof(cinfo, M_SOF9); /* SOF code for sequential arithmetic */ + } else { + if (cinfo->progressive_mode) + emit_sof(cinfo, M_SOF2); /* SOF code for progressive Huffman */ + else if (is_baseline) + emit_sof(cinfo, M_SOF0); /* SOF code for baseline implementation */ + else + emit_sof(cinfo, M_SOF1); /* SOF code for non-baseline Huffman file */ + } + + /* Check to emit pseudo SOS marker */ + if (cinfo->progressive_mode && cinfo->block_size != DCTSIZE) + emit_pseudo_sos(cinfo); +} + + +/* + * Write scan header. + * This consists of DHT or DAC markers, optional DRI, and SOS. + * Compressed data will be written following the SOS. + */ + +METHODDEF(void) +write_scan_header (j_compress_ptr cinfo) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + int i; + jpeg_component_info *compptr; + + if (cinfo->arith_code) { + /* Emit arith conditioning info. We may have some duplication + * if the file has multiple scans, but it's so small it's hardly + * worth worrying about. + */ + emit_dac(cinfo); + } else { + /* Emit Huffman tables. + * Note that emit_dht() suppresses any duplicate tables. + */ + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + /* DC needs no table for refinement scan */ + if (cinfo->Ss == 0 && cinfo->Ah == 0) + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + /* AC needs no table when not present */ + if (cinfo->Se) + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } + + /* Emit DRI if required --- note that DRI value could change for each scan. + * We avoid wasting space with unnecessary DRIs, however. + */ + if (cinfo->restart_interval != marker->last_restart_interval) { + emit_dri(cinfo); + marker->last_restart_interval = cinfo->restart_interval; + } + + emit_sos(cinfo); +} + + +/* + * Write datastream trailer. + */ + +METHODDEF(void) +write_file_trailer (j_compress_ptr cinfo) +{ + emit_marker(cinfo, M_EOI); +} + + +/* + * Write an abbreviated table-specification datastream. + * This consists of SOI, DQT and DHT tables, and EOI. + * Any table that is defined and not marked sent_table = TRUE will be + * emitted. Note that all tables will be marked sent_table = TRUE at exit. + */ + +METHODDEF(void) +write_tables_only (j_compress_ptr cinfo) +{ + int i; + + emit_marker(cinfo, M_SOI); + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if (cinfo->quant_tbl_ptrs[i] != NULL) + (void) emit_dqt(cinfo, i); + } + + if (! cinfo->arith_code) { + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if (cinfo->dc_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, FALSE); + if (cinfo->ac_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, TRUE); + } + } + + emit_marker(cinfo, M_EOI); +} + + +/* + * Initialize the marker writer module. + */ + +GLOBAL(void) +jinit_marker_writer (j_compress_ptr cinfo) +{ + my_marker_ptr marker; + + /* Create the subobject */ + marker = (my_marker_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_marker_writer)); + cinfo->marker = (struct jpeg_marker_writer *) marker; + /* Initialize method pointers */ + marker->pub.write_file_header = write_file_header; + marker->pub.write_frame_header = write_frame_header; + marker->pub.write_scan_header = write_scan_header; + marker->pub.write_file_trailer = write_file_trailer; + marker->pub.write_tables_only = write_tables_only; + marker->pub.write_marker_header = write_marker_header; + marker->pub.write_marker_byte = write_marker_byte; + /* Initialize private state */ + marker->last_restart_interval = 0; +} diff --git a/src/jpeg-8c/jcmaster.c b/src/jpeg-8c/jcmaster.c new file mode 100644 index 0000000..caf80a5 --- /dev/null +++ b/src/jpeg-8c/jcmaster.c @@ -0,0 +1,858 @@ +/* + * jcmaster.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 2003-2011 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG compressor. + * These routines are concerned with parameter validation, initial setup, + * and inter-pass control (determining the number of passes and the work + * to be done in each pass). + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef enum { + main_pass, /* input data, also do first output step */ + huff_opt_pass, /* Huffman code optimization pass */ + output_pass /* data output pass */ +} c_pass_type; + +typedef struct { + struct jpeg_comp_master pub; /* public fields */ + + c_pass_type pass_type; /* the type of the current pass */ + + int pass_number; /* # of passes completed */ + int total_passes; /* total # of passes needed */ + + int scan_number; /* current index in scan_info[] */ +} my_comp_master; + +typedef my_comp_master * my_master_ptr; + + +/* + * Support routines that do various essential calculations. + */ + +/* + * Compute JPEG image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + */ + +GLOBAL(void) +jpeg_calc_jpeg_dimensions (j_compress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ +#ifdef DCT_SCALING_SUPPORTED + + /* Sanity check on input image dimensions to prevent overflow in + * following calculation. + * We do check jpeg_width and jpeg_height in initial_setup below, + * but image_width and image_height can come from arbitrary data, + * and we need some space for multiplication by block_size. + */ + if (((long) cinfo->image_width >> 24) || ((long) cinfo->image_height >> 24)) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* Compute actual JPEG image dimensions and DCT scaling choices. */ + if (cinfo->scale_num >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/1 scaling */ + cinfo->jpeg_width = cinfo->image_width * cinfo->block_size; + cinfo->jpeg_height = cinfo->image_height * cinfo->block_size; + cinfo->min_DCT_h_scaled_size = 1; + cinfo->min_DCT_v_scaled_size = 1; + } else if (cinfo->scale_num * 2 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/2 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 2L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 2L); + cinfo->min_DCT_h_scaled_size = 2; + cinfo->min_DCT_v_scaled_size = 2; + } else if (cinfo->scale_num * 3 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/3 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 3L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 3L); + cinfo->min_DCT_h_scaled_size = 3; + cinfo->min_DCT_v_scaled_size = 3; + } else if (cinfo->scale_num * 4 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/4 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 4L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 4L); + cinfo->min_DCT_h_scaled_size = 4; + cinfo->min_DCT_v_scaled_size = 4; + } else if (cinfo->scale_num * 5 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/5 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 5L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 5L); + cinfo->min_DCT_h_scaled_size = 5; + cinfo->min_DCT_v_scaled_size = 5; + } else if (cinfo->scale_num * 6 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/6 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 6L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 6L); + cinfo->min_DCT_h_scaled_size = 6; + cinfo->min_DCT_v_scaled_size = 6; + } else if (cinfo->scale_num * 7 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/7 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 7L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 7L); + cinfo->min_DCT_h_scaled_size = 7; + cinfo->min_DCT_v_scaled_size = 7; + } else if (cinfo->scale_num * 8 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/8 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 8L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 8L); + cinfo->min_DCT_h_scaled_size = 8; + cinfo->min_DCT_v_scaled_size = 8; + } else if (cinfo->scale_num * 9 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/9 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 9L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 9L); + cinfo->min_DCT_h_scaled_size = 9; + cinfo->min_DCT_v_scaled_size = 9; + } else if (cinfo->scale_num * 10 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/10 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 10L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 10L); + cinfo->min_DCT_h_scaled_size = 10; + cinfo->min_DCT_v_scaled_size = 10; + } else if (cinfo->scale_num * 11 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/11 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 11L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 11L); + cinfo->min_DCT_h_scaled_size = 11; + cinfo->min_DCT_v_scaled_size = 11; + } else if (cinfo->scale_num * 12 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/12 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 12L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 12L); + cinfo->min_DCT_h_scaled_size = 12; + cinfo->min_DCT_v_scaled_size = 12; + } else if (cinfo->scale_num * 13 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/13 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 13L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 13L); + cinfo->min_DCT_h_scaled_size = 13; + cinfo->min_DCT_v_scaled_size = 13; + } else if (cinfo->scale_num * 14 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/14 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 14L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 14L); + cinfo->min_DCT_h_scaled_size = 14; + cinfo->min_DCT_v_scaled_size = 14; + } else if (cinfo->scale_num * 15 >= cinfo->scale_denom * cinfo->block_size) { + /* Provide block_size/15 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 15L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 15L); + cinfo->min_DCT_h_scaled_size = 15; + cinfo->min_DCT_v_scaled_size = 15; + } else { + /* Provide block_size/16 scaling */ + cinfo->jpeg_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * cinfo->block_size, 16L); + cinfo->jpeg_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * cinfo->block_size, 16L); + cinfo->min_DCT_h_scaled_size = 16; + cinfo->min_DCT_v_scaled_size = 16; + } + +#else /* !DCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->jpeg_width = cinfo->image_width; + cinfo->jpeg_height = cinfo->image_height; + cinfo->min_DCT_h_scaled_size = DCTSIZE; + cinfo->min_DCT_v_scaled_size = DCTSIZE; + +#endif /* DCT_SCALING_SUPPORTED */ +} + + +LOCAL(void) +jpeg_calc_trans_dimensions (j_compress_ptr cinfo) +{ + if (cinfo->min_DCT_h_scaled_size != cinfo->min_DCT_v_scaled_size) + ERREXIT2(cinfo, JERR_BAD_DCTSIZE, + cinfo->min_DCT_h_scaled_size, cinfo->min_DCT_v_scaled_size); + + cinfo->block_size = cinfo->min_DCT_h_scaled_size; +} + + +LOCAL(void) +initial_setup (j_compress_ptr cinfo, boolean transcode_only) +/* Do computations that are needed before master selection phase */ +{ + int ci, ssize; + jpeg_component_info *compptr; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + if (transcode_only) + jpeg_calc_trans_dimensions(cinfo); + else + jpeg_calc_jpeg_dimensions(cinfo); + + /* Sanity check on block_size */ + if (cinfo->block_size < 1 || cinfo->block_size > 16) + ERREXIT2(cinfo, JERR_BAD_DCTSIZE, cinfo->block_size, cinfo->block_size); + + /* Derive natural_order from block_size */ + switch (cinfo->block_size) { + case 2: cinfo->natural_order = jpeg_natural_order2; break; + case 3: cinfo->natural_order = jpeg_natural_order3; break; + case 4: cinfo->natural_order = jpeg_natural_order4; break; + case 5: cinfo->natural_order = jpeg_natural_order5; break; + case 6: cinfo->natural_order = jpeg_natural_order6; break; + case 7: cinfo->natural_order = jpeg_natural_order7; break; + default: cinfo->natural_order = jpeg_natural_order; break; + } + + /* Derive lim_Se from block_size */ + cinfo->lim_Se = cinfo->block_size < DCTSIZE ? + cinfo->block_size * cinfo->block_size - 1 : DCTSIZE2-1; + + /* Sanity check on image dimensions */ + if (cinfo->jpeg_height <= 0 || cinfo->jpeg_width <= 0 || + cinfo->num_components <= 0 || cinfo->input_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->jpeg_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->jpeg_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* Width of an input scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->image_width * (long) cinfo->input_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Fill in the correct component_index value; don't rely on application */ + compptr->component_index = ci; + /* In selecting the actual DCT scaling for each component, we try to + * scale down the chroma components via DCT scaling rather than downsampling. + * This saves time if the downsampler gets to use 1:1 scaling. + * Note this code adapts subsampling ratios which are powers of 2. + */ + ssize = 1; +#ifdef DCT_SCALING_SUPPORTED + while (cinfo->min_DCT_h_scaled_size * ssize <= + (cinfo->do_fancy_downsampling ? DCTSIZE : DCTSIZE / 2) && + (cinfo->max_h_samp_factor % (compptr->h_samp_factor * ssize * 2)) == 0) { + ssize = ssize * 2; + } +#endif + compptr->DCT_h_scaled_size = cinfo->min_DCT_h_scaled_size * ssize; + ssize = 1; +#ifdef DCT_SCALING_SUPPORTED + while (cinfo->min_DCT_v_scaled_size * ssize <= + (cinfo->do_fancy_downsampling ? DCTSIZE : DCTSIZE / 2) && + (cinfo->max_v_samp_factor % (compptr->v_samp_factor * ssize * 2)) == 0) { + ssize = ssize * 2; + } +#endif + compptr->DCT_v_scaled_size = cinfo->min_DCT_v_scaled_size * ssize; + + /* We don't support DCT ratios larger than 2. */ + if (compptr->DCT_h_scaled_size > compptr->DCT_v_scaled_size * 2) + compptr->DCT_h_scaled_size = compptr->DCT_v_scaled_size * 2; + else if (compptr->DCT_v_scaled_size > compptr->DCT_h_scaled_size * 2) + compptr->DCT_v_scaled_size = compptr->DCT_h_scaled_size * 2; + + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->jpeg_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * cinfo->block_size)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->jpeg_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * cinfo->block_size)); + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->jpeg_width * + (long) (compptr->h_samp_factor * compptr->DCT_h_scaled_size), + (long) (cinfo->max_h_samp_factor * cinfo->block_size)); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->jpeg_height * + (long) (compptr->v_samp_factor * compptr->DCT_v_scaled_size), + (long) (cinfo->max_v_samp_factor * cinfo->block_size)); + /* Mark component needed (this flag isn't actually used for compression) */ + compptr->component_needed = TRUE; + } + + /* Compute number of fully interleaved MCU rows (number of times that + * main controller will call coefficient controller). + */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->jpeg_height, + (long) (cinfo->max_v_samp_factor * cinfo->block_size)); +} + + +#ifdef C_MULTISCAN_FILES_SUPPORTED + +LOCAL(void) +validate_script (j_compress_ptr cinfo) +/* Verify that the scan script in cinfo->scan_info[] is valid; also + * determine whether it uses progressive JPEG, and set cinfo->progressive_mode. + */ +{ + const jpeg_scan_info * scanptr; + int scanno, ncomps, ci, coefi, thisi; + int Ss, Se, Ah, Al; + boolean component_sent[MAX_COMPONENTS]; +#ifdef C_PROGRESSIVE_SUPPORTED + int * last_bitpos_ptr; + int last_bitpos[MAX_COMPONENTS][DCTSIZE2]; + /* -1 until that coefficient has been seen; then last Al for it */ +#endif + + if (cinfo->num_scans <= 0) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, 0); + + /* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1; + * for progressive JPEG, no scan can have this. + */ + scanptr = cinfo->scan_info; + if (scanptr->Ss != 0 || scanptr->Se != DCTSIZE2-1) { +#ifdef C_PROGRESSIVE_SUPPORTED + cinfo->progressive_mode = TRUE; + last_bitpos_ptr = & last_bitpos[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (coefi = 0; coefi < DCTSIZE2; coefi++) + *last_bitpos_ptr++ = -1; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + for (ci = 0; ci < cinfo->num_components; ci++) + component_sent[ci] = FALSE; + } + + for (scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++) { + /* Validate component indexes */ + ncomps = scanptr->comps_in_scan; + if (ncomps <= 0 || ncomps > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN); + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (thisi < 0 || thisi >= cinfo->num_components) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + /* Components must appear in SOF order within each scan */ + if (ci > 0 && thisi <= scanptr->component_index[ci-1]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + } + /* Validate progression parameters */ + Ss = scanptr->Ss; + Se = scanptr->Se; + Ah = scanptr->Ah; + Al = scanptr->Al; + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* The JPEG spec simply gives the ranges 0..13 for Ah and Al, but that + * seems wrong: the upper bound ought to depend on data precision. + * Perhaps they really meant 0..N+1 for N-bit precision. + * Here we allow 0..10 for 8-bit data; Al larger than 10 results in + * out-of-range reconstructed DC values during the first DC scan, + * which might cause problems for some decoders. + */ +#if BITS_IN_JSAMPLE == 8 +#define MAX_AH_AL 10 +#else +#define MAX_AH_AL 13 +#endif + if (Ss < 0 || Ss >= DCTSIZE2 || Se < Ss || Se >= DCTSIZE2 || + Ah < 0 || Ah > MAX_AH_AL || Al < 0 || Al > MAX_AH_AL) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + if (Ss == 0) { + if (Se != 0) /* DC and AC together not OK */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + if (ncomps != 1) /* AC scans must be for only one component */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + for (ci = 0; ci < ncomps; ci++) { + last_bitpos_ptr = & last_bitpos[scanptr->component_index[ci]][0]; + if (Ss != 0 && last_bitpos_ptr[0] < 0) /* AC without prior DC scan */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + for (coefi = Ss; coefi <= Se; coefi++) { + if (last_bitpos_ptr[coefi] < 0) { + /* first scan of this coefficient */ + if (Ah != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + /* not first scan */ + if (Ah != last_bitpos_ptr[coefi] || Al != Ah-1) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + last_bitpos_ptr[coefi] = Al; + } + } +#endif + } else { + /* For sequential JPEG, all progression parameters must be these: */ + if (Ss != 0 || Se != DCTSIZE2-1 || Ah != 0 || Al != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + /* Make sure components are not sent twice */ + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (component_sent[thisi]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + component_sent[thisi] = TRUE; + } + } + } + + /* Now verify that everything got sent. */ + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* For progressive mode, we only check that at least some DC data + * got sent for each component; the spec does not require that all bits + * of all coefficients be transmitted. Would it be wiser to enforce + * transmission of all coefficient bits?? + */ + for (ci = 0; ci < cinfo->num_components; ci++) { + if (last_bitpos[ci][0] < 0) + ERREXIT(cinfo, JERR_MISSING_DATA); + } +#endif + } else { + for (ci = 0; ci < cinfo->num_components; ci++) { + if (! component_sent[ci]) + ERREXIT(cinfo, JERR_MISSING_DATA); + } + } +} + + +LOCAL(void) +reduce_script (j_compress_ptr cinfo) +/* Adapt scan script for use with reduced block size; + * assume that script has been validated before. + */ +{ + jpeg_scan_info * scanptr; + int idxout, idxin; + + /* Circumvent const declaration for this function */ + scanptr = (jpeg_scan_info *) cinfo->scan_info; + idxout = 0; + + for (idxin = 0; idxin < cinfo->num_scans; idxin++) { + /* After skipping, idxout becomes smaller than idxin */ + if (idxin != idxout) + /* Copy rest of data; + * note we stay in given chunk of allocated memory. + */ + scanptr[idxout] = scanptr[idxin]; + if (scanptr[idxout].Ss > cinfo->lim_Se) + /* Entire scan out of range - skip this entry */ + continue; + if (scanptr[idxout].Se > cinfo->lim_Se) + /* Limit scan to end of block */ + scanptr[idxout].Se = cinfo->lim_Se; + idxout++; + } + + cinfo->num_scans = idxout; +} + +#endif /* C_MULTISCAN_FILES_SUPPORTED */ + + +LOCAL(void) +select_scan_parameters (j_compress_ptr cinfo) +/* Set up the scan parameters for the current scan */ +{ + int ci; + +#ifdef C_MULTISCAN_FILES_SUPPORTED + if (cinfo->scan_info != NULL) { + /* Prepare for current scan --- the script is already validated */ + my_master_ptr master = (my_master_ptr) cinfo->master; + const jpeg_scan_info * scanptr = cinfo->scan_info + master->scan_number; + + cinfo->comps_in_scan = scanptr->comps_in_scan; + for (ci = 0; ci < scanptr->comps_in_scan; ci++) { + cinfo->cur_comp_info[ci] = + &cinfo->comp_info[scanptr->component_index[ci]]; + } + if (cinfo->progressive_mode) { + cinfo->Ss = scanptr->Ss; + cinfo->Se = scanptr->Se; + cinfo->Ah = scanptr->Ah; + cinfo->Al = scanptr->Al; + return; + } + } + else +#endif + { + /* Prepare for single sequential-JPEG scan containing all components */ + if (cinfo->num_components > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPS_IN_SCAN); + cinfo->comps_in_scan = cinfo->num_components; + for (ci = 0; ci < cinfo->num_components; ci++) { + cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci]; + } + } + cinfo->Ss = 0; + cinfo->Se = cinfo->block_size * cinfo->block_size - 1; + cinfo->Ah = 0; + cinfo->Al = 0; +} + + +LOCAL(void) +per_scan_setup (j_compress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_h_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->jpeg_width, + (long) (cinfo->max_h_samp_factor * cinfo->block_size)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->jpeg_height, + (long) (cinfo->max_v_samp_factor * cinfo->block_size)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_h_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } + + /* Convert restart specified in rows to actual MCU count. */ + /* Note that count must fit in 16 bits, so we provide limiting. */ + if (cinfo->restart_in_rows > 0) { + long nominal = (long) cinfo->restart_in_rows * (long) cinfo->MCUs_per_row; + cinfo->restart_interval = (unsigned int) MIN(nominal, 65535L); + } +} + + +/* + * Per-pass setup. + * This is called at the beginning of each pass. We determine which modules + * will be active during this pass and give them appropriate start_pass calls. + * We also set is_last_pass to indicate whether any more passes will be + * required. + */ + +METHODDEF(void) +prepare_for_pass (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + switch (master->pass_type) { + case main_pass: + /* Initial pass: will collect input data, and do either Huffman + * optimization or data output for the first scan. + */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (! cinfo->raw_data_in) { + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->downsample->start_pass) (cinfo); + (*cinfo->prep->start_pass) (cinfo, JBUF_PASS_THRU); + } + (*cinfo->fdct->start_pass) (cinfo); + (*cinfo->entropy->start_pass) (cinfo, cinfo->optimize_coding); + (*cinfo->coef->start_pass) (cinfo, + (master->total_passes > 1 ? + JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + if (cinfo->optimize_coding) { + /* No immediate data output; postpone writing frame/scan headers */ + master->pub.call_pass_startup = FALSE; + } else { + /* Will write frame/scan headers at first jpeg_write_scanlines call */ + master->pub.call_pass_startup = TRUE; + } + break; +#ifdef ENTROPY_OPT_SUPPORTED + case huff_opt_pass: + /* Do Huffman optimization for a scan after the first one. */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (cinfo->Ss != 0 || cinfo->Ah == 0) { + (*cinfo->entropy->start_pass) (cinfo, TRUE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + master->pub.call_pass_startup = FALSE; + break; + } + /* Special case: Huffman DC refinement scans need no Huffman table + * and therefore we can skip the optimization pass for them. + */ + master->pass_type = output_pass; + master->pass_number++; + /*FALLTHROUGH*/ +#endif + case output_pass: + /* Do a data-output pass. */ + /* We need not repeat per-scan setup if prior optimization pass did it. */ + if (! cinfo->optimize_coding) { + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + } + (*cinfo->entropy->start_pass) (cinfo, FALSE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + /* We emit frame/scan headers now */ + if (master->scan_number == 0) + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); + master->pub.call_pass_startup = FALSE; + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + } + + master->pub.is_last_pass = (master->pass_number == master->total_passes-1); + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->total_passes; + } +} + + +/* + * Special start-of-pass hook. + * This is called by jpeg_write_scanlines if call_pass_startup is TRUE. + * In single-pass processing, we need this hook because we don't want to + * write frame/scan headers during jpeg_start_compress; we want to let the + * application write COM markers etc. between jpeg_start_compress and the + * jpeg_write_scanlines loop. + * In multi-pass processing, this routine is not used. + */ + +METHODDEF(void) +pass_startup (j_compress_ptr cinfo) +{ + cinfo->master->call_pass_startup = FALSE; /* reset flag so call only once */ + + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); +} + + +/* + * Finish up at end of pass. + */ + +METHODDEF(void) +finish_pass_master (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* The entropy coder always needs an end-of-pass call, + * either to analyze statistics or to flush its output buffer. + */ + (*cinfo->entropy->finish_pass) (cinfo); + + /* Update state for next pass */ + switch (master->pass_type) { + case main_pass: + /* next pass is either output of scan 0 (after optimization) + * or output of scan 1 (if no optimization). + */ + master->pass_type = output_pass; + if (! cinfo->optimize_coding) + master->scan_number++; + break; + case huff_opt_pass: + /* next pass is always output of current scan */ + master->pass_type = output_pass; + break; + case output_pass: + /* next pass is either optimization or output of next scan */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + master->scan_number++; + break; + } + + master->pass_number++; +} + + +/* + * Initialize master compression control. + */ + +GLOBAL(void) +jinit_c_master_control (j_compress_ptr cinfo, boolean transcode_only) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_comp_master)); + cinfo->master = (struct jpeg_comp_master *) master; + master->pub.prepare_for_pass = prepare_for_pass; + master->pub.pass_startup = pass_startup; + master->pub.finish_pass = finish_pass_master; + master->pub.is_last_pass = FALSE; + + /* Validate parameters, determine derived values */ + initial_setup(cinfo, transcode_only); + + if (cinfo->scan_info != NULL) { +#ifdef C_MULTISCAN_FILES_SUPPORTED + validate_script(cinfo); + if (cinfo->block_size < DCTSIZE) + reduce_script(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + cinfo->num_scans = 1; + } + + if ((cinfo->progressive_mode || cinfo->block_size < DCTSIZE) && + !cinfo->arith_code) /* TEMPORARY HACK ??? */ + /* assume default tables no good for progressive or downscale mode */ + cinfo->optimize_coding = TRUE; + + /* Initialize my private state */ + if (transcode_only) { + /* no main pass in transcoding */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + else + master->pass_type = output_pass; + } else { + /* for normal compression, first pass is always this type: */ + master->pass_type = main_pass; + } + master->scan_number = 0; + master->pass_number = 0; + if (cinfo->optimize_coding) + master->total_passes = cinfo->num_scans * 2; + else + master->total_passes = cinfo->num_scans; +} diff --git a/src/jpeg-8c/jcomapi.c b/src/jpeg-8c/jcomapi.c new file mode 100644 index 0000000..9b1fa75 --- /dev/null +++ b/src/jpeg-8c/jcomapi.c @@ -0,0 +1,106 @@ +/* + * jcomapi.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface routines that are used for both + * compression and decompression. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Abort processing of a JPEG compression or decompression operation, + * but don't destroy the object itself. + * + * For this, we merely clean up all the nonpermanent memory pools. + * Note that temp files (virtual arrays) are not allowed to belong to + * the permanent pool, so we will be able to close all temp files here. + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL(void) +jpeg_abort (j_common_ptr cinfo) +{ + int pool; + + /* Do nothing if called on a not-initialized or destroyed JPEG object. */ + if (cinfo->mem == NULL) + return; + + /* Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) { + (*cinfo->mem->free_pool) (cinfo, pool); + } + + /* Reset overall state for possible reuse of object */ + if (cinfo->is_decompressor) { + cinfo->global_state = DSTATE_START; + /* Try to keep application from accessing now-deleted marker list. + * A bit kludgy to do it here, but this is the most central place. + */ + ((j_decompress_ptr) cinfo)->marker_list = NULL; + } else { + cinfo->global_state = CSTATE_START; + } +} + + +/* + * Destruction of a JPEG object. + * + * Everything gets deallocated except the master jpeg_compress_struct itself + * and the error manager struct. Both of these are supplied by the application + * and must be freed, if necessary, by the application. (Often they are on + * the stack and so don't need to be freed anyway.) + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL(void) +jpeg_destroy (j_common_ptr cinfo) +{ + /* We need only tell the memory manager to release everything. */ + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + if (cinfo->mem != NULL) + (*cinfo->mem->self_destruct) (cinfo); + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0; /* mark it destroyed */ +} + + +/* + * Convenience routines for allocating quantization and Huffman tables. + * (Would jutils.c be a more reasonable place to put these?) + */ + +GLOBAL(JQUANT_TBL *) +jpeg_alloc_quant_table (j_common_ptr cinfo) +{ + JQUANT_TBL *tbl; + + tbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} + + +GLOBAL(JHUFF_TBL *) +jpeg_alloc_huff_table (j_common_ptr cinfo) +{ + JHUFF_TBL *tbl; + + tbl = (JHUFF_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} diff --git a/src/jpeg-8c/jconfig.h b/src/jpeg-8c/jconfig.h new file mode 100644 index 0000000..ee83f41 --- /dev/null +++ b/src/jpeg-8c/jconfig.h @@ -0,0 +1,56 @@ +/* jconfig.h. Generated from jconfig.cfg by configure. */ +/* jconfig.cfg --- source file edited by configure script */ +/* see jconfig.txt for explanations */ + +/* Define this if you get warnings about undefined structures. */ +/* #undef INCOMPLETE_TYPES_BROKEN */ + +#define HAVE_PROTOTYPES 1 +#define HAVE_UNSIGNED_CHAR 1 +#define HAVE_UNSIGNED_SHORT 1 +/* #undef void */ +/* #undef const */ +/* #undef CHAR_IS_UNSIGNED */ + +#ifdef JPEG_INTERNALS + +#define HAVE_STDDEF_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_LOCALE_H 1 +/* #undef NEED_BSD_STRINGS */ +/* #undef NEED_SYS_TYPES_H */ +/* #undef NEED_FAR_POINTERS */ +/* #undef NEED_SHORT_EXTERNAL_NAMES */ + +/* Define "boolean" as unsigned char, not int, on Windows systems. */ +#ifdef _WIN32 +#ifndef __RPCNDR_H__ /* don't conflict if rpcndr.h already read */ +typedef unsigned char boolean; +#endif +#define HAVE_BOOLEAN /* prevent jmorecfg.h from redefining it */ +#endif + +/* #undef RIGHT_SHIFT_IS_UNSIGNED */ +#define INLINE __inline__ +/* These are for configuring the JPEG memory manager. */ +/* #undef DEFAULT_MAX_MEM */ +/* #undef NO_MKTEMP */ + +#endif /* JPEG_INTERNALS */ + +#ifdef JPEG_CJPEG_DJPEG + +#define BMP_SUPPORTED /* BMP image file format */ +#define GIF_SUPPORTED /* GIF image file format */ +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ +/* #undef RLE_SUPPORTED */ +#define TARGA_SUPPORTED /* Targa image file format */ + +/* #undef TWO_FILE_COMMANDLINE */ +/* #undef NEED_SIGNAL_CATCHER */ +/* #undef DONT_USE_B_MODE */ + +/* Define this if you want percent-done progress reports from cjpeg/djpeg. */ +/* #undef PROGRESS_REPORT */ + +#endif /* JPEG_CJPEG_DJPEG */ diff --git a/src/jpeg-8c/jcparam.c b/src/jpeg-8c/jcparam.c new file mode 100644 index 0000000..c5e85dd --- /dev/null +++ b/src/jpeg-8c/jcparam.c @@ -0,0 +1,632 @@ +/* + * jcparam.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * Modified 2003-2008 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains optional default-setting code for the JPEG compressor. + * Applications do not have to use this file, but those that don't use it + * must know a lot more about the innards of the JPEG code. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Quantization table setup routines + */ + +GLOBAL(void) +jpeg_add_quant_table (j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, boolean force_baseline) +/* Define a quantization table equal to the basic_table times + * a scale factor (given as a percentage). + * If force_baseline is TRUE, the computed quantization table entries + * are limited to 1..255 for JPEG baseline compatibility. + */ +{ + JQUANT_TBL ** qtblptr; + int i; + long temp; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (which_tbl < 0 || which_tbl >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, which_tbl); + + qtblptr = & cinfo->quant_tbl_ptrs[which_tbl]; + + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) cinfo); + + for (i = 0; i < DCTSIZE2; i++) { + temp = ((long) basic_table[i] * scale_factor + 50L) / 100L; + /* limit the values to the valid range */ + if (temp <= 0L) temp = 1L; + if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */ + if (force_baseline && temp > 255L) + temp = 255L; /* limit to baseline range if requested */ + (*qtblptr)->quantval[i] = (UINT16) temp; + } + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*qtblptr)->sent_table = FALSE; +} + + +/* These are the sample quantization tables given in JPEG spec section K.1. + * The spec says that the values given produce "good" quality, and + * when divided by 2, "very good" quality. + */ +static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = { + 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 unsigned int std_chrominance_quant_tbl[DCTSIZE2] = { + 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 +}; + + +GLOBAL(void) +jpeg_default_qtables (j_compress_ptr cinfo, boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables + * and straight percentage-scaling quality scales. + * This entry point allows different scalings for luminance and chrominance. + */ +{ + /* Set up two quantization tables using the specified scaling */ + jpeg_add_quant_table(cinfo, 0, std_luminance_quant_tbl, + cinfo->q_scale_factor[0], force_baseline); + jpeg_add_quant_table(cinfo, 1, std_chrominance_quant_tbl, + cinfo->q_scale_factor[1], force_baseline); +} + + +GLOBAL(void) +jpeg_set_linear_quality (j_compress_ptr cinfo, int scale_factor, + boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables + * and a straight percentage-scaling quality scale. In most cases it's better + * to use jpeg_set_quality (below); this entry point is provided for + * applications that insist on a linear percentage scaling. + */ +{ + /* Set up two quantization tables using the specified scaling */ + jpeg_add_quant_table(cinfo, 0, std_luminance_quant_tbl, + scale_factor, force_baseline); + jpeg_add_quant_table(cinfo, 1, std_chrominance_quant_tbl, + scale_factor, force_baseline); +} + + +GLOBAL(int) +jpeg_quality_scaling (int quality) +/* Convert a user-specified quality rating to a percentage scaling factor + * for an underlying quantization table, using our recommended scaling curve. + * The input 'quality' factor should be 0 (terrible) to 100 (very good). + */ +{ + /* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ + if (quality <= 0) quality = 1; + if (quality > 100) quality = 100; + + /* The basic table is used as-is (scaling 100) for a quality of 50. + * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; + * note that at Q=100 the scaling is 0, which will cause jpeg_add_quant_table + * to make all the table entries 1 (hence, minimum quantization loss). + * Qualities 1..50 are converted to scaling percentage 5000/Q. + */ + if (quality < 50) + quality = 5000 / quality; + else + quality = 200 - quality*2; + + return quality; +} + + +GLOBAL(void) +jpeg_set_quality (j_compress_ptr cinfo, int quality, boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables. + * This is the standard quality-adjusting entry point for typical user + * interfaces; only those who want detailed control over quantization tables + * would use the preceding three routines directly. + */ +{ + /* Convert user 0-100 rating to percentage scaling */ + quality = jpeg_quality_scaling(quality); + + /* Set up standard quality tables */ + jpeg_set_linear_quality(cinfo, quality, force_baseline); +} + + +/* + * Huffman table setup routines + */ + +LOCAL(void) +add_huff_table (j_compress_ptr cinfo, + JHUFF_TBL **htblptr, const UINT8 *bits, const UINT8 *val) +/* Define a Huffman table */ +{ + int nsymbols, len; + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + /* Copy the number-of-symbols-of-each-code-length counts */ + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + + /* Validate the counts. We do this here mainly so we can copy the right + * number of symbols from the val[] array, without risking marching off + * the end of memory. jchuff.c will do a more thorough test later. + */ + nsymbols = 0; + for (len = 1; len <= 16; len++) + nsymbols += bits[len]; + if (nsymbols < 1 || nsymbols > 256) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + + MEMCOPY((*htblptr)->huffval, val, nsymbols * SIZEOF(UINT8)); + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*htblptr)->sent_table = FALSE; +} + + +LOCAL(void) +std_huff_tables (j_compress_ptr cinfo) +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ +{ + static const UINT8 bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_luminance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_chrominance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; + static const UINT8 val_ac_luminance[] = + { 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 UINT8 bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + static const UINT8 val_ac_chrominance[] = + { 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 }; + + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[0], + bits_dc_luminance, val_dc_luminance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[0], + bits_ac_luminance, val_ac_luminance); + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[1], + bits_dc_chrominance, val_dc_chrominance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[1], + bits_ac_chrominance, val_ac_chrominance); +} + + +/* + * Default parameter setup for compression. + * + * Applications that don't choose to use this routine must do their + * own setup of all these parameters. Alternately, you can call this + * to establish defaults and then alter parameters selectively. This + * is the recommended approach since, if we add any new parameters, + * your code will still work (they'll be set to reasonable defaults). + */ + +GLOBAL(void) +jpeg_set_defaults (j_compress_ptr cinfo) +{ + int i; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Allocate comp_info array large enough for maximum component count. + * Array is made permanent in case application wants to compress + * multiple images at same param settings. + */ + if (cinfo->comp_info == NULL) + cinfo->comp_info = (jpeg_component_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + MAX_COMPONENTS * SIZEOF(jpeg_component_info)); + + /* Initialize everything not dependent on the color space */ + + cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_denom = 1; + cinfo->data_precision = BITS_IN_JSAMPLE; + /* Set up two quantization tables using default quality of 75 */ + jpeg_set_quality(cinfo, 75, TRUE); + /* Set up two Huffman tables */ + std_huff_tables(cinfo); + + /* Initialize default arithmetic coding conditioning */ + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + + /* Default is no multiple-scan output */ + cinfo->scan_info = NULL; + cinfo->num_scans = 0; + + /* Expect normal source image, not raw downsampled data */ + cinfo->raw_data_in = FALSE; + + /* Use Huffman coding, not arithmetic coding, by default */ + cinfo->arith_code = FALSE; + + /* By default, don't do extra passes to optimize entropy coding */ + cinfo->optimize_coding = FALSE; + /* The standard Huffman tables are only valid for 8-bit data precision. + * If the precision is higher, force optimization on so that usable + * tables will be computed. This test can be removed if default tables + * are supplied that are valid for the desired precision. + */ + if (cinfo->data_precision > 8) + cinfo->optimize_coding = TRUE; + + /* By default, use the simpler non-cosited sampling alignment */ + cinfo->CCIR601_sampling = FALSE; + + /* By default, apply fancy downsampling */ + cinfo->do_fancy_downsampling = TRUE; + + /* No input smoothing */ + cinfo->smoothing_factor = 0; + + /* DCT algorithm preference */ + cinfo->dct_method = JDCT_DEFAULT; + + /* No restart markers */ + cinfo->restart_interval = 0; + cinfo->restart_in_rows = 0; + + /* Fill in default JFIF marker parameters. Note that whether the marker + * will actually be written is determined by jpeg_set_colorspace. + * + * By default, the library emits JFIF version code 1.01. + * An application that wants to emit JFIF 1.02 extension markers should set + * JFIF_minor_version to 2. We could probably get away with just defaulting + * to 1.02, but there may still be some decoders in use that will complain + * about that; saying 1.01 should minimize compatibility problems. + */ + cinfo->JFIF_major_version = 1; /* Default JFIF version = 1.01 */ + cinfo->JFIF_minor_version = 1; + cinfo->density_unit = 0; /* Pixel size is unknown by default */ + cinfo->X_density = 1; /* Pixel aspect ratio is square by default */ + cinfo->Y_density = 1; + + /* Choose JPEG colorspace based on input space, set defaults accordingly */ + + jpeg_default_colorspace(cinfo); +} + + +/* + * Select an appropriate JPEG colorspace for in_color_space. + */ + +GLOBAL(void) +jpeg_default_colorspace (j_compress_ptr cinfo) +{ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); + break; + case JCS_RGB: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_YCbCr: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_CMYK: + jpeg_set_colorspace(cinfo, JCS_CMYK); /* By default, no translation */ + break; + case JCS_YCCK: + jpeg_set_colorspace(cinfo, JCS_YCCK); + break; + case JCS_UNKNOWN: + jpeg_set_colorspace(cinfo, JCS_UNKNOWN); + break; + default: + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + } +} + + +/* + * Set the JPEG colorspace, and choose colorspace-dependent default values. + */ + +GLOBAL(void) +jpeg_set_colorspace (j_compress_ptr cinfo, J_COLOR_SPACE colorspace) +{ + jpeg_component_info * compptr; + int ci; + +#define SET_COMP(index,id,hsamp,vsamp,quant,dctbl,actbl) \ + (compptr = &cinfo->comp_info[index], \ + compptr->component_id = (id), \ + compptr->h_samp_factor = (hsamp), \ + compptr->v_samp_factor = (vsamp), \ + compptr->quant_tbl_no = (quant), \ + compptr->dc_tbl_no = (dctbl), \ + compptr->ac_tbl_no = (actbl) ) + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* For all colorspaces, we use Q and Huff tables 0 for luminance components, + * tables 1 for chrominance components. + */ + + cinfo->jpeg_color_space = colorspace; + + cinfo->write_JFIF_header = FALSE; /* No marker for non-JFIF colorspaces */ + cinfo->write_Adobe_marker = FALSE; /* write no Adobe marker by default */ + + switch (colorspace) { + case JCS_GRAYSCALE: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 1; + /* JFIF specifies component ID 1 */ + SET_COMP(0, 1, 1,1, 0, 0,0); + break; + case JCS_RGB: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag RGB */ + cinfo->num_components = 3; + SET_COMP(0, 0x52 /* 'R' */, 1,1, 0, 0,0); + SET_COMP(1, 0x47 /* 'G' */, 1,1, 0, 0,0); + SET_COMP(2, 0x42 /* 'B' */, 1,1, 0, 0,0); + break; + case JCS_YCbCr: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 3; + /* JFIF specifies component IDs 1,2,3 */ + /* We default to 2x2 subsamples of chrominance */ + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + break; + case JCS_CMYK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag CMYK */ + cinfo->num_components = 4; + SET_COMP(0, 0x43 /* 'C' */, 1,1, 0, 0,0); + SET_COMP(1, 0x4D /* 'M' */, 1,1, 0, 0,0); + SET_COMP(2, 0x59 /* 'Y' */, 1,1, 0, 0,0); + SET_COMP(3, 0x4B /* 'K' */, 1,1, 0, 0,0); + break; + case JCS_YCCK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag YCCK */ + cinfo->num_components = 4; + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + SET_COMP(3, 4, 2,2, 0, 0,0); + break; + case JCS_UNKNOWN: + cinfo->num_components = cinfo->input_components; + if (cinfo->num_components < 1 || cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + for (ci = 0; ci < cinfo->num_components; ci++) { + SET_COMP(ci, ci, 1,1, 0, 0,0); + } + break; + default: + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + } +} + + +#ifdef C_PROGRESSIVE_SUPPORTED + +LOCAL(jpeg_scan_info *) +fill_a_scan (jpeg_scan_info * scanptr, int ci, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for specified component */ +{ + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + return scanptr; +} + +LOCAL(jpeg_scan_info *) +fill_scans (jpeg_scan_info * scanptr, int ncomps, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for each component */ +{ + int ci; + + for (ci = 0; ci < ncomps; ci++) { + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } + return scanptr; +} + +LOCAL(jpeg_scan_info *) +fill_dc_scans (jpeg_scan_info * scanptr, int ncomps, int Ah, int Al) +/* Support routine: generate interleaved DC scan if possible, else N scans */ +{ + int ci; + + if (ncomps <= MAX_COMPS_IN_SCAN) { + /* Single interleaved DC scan */ + scanptr->comps_in_scan = ncomps; + for (ci = 0; ci < ncomps; ci++) + scanptr->component_index[ci] = ci; + scanptr->Ss = scanptr->Se = 0; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } else { + /* Noninterleaved DC scan for each component */ + scanptr = fill_scans(scanptr, ncomps, 0, 0, Ah, Al); + } + return scanptr; +} + + +/* + * Create a recommended progressive-JPEG script. + * cinfo->num_components and cinfo->jpeg_color_space must be correct. + */ + +GLOBAL(void) +jpeg_simple_progression (j_compress_ptr cinfo) +{ + int ncomps = cinfo->num_components; + int nscans; + jpeg_scan_info * scanptr; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Figure space needed for script. Calculation must match code below! */ + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + nscans = 10; + } else { + /* All-purpose script for other color spaces. */ + if (ncomps > MAX_COMPS_IN_SCAN) + nscans = 6 * ncomps; /* 2 DC + 4 AC scans per component */ + else + nscans = 2 + 4 * ncomps; /* 2 DC scans; 4 AC scans per component */ + } + + /* Allocate space for script. + * We need to put it in the permanent pool in case the application performs + * multiple compressions without changing the settings. To avoid a memory + * leak if jpeg_simple_progression is called repeatedly for the same JPEG + * object, we try to re-use previously allocated space, and we allocate + * enough space to handle YCbCr even if initially asked for grayscale. + */ + if (cinfo->script_space == NULL || cinfo->script_space_size < nscans) { + cinfo->script_space_size = MAX(nscans, 10); + cinfo->script_space = (jpeg_scan_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + cinfo->script_space_size * SIZEOF(jpeg_scan_info)); + } + scanptr = cinfo->script_space; + cinfo->scan_info = scanptr; + cinfo->num_scans = nscans; + + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + /* Initial DC scan */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + /* Initial AC scan: get some luma data out in a hurry */ + scanptr = fill_a_scan(scanptr, 0, 1, 5, 0, 2); + /* Chroma data is too small to be worth expending many scans on */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 0, 1); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 0, 1); + /* Complete spectral selection for luma AC */ + scanptr = fill_a_scan(scanptr, 0, 6, 63, 0, 2); + /* Refine next bit of luma AC */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 2, 1); + /* Finish DC successive approximation */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + /* Finish AC successive approximation */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 1, 0); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 1, 0); + /* Luma bottom bit comes last since it's usually largest scan */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 1, 0); + } else { + /* All-purpose script for other color spaces. */ + /* Successive approximation first pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + scanptr = fill_scans(scanptr, ncomps, 1, 5, 0, 2); + scanptr = fill_scans(scanptr, ncomps, 6, 63, 0, 2); + /* Successive approximation second pass */ + scanptr = fill_scans(scanptr, ncomps, 1, 63, 2, 1); + /* Successive approximation final pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + scanptr = fill_scans(scanptr, ncomps, 1, 63, 1, 0); + } +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/src/jpeg-8c/jcprepct.c b/src/jpeg-8c/jcprepct.c new file mode 100644 index 0000000..be44cc4 --- /dev/null +++ b/src/jpeg-8c/jcprepct.c @@ -0,0 +1,358 @@ +/* + * jcprepct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the compression preprocessing controller. + * This controller manages the color conversion, downsampling, + * and edge expansion steps. + * + * Most of the complexity here is associated with buffering input rows + * as required by the downsampler. See the comments at the head of + * jcsample.c for the downsampler's needs. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* At present, jcsample.c can request context rows only for smoothing. + * In the future, we might also need context rows for CCIR601 sampling + * or other more-complex downsampling procedures. The code to support + * context rows should be compiled only if needed. + */ +#ifdef INPUT_SMOOTHING_SUPPORTED +#define CONTEXT_ROWS_SUPPORTED +#endif + + +/* + * For the simple (no-context-row) case, we just need to buffer one + * row group's worth of pixels for the downsampling step. At the bottom of + * the image, we pad to a full row group by replicating the last pixel row. + * The downsampler's last output row is then replicated if needed to pad + * out to a full iMCU row. + * + * When providing context rows, we must buffer three row groups' worth of + * pixels. Three row groups are physically allocated, but the row pointer + * arrays are made five row groups high, with the extra pointers above and + * below "wrapping around" to point to the last and first real row groups. + * This allows the downsampler to access the proper context rows. + * At the top and bottom of the image, we create dummy context rows by + * copying the first or last real pixel row. This copying could be avoided + * by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the + * trouble on the compression side. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_prep_controller pub; /* public fields */ + + /* Downsampling input buffer. This buffer holds color-converted data + * until we have enough to do a downsample step. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + JDIMENSION rows_to_go; /* counts rows remaining in source image */ + int next_buf_row; /* index of next row to store in color_buf */ + +#ifdef CONTEXT_ROWS_SUPPORTED /* only needed for context case */ + int this_row_group; /* starting row index of group to process */ + int next_buf_stop; /* downsample when we reach this index */ +#endif +} my_prep_controller; + +typedef my_prep_controller * my_prep_ptr; + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_prep (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + + if (pass_mode != JBUF_PASS_THRU) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Initialize total-height counter for detecting bottom of image */ + prep->rows_to_go = cinfo->image_height; + /* Mark the conversion buffer empty */ + prep->next_buf_row = 0; +#ifdef CONTEXT_ROWS_SUPPORTED + /* Preset additional state variables for context mode. + * These aren't used in non-context mode, so we needn't test which mode. + */ + prep->this_row_group = 0; + /* Set next_buf_stop to stop after two row groups have been read in. */ + prep->next_buf_stop = 2 * cinfo->max_v_samp_factor; +#endif +} + + +/* + * Expand an image vertically from height input_rows to height output_rows, + * by duplicating the bottom row. + */ + +LOCAL(void) +expand_bottom_edge (JSAMPARRAY image_data, JDIMENSION num_cols, + int input_rows, int output_rows) +{ + register int row; + + for (row = input_rows; row < output_rows; row++) { + jcopy_sample_rows(image_data, input_rows-1, image_data, row, + 1, num_cols); + } +} + + +/* + * Process some data in the simple no-context case. + * + * Preprocessor output data is counted in "row groups". A row group + * is defined to be v_samp_factor sample rows of each component. + * Downsampling will produce this much data from each max_v_samp_factor + * input rows. + */ + +METHODDEF(void) +pre_process_data (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while (*in_row_ctr < in_rows_avail && + *out_row_group_ctr < out_row_groups_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = cinfo->max_v_samp_factor - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + /* If at bottom of image, pad to fill the conversion buffer. */ + if (prep->rows_to_go == 0 && + prep->next_buf_row < cinfo->max_v_samp_factor) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, cinfo->max_v_samp_factor); + } + prep->next_buf_row = cinfo->max_v_samp_factor; + } + /* If we've filled the conversion buffer, empty it. */ + if (prep->next_buf_row == cinfo->max_v_samp_factor) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, (JDIMENSION) 0, + output_buf, *out_row_group_ctr); + prep->next_buf_row = 0; + (*out_row_group_ctr)++; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if (prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + numrows = (compptr->v_samp_factor * compptr->DCT_v_scaled_size) / + cinfo->min_DCT_v_scaled_size; + expand_bottom_edge(output_buf[ci], + compptr->width_in_blocks * compptr->DCT_h_scaled_size, + (int) (*out_row_group_ctr * numrows), + (int) (out_row_groups_avail * numrows)); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +#ifdef CONTEXT_ROWS_SUPPORTED + +/* + * Process some data in the context case. + */ + +METHODDEF(void) +pre_process_context (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + int buf_height = cinfo->max_v_samp_factor * 3; + JDIMENSION inrows; + + while (*out_row_group_ctr < out_row_groups_avail) { + if (*in_row_ctr < in_rows_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = prep->next_buf_stop - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + /* Pad at top of image, if first time through */ + if (prep->rows_to_go == cinfo->image_height) { + for (ci = 0; ci < cinfo->num_components; ci++) { + int row; + for (row = 1; row <= cinfo->max_v_samp_factor; row++) { + jcopy_sample_rows(prep->color_buf[ci], 0, + prep->color_buf[ci], -row, + 1, cinfo->image_width); + } + } + } + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + } else { + /* Return for more data, unless we are at the bottom of the image. */ + if (prep->rows_to_go != 0) + break; + /* When at bottom of image, pad to fill the conversion buffer. */ + if (prep->next_buf_row < prep->next_buf_stop) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, prep->next_buf_stop); + } + prep->next_buf_row = prep->next_buf_stop; + } + } + /* If we've gotten enough data, downsample a row group. */ + if (prep->next_buf_row == prep->next_buf_stop) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, + (JDIMENSION) prep->this_row_group, + output_buf, *out_row_group_ctr); + (*out_row_group_ctr)++; + /* Advance pointers with wraparound as necessary. */ + prep->this_row_group += cinfo->max_v_samp_factor; + if (prep->this_row_group >= buf_height) + prep->this_row_group = 0; + if (prep->next_buf_row >= buf_height) + prep->next_buf_row = 0; + prep->next_buf_stop = prep->next_buf_row + cinfo->max_v_samp_factor; + } + } +} + + +/* + * Create the wrapped-around downsampling input buffer needed for context mode. + */ + +LOCAL(void) +create_context_buffer (j_compress_ptr cinfo) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int rgroup_height = cinfo->max_v_samp_factor; + int ci, i; + jpeg_component_info * compptr; + JSAMPARRAY true_buffer, fake_buffer; + + /* Grab enough space for fake row pointers for all the components; + * we need five row groups' worth of pointers for each component. + */ + fake_buffer = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (cinfo->num_components * 5 * rgroup_height) * + SIZEOF(JSAMPROW)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate the actual buffer space (3 row groups) for this component. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + true_buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * + cinfo->min_DCT_h_scaled_size * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) (3 * rgroup_height)); + /* Copy true buffer row pointers into the middle of the fake row array */ + MEMCOPY(fake_buffer + rgroup_height, true_buffer, + 3 * rgroup_height * SIZEOF(JSAMPROW)); + /* Fill in the above and below wraparound pointers */ + for (i = 0; i < rgroup_height; i++) { + fake_buffer[i] = true_buffer[2 * rgroup_height + i]; + fake_buffer[4 * rgroup_height + i] = true_buffer[i]; + } + prep->color_buf[ci] = fake_buffer + rgroup_height; + fake_buffer += 5 * rgroup_height; /* point to space for next component */ + } +} + +#endif /* CONTEXT_ROWS_SUPPORTED */ + + +/* + * Initialize preprocessing controller. + */ + +GLOBAL(void) +jinit_c_prep_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_prep_ptr prep; + int ci; + jpeg_component_info * compptr; + + if (need_full_buffer) /* safety check */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + prep = (my_prep_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_prep_controller)); + cinfo->prep = (struct jpeg_c_prep_controller *) prep; + prep->pub.start_pass = start_pass_prep; + + /* Allocate the color conversion buffer. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + if (cinfo->downsample->need_context_rows) { + /* Set up to provide context rows */ +#ifdef CONTEXT_ROWS_SUPPORTED + prep->pub.pre_process_data = pre_process_context; + create_context_buffer(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* No context, just make it tall enough for one row group */ + prep->pub.pre_process_data = pre_process_data; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prep->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * + cinfo->min_DCT_h_scaled_size * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/src/jpeg-8c/jcsample.c b/src/jpeg-8c/jcsample.c new file mode 100644 index 0000000..4d36f85 --- /dev/null +++ b/src/jpeg-8c/jcsample.c @@ -0,0 +1,545 @@ +/* + * jcsample.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains downsampling routines. + * + * Downsampling input data is counted in "row groups". A row group + * is defined to be max_v_samp_factor pixel rows of each component, + * from which the downsampler produces v_samp_factor sample rows. + * A single row group is processed in each call to the downsampler module. + * + * The downsampler is responsible for edge-expansion of its output data + * to fill an integral number of DCT blocks horizontally. The source buffer + * may be modified if it is helpful for this purpose (the source buffer is + * allocated wide enough to correspond to the desired output width). + * The caller (the prep controller) is responsible for vertical padding. + * + * The downsampler may request "context rows" by setting need_context_rows + * during startup. In this case, the input arrays will contain at least + * one row group's worth of pixels above and below the passed-in data; + * the caller will create dummy rows at image top and bottom by replicating + * the first or last real pixel row. + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + * + * The downsampling algorithm used here is a simple average of the source + * pixels covered by the output pixel. The hi-falutin sampling literature + * refers to this as a "box filter". In general the characteristics of a box + * filter are not very good, but for the specific cases we normally use (1:1 + * and 2:1 ratios) the box is equivalent to a "triangle filter" which is not + * nearly so bad. If you intend to use other sampling ratios, you'd be well + * advised to improve this code. + * + * A simple input-smoothing capability is provided. This is mainly intended + * for cleaning up color-dithered GIF input files (if you find it inadequate, + * we suggest using an external filtering program such as pnmconvol). When + * enabled, each input pixel P is replaced by a weighted sum of itself and its + * eight neighbors. P's weight is 1-8*SF and each neighbor's weight is SF, + * where SF = (smoothing_factor / 1024). + * Currently, smoothing is only supported for 2h2v sampling factors. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to downsample a single component */ +typedef JMETHOD(void, downsample1_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data)); + +/* Private subobject */ + +typedef struct { + struct jpeg_downsampler pub; /* public fields */ + + /* Downsampling method pointers, one per component */ + downsample1_ptr methods[MAX_COMPONENTS]; + + /* Height of an output row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_downsample need not + * recompute them each time. They are unused for other downsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_downsampler; + +typedef my_downsampler * my_downsample_ptr; + + +/* + * Initialize for a downsampling pass. + */ + +METHODDEF(void) +start_pass_downsample (j_compress_ptr cinfo) +{ + /* no work for now */ +} + + +/* + * Expand a component horizontally from width input_cols to width output_cols, + * by duplicating the rightmost samples. + */ + +LOCAL(void) +expand_right_edge (JSAMPARRAY image_data, int num_rows, + JDIMENSION input_cols, JDIMENSION output_cols) +{ + register JSAMPROW ptr; + register JSAMPLE pixval; + register int count; + int row; + int numcols = (int) (output_cols - input_cols); + + if (numcols > 0) { + for (row = 0; row < num_rows; row++) { + ptr = image_data[row] + input_cols; + pixval = ptr[-1]; /* don't need GETJSAMPLE() here */ + for (count = numcols; count > 0; count--) + *ptr++ = pixval; + } + } +} + + +/* + * Do downsampling for a whole row group (all components). + * + * In this version we simply downsample each component independently. + */ + +METHODDEF(void) +sep_downsample (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, JDIMENSION out_row_group_index) +{ + my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample; + int ci; + jpeg_component_info * compptr; + JSAMPARRAY in_ptr, out_ptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + in_ptr = input_buf[ci] + in_row_index; + out_ptr = output_buf[ci] + + (out_row_group_index * downsample->rowgroup_height[ci]); + (*downsample->methods[ci]) (cinfo, compptr, in_ptr, out_ptr); + } +} + + +/* + * Downsample pixel values of a single component. + * One row group is processed per call. + * This version handles arbitrary integral sampling ratios, without smoothing. + * Note that this version is not actually used for customary sampling ratios. + */ + +METHODDEF(void) +int_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample; + int inrow, outrow, h_expand, v_expand, numpix, numpix2, h, v; + JDIMENSION outcol, outcol_h; /* outcol_h == outcol*h_expand */ + JDIMENSION output_cols = compptr->width_in_blocks * compptr->DCT_h_scaled_size; + JSAMPROW inptr, outptr; + INT32 outvalue; + + h_expand = downsample->h_expand[compptr->component_index]; + v_expand = downsample->v_expand[compptr->component_index]; + numpix = h_expand * v_expand; + numpix2 = numpix/2; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * h_expand); + + inrow = outrow = 0; + while (inrow < cinfo->max_v_samp_factor) { + outptr = output_data[outrow]; + for (outcol = 0, outcol_h = 0; outcol < output_cols; + outcol++, outcol_h += h_expand) { + outvalue = 0; + for (v = 0; v < v_expand; v++) { + inptr = input_data[inrow+v] + outcol_h; + for (h = 0; h < h_expand; h++) { + outvalue += (INT32) GETJSAMPLE(*inptr++); + } + } + *outptr++ = (JSAMPLE) ((outvalue + numpix2) / numpix); + } + inrow += v_expand; + outrow++; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * without smoothing. + */ + +METHODDEF(void) +fullsize_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + /* Copy the data */ + jcopy_sample_rows(input_data, 0, output_data, 0, + cinfo->max_v_samp_factor, cinfo->image_width); + /* Edge-expand */ + expand_right_edge(output_data, cinfo->max_v_samp_factor, cinfo->image_width, + compptr->width_in_blocks * compptr->DCT_h_scaled_size); +} + + +/* + * Downsample pixel values of a single component. + * This version handles the common case of 2:1 horizontal and 1:1 vertical, + * without smoothing. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF(void) +h2v1_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * compptr->DCT_h_scaled_size; + register JSAMPROW inptr, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + outptr = output_data[inrow]; + inptr = input_data[inrow]; + bias = 0; /* bias = 0,1,0,1,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr) + GETJSAMPLE(inptr[1]) + + bias) >> 1); + bias ^= 1; /* 0=>1, 1=>0 */ + inptr += 2; + } + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * without smoothing. + */ + +METHODDEF(void) +h2v2_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * compptr->DCT_h_scaled_size; + register JSAMPROW inptr0, inptr1, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + inrow = outrow = 0; + while (inrow < cinfo->max_v_samp_factor) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + bias = 1; /* bias = 1,2,1,2,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]) + + bias) >> 2); + bias ^= 3; /* 1=>2, 2=>1 */ + inptr0 += 2; inptr1 += 2; + } + inrow += 2; + outrow++; + } +} + + +#ifdef INPUT_SMOOTHING_SUPPORTED + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * with smoothing. One row of context is required. + */ + +METHODDEF(void) +h2v2_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * compptr->DCT_h_scaled_size; + register JSAMPROW inptr0, inptr1, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols * 2); + + /* We don't bother to form the individual "smoothed" input pixel values; + * we can directly compute the output which is the average of the four + * smoothed values. Each of the four member pixels contributes a fraction + * (1-8*SF) to its own smoothed image and a fraction SF to each of the three + * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final + * output. The four corner-adjacent neighbor pixels contribute a fraction + * SF to just one smoothed pixel, or SF/4 to the final output; while the + * eight edge-adjacent neighbors contribute SF to each of two smoothed + * pixels, or SF/2 overall. In order to use integer arithmetic, these + * factors are scaled by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 16384 - cinfo->smoothing_factor * 80; /* scaled (1-5*SF)/4 */ + neighscale = cinfo->smoothing_factor * 16; /* scaled SF/4 */ + + inrow = outrow = 0; + while (inrow < cinfo->max_v_samp_factor) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + above_ptr = input_data[inrow-1]; + below_ptr = input_data[inrow+2]; + + /* Special case for first column: pretend column -1 is same as column 0 */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[2]); + neighsum += neighsum; + neighsum += GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[2]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + /* sum of pixels directly mapped to this output element */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + /* sum of edge-neighbor pixels */ + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[2]); + /* The edge-neighbors count twice as much as corner-neighbors */ + neighsum += neighsum; + /* Add in the corner-neighbors */ + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[2]); + /* form final output scaled up by 2^16 */ + membersum = membersum * memberscale + neighsum * neighscale; + /* round, descale and output it */ + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[1]); + neighsum += neighsum; + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[1]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + inrow += 2; + outrow++; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * with smoothing. One row of context is required. + */ + +METHODDEF(void) +fullsize_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info *compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * compptr->DCT_h_scaled_size; + register JSAMPROW inptr, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + int colsum, lastcolsum, nextcolsum; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols); + + /* Each of the eight neighbor pixels contributes a fraction SF to the + * smoothed pixel, while the main pixel contributes (1-8*SF). In order + * to use integer arithmetic, these factors are multiplied by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 65536L - cinfo->smoothing_factor * 512L; /* scaled 1-8*SF */ + neighscale = cinfo->smoothing_factor * 64; /* scaled SF */ + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + outptr = output_data[inrow]; + inptr = input_data[inrow]; + above_ptr = input_data[inrow-1]; + below_ptr = input_data[inrow+1]; + + /* Special case for first column */ + colsum = GETJSAMPLE(*above_ptr++) + GETJSAMPLE(*below_ptr++) + + GETJSAMPLE(*inptr); + membersum = GETJSAMPLE(*inptr++); + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = colsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + membersum = GETJSAMPLE(*inptr++); + above_ptr++; below_ptr++; + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + colsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + } +} + +#endif /* INPUT_SMOOTHING_SUPPORTED */ + + +/* + * Module initialization routine for downsampling. + * Note that we must select a routine for each component. + */ + +GLOBAL(void) +jinit_downsampler (j_compress_ptr cinfo) +{ + my_downsample_ptr downsample; + int ci; + jpeg_component_info * compptr; + boolean smoothok = TRUE; + int h_in_group, v_in_group, h_out_group, v_out_group; + + downsample = (my_downsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_downsampler)); + cinfo->downsample = (struct jpeg_downsampler *) downsample; + downsample->pub.start_pass = start_pass_downsample; + downsample->pub.downsample = sep_downsample; + downsample->pub.need_context_rows = FALSE; + + if (cinfo->CCIR601_sampling) + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* Verify we can handle the sampling factors, and set up method pointers */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Compute size of an "output group" for DCT scaling. This many samples + * are to be converted from max_h_samp_factor * max_v_samp_factor pixels. + */ + h_out_group = (compptr->h_samp_factor * compptr->DCT_h_scaled_size) / + cinfo->min_DCT_h_scaled_size; + v_out_group = (compptr->v_samp_factor * compptr->DCT_v_scaled_size) / + cinfo->min_DCT_v_scaled_size; + h_in_group = cinfo->max_h_samp_factor; + v_in_group = cinfo->max_v_samp_factor; + downsample->rowgroup_height[ci] = v_out_group; /* save for use later */ + if (h_in_group == h_out_group && v_in_group == v_out_group) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = fullsize_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = fullsize_downsample; + } else if (h_in_group == h_out_group * 2 && + v_in_group == v_out_group) { + smoothok = FALSE; + downsample->methods[ci] = h2v1_downsample; + } else if (h_in_group == h_out_group * 2 && + v_in_group == v_out_group * 2) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = h2v2_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = h2v2_downsample; + } else if ((h_in_group % h_out_group) == 0 && + (v_in_group % v_out_group) == 0) { + smoothok = FALSE; + downsample->methods[ci] = int_downsample; + downsample->h_expand[ci] = (UINT8) (h_in_group / h_out_group); + downsample->v_expand[ci] = (UINT8) (v_in_group / v_out_group); + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + } + +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor && !smoothok) + TRACEMS(cinfo, 0, JTRC_SMOOTH_NOTIMPL); +#endif +} diff --git a/src/jpeg-8c/jctrans.c b/src/jpeg-8c/jctrans.c new file mode 100644 index 0000000..cee6b0f --- /dev/null +++ b/src/jpeg-8c/jctrans.c @@ -0,0 +1,382 @@ +/* + * jctrans.c + * + * Copyright (C) 1995-1998, Thomas G. Lane. + * Modified 2000-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding compression, + * that is, writing raw DCT coefficient arrays to an output JPEG file. + * The routines in jcapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(void) transencode_master_selection + JPP((j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays)); +LOCAL(void) transencode_coef_controller + JPP((j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays)); + + +/* + * Compression initialization for writing raw-coefficient data. + * Before calling this, all parameters and a data destination must be set up. + * Call jpeg_finish_compress() to actually write the data. + * + * The number of passed virtual arrays must match cinfo->num_components. + * Note that the virtual arrays need not be filled or even realized at + * the time write_coefficients is called; indeed, if the virtual arrays + * were requested from this compression object's memory manager, they + * typically will be realized during this routine and filled afterwards. + */ + +GLOBAL(void) +jpeg_write_coefficients (j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Mark all tables to be written */ + jpeg_suppress_tables(cinfo, FALSE); + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + transencode_master_selection(cinfo, coef_arrays); + /* Wait for jpeg_finish_compress() call */ + cinfo->next_scanline = 0; /* so jpeg_write_marker works */ + cinfo->global_state = CSTATE_WRCOEFS; +} + + +/* + * Initialize the compression object with default parameters, + * then copy from the source object all parameters needed for lossless + * transcoding. Parameters that can be varied without loss (such as + * scan script and Huffman optimization) are left in their default states. + */ + +GLOBAL(void) +jpeg_copy_critical_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo) +{ + JQUANT_TBL ** qtblptr; + jpeg_component_info *incomp, *outcomp; + JQUANT_TBL *c_quant, *slot_quant; + int tblno, ci, coefi; + + /* Safety check to ensure start_compress not called yet. */ + if (dstinfo->global_state != CSTATE_START) + ERREXIT1(dstinfo, JERR_BAD_STATE, dstinfo->global_state); + /* Copy fundamental image dimensions */ + dstinfo->image_width = srcinfo->image_width; + dstinfo->image_height = srcinfo->image_height; + dstinfo->input_components = srcinfo->num_components; + dstinfo->in_color_space = srcinfo->jpeg_color_space; + dstinfo->jpeg_width = srcinfo->output_width; + dstinfo->jpeg_height = srcinfo->output_height; + dstinfo->min_DCT_h_scaled_size = srcinfo->min_DCT_h_scaled_size; + dstinfo->min_DCT_v_scaled_size = srcinfo->min_DCT_v_scaled_size; + /* Initialize all parameters to default values */ + jpeg_set_defaults(dstinfo); + /* jpeg_set_defaults may choose wrong colorspace, eg YCbCr if input is RGB. + * Fix it to get the right header markers for the image colorspace. + */ + jpeg_set_colorspace(dstinfo, srcinfo->jpeg_color_space); + dstinfo->data_precision = srcinfo->data_precision; + dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling; + /* Copy the source's quantization tables. */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + if (srcinfo->quant_tbl_ptrs[tblno] != NULL) { + qtblptr = & dstinfo->quant_tbl_ptrs[tblno]; + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) dstinfo); + MEMCOPY((*qtblptr)->quantval, + srcinfo->quant_tbl_ptrs[tblno]->quantval, + SIZEOF((*qtblptr)->quantval)); + (*qtblptr)->sent_table = FALSE; + } + } + /* Copy the source's per-component info. + * Note we assume jpeg_set_defaults has allocated the dest comp_info array. + */ + dstinfo->num_components = srcinfo->num_components; + if (dstinfo->num_components < 1 || dstinfo->num_components > MAX_COMPONENTS) + ERREXIT2(dstinfo, JERR_COMPONENT_COUNT, dstinfo->num_components, + MAX_COMPONENTS); + for (ci = 0, incomp = srcinfo->comp_info, outcomp = dstinfo->comp_info; + ci < dstinfo->num_components; ci++, incomp++, outcomp++) { + outcomp->component_id = incomp->component_id; + outcomp->h_samp_factor = incomp->h_samp_factor; + outcomp->v_samp_factor = incomp->v_samp_factor; + outcomp->quant_tbl_no = incomp->quant_tbl_no; + /* Make sure saved quantization table for component matches the qtable + * slot. If not, the input file re-used this qtable slot. + * IJG encoder currently cannot duplicate this. + */ + tblno = outcomp->quant_tbl_no; + if (tblno < 0 || tblno >= NUM_QUANT_TBLS || + srcinfo->quant_tbl_ptrs[tblno] == NULL) + ERREXIT1(dstinfo, JERR_NO_QUANT_TABLE, tblno); + slot_quant = srcinfo->quant_tbl_ptrs[tblno]; + c_quant = incomp->quant_table; + if (c_quant != NULL) { + for (coefi = 0; coefi < DCTSIZE2; coefi++) { + if (c_quant->quantval[coefi] != slot_quant->quantval[coefi]) + ERREXIT1(dstinfo, JERR_MISMATCHED_QUANT_TABLE, tblno); + } + } + /* Note: we do not copy the source's Huffman table assignments; + * instead we rely on jpeg_set_colorspace to have made a suitable choice. + */ + } + /* Also copy JFIF version and resolution information, if available. + * Strictly speaking this isn't "critical" info, but it's nearly + * always appropriate to copy it if available. In particular, + * if the application chooses to copy JFIF 1.02 extension markers from + * the source file, we need to copy the version to make sure we don't + * emit a file that has 1.02 extensions but a claimed version of 1.01. + * We will *not*, however, copy version info from mislabeled "2.01" files. + */ + if (srcinfo->saw_JFIF_marker) { + if (srcinfo->JFIF_major_version == 1) { + dstinfo->JFIF_major_version = srcinfo->JFIF_major_version; + dstinfo->JFIF_minor_version = srcinfo->JFIF_minor_version; + } + dstinfo->density_unit = srcinfo->density_unit; + dstinfo->X_density = srcinfo->X_density; + dstinfo->Y_density = srcinfo->Y_density; + } +} + + +/* + * Master selection of compression modules for transcoding. + * This substitutes for jcinit.c's initialization of the full compressor. + */ + +LOCAL(void) +transencode_master_selection (j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays) +{ + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, TRUE /* transcode only */); + + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) + jinit_arith_encoder(cinfo); + else { + jinit_huff_encoder(cinfo); + } + + /* We need a special coefficient buffer controller. */ + transencode_coef_controller(cinfo, coef_arrays); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI, JFIF) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} + + +/* + * The rest of this file is a special implementation of the coefficient + * buffer controller. This is similar to jccoefct.c, but it handles only + * output from presupplied virtual arrays. Furthermore, we generate any + * dummy padding blocks on-the-fly rather than expecting them to be present + * in the arrays. + */ + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* Virtual block array for each component. */ + jvirt_barray_ptr * whole_image; + + /* Workspace for constructing dummy blocks at right/bottom edges. */ + JBLOCKROW dummy_buffer[C_MAX_BLOCKS_IN_MCU]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +LOCAL(void) +start_iMCU_row (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + if (pass_mode != JBUF_CRANK_DEST) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + coef->iMCU_row_num = 0; + start_iMCU_row(cinfo); +} + + +/* + * Process some data. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF(boolean) +compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, blockcnt; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yindex+yoffset < compptr->last_row_height) { + /* Fill in pointers to real blocks in this row */ + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < blockcnt; xindex++) + MCU_buffer[blkn++] = buffer_ptr++; + } else { + /* At bottom of image, need a whole row of dummy blocks */ + xindex = 0; + } + /* Fill in any dummy blocks needed in this row. + * Dummy blocks are filled in the same way as in jccoefct.c: + * all zeroes in the AC entries, DC entries equal to previous + * block's DC value. The init routine has already zeroed the + * AC entries, so we need only set the DC entries correctly. + */ + for (; xindex < compptr->MCU_width; xindex++) { + MCU_buffer[blkn] = coef->dummy_buffer[blkn]; + MCU_buffer[blkn][0][0] = MCU_buffer[blkn-1][0][0]; + blkn++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + + +/* + * Initialize coefficient buffer controller. + * + * Each passed coefficient array must be the right size for that + * coefficient: width_in_blocks wide and height_in_blocks high, + * with unitheight at least v_samp_factor. + */ + +LOCAL(void) +transencode_coef_controller (j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays) +{ + my_coef_ptr coef; + JBLOCKROW buffer; + int i; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + coef->pub.compress_data = compress_output; + + /* Save pointer to virtual arrays */ + coef->whole_image = coef_arrays; + + /* Allocate and pre-zero space for dummy DCT blocks. */ + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + jzero_far((void FAR *) buffer, C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->dummy_buffer[i] = buffer + i; + } +} diff --git a/src/jpeg-8c/jdapimin.c b/src/jpeg-8c/jdapimin.c new file mode 100644 index 0000000..7f1ce4c --- /dev/null +++ b/src/jpeg-8c/jdapimin.c @@ -0,0 +1,396 @@ +/* + * jdapimin.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * Modified 2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-decompression case or the + * transcoding-only case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + * shared by compression and decompression, and jdtrans.c for the transcoding + * case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG decompression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL(void) +jpeg_CreateDecompress (j_decompress_ptr cinfo, int version, size_t structsize) +{ + int i; + + /* Guard against version mismatches between library and caller. */ + cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */ + if (version != JPEG_LIB_VERSION) + ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); + if (structsize != SIZEOF(struct jpeg_decompress_struct)) + ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, + (int) SIZEOF(struct jpeg_decompress_struct), (int) structsize); + + /* For debugging purposes, we zero the whole master structure. + * But the application has already set the err pointer, and may have set + * client_data, so we have to save and restore those fields. + * Note: if application hasn't set client_data, tools like Purify may + * complain here. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + void * client_data = cinfo->client_data; /* ignore Purify complaint here */ + MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct)); + cinfo->err = err; + cinfo->client_data = client_data; + } + cinfo->is_decompressor = TRUE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->src = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Initialize marker processor so application can override methods + * for COM, APPn markers before calling jpeg_read_header. + */ + cinfo->marker_list = NULL; + jinit_marker_reader(cinfo); + + /* And initialize the overall input controller. */ + jinit_input_controller(cinfo); + + /* OK, I'm ready */ + cinfo->global_state = DSTATE_START; +} + + +/* + * Destruction of a JPEG decompression object + */ + +GLOBAL(void) +jpeg_destroy_decompress (j_decompress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG decompression operation, + * but don't destroy the object itself. + */ + +GLOBAL(void) +jpeg_abort_decompress (j_decompress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Set default decompression parameters. + */ + +LOCAL(void) +default_decompress_parms (j_decompress_ptr cinfo) +{ + /* Guess the input colorspace, and set output colorspace accordingly. */ + /* (Wish JPEG committee had provided a real way to specify this...) */ + /* Note application may override our guesses. */ + switch (cinfo->num_components) { + case 1: + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + break; + + case 3: + if (cinfo->saw_JFIF_marker) { + cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ + } else if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_RGB; + break; + case 1: + cinfo->jpeg_color_space = JCS_YCbCr; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + break; + } + } else { + /* Saw no special markers, try to guess from the component IDs */ + int cid0 = cinfo->comp_info[0].component_id; + int cid1 = cinfo->comp_info[1].component_id; + int cid2 = cinfo->comp_info[2].component_id; + + if (cid0 == 1 && cid1 == 2 && cid2 == 3) + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + else if (cid0 == 82 && cid1 == 71 && cid2 == 66) + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + else { + TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + } + } + /* Always guess RGB is proper output colorspace. */ + cinfo->out_color_space = JCS_RGB; + break; + + case 4: + if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_CMYK; + break; + case 2: + cinfo->jpeg_color_space = JCS_YCCK; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + break; + } + } else { + /* No special markers, assume straight CMYK. */ + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + break; + + default: + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + break; + } + + /* Set defaults for other decompression parameters. */ + cinfo->scale_num = cinfo->block_size; /* 1:1 scaling */ + cinfo->scale_denom = cinfo->block_size; + cinfo->output_gamma = 1.0; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + /* We set these in case application only sets quantize_colors. */ + cinfo->dither_mode = JDITHER_FS; +#ifdef QUANT_2PASS_SUPPORTED + cinfo->two_pass_quantize = TRUE; +#else + cinfo->two_pass_quantize = FALSE; +#endif + cinfo->desired_number_of_colors = 256; + cinfo->colormap = NULL; + /* Initialize for no mode change in buffered-image mode. */ + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; +} + + +/* + * Decompression startup: read start of JPEG datastream to see what's there. + * Need only initialize JPEG object and supply a data source before calling. + * + * This routine will read as far as the first SOS marker (ie, actual start of + * compressed data), and will save all tables and parameters in the JPEG + * object. It will also initialize the decompression parameters to default + * values, and finally return JPEG_HEADER_OK. On return, the application may + * adjust the decompression parameters and then call jpeg_start_decompress. + * (Or, if the application only wanted to determine the image parameters, + * the data need not be decompressed. In that case, call jpeg_abort or + * jpeg_destroy to release any temporary space.) + * If an abbreviated (tables only) datastream is presented, the routine will + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + * re-use the JPEG object to read the abbreviated image datastream(s). + * It is unnecessary (but OK) to call jpeg_abort in this case. + * The JPEG_SUSPENDED return code only occurs if the data source module + * requests suspension of the decompressor. In this case the application + * should load more source data and then re-call jpeg_read_header to resume + * processing. + * If a non-suspending data source is used and require_image is TRUE, then the + * return code need not be inspected since only JPEG_HEADER_OK is possible. + * + * This routine is now just a front end to jpeg_consume_input, with some + * extra error checking. + */ + +GLOBAL(int) +jpeg_read_header (j_decompress_ptr cinfo, boolean require_image) +{ + int retcode; + + if (cinfo->global_state != DSTATE_START && + cinfo->global_state != DSTATE_INHEADER) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + retcode = jpeg_consume_input(cinfo); + + switch (retcode) { + case JPEG_REACHED_SOS: + retcode = JPEG_HEADER_OK; + break; + case JPEG_REACHED_EOI: + if (require_image) /* Complain if application wanted an image */ + ERREXIT(cinfo, JERR_NO_IMAGE); + /* Reset to start state; it would be safer to require the application to + * call jpeg_abort, but we can't change it now for compatibility reasons. + * A side effect is to free any temporary memory (there shouldn't be any). + */ + jpeg_abort((j_common_ptr) cinfo); /* sets state = DSTATE_START */ + retcode = JPEG_HEADER_TABLES_ONLY; + break; + case JPEG_SUSPENDED: + /* no work */ + break; + } + + return retcode; +} + + +/* + * Consume data in advance of what the decompressor requires. + * This can be called at any time once the decompressor object has + * been created and a data source has been set up. + * + * This routine is essentially a state machine that handles a couple + * of critical state-transition actions, namely initial setup and + * transition from header scanning to ready-for-start_decompress. + * All the actual input is done via the input controller's consume_input + * method. + */ + +GLOBAL(int) +jpeg_consume_input (j_decompress_ptr cinfo) +{ + int retcode = JPEG_SUSPENDED; + + /* NB: every possible DSTATE value should be listed in this switch */ + switch (cinfo->global_state) { + case DSTATE_START: + /* Start-of-datastream actions: reset appropriate modules */ + (*cinfo->inputctl->reset_input_controller) (cinfo); + /* Initialize application's data source module */ + (*cinfo->src->init_source) (cinfo); + cinfo->global_state = DSTATE_INHEADER; + /*FALLTHROUGH*/ + case DSTATE_INHEADER: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */ + /* Set up default parameters based on header data */ + default_decompress_parms(cinfo); + /* Set global state: ready for start_decompress */ + cinfo->global_state = DSTATE_READY; + } + break; + case DSTATE_READY: + /* Can't advance past first SOS until start_decompress is called */ + retcode = JPEG_REACHED_SOS; + break; + case DSTATE_PRELOAD: + case DSTATE_PRESCAN: + case DSTATE_SCANNING: + case DSTATE_RAW_OK: + case DSTATE_BUFIMAGE: + case DSTATE_BUFPOST: + case DSTATE_STOPPING: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + break; + default: + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + return retcode; +} + + +/* + * Have we finished reading the input file? + */ + +GLOBAL(boolean) +jpeg_input_complete (j_decompress_ptr cinfo) +{ + /* Check for valid jpeg object */ + if (cinfo->global_state < DSTATE_START || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->eoi_reached; +} + + +/* + * Is there more than one scan? + */ + +GLOBAL(boolean) +jpeg_has_multiple_scans (j_decompress_ptr cinfo) +{ + /* Only valid after jpeg_read_header completes */ + if (cinfo->global_state < DSTATE_READY || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->has_multiple_scans; +} + + +/* + * Finish JPEG decompression. + * + * This will normally just verify the file trailer and release temp storage. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_finish_decompress (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && ! cinfo->buffered_image) { + /* Terminate final pass of non-buffered mode */ + if (cinfo->output_scanline < cinfo->output_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state == DSTATE_BUFIMAGE) { + /* Finishing after a buffered-image operation */ + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state != DSTATE_STOPPING) { + /* STOPPING = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read until EOI */ + while (! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + /* Do final cleanup */ + (*cinfo->src->term_source) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); + return TRUE; +} diff --git a/src/jpeg-8c/jdapistd.c b/src/jpeg-8c/jdapistd.c new file mode 100644 index 0000000..9d74537 --- /dev/null +++ b/src/jpeg-8c/jdapistd.c @@ -0,0 +1,275 @@ +/* + * jdapistd.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-decompression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_decompress, it will end up linking in the entire decompressor. + * We thus must separate this file from jdapimin.c to avoid linking the + * whole decompression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(boolean) output_pass_setup JPP((j_decompress_ptr cinfo)); + + +/* + * Decompression initialization. + * jpeg_read_header must be completed before calling this. + * + * If a multipass operating mode was selected, this will do all but the + * last pass, and thus may take a great deal of time. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_start_decompress (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize master control, select active modules */ + jinit_master_decompress(cinfo); + if (cinfo->buffered_image) { + /* No more work here; expecting jpeg_start_output next */ + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; + } + cinfo->global_state = DSTATE_PRELOAD; + } + if (cinfo->global_state == DSTATE_PRELOAD) { + /* If file has multiple scans, absorb them all into the coef buffer */ + if (cinfo->inputctl->has_multiple_scans) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return FALSE; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + } + cinfo->output_scan_number = cinfo->input_scan_number; + } else if (cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any dummy output passes, and set up for the final pass */ + return output_pass_setup(cinfo); +} + + +/* + * Set up for an output pass, and perform any dummy pass(es) needed. + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + * Exit: If done, returns TRUE and sets global_state for proper output mode. + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + */ + +LOCAL(boolean) +output_pass_setup (j_decompress_ptr cinfo) +{ + if (cinfo->global_state != DSTATE_PRESCAN) { + /* First call: do pass setup */ + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; + cinfo->global_state = DSTATE_PRESCAN; + } + /* Loop over any required dummy passes */ + while (cinfo->master->is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Crank through the dummy pass */ + while (cinfo->output_scanline < cinfo->output_height) { + JDIMENSION last_scanline; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* Process some data */ + last_scanline = cinfo->output_scanline; + (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0); + if (cinfo->output_scanline == last_scanline) + return FALSE; /* No progress made, must suspend */ + } + /* Finish up dummy pass, and set up for another one */ + (*cinfo->master->finish_output_pass) (cinfo); + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } + /* Ready for application to drive output pass through + * jpeg_read_scanlines or jpeg_read_raw_data. + */ + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + return TRUE; +} + + +/* + * Read some scanlines of data from the JPEG decompressor. + * + * The return value will be the number of lines actually read. + * This may be less than the number requested in several cases, + * including bottom of image, data source suspension, and operating + * modes that emit multiple scanlines at a time. + * + * Note: we warn about excess calls to jpeg_read_scanlines() since + * this likely signals an application programmer error. However, + * an oversize buffer (max_lines > scanlines remaining) is not an error. + */ + +GLOBAL(JDIMENSION) +jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines) +{ + JDIMENSION row_ctr; + + if (cinfo->global_state != DSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Process some data */ + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines); + cinfo->output_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to read raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL(JDIMENSION) +jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != DSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Verify that at least one iMCU row can be returned. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_v_scaled_size; + if (max_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Decompress directly into user's buffer. */ + if (! (*cinfo->coef->decompress_data) (cinfo, data)) + return 0; /* suspension forced, can do nothing more */ + + /* OK, we processed one iMCU row. */ + cinfo->output_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} + + +/* Additional entry points for buffered-image mode. */ + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Initialize for an output pass in buffered-image mode. + */ + +GLOBAL(boolean) +jpeg_start_output (j_decompress_ptr cinfo, int scan_number) +{ + if (cinfo->global_state != DSTATE_BUFIMAGE && + cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Limit scan number to valid range */ + if (scan_number <= 0) + scan_number = 1; + if (cinfo->inputctl->eoi_reached && + scan_number > cinfo->input_scan_number) + scan_number = cinfo->input_scan_number; + cinfo->output_scan_number = scan_number; + /* Perform any dummy output passes, and set up for the real pass */ + return output_pass_setup(cinfo); +} + + +/* + * Finish up after an output pass in buffered-image mode. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_finish_output (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && cinfo->buffered_image) { + /* Terminate this pass. */ + /* We do not require the whole pass to have been completed. */ + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_BUFPOST; + } else if (cinfo->global_state != DSTATE_BUFPOST) { + /* BUFPOST = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read markers looking for SOS or EOI */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ diff --git a/src/jpeg-8c/jdarith.c b/src/jpeg-8c/jdarith.c new file mode 100644 index 0000000..c858b24 --- /dev/null +++ b/src/jpeg-8c/jdarith.c @@ -0,0 +1,772 @@ +/* + * jdarith.c + * + * Developed 1997-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains portable arithmetic entropy decoding routines for JPEG + * (implementing the ISO/IEC IS 10918-1 and CCITT Recommendation ITU-T T.81). + * + * Both sequential and progressive modes are supported in this single module. + * + * Suspension is not currently supported in this module. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Expanded entropy decoder object for arithmetic decoding. */ + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + INT32 c; /* C register, base of coding interval + input bit buffer */ + INT32 a; /* A register, normalized size of coding interval */ + int ct; /* bit shift counter, # of bits left in bit buffer part of C */ + /* init: ct = -16 */ + /* run: ct = 0..7 */ + /* error: ct = -1 */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ + int dc_context[MAX_COMPS_IN_SCAN]; /* context index for DC conditioning */ + + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to statistics areas (these workspaces have image lifespan) */ + unsigned char * dc_stats[NUM_ARITH_TBLS]; + unsigned char * ac_stats[NUM_ARITH_TBLS]; + + /* Statistics bin for coding with fixed probability 0.5 */ + unsigned char fixed_bin[4]; +} arith_entropy_decoder; + +typedef arith_entropy_decoder * arith_entropy_ptr; + +/* The following two definitions specify the allocation chunk size + * for the statistics area. + * According to sections F.1.4.4.1.3 and F.1.4.4.2, we need at least + * 49 statistics bins for DC, and 245 statistics bins for AC coding. + * + * We use a compact representation with 1 byte per statistics bin, + * thus the numbers directly represent byte sizes. + * This 1 byte per statistics bin contains the meaning of the MPS + * (more probable symbol) in the highest bit (mask 0x80), and the + * index into the probability estimation state machine table + * in the lower bits (mask 0x7F). + */ + +#define DC_STAT_BINS 64 +#define AC_STAT_BINS 256 + + +LOCAL(int) +get_byte (j_decompress_ptr cinfo) +/* Read next input byte; we do not support suspension in this module. */ +{ + struct jpeg_source_mgr * src = cinfo->src; + + if (src->bytes_in_buffer == 0) + if (! (*src->fill_input_buffer) (cinfo)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + src->bytes_in_buffer--; + return GETJOCTET(*src->next_input_byte++); +} + + +/* + * The core arithmetic decoding routine (common in JPEG and JBIG). + * This needs to go as fast as possible. + * Machine-dependent optimization facilities + * are not utilized in this portable implementation. + * However, this code should be fairly efficient and + * may be a good base for further optimizations anyway. + * + * Return value is 0 or 1 (binary decision). + * + * Note: I've changed the handling of the code base & bit + * buffer register C compared to other implementations + * based on the standards layout & procedures. + * While it also contains both the actual base of the + * coding interval (16 bits) and the next-bits buffer, + * the cut-point between these two parts is floating + * (instead of fixed) with the bit shift counter CT. + * Thus, we also need only one (variable instead of + * fixed size) shift for the LPS/MPS decision, and + * we can get away with any renormalization update + * of C (except for new data insertion, of course). + * + * I've also introduced a new scheme for accessing + * the probability estimation state machine table, + * derived from Markus Kuhn's JBIG implementation. + */ + +LOCAL(int) +arith_decode (j_decompress_ptr cinfo, unsigned char *st) +{ + register arith_entropy_ptr e = (arith_entropy_ptr) cinfo->entropy; + register unsigned char nl, nm; + register INT32 qe, temp; + register int sv, data; + + /* Renormalization & data input per section D.2.6 */ + while (e->a < 0x8000L) { + if (--e->ct < 0) { + /* Need to fetch next data byte */ + if (cinfo->unread_marker) + data = 0; /* stuff zero data */ + else { + data = get_byte(cinfo); /* read next input byte */ + if (data == 0xFF) { /* zero stuff or marker code */ + do data = get_byte(cinfo); + while (data == 0xFF); /* swallow extra 0xFF bytes */ + if (data == 0) + data = 0xFF; /* discard stuffed zero byte */ + else { + /* Note: Different from the Huffman decoder, hitting + * a marker while processing the compressed data + * segment is legal in arithmetic coding. + * The convention is to supply zero data + * then until decoding is complete. + */ + cinfo->unread_marker = data; + data = 0; + } + } + } + e->c = (e->c << 8) | data; /* insert data into C register */ + if ((e->ct += 8) < 0) /* update bit shift counter */ + /* Need more initial bytes */ + if (++e->ct == 0) + /* Got 2 initial bytes -> re-init A and exit loop */ + e->a = 0x8000L; /* => e->a = 0x10000L after loop exit */ + } + e->a <<= 1; + } + + /* Fetch values from our compact representation of Table D.2: + * Qe values and probability estimation state machine + */ + sv = *st; + qe = jpeg_aritab[sv & 0x7F]; /* => Qe_Value */ + nl = qe & 0xFF; qe >>= 8; /* Next_Index_LPS + Switch_MPS */ + nm = qe & 0xFF; qe >>= 8; /* Next_Index_MPS */ + + /* Decode & estimation procedures per sections D.2.4 & D.2.5 */ + temp = e->a - qe; + e->a = temp; + temp <<= e->ct; + if (e->c >= temp) { + e->c -= temp; + /* Conditional LPS (less probable symbol) exchange */ + if (e->a < qe) { + e->a = qe; + *st = (sv & 0x80) ^ nm; /* Estimate_after_MPS */ + } else { + e->a = qe; + *st = (sv & 0x80) ^ nl; /* Estimate_after_LPS */ + sv ^= 0x80; /* Exchange LPS/MPS */ + } + } else if (e->a < 0x8000L) { + /* Conditional MPS (more probable symbol) exchange */ + if (e->a < qe) { + *st = (sv & 0x80) ^ nl; /* Estimate_after_LPS */ + sv ^= 0x80; /* Exchange LPS/MPS */ + } else { + *st = (sv & 0x80) ^ nm; /* Estimate_after_MPS */ + } + } + + return sv >> 7; +} + + +/* + * Check for a restart marker & resynchronize decoder. + */ + +LOCAL(void) +process_restart (j_decompress_ptr cinfo) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + int ci; + jpeg_component_info * compptr; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + + /* Re-initialize statistics areas */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + if (! cinfo->progressive_mode || (cinfo->Ss == 0 && cinfo->Ah == 0)) { + MEMZERO(entropy->dc_stats[compptr->dc_tbl_no], DC_STAT_BINS); + /* Reset DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + entropy->dc_context[ci] = 0; + } + if ((! cinfo->progressive_mode && cinfo->lim_Se) || + (cinfo->progressive_mode && cinfo->Ss)) { + MEMZERO(entropy->ac_stats[compptr->ac_tbl_no], AC_STAT_BINS); + } + } + + /* Reset arithmetic decoding variables */ + entropy->c = 0; + entropy->a = 0; + entropy->ct = -16; /* force reading 2 initial bytes to fill C */ + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Arithmetic MCU decoding. + * Each of these routines decodes and returns one MCU's worth of + * arithmetic-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA IS INITIALLY ZEROED BY THE CALLER. + */ + +/* + * MCU decoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + JBLOCKROW block; + unsigned char *st; + int blkn, ci, tbl, sign; + int v, m; + + /* Process restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + process_restart(cinfo); + entropy->restarts_to_go--; + } + + if (entropy->ct == -1) return TRUE; /* if error do nothing */ + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + tbl = cinfo->cur_comp_info[ci]->dc_tbl_no; + + /* Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients */ + + /* Table F.4: Point to statistics bin S0 for DC coefficient coding */ + st = entropy->dc_stats[tbl] + entropy->dc_context[ci]; + + /* Figure F.19: Decode_DC_DIFF */ + if (arith_decode(cinfo, st) == 0) + entropy->dc_context[ci] = 0; + else { + /* Figure F.21: Decoding nonzero value v */ + /* Figure F.22: Decoding the sign of v */ + sign = arith_decode(cinfo, st + 1); + st += 2; st += sign; + /* Figure F.23: Decoding the magnitude category of v */ + if ((m = arith_decode(cinfo, st)) != 0) { + st = entropy->dc_stats[tbl] + 20; /* Table F.4: X1 = 20 */ + while (arith_decode(cinfo, st)) { + if ((m <<= 1) == 0x8000) { + WARNMS(cinfo, JWRN_ARITH_BAD_CODE); + entropy->ct = -1; /* magnitude overflow */ + return TRUE; + } + st += 1; + } + } + /* Section F.1.4.4.1.2: Establish dc_context conditioning category */ + if (m < (int) ((1L << cinfo->arith_dc_L[tbl]) >> 1)) + entropy->dc_context[ci] = 0; /* zero diff category */ + else if (m > (int) ((1L << cinfo->arith_dc_U[tbl]) >> 1)) + entropy->dc_context[ci] = 12 + (sign * 4); /* large diff category */ + else + entropy->dc_context[ci] = 4 + (sign * 4); /* small diff category */ + v = m; + /* Figure F.24: Decoding the magnitude bit pattern of v */ + st += 14; + while (m >>= 1) + if (arith_decode(cinfo, st)) v |= m; + v += 1; if (sign) v = -v; + entropy->last_dc_val[ci] += v; + } + + /* Scale and output the DC coefficient (assumes jpeg_natural_order[0]=0) */ + (*block)[0] = (JCOEF) (entropy->last_dc_val[ci] << cinfo->Al); + } + + return TRUE; +} + + +/* + * MCU decoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + JBLOCKROW block; + unsigned char *st; + int tbl, sign, k; + int v, m; + const int * natural_order; + + /* Process restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + process_restart(cinfo); + entropy->restarts_to_go--; + } + + if (entropy->ct == -1) return TRUE; /* if error do nothing */ + + natural_order = cinfo->natural_order; + + /* There is always only one block per MCU */ + block = MCU_data[0]; + tbl = cinfo->cur_comp_info[0]->ac_tbl_no; + + /* Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients */ + + /* Figure F.20: Decode_AC_coefficients */ + for (k = cinfo->Ss; k <= cinfo->Se; k++) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + if (arith_decode(cinfo, st)) break; /* EOB flag */ + while (arith_decode(cinfo, st + 1) == 0) { + st += 3; k++; + if (k > cinfo->Se) { + WARNMS(cinfo, JWRN_ARITH_BAD_CODE); + entropy->ct = -1; /* spectral overflow */ + return TRUE; + } + } + /* Figure F.21: Decoding nonzero value v */ + /* Figure F.22: Decoding the sign of v */ + sign = arith_decode(cinfo, entropy->fixed_bin); + st += 2; + /* Figure F.23: Decoding the magnitude category of v */ + if ((m = arith_decode(cinfo, st)) != 0) { + if (arith_decode(cinfo, st)) { + m <<= 1; + st = entropy->ac_stats[tbl] + + (k <= cinfo->arith_ac_K[tbl] ? 189 : 217); + while (arith_decode(cinfo, st)) { + if ((m <<= 1) == 0x8000) { + WARNMS(cinfo, JWRN_ARITH_BAD_CODE); + entropy->ct = -1; /* magnitude overflow */ + return TRUE; + } + st += 1; + } + } + } + v = m; + /* Figure F.24: Decoding the magnitude bit pattern of v */ + st += 14; + while (m >>= 1) + if (arith_decode(cinfo, st)) v |= m; + v += 1; if (sign) v = -v; + /* Scale and output coefficient in natural (dezigzagged) order */ + (*block)[natural_order[k]] = (JCOEF) (v << cinfo->Al); + } + + return TRUE; +} + + +/* + * MCU decoding for DC successive approximation refinement scan. + */ + +METHODDEF(boolean) +decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + unsigned char *st; + int p1, blkn; + + /* Process restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + process_restart(cinfo); + entropy->restarts_to_go--; + } + + st = entropy->fixed_bin; /* use fixed probability estimation */ + p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + /* Encoded data is simply the next bit of the two's-complement DC value */ + if (arith_decode(cinfo, st)) + MCU_data[blkn][0][0] |= p1; + } + + return TRUE; +} + + +/* + * MCU decoding for AC successive approximation refinement scan. + */ + +METHODDEF(boolean) +decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + JBLOCKROW block; + JCOEFPTR thiscoef; + unsigned char *st; + int tbl, k, kex; + int p1, m1; + const int * natural_order; + + /* Process restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + process_restart(cinfo); + entropy->restarts_to_go--; + } + + if (entropy->ct == -1) return TRUE; /* if error do nothing */ + + natural_order = cinfo->natural_order; + + /* There is always only one block per MCU */ + block = MCU_data[0]; + tbl = cinfo->cur_comp_info[0]->ac_tbl_no; + + p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + m1 = (-1) << cinfo->Al; /* -1 in the bit position being coded */ + + /* Establish EOBx (previous stage end-of-block) index */ + for (kex = cinfo->Se; kex > 0; kex--) + if ((*block)[natural_order[kex]]) break; + + for (k = cinfo->Ss; k <= cinfo->Se; k++) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + if (k > kex) + if (arith_decode(cinfo, st)) break; /* EOB flag */ + for (;;) { + thiscoef = *block + natural_order[k]; + if (*thiscoef) { /* previously nonzero coef */ + if (arith_decode(cinfo, st + 2)) { + if (*thiscoef < 0) + *thiscoef += m1; + else + *thiscoef += p1; + } + break; + } + if (arith_decode(cinfo, st + 1)) { /* newly nonzero coef */ + if (arith_decode(cinfo, entropy->fixed_bin)) + *thiscoef = m1; + else + *thiscoef = p1; + break; + } + st += 3; k++; + if (k > cinfo->Se) { + WARNMS(cinfo, JWRN_ARITH_BAD_CODE); + entropy->ct = -1; /* spectral overflow */ + return TRUE; + } + } + } + + return TRUE; +} + + +/* + * Decode one MCU's worth of arithmetic-compressed coefficients. + */ + +METHODDEF(boolean) +decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + jpeg_component_info * compptr; + JBLOCKROW block; + unsigned char *st; + int blkn, ci, tbl, sign, k; + int v, m; + const int * natural_order; + + /* Process restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + process_restart(cinfo); + entropy->restarts_to_go--; + } + + if (entropy->ct == -1) return TRUE; /* if error do nothing */ + + natural_order = cinfo->natural_order; + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients */ + + tbl = compptr->dc_tbl_no; + + /* Table F.4: Point to statistics bin S0 for DC coefficient coding */ + st = entropy->dc_stats[tbl] + entropy->dc_context[ci]; + + /* Figure F.19: Decode_DC_DIFF */ + if (arith_decode(cinfo, st) == 0) + entropy->dc_context[ci] = 0; + else { + /* Figure F.21: Decoding nonzero value v */ + /* Figure F.22: Decoding the sign of v */ + sign = arith_decode(cinfo, st + 1); + st += 2; st += sign; + /* Figure F.23: Decoding the magnitude category of v */ + if ((m = arith_decode(cinfo, st)) != 0) { + st = entropy->dc_stats[tbl] + 20; /* Table F.4: X1 = 20 */ + while (arith_decode(cinfo, st)) { + if ((m <<= 1) == 0x8000) { + WARNMS(cinfo, JWRN_ARITH_BAD_CODE); + entropy->ct = -1; /* magnitude overflow */ + return TRUE; + } + st += 1; + } + } + /* Section F.1.4.4.1.2: Establish dc_context conditioning category */ + if (m < (int) ((1L << cinfo->arith_dc_L[tbl]) >> 1)) + entropy->dc_context[ci] = 0; /* zero diff category */ + else if (m > (int) ((1L << cinfo->arith_dc_U[tbl]) >> 1)) + entropy->dc_context[ci] = 12 + (sign * 4); /* large diff category */ + else + entropy->dc_context[ci] = 4 + (sign * 4); /* small diff category */ + v = m; + /* Figure F.24: Decoding the magnitude bit pattern of v */ + st += 14; + while (m >>= 1) + if (arith_decode(cinfo, st)) v |= m; + v += 1; if (sign) v = -v; + entropy->last_dc_val[ci] += v; + } + + (*block)[0] = (JCOEF) entropy->last_dc_val[ci]; + + /* Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients */ + + tbl = compptr->ac_tbl_no; + + /* Figure F.20: Decode_AC_coefficients */ + for (k = 1; k <= cinfo->lim_Se; k++) { + st = entropy->ac_stats[tbl] + 3 * (k - 1); + if (arith_decode(cinfo, st)) break; /* EOB flag */ + while (arith_decode(cinfo, st + 1) == 0) { + st += 3; k++; + if (k > cinfo->lim_Se) { + WARNMS(cinfo, JWRN_ARITH_BAD_CODE); + entropy->ct = -1; /* spectral overflow */ + return TRUE; + } + } + /* Figure F.21: Decoding nonzero value v */ + /* Figure F.22: Decoding the sign of v */ + sign = arith_decode(cinfo, entropy->fixed_bin); + st += 2; + /* Figure F.23: Decoding the magnitude category of v */ + if ((m = arith_decode(cinfo, st)) != 0) { + if (arith_decode(cinfo, st)) { + m <<= 1; + st = entropy->ac_stats[tbl] + + (k <= cinfo->arith_ac_K[tbl] ? 189 : 217); + while (arith_decode(cinfo, st)) { + if ((m <<= 1) == 0x8000) { + WARNMS(cinfo, JWRN_ARITH_BAD_CODE); + entropy->ct = -1; /* magnitude overflow */ + return TRUE; + } + st += 1; + } + } + } + v = m; + /* Figure F.24: Decoding the magnitude bit pattern of v */ + st += 14; + while (m >>= 1) + if (arith_decode(cinfo, st)) v |= m; + v += 1; if (sign) v = -v; + (*block)[natural_order[k]] = (JCOEF) v; + } + } + + return TRUE; +} + + +/* + * Initialize for an arithmetic-compressed scan. + */ + +METHODDEF(void) +start_pass (j_decompress_ptr cinfo) +{ + arith_entropy_ptr entropy = (arith_entropy_ptr) cinfo->entropy; + int ci, tbl; + jpeg_component_info * compptr; + + if (cinfo->progressive_mode) { + /* Validate progressive scan parameters */ + if (cinfo->Ss == 0) { + if (cinfo->Se != 0) + goto bad; + } else { + /* need not check Ss/Se < 0 since they came from unsigned bytes */ + if (cinfo->Se < cinfo->Ss || cinfo->Se > cinfo->lim_Se) + goto bad; + /* AC scans may have only one component */ + if (cinfo->comps_in_scan != 1) + goto bad; + } + if (cinfo->Ah != 0) { + /* Successive approximation refinement scan: must have Al = Ah-1. */ + if (cinfo->Ah-1 != cinfo->Al) + goto bad; + } + if (cinfo->Al > 13) { /* need not check for < 0 */ + bad: + ERREXIT4(cinfo, JERR_BAD_PROGRESSION, + cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al); + } + /* Update progression status, and verify that scan order is legal. + * Note that inter-scan inconsistencies are treated as warnings + * not fatal errors ... not clear if this is right way to behave. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + int coefi, cindex = cinfo->cur_comp_info[ci]->component_index; + int *coef_bit_ptr = & cinfo->coef_bits[cindex][0]; + if (cinfo->Ss && coef_bit_ptr[0] < 0) /* AC without prior DC scan */ + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, 0); + for (coefi = cinfo->Ss; coefi <= cinfo->Se; coefi++) { + int expected = (coef_bit_ptr[coefi] < 0) ? 0 : coef_bit_ptr[coefi]; + if (cinfo->Ah != expected) + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi); + coef_bit_ptr[coefi] = cinfo->Al; + } + } + /* Select MCU decoding routine */ + if (cinfo->Ah == 0) { + if (cinfo->Ss == 0) + entropy->pub.decode_mcu = decode_mcu_DC_first; + else + entropy->pub.decode_mcu = decode_mcu_AC_first; + } else { + if (cinfo->Ss == 0) + entropy->pub.decode_mcu = decode_mcu_DC_refine; + else + entropy->pub.decode_mcu = decode_mcu_AC_refine; + } + } else { + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning. + */ + if (cinfo->Ss != 0 || cinfo->Ah != 0 || cinfo->Al != 0 || + (cinfo->Se < DCTSIZE2 && cinfo->Se != cinfo->lim_Se)) + WARNMS(cinfo, JWRN_NOT_SEQUENTIAL); + /* Select MCU decoding routine */ + entropy->pub.decode_mcu = decode_mcu; + } + + /* Allocate & initialize requested statistics areas */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + if (! cinfo->progressive_mode || (cinfo->Ss == 0 && cinfo->Ah == 0)) { + tbl = compptr->dc_tbl_no; + if (tbl < 0 || tbl >= NUM_ARITH_TBLS) + ERREXIT1(cinfo, JERR_NO_ARITH_TABLE, tbl); + if (entropy->dc_stats[tbl] == NULL) + entropy->dc_stats[tbl] = (unsigned char *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, DC_STAT_BINS); + MEMZERO(entropy->dc_stats[tbl], DC_STAT_BINS); + /* Initialize DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + entropy->dc_context[ci] = 0; + } + if ((! cinfo->progressive_mode && cinfo->lim_Se) || + (cinfo->progressive_mode && cinfo->Ss)) { + tbl = compptr->ac_tbl_no; + if (tbl < 0 || tbl >= NUM_ARITH_TBLS) + ERREXIT1(cinfo, JERR_NO_ARITH_TABLE, tbl); + if (entropy->ac_stats[tbl] == NULL) + entropy->ac_stats[tbl] = (unsigned char *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, AC_STAT_BINS); + MEMZERO(entropy->ac_stats[tbl], AC_STAT_BINS); + } + } + + /* Initialize arithmetic decoding variables */ + entropy->c = 0; + entropy->a = 0; + entropy->ct = -16; /* force reading 2 initial bytes to fill C */ + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Module initialization routine for arithmetic entropy decoding. + */ + +GLOBAL(void) +jinit_arith_decoder (j_decompress_ptr cinfo) +{ + arith_entropy_ptr entropy; + int i; + + entropy = (arith_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(arith_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_ARITH_TBLS; i++) { + entropy->dc_stats[i] = NULL; + entropy->ac_stats[i] = NULL; + } + + /* Initialize index for fixed probability estimation */ + entropy->fixed_bin[0] = 113; + + if (cinfo->progressive_mode) { + /* Create progression status table */ + int *coef_bit_ptr, ci; + cinfo->coef_bits = (int (*)[DCTSIZE2]) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components*DCTSIZE2*SIZEOF(int)); + coef_bit_ptr = & cinfo->coef_bits[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (i = 0; i < DCTSIZE2; i++) + *coef_bit_ptr++ = -1; + } +} diff --git a/src/jpeg-8c/jdatadst.c b/src/jpeg-8c/jdatadst.c new file mode 100644 index 0000000..472d5f3 --- /dev/null +++ b/src/jpeg-8c/jdatadst.c @@ -0,0 +1,267 @@ +/* + * jdatadst.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * Modified 2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains compression data destination routines for the case of + * emitting JPEG data to memory or to a file (or any stdio stream). + * While these routines are sufficient for most applications, + * some will want to use a different destination manager. + * IMPORTANT: we assume that fwrite() will correctly transcribe an array of + * JOCTETs into 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + +#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ +extern void * malloc JPP((size_t size)); +extern void free JPP((void *ptr)); +#endif + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + FILE * outfile; /* target stream */ + JOCTET * buffer; /* start of buffer */ +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ + + +/* Expanded data destination object for memory output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + unsigned char ** outbuffer; /* target buffer */ + unsigned long * outsize; + unsigned char * newbuffer; /* newly allocated buffer */ + JOCTET * buffer; /* start of buffer */ + size_t bufsize; +} my_mem_destination_mgr; + +typedef my_mem_destination_mgr * my_mem_dest_ptr; + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +METHODDEF(void) +init_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + /* Allocate the output buffer --- it will be released when done with image */ + dest->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + OUTPUT_BUF_SIZE * SIZEOF(JOCTET)); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; +} + +METHODDEF(void) +init_mem_destination (j_compress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +METHODDEF(boolean) +empty_output_buffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) != + (size_t) OUTPUT_BUF_SIZE) + ERREXIT(cinfo, JERR_FILE_WRITE); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; + + return TRUE; +} + +METHODDEF(boolean) +empty_mem_output_buffer (j_compress_ptr cinfo) +{ + size_t nextsize; + JOCTET * nextbuffer; + my_mem_dest_ptr dest = (my_mem_dest_ptr) cinfo->dest; + + /* Try to allocate new buffer with double size */ + nextsize = dest->bufsize * 2; + nextbuffer = malloc(nextsize); + + if (nextbuffer == NULL) + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10); + + MEMCOPY(nextbuffer, dest->buffer, dest->bufsize); + + if (dest->newbuffer != NULL) + free(dest->newbuffer); + + dest->newbuffer = nextbuffer; + + dest->pub.next_output_byte = nextbuffer + dest->bufsize; + dest->pub.free_in_buffer = dest->bufsize; + + dest->buffer = nextbuffer; + dest->bufsize = nextsize; + + return TRUE; +} + + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF(void) +term_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + /* Write any data remaining in the buffer */ + if (datacount > 0) { + if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount) + ERREXIT(cinfo, JERR_FILE_WRITE); + } + fflush(dest->outfile); + /* Make sure we wrote the output file OK */ + if (ferror(dest->outfile)) + ERREXIT(cinfo, JERR_FILE_WRITE); +} + +METHODDEF(void) +term_mem_destination (j_compress_ptr cinfo) +{ + my_mem_dest_ptr dest = (my_mem_dest_ptr) cinfo->dest; + + *dest->outbuffer = dest->buffer; + *dest->outsize = dest->bufsize - dest->pub.free_in_buffer; +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +GLOBAL(void) +jpeg_stdio_dest (j_compress_ptr cinfo, FILE * outfile) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; +} + + +/* + * Prepare for output to a memory buffer. + * The caller may supply an own initial buffer with appropriate size. + * Otherwise, or when the actual data output exceeds the given size, + * the library adapts the buffer size as necessary. + * The standard library functions malloc/free are used for allocating + * larger memory, so the buffer is available to the application after + * finishing compression, and then the application is responsible for + * freeing the requested memory. + */ + +GLOBAL(void) +jpeg_mem_dest (j_compress_ptr cinfo, + unsigned char ** outbuffer, unsigned long * outsize) +{ + my_mem_dest_ptr dest; + + if (outbuffer == NULL || outsize == NULL) /* sanity check */ + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same buffer without re-executing jpeg_mem_dest. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_mem_destination_mgr)); + } + + dest = (my_mem_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_mem_destination; + dest->pub.empty_output_buffer = empty_mem_output_buffer; + dest->pub.term_destination = term_mem_destination; + dest->outbuffer = outbuffer; + dest->outsize = outsize; + dest->newbuffer = NULL; + + if (*outbuffer == NULL || *outsize == 0) { + /* Allocate initial buffer */ + dest->newbuffer = *outbuffer = malloc(OUTPUT_BUF_SIZE); + if (dest->newbuffer == NULL) + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10); + *outsize = OUTPUT_BUF_SIZE; + } + + dest->pub.next_output_byte = dest->buffer = *outbuffer; + dest->pub.free_in_buffer = dest->bufsize = *outsize; +} diff --git a/src/jpeg-8c/jdatasrc.c b/src/jpeg-8c/jdatasrc.c new file mode 100644 index 0000000..c8fe3da --- /dev/null +++ b/src/jpeg-8c/jdatasrc.c @@ -0,0 +1,274 @@ +/* + * jdatasrc.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * Modified 2009-2010 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from memory or from a file (or any stdio stream). + * While these routines are sufficient for most applications, + * some will want to use a different source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + FILE * infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF(void) +init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + +METHODDEF(void) +init_mem_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF(boolean) +fill_input_buffer (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + size_t nbytes; + + nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE); + + if (nbytes <= 0) { + if (src->start_of_file) /* Treat empty input file as fatal error */ + ERREXIT(cinfo, JERR_INPUT_EMPTY); + WARNMS(cinfo, JWRN_JPEG_EOF); + /* Insert a fake EOI marker */ + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + } + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = nbytes; + src->start_of_file = FALSE; + + return TRUE; +} + +METHODDEF(boolean) +fill_mem_input_buffer (j_decompress_ptr cinfo) +{ + static JOCTET mybuffer[4]; + + /* The whole JPEG data is expected to reside in the supplied memory + * buffer, so any request for more data beyond the given buffer size + * is treated as an error. + */ + WARNMS(cinfo, JWRN_JPEG_EOF); + /* Insert a fake EOI marker */ + mybuffer[0] = (JOCTET) 0xFF; + mybuffer[1] = (JOCTET) JPEG_EOI; + + cinfo->src->next_input_byte = mybuffer; + cinfo->src->bytes_in_buffer = 2; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF(void) +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + struct jpeg_source_mgr * src = cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->bytes_in_buffer) { + num_bytes -= (long) src->bytes_in_buffer; + (void) (*src->fill_input_buffer) (cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->next_input_byte += (size_t) num_bytes; + src->bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF(void) +term_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +GLOBAL(void) +jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile) +{ + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF(JOCTET)); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} + + +/* + * Prepare for input from a supplied memory buffer. + * The buffer must contain the whole JPEG data. + */ + +GLOBAL(void) +jpeg_mem_src (j_decompress_ptr cinfo, + unsigned char * inbuffer, unsigned long insize) +{ + struct jpeg_source_mgr * src; + + if (inbuffer == NULL || insize == 0) /* Treat empty input as fatal error */ + ERREXIT(cinfo, JERR_INPUT_EMPTY); + + /* The source object is made permanent so that a series of JPEG images + * can be read from the same buffer by calling jpeg_mem_src only before + * the first one. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(struct jpeg_source_mgr)); + } + + src = cinfo->src; + src->init_source = init_mem_source; + src->fill_input_buffer = fill_mem_input_buffer; + src->skip_input_data = skip_input_data; + src->resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->term_source = term_source; + src->bytes_in_buffer = (size_t) insize; + src->next_input_byte = (JOCTET *) inbuffer; +} diff --git a/src/jpeg-8c/jdcoefct.c b/src/jpeg-8c/jdcoefct.c new file mode 100644 index 0000000..462e92c --- /dev/null +++ b/src/jpeg-8c/jdcoefct.c @@ -0,0 +1,736 @@ +/* + * jdcoefct.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for decompression. + * This controller is the top level of the JPEG decompressor proper. + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + * + * In buffered-image mode, this controller is the interface between + * input-oriented processing and output-oriented processing. + * Also, the input side (only) is used when reading a file for transcoding. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +/* Block smoothing is only applicable for progressive JPEG, so: */ +#ifndef D_PROGRESSIVE_SUPPORTED +#undef BLOCK_SMOOTHING_SUPPORTED +#endif + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_coef_controller pub; /* public fields */ + + /* These variables keep track of the current location of the input side. */ + /* cinfo->input_iMCU_row is also used for this. */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + /* In single-pass modes, it's sufficient to buffer just one MCU. + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + * and let the entropy decoder write into that workspace each time. + * (On 80x86, the workspace is FAR even though it's not really very big; + * this is to keep the module interfaces unchanged when a large coefficient + * buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays; it is used only by the input side. + */ + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* When doing block smoothing, we latch coefficient Al values here */ + int * coef_bits_latch; +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#endif +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + +/* Forward declarations */ +METHODDEF(int) decompress_onepass + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#ifdef D_MULTISCAN_FILES_SUPPORTED +METHODDEF(int) decompress_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif +#ifdef BLOCK_SMOOTHING_SUPPORTED +LOCAL(boolean) smoothing_ok JPP((j_decompress_ptr cinfo)); +METHODDEF(int) decompress_smooth_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif + + +LOCAL(void) +start_iMCU_row (j_decompress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row (input side) */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (cinfo->input_iMCU_row < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->MCU_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for an input processing pass. + */ + +METHODDEF(void) +start_input_pass (j_decompress_ptr cinfo) +{ + cinfo->input_iMCU_row = 0; + start_iMCU_row(cinfo); +} + + +/* + * Initialize for an output processing pass. + */ + +METHODDEF(void) +start_output_pass (j_decompress_ptr cinfo) +{ +#ifdef BLOCK_SMOOTHING_SUPPORTED + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* If multipass, check to see whether to use block smoothing on this pass */ + if (coef->pub.coef_arrays != NULL) { + if (cinfo->do_block_smoothing && smoothing_ok(cinfo)) + coef->pub.decompress_data = decompress_smooth_data; + else + coef->pub.decompress_data = decompress_data; + } +#endif + cinfo->output_iMCU_row = 0; +} + + +/* + * Decompress and return some data in the single-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Input and output must run in lockstep since we have only a one-MCU buffer. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image, + * which we index according to the component's SOF position. + */ + +METHODDEF(int) +decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, useful_width; + JSAMPARRAY output_ptr; + JDIMENSION start_col, output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Loop to process as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + jzero_far((void FAR *) coef->MCU_buffer[0], + (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + /* Determine where data should go in output_buf and do the IDCT thing. + * We skip dummy blocks at the right and bottom edges (but blkn gets + * incremented past them!). Note the inner loop relies on having + * allocated the MCU_buffer[] blocks sequentially. + */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[compptr->component_index] + + yoffset * compptr->DCT_v_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (cinfo->input_iMCU_row < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + output_col = start_col; + for (xindex = 0; xindex < useful_width; xindex++) { + (*inverse_DCT) (cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn+xindex], + output_ptr, output_col); + output_col += compptr->DCT_h_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_v_scaled_size; + } + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + cinfo->output_iMCU_row++; + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Dummy consume-input routine for single-pass operation. + */ + +METHODDEF(int) +dummy_consume_data (j_decompress_ptr cinfo) +{ + return JPEG_SUSPENDED; /* Always indicate nothing was done */ +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Consume input data and store it in the full-image coefficient buffer. + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + * ie, v_samp_factor block rows for each component in the scan. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + */ + +METHODDEF(int) +consume_data (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + cinfo->input_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Note: entropy decoder expects buffer to be zeroed, + * but this is handled automatically by the memory manager + * because we requested a pre-zeroed array. + */ + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to fetch the MCU. */ + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Decompress and return some data in the multi-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + */ + +METHODDEF(int) +decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num; + int ci, block_row, block_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number < cinfo->output_scan_number || + (cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + cinfo->output_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + output_col = 0; + for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) { + (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col); + buffer_ptr++; + output_col += compptr->DCT_h_scaled_size; + } + output_ptr += compptr->DCT_v_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +/* + * This code applies interblock smoothing as described by section K.8 + * of the JPEG standard: the first 5 AC coefficients are estimated from + * the DC values of a DCT block and its 8 neighboring blocks. + * We apply smoothing only for progressive JPEG decoding, and only if + * the coefficients it can estimate are not yet known to full precision. + */ + +/* Natural-order array positions of the first 5 zigzag-order coefficients */ +#define Q01_POS 1 +#define Q10_POS 8 +#define Q20_POS 16 +#define Q11_POS 9 +#define Q02_POS 2 + +/* + * Determine whether block smoothing is applicable and safe. + * We also latch the current states of the coef_bits[] entries for the + * AC coefficients; otherwise, if the input side of the decompressor + * advances into a new scan, we might think the coefficients are known + * more accurately than they really are. + */ + +LOCAL(boolean) +smoothing_ok (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + boolean smoothing_useful = FALSE; + int ci, coefi; + jpeg_component_info *compptr; + JQUANT_TBL * qtable; + int * coef_bits; + int * coef_bits_latch; + + if (! cinfo->progressive_mode || cinfo->coef_bits == NULL) + return FALSE; + + /* Allocate latch area if not already done */ + if (coef->coef_bits_latch == NULL) + coef->coef_bits_latch = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * + (SAVED_COEFS * SIZEOF(int))); + coef_bits_latch = coef->coef_bits_latch; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* All components' quantization values must already be latched. */ + if ((qtable = compptr->quant_table) == NULL) + return FALSE; + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + if (qtable->quantval[0] == 0 || + qtable->quantval[Q01_POS] == 0 || + qtable->quantval[Q10_POS] == 0 || + qtable->quantval[Q20_POS] == 0 || + qtable->quantval[Q11_POS] == 0 || + qtable->quantval[Q02_POS] == 0) + return FALSE; + /* DC values must be at least partly known for all components. */ + coef_bits = cinfo->coef_bits[ci]; + if (coef_bits[0] < 0) + return FALSE; + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + for (coefi = 1; coefi <= 5; coefi++) { + coef_bits_latch[coefi] = coef_bits[coefi]; + if (coef_bits[coefi] != 0) + smoothing_useful = TRUE; + } + coef_bits_latch += SAVED_COEFS; + } + + return smoothing_useful; +} + + +/* + * Variant of decompress_data for use when doing block smoothing. + */ + +METHODDEF(int) +decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num, last_block_column; + int ci, block_row, block_rows, access_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + boolean first_row, last_row; + JBLOCK workspace; + int *coef_bits; + JQUANT_TBL *quanttbl; + INT32 Q00,Q01,Q02,Q10,Q11,Q20, num; + int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9; + int Al, pred; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if (cinfo->input_scan_number == cinfo->output_scan_number) { + /* If input is working on current scan, we ordinarily want it to + * have completed the current row. But if input scan is DC, + * we want it to keep one row ahead so that next block row's DC + * values are up to date. + */ + JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0; + if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta) + break; + } + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) { + block_rows = compptr->v_samp_factor; + access_rows = block_rows * 2; /* this and next iMCU row */ + last_row = FALSE; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + access_rows = block_rows; /* this iMCU row only */ + last_row = TRUE; + } + /* Align the virtual buffer for this component. */ + if (cinfo->output_iMCU_row > 0) { + access_rows += compptr->v_samp_factor; /* prior iMCU row too */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE); + buffer += compptr->v_samp_factor; /* point to current iMCU row */ + first_row = FALSE; + } else { + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); + first_row = TRUE; + } + /* Fetch component-dependent info */ + coef_bits = coef->coef_bits_latch + (ci * SAVED_COEFS); + quanttbl = compptr->quant_table; + Q00 = quanttbl->quantval[0]; + Q01 = quanttbl->quantval[Q01_POS]; + Q10 = quanttbl->quantval[Q10_POS]; + Q20 = quanttbl->quantval[Q20_POS]; + Q11 = quanttbl->quantval[Q11_POS]; + Q02 = quanttbl->quantval[Q02_POS]; + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + if (first_row && block_row == 0) + prev_block_row = buffer_ptr; + else + prev_block_row = buffer[block_row-1]; + if (last_row && block_row == block_rows-1) + next_block_row = buffer_ptr; + else + next_block_row = buffer[block_row+1]; + /* We fetch the surrounding DC values using a sliding-register approach. + * Initialize all nine here so as to do the right thing on narrow pics. + */ + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + output_col = 0; + last_block_column = compptr->width_in_blocks - 1; + for (block_num = 0; block_num <= last_block_column; block_num++) { + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); + /* Update DC values */ + if (block_num < last_block_column) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { + num = 36 * Q00 * (DC4 - DC6); + if (num >= 0) { + pred = (int) (((Q01<<7) + num) / (Q01<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q10<<7) + num) / (Q10<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q20<<7) + num) / (Q20<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q11<<7) + num) / (Q11<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q02<<7) + num) / (Q02<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_h_scaled_size; + } + output_ptr += compptr->DCT_v_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL(void) +jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + coef->pub.start_input_pass = start_input_pass; + coef->pub.start_output_pass = start_output_pass; +#ifdef BLOCK_SMOOTHING_SUPPORTED + coef->coef_bits_latch = NULL; +#endif + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + /* Note we ask for a pre-zeroed array. */ + int ci, access_rows; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + access_rows = compptr->v_samp_factor; +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* If block smoothing could be used, need a bigger window */ + if (cinfo->progressive_mode) + access_rows *= 3; +#endif + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) access_rows); + } + coef->pub.consume_data = consume_data; + coef->pub.decompress_data = decompress_data; + coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->pub.consume_data = dummy_consume_data; + coef->pub.decompress_data = decompress_onepass; + coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */ + } +} diff --git a/src/jpeg-8c/jdcolor.c b/src/jpeg-8c/jdcolor.c new file mode 100644 index 0000000..6c04dfe --- /dev/null +++ b/src/jpeg-8c/jdcolor.c @@ -0,0 +1,396 @@ +/* + * jdcolor.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains output colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_deconverter pub; /* public fields */ + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ +} my_color_deconverter; + +typedef my_color_deconverter * my_cconvert_ptr; + + +/**************** YCbCr -> RGB conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * Notice that Y, being an integral input, does not contribute any fraction + * so it need not participate in the rounding. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times Cb and Cr for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + * values for the G calculation are left scaled up, since we must add them + * together before rounding. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + */ + +LOCAL(void) +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + int i; + INT32 x; + SHIFT_TEMPS + + cconvert->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + cconvert->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + cconvert->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + cconvert->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Convert some rows of samples to the output colorspace. + * + * Note that we change from noninterleaved, one-plane-per-component format + * to interleaved-pixel format. The output buffer is therefore three times + * as wide as the input buffer. + * A starting row offset is provided only for the input buffer. The caller + * can easily adjust the passed output_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF(void) +ycc_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + outptr[RGB_GREEN] = range_limit[y + + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS))]; + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/**************** Cases other than YCbCr -> RGB **************/ + + +/* + * Color conversion for no colorspace change: just copy the data, + * converting from separate-planes to interleaved representation. + */ + +METHODDEF(void) +null_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION count; + register int num_components = cinfo->num_components; + JDIMENSION num_cols = cinfo->output_width; + int ci; + + while (--num_rows >= 0) { + for (ci = 0; ci < num_components; ci++) { + inptr = input_buf[ci][input_row]; + outptr = output_buf[0] + ci; + for (count = num_cols; count > 0; count--) { + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + outptr += num_components; + } + } + input_row++; + output_buf++; + } +} + + +/* + * Color conversion for grayscale: just copy the data. + * This also works for YCbCr -> grayscale conversion, in which + * we just copy the Y (luminance) component and ignore chrominance. + */ + +METHODDEF(void) +grayscale_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0, + num_rows, cinfo->output_width); +} + + +/* + * Convert grayscale to RGB: just duplicate the graylevel three times. + * This is provided to support applications that don't want to cope + * with grayscale as a separate case. + */ + +METHODDEF(void) +gray_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + + while (--num_rows >= 0) { + inptr = input_buf[0][input_row++]; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + /* We can dispense with GETJSAMPLE() here */ + outptr[RGB_RED] = outptr[RGB_GREEN] = outptr[RGB_BLUE] = inptr[col]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + +METHODDEF(void) +ycck_cmyk_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + inptr3 = input_buf[3][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ + outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS)))]; + outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ + /* K passes through unchanged */ + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + outptr += 4; + } + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF(void) +start_pass_dcolor (j_decompress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for output colorspace conversion. + */ + +GLOBAL(void) +jinit_color_deconverter (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + int ci; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_deconverter)); + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + cconvert->pub.start_pass = start_pass_dcolor; + + /* Make sure num_components agrees with jpeg_color_space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_RGB: + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->num_components < 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + } + + /* Set out_color_components and conversion method based on requested space. + * Also clear the component_needed flags for any unused components, + * so that earlier pipeline stages can avoid useless computation. + */ + + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = grayscale_convert; + /* For color->grayscale conversion, only the Y (0) component is needed */ + for (ci = 1; ci < cinfo->num_components; ci++) + cinfo->comp_info[ci].component_needed = FALSE; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + cinfo->out_color_components = RGB_PIXELSIZE; + if (cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = ycc_rgb_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { + cconvert->pub.color_convert = gray_rgb_convert; + } else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + cinfo->out_color_components = 4; + if (cinfo->jpeg_color_space == JCS_YCCK) { + cconvert->pub.color_convert = ycck_cmyk_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_CMYK) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: + /* Permit null conversion to same output space */ + if (cinfo->out_color_space == cinfo->jpeg_color_space) { + cinfo->out_color_components = cinfo->num_components; + cconvert->pub.color_convert = null_convert; + } else /* unsupported non-null conversion */ + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + } + + if (cinfo->quantize_colors) + cinfo->output_components = 1; /* single colormapped output component */ + else + cinfo->output_components = cinfo->out_color_components; +} diff --git a/src/jpeg-8c/jdct.h b/src/jpeg-8c/jdct.h new file mode 100644 index 0000000..360dec8 --- /dev/null +++ b/src/jpeg-8c/jdct.h @@ -0,0 +1,393 @@ +/* + * jdct.h + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file contains common declarations for the forward and + * inverse DCT modules. These declarations are private to the DCT managers + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + * The individual DCT algorithms are kept in separate files to ease + * machine-dependent tuning (e.g., assembly coding). + */ + + +/* + * A forward DCT routine is given a pointer to an input sample array and + * a pointer to a work area of type DCTELEM[]; the DCT is to be performed + * in-place in that buffer. Type DCTELEM is int for 8-bit samples, INT32 + * for 12-bit samples. (NOTE: Floating-point DCT implementations use an + * array of type FAST_FLOAT, instead.) + * The input data is to be fetched from the sample array starting at a + * specified column. (Any row offset needed will be applied to the array + * pointer before it is passed to the FDCT code.) + * Note that the number of samples fetched by the FDCT routine is + * DCT_h_scaled_size * DCT_v_scaled_size. + * The DCT outputs are returned scaled up by a factor of 8; they therefore + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + * convention improves accuracy in integer implementations and saves some + * work in floating-point ones. + * Quantization of the output coefficients is done by jcdctmgr.c. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef int DCTELEM; /* 16 or 32 bits is fine */ +#else +typedef INT32 DCTELEM; /* must have 32 bits */ +#endif + +typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data, + JSAMPARRAY sample_data, + JDIMENSION start_col)); +typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data, + JSAMPARRAY sample_data, + JDIMENSION start_col)); + + +/* + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + * to an output sample array. The routine must dequantize the input data as + * well as perform the IDCT; for dequantization, it uses the multiplier table + * pointed to by compptr->dct_table. The output data is to be placed into the + * sample array starting at a specified column. (Any row offset needed will + * be applied to the array pointer before it is passed to the IDCT code.) + * Note that the number of samples emitted by the IDCT routine is + * DCT_h_scaled_size * DCT_v_scaled_size. + */ + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + +/* + * Each IDCT routine has its own ideas about the best dct_table element type. + */ + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ +#if BITS_IN_JSAMPLE == 8 +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#else +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +#endif +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + +/* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly. See the comments with + * prepare_range_limit_table (in jdmaster.c) for more info. + */ + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_fdct_7x7 jFD7x7 +#define jpeg_fdct_6x6 jFD6x6 +#define jpeg_fdct_5x5 jFD5x5 +#define jpeg_fdct_4x4 jFD4x4 +#define jpeg_fdct_3x3 jFD3x3 +#define jpeg_fdct_2x2 jFD2x2 +#define jpeg_fdct_1x1 jFD1x1 +#define jpeg_fdct_9x9 jFD9x9 +#define jpeg_fdct_10x10 jFD10x10 +#define jpeg_fdct_11x11 jFD11x11 +#define jpeg_fdct_12x12 jFD12x12 +#define jpeg_fdct_13x13 jFD13x13 +#define jpeg_fdct_14x14 jFD14x14 +#define jpeg_fdct_15x15 jFD15x15 +#define jpeg_fdct_16x16 jFD16x16 +#define jpeg_fdct_16x8 jFD16x8 +#define jpeg_fdct_14x7 jFD14x7 +#define jpeg_fdct_12x6 jFD12x6 +#define jpeg_fdct_10x5 jFD10x5 +#define jpeg_fdct_8x4 jFD8x4 +#define jpeg_fdct_6x3 jFD6x3 +#define jpeg_fdct_4x2 jFD4x2 +#define jpeg_fdct_2x1 jFD2x1 +#define jpeg_fdct_8x16 jFD8x16 +#define jpeg_fdct_7x14 jFD7x14 +#define jpeg_fdct_6x12 jFD6x12 +#define jpeg_fdct_5x10 jFD5x10 +#define jpeg_fdct_4x8 jFD4x8 +#define jpeg_fdct_3x6 jFD3x6 +#define jpeg_fdct_2x4 jFD2x4 +#define jpeg_fdct_1x2 jFD1x2 +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_7x7 jRD7x7 +#define jpeg_idct_6x6 jRD6x6 +#define jpeg_idct_5x5 jRD5x5 +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_3x3 jRD3x3 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 +#define jpeg_idct_9x9 jRD9x9 +#define jpeg_idct_10x10 jRD10x10 +#define jpeg_idct_11x11 jRD11x11 +#define jpeg_idct_12x12 jRD12x12 +#define jpeg_idct_13x13 jRD13x13 +#define jpeg_idct_14x14 jRD14x14 +#define jpeg_idct_15x15 jRD15x15 +#define jpeg_idct_16x16 jRD16x16 +#define jpeg_idct_16x8 jRD16x8 +#define jpeg_idct_14x7 jRD14x7 +#define jpeg_idct_12x6 jRD12x6 +#define jpeg_idct_10x5 jRD10x5 +#define jpeg_idct_8x4 jRD8x4 +#define jpeg_idct_6x3 jRD6x3 +#define jpeg_idct_4x2 jRD4x2 +#define jpeg_idct_2x1 jRD2x1 +#define jpeg_idct_8x16 jRD8x16 +#define jpeg_idct_7x14 jRD7x14 +#define jpeg_idct_6x12 jRD6x12 +#define jpeg_idct_5x10 jRD5x10 +#define jpeg_idct_4x8 jRD4x8 +#define jpeg_idct_3x6 jRD3x8 +#define jpeg_idct_2x4 jRD2x4 +#define jpeg_idct_1x2 jRD1x2 +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Extern declarations for the forward and inverse DCT routines. */ + +EXTERN(void) jpeg_fdct_islow + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_ifast + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_float + JPP((FAST_FLOAT * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_7x7 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_6x6 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_5x5 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_4x4 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_3x3 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_2x2 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_1x1 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_9x9 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_10x10 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_11x11 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_12x12 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_13x13 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_14x14 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_15x15 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_16x16 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_16x8 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_14x7 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_12x6 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_10x5 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_8x4 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_6x3 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_4x2 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_2x1 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_8x16 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_7x14 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_6x12 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_5x10 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_4x8 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_3x6 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_2x4 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); +EXTERN(void) jpeg_fdct_1x2 + JPP((DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col)); + +EXTERN(void) jpeg_idct_islow + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_ifast + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_float + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_7x7 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_6x6 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_5x5 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_4x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_3x3 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_2x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_1x1 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_9x9 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_10x10 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_11x11 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_12x12 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_13x13 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_14x14 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_15x15 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_16x16 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_16x8 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_14x7 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_12x6 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_10x5 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_8x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_6x3 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_4x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_2x1 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_8x16 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_7x14 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_6x12 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_5x10 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_4x8 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_3x6 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_2x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_1x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + + +/* + * Macros for handling fixed-point arithmetic; these are used by many + * but not all of the DCT/IDCT modules. + * + * All values are expected to be of type INT32. + * Fractional constants are scaled left by CONST_BITS bits. + * CONST_BITS is defined within each module using these macros, + * and may differ from one module to the next. + */ + +#define ONE ((INT32) 1) +#define CONST_SCALE (ONE << CONST_BITS) + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + * thus causing a lot of useless floating-point operations at run time. + */ + +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) + +/* Descale and correctly round an INT32 value that's scaled by N bits. + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + * the fudge factor is correct for either sign of X. + */ + +#define DESCALE(x,n) RIGHT_SHIFT((x) + (ONE << ((n)-1)), n) + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * This macro is used only when the two inputs will actually be no more than + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + * full 32x32 multiply. This provides a useful speedup on many machines. + * Unfortunately there is no way to specify a 16x16->32 multiply portably + * in C, but some C compilers will do the right thing if you provide the + * correct combination of casts. + */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) +#endif +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) +#endif + +#ifndef MULTIPLY16C16 /* default definition */ +#define MULTIPLY16C16(var,const) ((var) * (const)) +#endif + +/* Same except both inputs are variables. */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) +#endif + +#ifndef MULTIPLY16V16 /* default definition */ +#define MULTIPLY16V16(var1,var2) ((var1) * (var2)) +#endif diff --git a/src/jpeg-8c/jddctmgr.c b/src/jpeg-8c/jddctmgr.c new file mode 100644 index 0000000..0ded9d5 --- /dev/null +++ b/src/jpeg-8c/jddctmgr.c @@ -0,0 +1,384 @@ +/* + * jddctmgr.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * Modified 2002-2010 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the inverse-DCT management logic. + * This code selects a particular IDCT implementation to be used, + * and it performs related housekeeping chores. No code in this file + * is executed per IDCT step, only during output pass setup. + * + * Note that the IDCT routines are responsible for performing coefficient + * dequantization as well as the IDCT proper. This module sets up the + * dequantization multiplier table needed by the IDCT routine. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* + * The decompressor input side (jdinput.c) saves away the appropriate + * quantization table for each component at the start of the first scan + * involving that component. (This is necessary in order to correctly + * decode files that reuse Q-table slots.) + * When we are ready to make an output pass, the saved Q-table is converted + * to a multiplier table that will actually be used by the IDCT routine. + * The multiplier table contents are IDCT-method-dependent. To support + * application changes in IDCT method between scans, we can remake the + * multiplier tables if necessary. + * In buffered-image mode, the first output pass may occur before any data + * has been seen for some components, and thus before their Q-tables have + * been saved away. To handle this case, multiplier tables are preset + * to zeroes; the result of the IDCT will be a neutral gray level. + */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_inverse_dct pub; /* public fields */ + + /* This array contains the IDCT method code that each multiplier table + * is currently set up for, or -1 if it's not yet set up. + * The actual multiplier tables are pointed to by dct_table in the + * per-component comp_info structures. + */ + int cur_method[MAX_COMPONENTS]; +} my_idct_controller; + +typedef my_idct_controller * my_idct_ptr; + + +/* Allocated multiplier tables: big enough for any supported variant */ + +typedef union { + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; +#ifdef DCT_IFAST_SUPPORTED + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; +#endif +#ifdef DCT_FLOAT_SUPPORTED + FLOAT_MULT_TYPE float_array[DCTSIZE2]; +#endif +} multiplier_table; + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef IDCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Prepare for an output pass. + * Here we select the proper IDCT routine for each component and build + * a matching multiplier table. + */ + +METHODDEF(void) +start_pass (j_decompress_ptr cinfo) +{ + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + int ci, i; + jpeg_component_info *compptr; + int method = 0; + inverse_DCT_method_ptr method_ptr = NULL; + JQUANT_TBL * qtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Select the proper IDCT routine for this component's scaling */ + switch ((compptr->DCT_h_scaled_size << 8) + compptr->DCT_v_scaled_size) { +#ifdef IDCT_SCALING_SUPPORTED + case ((1 << 8) + 1): + method_ptr = jpeg_idct_1x1; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((2 << 8) + 2): + method_ptr = jpeg_idct_2x2; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((3 << 8) + 3): + method_ptr = jpeg_idct_3x3; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((4 << 8) + 4): + method_ptr = jpeg_idct_4x4; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((5 << 8) + 5): + method_ptr = jpeg_idct_5x5; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((6 << 8) + 6): + method_ptr = jpeg_idct_6x6; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((7 << 8) + 7): + method_ptr = jpeg_idct_7x7; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((9 << 8) + 9): + method_ptr = jpeg_idct_9x9; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((10 << 8) + 10): + method_ptr = jpeg_idct_10x10; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((11 << 8) + 11): + method_ptr = jpeg_idct_11x11; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((12 << 8) + 12): + method_ptr = jpeg_idct_12x12; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((13 << 8) + 13): + method_ptr = jpeg_idct_13x13; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((14 << 8) + 14): + method_ptr = jpeg_idct_14x14; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((15 << 8) + 15): + method_ptr = jpeg_idct_15x15; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((16 << 8) + 16): + method_ptr = jpeg_idct_16x16; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((16 << 8) + 8): + method_ptr = jpeg_idct_16x8; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((14 << 8) + 7): + method_ptr = jpeg_idct_14x7; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((12 << 8) + 6): + method_ptr = jpeg_idct_12x6; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((10 << 8) + 5): + method_ptr = jpeg_idct_10x5; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((8 << 8) + 4): + method_ptr = jpeg_idct_8x4; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((6 << 8) + 3): + method_ptr = jpeg_idct_6x3; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((4 << 8) + 2): + method_ptr = jpeg_idct_4x2; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((2 << 8) + 1): + method_ptr = jpeg_idct_2x1; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((8 << 8) + 16): + method_ptr = jpeg_idct_8x16; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((7 << 8) + 14): + method_ptr = jpeg_idct_7x14; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((6 << 8) + 12): + method_ptr = jpeg_idct_6x12; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((5 << 8) + 10): + method_ptr = jpeg_idct_5x10; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((4 << 8) + 8): + method_ptr = jpeg_idct_4x8; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((3 << 8) + 6): + method_ptr = jpeg_idct_3x6; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((2 << 8) + 4): + method_ptr = jpeg_idct_2x4; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; + case ((1 << 8) + 2): + method_ptr = jpeg_idct_1x2; + method = JDCT_ISLOW; /* jidctint uses islow-style table */ + break; +#endif + case ((DCTSIZE << 8) + DCTSIZE): + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + break; + default: + ERREXIT2(cinfo, JERR_BAD_DCTSIZE, + compptr->DCT_h_scaled_size, compptr->DCT_v_scaled_size); + break; + } + idct->pub.inverse_DCT[ci] = method_ptr; + /* Create multiplier table from quant table. + * However, we can skip this if the component is uninteresting + * or if we already built the table. Also, if no quant table + * has yet been saved for the component, we leave the + * multiplier table all-zero; we'll be reading zeroes from the + * coefficient controller's buffer anyway. + */ + if (! compptr->component_needed || idct->cur_method[ci] == method) + continue; + qtbl = compptr->quant_table; + if (qtbl == NULL) /* happens if no data yet for component */ + continue; + idct->cur_method[ci] = method; + switch (method) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + { + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored as ints to ensure access efficiency. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for (i = 0; i < DCTSIZE2; i++) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[i]; + } + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for (i = 0; i < DCTSIZE2; i++) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i], + (INT32) aanscales[i]), + CONST_BITS-IFAST_SCALE_BITS); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 1/8. + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ((double) qtbl->quantval[i] * + aanscalefactor[row] * aanscalefactor[col] * 0.125); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Initialize IDCT manager. + */ + +GLOBAL(void) +jinit_inverse_dct (j_decompress_ptr cinfo) +{ + my_idct_ptr idct; + int ci; + jpeg_component_info *compptr; + + idct = (my_idct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_idct_controller)); + cinfo->idct = (struct jpeg_inverse_dct *) idct; + idct->pub.start_pass = start_pass; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate and pre-zero a multiplier table for each component */ + compptr->dct_table = + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(multiplier_table)); + MEMZERO(compptr->dct_table, SIZEOF(multiplier_table)); + /* Mark multiplier table not yet set up for any method */ + idct->cur_method[ci] = -1; + } +} diff --git a/src/jpeg-8c/jdhuff.c b/src/jpeg-8c/jdhuff.c new file mode 100644 index 0000000..06f92fe --- /dev/null +++ b/src/jpeg-8c/jdhuff.c @@ -0,0 +1,1541 @@ +/* + * jdhuff.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 2006-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * Both sequential and progressive modes are supported in this single module. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + INT32 valoffset[17]; /* huffval[] offset for codes of length k */ + /* valoffset[k] = huffval[] index of 1st symbol of code length k, less + * the smallest code of length k; so given a code of length k, the + * corresponding symbol is huffval[code + valoffset[k]] + */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1< 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't define the size + * with something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* Current data source location */ + /* We need a copy, rather than munging the original, in case of suspension */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + /* Bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* Pointer needed by jpeg_fill_bit_buffer. */ + j_decompress_ptr cinfo; /* back link to decompress master record */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE(cinfop,permstate) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; + +#define BITREAD_SAVE_STATE(cinfop,permstate) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER(state,nbits,action) \ + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + +#define GET_BITS(nbits) \ + (((int) (get_buffer >> (bits_left -= (nbits)))) & BIT_MASK(nbits)) + +#define PEEK_BITS(nbits) \ + (((int) (get_buffer >> (bits_left - (nbits)))) & BIT_MASK(nbits)) + +#define DROP_BITS(nbits) \ + (bits_left -= (nbits)) + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ +{ register int nb, look; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS(HUFF_LOOKAHEAD); \ + if ((nb = htbl->look_nbits[look]) != 0) { \ + DROP_BITS(nb); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD+1; \ +slowlabel: \ + if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ +} + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + unsigned int EOBRUN; /* remaining EOBs in EOBRUN */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).EOBRUN = (src).EOBRUN, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + boolean insufficient_data; /* set TRUE after emitting warning */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Following two fields used only in progressive mode */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + d_derived_tbl * ac_derived_tbl; /* active table during an AC scan */ + + /* Following fields used only in sequential mode */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + + /* Precalculated info set up by start_pass for use in decode_mcu: */ + + /* Pointers to derived tables to be used for each block within an MCU */ + d_derived_tbl * dc_cur_tbls[D_MAX_BLOCKS_IN_MCU]; + d_derived_tbl * ac_cur_tbls[D_MAX_BLOCKS_IN_MCU]; + /* Whether we care about the DC and AC coefficient values for each block */ + int coef_limit[D_MAX_BLOCKS_IN_MCU]; +} huff_entropy_decoder; + +typedef huff_entropy_decoder * huff_entropy_ptr; + + +static const int jpeg_zigzag_order[8][8] = { + { 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 const int jpeg_zigzag_order7[7][7] = { + { 0, 1, 5, 6, 14, 15, 27 }, + { 2, 4, 7, 13, 16, 26, 28 }, + { 3, 8, 12, 17, 25, 29, 38 }, + { 9, 11, 18, 24, 30, 37, 39 }, + { 10, 19, 23, 31, 36, 40, 45 }, + { 20, 22, 32, 35, 41, 44, 46 }, + { 21, 33, 34, 42, 43, 47, 48 } +}; + +static const int jpeg_zigzag_order6[6][6] = { + { 0, 1, 5, 6, 14, 15 }, + { 2, 4, 7, 13, 16, 25 }, + { 3, 8, 12, 17, 24, 26 }, + { 9, 11, 18, 23, 27, 32 }, + { 10, 19, 22, 28, 31, 33 }, + { 20, 21, 29, 30, 34, 35 } +}; + +static const int jpeg_zigzag_order5[5][5] = { + { 0, 1, 5, 6, 14 }, + { 2, 4, 7, 13, 15 }, + { 3, 8, 12, 16, 21 }, + { 9, 11, 17, 20, 22 }, + { 10, 18, 19, 23, 24 } +}; + +static const int jpeg_zigzag_order4[4][4] = { + { 0, 1, 5, 6 }, + { 2, 4, 7, 12 }, + { 3, 8, 11, 13 }, + { 9, 10, 14, 15 } +}; + +static const int jpeg_zigzag_order3[3][3] = { + { 0, 1, 5 }, + { 2, 4, 6 }, + { 3, 7, 8 } +}; + +static const int jpeg_zigzag_order2[2][2] = { + { 0, 1 }, + { 2, 3 } +}; + + +/* + * Compute the derived values for a Huffman table. + * This routine also performs some validation checks on the table. + */ + +LOCAL(void) +jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, + d_derived_tbl ** pdtbl) +{ + JHUFF_TBL *htbl; + d_derived_tbl *dtbl; + int p, i, l, si, numsymbols; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Note that huffsize[] and huffcode[] are filled in code-length order, + * paralleling the order of the symbols themselves in htbl->huffval[]. + */ + + /* Find the input Huffman table */ + if (tblno < 0 || tblno >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + htbl = + isDC ? cinfo->dc_huff_tbl_ptrs[tblno] : cinfo->ac_huff_tbl_ptrs[tblno]; + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (d_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(d_derived_tbl)); + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + + p = 0; + for (l = 1; l <= 16; l++) { + i = (int) htbl->bits[l]; + if (i < 0 || p + i > 256) /* protect against table overrun */ + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + while (i--) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + numsymbols = p; + + /* Figure C.2: generate the codes themselves */ + /* We also validate that the counts represent a legal Huffman code tree. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + /* code is now 1 more than the last code used for codelength si; but + * it must still fit in si bits, since no code is allowed to be all ones. + */ + if (((INT32) code) >= (((INT32) 1) << si)) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for (l = 1; l <= 16; l++) { + if (htbl->bits[l]) { + /* valoffset[l] = huffval[] index of 1st symbol of code length l, + * minus the minimum code of length l + */ + dtbl->valoffset[l] = (INT32) p - (INT32) huffcode[p]; + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO(dtbl->look_nbits, SIZEOF(dtbl->look_nbits)); + + p = 0; + for (l = 1; l <= HUFF_LOOKAHEAD; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++, p++) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l); + for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } + + /* Validate symbols as being reasonable. + * For AC tables, we make no check, but accept all byte values 0..255. + * For DC tables, we require the symbols to be in range 0..15. + * (Tighter bounds could be applied depending on the data depth and mode, + * but this is sufficient to ensure safe decoding.) + */ + if (isDC) { + for (i = 0; i < numsymbols; i++) { + int sym = htbl->huffval[i]; + if (sym < 0 || sym > 15) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + } + } +} + + +/* + * Out-of-line code for bit fetching. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS (BIT_BUF_SIZE-7) +#endif + + +LOCAL(boolean) +jpeg_fill_bit_buffer (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits) +/* Load up the bit buffer to a depth of at least nbits */ +{ + /* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + j_decompress_ptr cinfo = state->cinfo; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + /* We fail to do so only if we hit a marker or are forced to suspend. */ + + if (cinfo->unread_marker == 0) { /* cannot advance past a marker */ + while (bits_left < MIN_GET_BITS) { + register int c; + + /* Attempt to read a byte */ + if (bytes_in_buffer == 0) { + if (! (*cinfo->src->fill_input_buffer) (cinfo)) + return FALSE; + next_input_byte = cinfo->src->next_input_byte; + bytes_in_buffer = cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if (c == 0xFF) { + /* Loop here to discard any padding FF's on terminating marker, + * so that we can save a valid unread_marker value. NOTE: we will + * accept multiple FF's followed by a 0 as meaning a single FF data + * byte. This data pattern is not valid according to the standard. + */ + do { + if (bytes_in_buffer == 0) { + if (! (*cinfo->src->fill_input_buffer) (cinfo)) + return FALSE; + next_input_byte = cinfo->src->next_input_byte; + bytes_in_buffer = cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + } while (c == 0xFF); + + if (c == 0) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. + * Save the marker code for later use. + * Fine point: it might appear that we should save the marker into + * bitread working state, not straight into permanent state. But + * once we have hit a marker, we cannot need to suspend within the + * current MCU, because we will read no more bytes from the data + * source. So it is OK to update permanent state right away. + */ + cinfo->unread_marker = c; + /* See if we need to insert some fake zero bits. */ + goto no_more_bytes; + } + } + + /* OK, load c into get_buffer */ + get_buffer = (get_buffer << 8) | c; + bits_left += 8; + } /* end while */ + } else { + no_more_bytes: + /* We get here if we've read the marker that terminates the compressed + * data segment. There should be enough bits in the buffer register + * to satisfy the request; if so, no problem. + */ + if (nbits > bits_left) { + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * We use a nonvolatile flag to ensure that only one warning message + * appears per data segment. + */ + if (! ((huff_entropy_ptr) cinfo->entropy)->insufficient_data) { + WARNMS(cinfo, JWRN_HIT_MARKER); + ((huff_entropy_ptr) cinfo->entropy)->insufficient_data = TRUE; + } + /* Fill the buffer with zero bits */ + get_buffer <<= MIN_GET_BITS - bits_left; + bits_left = MIN_GET_BITS; + } + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and sub will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define BIT_MASK(nbits) ((1<<(nbits))-1) +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) - ((1<<(s))-1) : (x)) + +#else + +#define BIT_MASK(nbits) bmask[nbits] +#define HUFF_EXTEND(x,s) ((x) <= bmask[(s) - 1] ? (x) - bmask[s] : (x)) + +static const int bmask[16] = /* bmask[n] is mask for n rightmost bits */ + { 0, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF }; + +#endif /* AVOID_TABLES */ + + +/* + * Out-of-line code for Huffman code decoding. + */ + +LOCAL(int) +jpeg_huff_decode (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits) +{ + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER(*state, l, return -1); + code = GET_BITS(l); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while (code > htbl->maxcode[l]) { + code <<= 1; + CHECK_BIT_BUFFER(*state, 1, return -1); + code |= GET_BITS(1); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if (l > 16) { + WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ (int) (code + htbl->valoffset[l]) ]; +} + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL(boolean) +process_restart (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Re-init EOB run count, too */ + entropy->saved.EOBRUN = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Reset out-of-data flag, unless read_restart_marker left us smack up + * against a marker. In that case we will end up treating the next data + * segment as empty, and we can avoid producing bogus output pixels by + * leaving the flag set. + */ + if (cinfo->unread_marker == 0) + entropy->insufficient_data = FALSE; + + return TRUE; +} + + +/* + * Huffman MCU decoding. + * Each of these routines decodes and returns one MCU's worth of + * Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA IS INITIALLY ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * We return FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * spectral selection, since we'll just re-assign them on the next call. + * Successive approximation AC refinement has to be more careful, however.) + */ + +/* + * MCU decoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int Al = cinfo->Al; + register int s, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * tbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->insufficient_data) { + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + tbl = entropy->derived_tbls[compptr->dc_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, tbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Scale and output the coefficient (assumes jpeg_natural_order[0]=0) */ + (*block)[0] = (JCOEF) (s << Al); + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int s, k, r; + unsigned int EOBRUN; + int Se, Al; + const int * natural_order; + JBLOCKROW block; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->insufficient_data) { + + Se = cinfo->Se; + Al = cinfo->Al; + natural_order = cinfo->natural_order; + + /* Load up working state. + * We can avoid loading/saving bitread state if in an EOB run. + */ + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */ + + /* There is always only one block per MCU */ + + if (EOBRUN > 0) /* if it's a band of zeroes... */ + EOBRUN--; /* ...process it now (we do nothing) */ + else { + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + for (k = cinfo->Ss; k <= Se; k++) { + HUFF_DECODE(s, br_state, tbl, return FALSE, label2); + r = s >> 4; + s &= 15; + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Scale and output coefficient in natural (dezigzagged) order */ + (*block)[natural_order[k]] = (JCOEF) (s << Al); + } else { + if (r == 15) { /* ZRL */ + k += 15; /* skip 15 zeroes in band */ + } else { /* EOBr, run length is 2^r + appended bits */ + EOBRUN = 1 << r; + if (r) { /* EOBr, r > 0 */ + CHECK_BIT_BUFFER(br_state, r, return FALSE); + r = GET_BITS(r); + EOBRUN += r; + } + EOBRUN--; /* this band is processed at this moment */ + break; /* force end-of-band */ + } + } + } + + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + } + + /* Completed MCU, so update state */ + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */ + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF(boolean) +decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int blkn; + JBLOCKROW block; + BITREAD_STATE_VARS; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Not worth the cycles to check insufficient_data here, + * since we will not change the data anyway if we read zeroes. + */ + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* Encoded data is simply the next bit of the two's-complement DC value */ + CHECK_BIT_BUFFER(br_state, 1, return FALSE); + if (GET_BITS(1)) + (*block)[0] |= p1; + /* Note: since we use |=, repeating the assignment later is safe */ + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC successive approximation refinement scan. + */ + +METHODDEF(boolean) +decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int s, k, r; + unsigned int EOBRUN; + int Se, p1, m1; + const int * natural_order; + JBLOCKROW block; + JCOEFPTR thiscoef; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + int num_newnz; + int newnz_pos[DCTSIZE2]; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, don't modify the MCU. + */ + if (! entropy->insufficient_data) { + + Se = cinfo->Se; + p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + m1 = (-1) << cinfo->Al; /* -1 in the bit position being coded */ + natural_order = cinfo->natural_order; + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */ + + /* There is always only one block per MCU */ + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + /* If we are forced to suspend, we must undo the assignments to any newly + * nonzero coefficients in the block, because otherwise we'd get confused + * next time about which coefficients were already nonzero. + * But we need not undo addition of bits to already-nonzero coefficients; + * instead, we can test the current bit to see if we already did it. + */ + num_newnz = 0; + + /* initialize coefficient loop counter to start of band */ + k = cinfo->Ss; + + if (EOBRUN == 0) { + for (; k <= Se; k++) { + HUFF_DECODE(s, br_state, tbl, goto undoit, label3); + r = s >> 4; + s &= 15; + if (s) { + if (s != 1) /* size of new coef should always be 1 */ + WARNMS(cinfo, JWRN_HUFF_BAD_CODE); + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) + s = p1; /* newly nonzero coef is positive */ + else + s = m1; /* newly nonzero coef is negative */ + } else { + if (r != 15) { + EOBRUN = 1 << r; /* EOBr, run length is 2^r + appended bits */ + if (r) { + CHECK_BIT_BUFFER(br_state, r, goto undoit); + r = GET_BITS(r); + EOBRUN += r; + } + break; /* rest of block is handled by EOB logic */ + } + /* note s = 0 for processing ZRL */ + } + /* Advance over already-nonzero coefs and r still-zero coefs, + * appending correction bits to the nonzeroes. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + do { + thiscoef = *block + natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already set it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } else { + if (--r < 0) + break; /* reached target zero coefficient */ + } + k++; + } while (k <= Se); + if (s) { + int pos = natural_order[k]; + /* Output newly nonzero coefficient */ + (*block)[pos] = (JCOEF) s; + /* Remember its position in case we have to suspend */ + newnz_pos[num_newnz++] = pos; + } + } + } + + if (EOBRUN > 0) { + /* Scan any remaining coefficient positions after the end-of-band + * (the last newly nonzero coefficient, if any). Append a correction + * bit to each already-nonzero coefficient. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + for (; k <= Se; k++) { + thiscoef = *block + natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } + } + /* Count one block completed in EOB run */ + EOBRUN--; + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */ + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; + +undoit: + /* Re-zero any output coefficients that we made newly nonzero */ + while (num_newnz > 0) + (*block)[newnz_pos[--num_newnz]] = 0; + + return FALSE; +} + + +/* + * Decode one MCU's worth of Huffman-compressed coefficients, + * partial blocks. + */ + +METHODDEF(boolean) +decode_mcu_sub (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + const int * natural_order; + int Se, blkn; + BITREAD_STATE_VARS; + savable_state state; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->insufficient_data) { + + natural_order = cinfo->natural_order; + Se = cinfo->lim_Se; + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + JBLOCKROW block = MCU_data[blkn]; + d_derived_tbl * htbl; + register int s, k, r; + int coef_limit, ci; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + htbl = entropy->dc_cur_tbls[blkn]; + HUFF_DECODE(s, br_state, htbl, return FALSE, label1); + + htbl = entropy->ac_cur_tbls[blkn]; + k = 1; + coef_limit = entropy->coef_limit[blkn]; + if (coef_limit) { + /* Convert DC difference to actual value, update last_dc_val */ + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + ci = cinfo->MCU_membership[blkn]; + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient */ + (*block)[0] = (JCOEF) s; + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (; k < coef_limit; k++) { + HUFF_DECODE(s, br_state, htbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in natural_order[] will save us + * if k > Se, which could happen if the data is corrupted. + */ + (*block)[natural_order[k]] = (JCOEF) s; + } else { + if (r != 15) + goto EndOfBlock; + k += 15; + } + } + } else { + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } + } + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (; k <= Se; k++) { + HUFF_DECODE(s, br_state, htbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } + + EndOfBlock: ; + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Decode one MCU's worth of Huffman-compressed coefficients, + * full-size blocks. + */ + +METHODDEF(boolean) +decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn; + BITREAD_STATE_VARS; + savable_state state; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->insufficient_data) { + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + JBLOCKROW block = MCU_data[blkn]; + d_derived_tbl * htbl; + register int s, k, r; + int coef_limit, ci; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + htbl = entropy->dc_cur_tbls[blkn]; + HUFF_DECODE(s, br_state, htbl, return FALSE, label1); + + htbl = entropy->ac_cur_tbls[blkn]; + k = 1; + coef_limit = entropy->coef_limit[blkn]; + if (coef_limit) { + /* Convert DC difference to actual value, update last_dc_val */ + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + ci = cinfo->MCU_membership[blkn]; + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient */ + (*block)[0] = (JCOEF) s; + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (; k < coef_limit; k++) { + HUFF_DECODE(s, br_state, htbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + (*block)[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if (r != 15) + goto EndOfBlock; + k += 15; + } + } + } else { + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } + } + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, htbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } + + EndOfBlock: ; + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF(void) +start_pass_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, blkn, tbl, i; + jpeg_component_info * compptr; + + if (cinfo->progressive_mode) { + /* Validate progressive scan parameters */ + if (cinfo->Ss == 0) { + if (cinfo->Se != 0) + goto bad; + } else { + /* need not check Ss/Se < 0 since they came from unsigned bytes */ + if (cinfo->Se < cinfo->Ss || cinfo->Se > cinfo->lim_Se) + goto bad; + /* AC scans may have only one component */ + if (cinfo->comps_in_scan != 1) + goto bad; + } + if (cinfo->Ah != 0) { + /* Successive approximation refinement scan: must have Al = Ah-1. */ + if (cinfo->Ah-1 != cinfo->Al) + goto bad; + } + if (cinfo->Al > 13) { /* need not check for < 0 */ + /* Arguably the maximum Al value should be less than 13 for 8-bit precision, + * but the spec doesn't say so, and we try to be liberal about what we + * accept. Note: large Al values could result in out-of-range DC + * coefficients during early scans, leading to bizarre displays due to + * overflows in the IDCT math. But we won't crash. + */ + bad: + ERREXIT4(cinfo, JERR_BAD_PROGRESSION, + cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al); + } + /* Update progression status, and verify that scan order is legal. + * Note that inter-scan inconsistencies are treated as warnings + * not fatal errors ... not clear if this is right way to behave. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + int coefi, cindex = cinfo->cur_comp_info[ci]->component_index; + int *coef_bit_ptr = & cinfo->coef_bits[cindex][0]; + if (cinfo->Ss && coef_bit_ptr[0] < 0) /* AC without prior DC scan */ + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, 0); + for (coefi = cinfo->Ss; coefi <= cinfo->Se; coefi++) { + int expected = (coef_bit_ptr[coefi] < 0) ? 0 : coef_bit_ptr[coefi]; + if (cinfo->Ah != expected) + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi); + coef_bit_ptr[coefi] = cinfo->Al; + } + } + + /* Select MCU decoding routine */ + if (cinfo->Ah == 0) { + if (cinfo->Ss == 0) + entropy->pub.decode_mcu = decode_mcu_DC_first; + else + entropy->pub.decode_mcu = decode_mcu_AC_first; + } else { + if (cinfo->Ss == 0) + entropy->pub.decode_mcu = decode_mcu_DC_refine; + else + entropy->pub.decode_mcu = decode_mcu_AC_refine; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Make sure requested tables are present, and compute derived tables. + * We may build same derived table more than once, but it's not expensive. + */ + if (cinfo->Ss == 0) { + if (cinfo->Ah == 0) { /* DC refinement needs no table */ + tbl = compptr->dc_tbl_no; + jpeg_make_d_derived_tbl(cinfo, TRUE, tbl, + & entropy->derived_tbls[tbl]); + } + } else { + tbl = compptr->ac_tbl_no; + jpeg_make_d_derived_tbl(cinfo, FALSE, tbl, + & entropy->derived_tbls[tbl]); + /* remember the single active table */ + entropy->ac_derived_tbl = entropy->derived_tbls[tbl]; + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize private state variables */ + entropy->saved.EOBRUN = 0; + } else { + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if (cinfo->Ss != 0 || cinfo->Ah != 0 || cinfo->Al != 0 || + ((cinfo->is_baseline || cinfo->Se < DCTSIZE2) && + cinfo->Se != cinfo->lim_Se)) + WARNMS(cinfo, JWRN_NOT_SEQUENTIAL); + + /* Select MCU decoding routine */ + /* We retain the hard-coded case for full-size blocks. + * This is not necessary, but it appears that this version is slightly + * more performant in the given implementation. + * With an improved implementation we would prefer a single optimized + * function. + */ + if (cinfo->lim_Se != DCTSIZE2-1) + entropy->pub.decode_mcu = decode_mcu_sub; + else + entropy->pub.decode_mcu = decode_mcu; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + tbl = compptr->dc_tbl_no; + jpeg_make_d_derived_tbl(cinfo, TRUE, tbl, + & entropy->dc_derived_tbls[tbl]); + if (cinfo->lim_Se) { /* AC needs no table when not present */ + tbl = compptr->ac_tbl_no; + jpeg_make_d_derived_tbl(cinfo, FALSE, tbl, + & entropy->ac_derived_tbls[tbl]); + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Precalculate decoding info for each block in an MCU of this scan */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + /* Precalculate which table to use for each block */ + entropy->dc_cur_tbls[blkn] = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + entropy->ac_cur_tbls[blkn] = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + /* Decide whether we really care about the coefficient values */ + if (compptr->component_needed) { + ci = compptr->DCT_v_scaled_size; + i = compptr->DCT_h_scaled_size; + switch (cinfo->lim_Se) { + case (1*1-1): + entropy->coef_limit[blkn] = 1; + break; + case (2*2-1): + if (ci <= 0 || ci > 2) ci = 2; + if (i <= 0 || i > 2) i = 2; + entropy->coef_limit[blkn] = 1 + jpeg_zigzag_order2[ci - 1][i - 1]; + break; + case (3*3-1): + if (ci <= 0 || ci > 3) ci = 3; + if (i <= 0 || i > 3) i = 3; + entropy->coef_limit[blkn] = 1 + jpeg_zigzag_order3[ci - 1][i - 1]; + break; + case (4*4-1): + if (ci <= 0 || ci > 4) ci = 4; + if (i <= 0 || i > 4) i = 4; + entropy->coef_limit[blkn] = 1 + jpeg_zigzag_order4[ci - 1][i - 1]; + break; + case (5*5-1): + if (ci <= 0 || ci > 5) ci = 5; + if (i <= 0 || i > 5) i = 5; + entropy->coef_limit[blkn] = 1 + jpeg_zigzag_order5[ci - 1][i - 1]; + break; + case (6*6-1): + if (ci <= 0 || ci > 6) ci = 6; + if (i <= 0 || i > 6) i = 6; + entropy->coef_limit[blkn] = 1 + jpeg_zigzag_order6[ci - 1][i - 1]; + break; + case (7*7-1): + if (ci <= 0 || ci > 7) ci = 7; + if (i <= 0 || i > 7) i = 7; + entropy->coef_limit[blkn] = 1 + jpeg_zigzag_order7[ci - 1][i - 1]; + break; + default: + if (ci <= 0 || ci > 8) ci = 8; + if (i <= 0 || i > 8) i = 8; + entropy->coef_limit[blkn] = 1 + jpeg_zigzag_order[ci - 1][i - 1]; + break; + } + } else { + entropy->coef_limit[blkn] = 0; + } + } + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->insufficient_data = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL(void) +jinit_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + + if (cinfo->progressive_mode) { + /* Create progression status table */ + int *coef_bit_ptr, ci; + cinfo->coef_bits = (int (*)[DCTSIZE2]) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components*DCTSIZE2*SIZEOF(int)); + coef_bit_ptr = & cinfo->coef_bits[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (i = 0; i < DCTSIZE2; i++) + *coef_bit_ptr++ = -1; + + /* Mark derived tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->derived_tbls[i] = NULL; + } + } else { + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } + } +} diff --git a/src/jpeg-8c/jdinput.c b/src/jpeg-8c/jdinput.c new file mode 100644 index 0000000..2c5c717 --- /dev/null +++ b/src/jpeg-8c/jdinput.c @@ -0,0 +1,661 @@ +/* + * jdinput.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 2002-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input control logic for the JPEG decompressor. + * These routines are concerned with controlling the decompressor's input + * processing (marker reading and coefficient decoding). The actual input + * reading is done in jdmarker.c, jdhuff.c, and jdarith.c. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_input_controller pub; /* public fields */ + + int inheaders; /* Nonzero until first SOS is reached */ +} my_input_controller; + +typedef my_input_controller * my_inputctl_ptr; + + +/* Forward declarations */ +METHODDEF(int) consume_markers JPP((j_decompress_ptr cinfo)); + + +/* + * Routines to calculate various quantities related to the size of the image. + */ + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + */ + +GLOBAL(void) +jpeg_core_output_dimensions (j_decompress_ptr cinfo) +/* Do computations that are needed before master selection phase. + * This function is used for transcoding and full decompression. + */ +{ +#ifdef IDCT_SCALING_SUPPORTED + int ci; + jpeg_component_info *compptr; + + /* Compute actual output image dimensions and DCT scaling choices. */ + if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom) { + /* Provide 1/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 1; + cinfo->min_DCT_v_scaled_size = 1; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 2) { + /* Provide 2/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 2L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 2L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 2; + cinfo->min_DCT_v_scaled_size = 2; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 3) { + /* Provide 3/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 3L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 3L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 3; + cinfo->min_DCT_v_scaled_size = 3; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 4) { + /* Provide 4/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 4L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 4L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 4; + cinfo->min_DCT_v_scaled_size = 4; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 5) { + /* Provide 5/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 5L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 5L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 5; + cinfo->min_DCT_v_scaled_size = 5; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 6) { + /* Provide 6/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 6L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 6L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 6; + cinfo->min_DCT_v_scaled_size = 6; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 7) { + /* Provide 7/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 7L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 7L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 7; + cinfo->min_DCT_v_scaled_size = 7; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 8) { + /* Provide 8/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 8L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 8L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 8; + cinfo->min_DCT_v_scaled_size = 8; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 9) { + /* Provide 9/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 9L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 9L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 9; + cinfo->min_DCT_v_scaled_size = 9; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 10) { + /* Provide 10/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 10L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 10L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 10; + cinfo->min_DCT_v_scaled_size = 10; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 11) { + /* Provide 11/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 11L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 11L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 11; + cinfo->min_DCT_v_scaled_size = 11; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 12) { + /* Provide 12/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 12L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 12L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 12; + cinfo->min_DCT_v_scaled_size = 12; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 13) { + /* Provide 13/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 13L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 13L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 13; + cinfo->min_DCT_v_scaled_size = 13; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 14) { + /* Provide 14/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 14L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 14L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 14; + cinfo->min_DCT_v_scaled_size = 14; + } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 15) { + /* Provide 15/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 15L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 15L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 15; + cinfo->min_DCT_v_scaled_size = 15; + } else { + /* Provide 16/block_size scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * 16L, (long) cinfo->block_size); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * 16L, (long) cinfo->block_size); + cinfo->min_DCT_h_scaled_size = 16; + cinfo->min_DCT_v_scaled_size = 16; + } + + /* Recompute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->DCT_h_scaled_size = cinfo->min_DCT_h_scaled_size; + compptr->DCT_v_scaled_size = cinfo->min_DCT_v_scaled_size; + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ +} + + +LOCAL(void) +initial_setup (j_decompress_ptr cinfo) +/* Called once, when first SOS marker is reached */ +{ + int ci; + jpeg_component_info *compptr; + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* Derive block_size, natural_order, and lim_Se */ + if (cinfo->is_baseline || (cinfo->progressive_mode && + cinfo->comps_in_scan)) { /* no pseudo SOS marker */ + cinfo->block_size = DCTSIZE; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + } else + switch (cinfo->Se) { + case (1*1-1): + cinfo->block_size = 1; + cinfo->natural_order = jpeg_natural_order; /* not needed */ + cinfo->lim_Se = cinfo->Se; + break; + case (2*2-1): + cinfo->block_size = 2; + cinfo->natural_order = jpeg_natural_order2; + cinfo->lim_Se = cinfo->Se; + break; + case (3*3-1): + cinfo->block_size = 3; + cinfo->natural_order = jpeg_natural_order3; + cinfo->lim_Se = cinfo->Se; + break; + case (4*4-1): + cinfo->block_size = 4; + cinfo->natural_order = jpeg_natural_order4; + cinfo->lim_Se = cinfo->Se; + break; + case (5*5-1): + cinfo->block_size = 5; + cinfo->natural_order = jpeg_natural_order5; + cinfo->lim_Se = cinfo->Se; + break; + case (6*6-1): + cinfo->block_size = 6; + cinfo->natural_order = jpeg_natural_order6; + cinfo->lim_Se = cinfo->Se; + break; + case (7*7-1): + cinfo->block_size = 7; + cinfo->natural_order = jpeg_natural_order7; + cinfo->lim_Se = cinfo->Se; + break; + case (8*8-1): + cinfo->block_size = 8; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + case (9*9-1): + cinfo->block_size = 9; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + case (10*10-1): + cinfo->block_size = 10; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + case (11*11-1): + cinfo->block_size = 11; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + case (12*12-1): + cinfo->block_size = 12; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + case (13*13-1): + cinfo->block_size = 13; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + case (14*14-1): + cinfo->block_size = 14; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + case (15*15-1): + cinfo->block_size = 15; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + case (16*16-1): + cinfo->block_size = 16; + cinfo->natural_order = jpeg_natural_order; + cinfo->lim_Se = DCTSIZE2-1; + break; + default: + ERREXIT4(cinfo, JERR_BAD_PROGRESSION, + cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al); + break; + } + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to block_size. + * In the full decompressor, + * this will be overridden by jpeg_calc_output_dimensions in jdmaster.c; + * but in the transcoder, + * jpeg_calc_output_dimensions is not used, so we must do it here. + */ + cinfo->min_DCT_h_scaled_size = cinfo->block_size; + cinfo->min_DCT_v_scaled_size = cinfo->block_size; + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->DCT_h_scaled_size = cinfo->block_size; + compptr->DCT_v_scaled_size = cinfo->block_size; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * cinfo->block_size)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * cinfo->block_size)); + /* downsampled_width and downsampled_height will also be overridden by + * jdmaster.c if we are doing full decompression. The transcoder library + * doesn't use these values, but the calling application might. + */ + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed, until color conversion says otherwise */ + compptr->component_needed = TRUE; + /* Mark no quantization table yet saved for component */ + compptr->quant_table = NULL; + } + + /* Compute number of fully interleaved MCU rows. */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor * cinfo->block_size)); + + /* Decide whether file contains multiple scans */ + if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode) + cinfo->inputctl->has_multiple_scans = TRUE; + else + cinfo->inputctl->has_multiple_scans = FALSE; +} + + +LOCAL(void) +per_scan_setup (j_decompress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_h_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor * cinfo->block_size)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor * cinfo->block_size)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_h_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } +} + + +/* + * Save away a copy of the Q-table referenced by each component present + * in the current scan, unless already saved during a prior scan. + * + * In a multiple-scan JPEG file, the encoder could assign different components + * the same Q-table slot number, but change table definitions between scans + * so that each component uses a different Q-table. (The IJG encoder is not + * currently capable of doing this, but other encoders might.) Since we want + * to be able to dequantize all the components at the end of the file, this + * means that we have to save away the table actually used for each component. + * We do this by copying the table at the start of the first scan containing + * the component. + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + * slot between scans of a component using that slot. If the encoder does so + * anyway, this decoder will simply use the Q-table values that were current + * at the start of the first scan for the component. + * + * The decompressor output side looks only at the saved quant tables, + * not at the current Q-table slots. + */ + +LOCAL(void) +latch_quant_tables (j_decompress_ptr cinfo) +{ + int ci, qtblno; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* No work if we already saved Q-table for this component */ + if (compptr->quant_table != NULL) + continue; + /* Make sure specified quantization table is present */ + qtblno = compptr->quant_tbl_no; + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + /* OK, save away the quantization table */ + qtbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(JQUANT_TBL)); + MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL)); + compptr->quant_table = qtbl; + } +} + + +/* + * Initialize the input modules to read a scan of compressed data. + * The first call to this is done by jdmaster.c after initializing + * the entire decompressor (during jpeg_start_decompress). + * Subsequent calls come from consume_markers, below. + */ + +METHODDEF(void) +start_input_pass (j_decompress_ptr cinfo) +{ + per_scan_setup(cinfo); + latch_quant_tables(cinfo); + (*cinfo->entropy->start_pass) (cinfo); + (*cinfo->coef->start_input_pass) (cinfo); + cinfo->inputctl->consume_input = cinfo->coef->consume_data; +} + + +/* + * Finish up after inputting a compressed-data scan. + * This is called by the coefficient controller after it's read all + * the expected data of the scan. + */ + +METHODDEF(void) +finish_input_pass (j_decompress_ptr cinfo) +{ + cinfo->inputctl->consume_input = consume_markers; +} + + +/* + * Read JPEG markers before, between, or after compressed-data scans. + * Change state as necessary when a new scan is reached. + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * The consume_input method pointer points either here or to the + * coefficient controller's consume_data routine, depending on whether + * we are reading a compressed data segment or inter-segment markers. + * + * Note: This function should NOT return a pseudo SOS marker (with zero + * component number) to the caller. A pseudo marker received by + * read_markers is processed and then skipped for other markers. + */ + +METHODDEF(int) +consume_markers (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + int val; + + if (inputctl->pub.eoi_reached) /* After hitting EOI, read no further */ + return JPEG_REACHED_EOI; + + for (;;) { /* Loop to pass pseudo SOS marker */ + val = (*cinfo->marker->read_markers) (cinfo); + + switch (val) { + case JPEG_REACHED_SOS: /* Found SOS */ + if (inputctl->inheaders) { /* 1st SOS */ + if (inputctl->inheaders == 1) + initial_setup(cinfo); + if (cinfo->comps_in_scan == 0) { /* pseudo SOS marker */ + inputctl->inheaders = 2; + break; + } + inputctl->inheaders = 0; + /* Note: start_input_pass must be called by jdmaster.c + * before any more input can be consumed. jdapimin.c is + * responsible for enforcing this sequencing. + */ + } else { /* 2nd or later SOS marker */ + if (! inputctl->pub.has_multiple_scans) + ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ + if (cinfo->comps_in_scan == 0) /* unexpected pseudo SOS marker */ + break; + start_input_pass(cinfo); + } + return val; + case JPEG_REACHED_EOI: /* Found EOI */ + inputctl->pub.eoi_reached = TRUE; + if (inputctl->inheaders) { /* Tables-only datastream, apparently */ + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_NO_SOS); + } else { + /* Prevent infinite loop in coef ctlr's decompress_data routine + * if user set output_scan_number larger than number of scans. + */ + if (cinfo->output_scan_number > cinfo->input_scan_number) + cinfo->output_scan_number = cinfo->input_scan_number; + } + return val; + case JPEG_SUSPENDED: + return val; + default: + return val; + } + } +} + + +/* + * Reset state to begin a fresh datastream. + */ + +METHODDEF(void) +reset_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + inputctl->pub.consume_input = consume_markers; + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = 1; + /* Reset other modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->marker->reset_marker_reader) (cinfo); + /* Reset progression state -- would be cleaner if entropy decoder did this */ + cinfo->coef_bits = NULL; +} + + +/* + * Initialize the input controller module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL(void) +jinit_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl; + + /* Create subobject in permanent pool */ + inputctl = (my_inputctl_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_input_controller)); + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + /* Initialize method pointers */ + inputctl->pub.consume_input = consume_markers; + inputctl->pub.reset_input_controller = reset_input_controller; + inputctl->pub.start_input_pass = start_input_pass; + inputctl->pub.finish_input_pass = finish_input_pass; + /* Initialize state: can't use reset_input_controller since we don't + * want to try to reset other modules yet. + */ + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = 1; +} diff --git a/src/jpeg-8c/jdmainct.c b/src/jpeg-8c/jdmainct.c new file mode 100644 index 0000000..c04e822 --- /dev/null +++ b/src/jpeg-8c/jdmainct.c @@ -0,0 +1,512 @@ +/* + * jdmainct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for decompression. + * The main buffer lies between the JPEG decompressor proper and the + * post-processor; it holds downsampled data in the JPEG colorspace. + * + * Note that this code is bypassed in raw-data mode, since the application + * supplies the equivalent of the main buffer in that case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * In the current system design, the main buffer need never be a full-image + * buffer; any full-height buffers will be found inside the coefficient or + * postprocessing controllers. Nonetheless, the main controller is not + * trivial. Its responsibility is to provide context rows for upsampling/ + * rescaling, and doing this in an efficient fashion is a bit tricky. + * + * Postprocessor input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. (We require DCT_scaled_size values to be + * chosen such that these numbers are integers. In practice DCT_scaled_size + * values will likely be powers of two, so we actually have the stronger + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + * Upsampling will typically produce max_v_samp_factor pixel rows from each + * row group (times any additional scale factor that the upsampler is + * applying). + * + * The coefficient controller will deliver data to us one iMCU row at a time; + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + * to one row of MCUs when the image is fully interleaved.) Note that the + * number of sample rows varies across components, but the number of row + * groups does not. Some garbage sample rows may be included in the last iMCU + * row at the bottom of the image. + * + * Depending on the vertical scaling algorithm used, the upsampler may need + * access to the sample row(s) above and below its current input row group. + * The upsampler is required to set need_context_rows TRUE at global selection + * time if so. When need_context_rows is FALSE, this controller can simply + * obtain one iMCU row at a time from the coefficient controller and dole it + * out as row groups to the postprocessor. + * + * When need_context_rows is TRUE, this controller guarantees that the buffer + * passed to postprocessing contains at least one row group's worth of samples + * above and below the row group(s) being processed. Note that the context + * rows "above" the first passed row group appear at negative row offsets in + * the passed buffer. At the top and bottom of the image, the required + * context rows are manufactured by duplicating the first or last real sample + * row; this avoids having special cases in the upsampling inner loops. + * + * The amount of context is fixed at one row group just because that's a + * convenient number for this controller to work with. The existing + * upsamplers really only need one sample row of context. An upsampler + * supporting arbitrary output rescaling might wish for more than one row + * group of context when shrinking the image; tough, we don't handle that. + * (This is justified by the assumption that downsizing will be handled mostly + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + * the upsample step needn't be much less than one.) + * + * To provide the desired context, we have to retain the last two row groups + * of one iMCU row while reading in the next iMCU row. (The last row group + * can't be processed until we have another row group for its below-context, + * and so we have to save the next-to-last group too for its above-context.) + * We could do this most simply by copying data around in our buffer, but + * that'd be very slow. We can avoid copying any data by creating a rather + * strange pointer structure. Here's how it works. We allocate a workspace + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + * of row groups per iMCU row). We create two sets of redundant pointers to + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + * pointer lists look like this: + * M+1 M-1 + * master pointer --> 0 master pointer --> 0 + * 1 1 + * ... ... + * M-3 M-3 + * M-2 M + * M-1 M+1 + * M M-2 + * M+1 M-1 + * 0 0 + * We read alternate iMCU rows using each master pointer; thus the last two + * row groups of the previous iMCU row remain un-overwritten in the workspace. + * The pointer lists are set up so that the required context rows appear to + * be adjacent to the proper places when we pass the pointer lists to the + * upsampler. + * + * The above pictures describe the normal state of the pointer lists. + * At top and bottom of the image, we diddle the pointer lists to duplicate + * the first or last sample row as necessary (this is cheaper than copying + * sample rows around). + * + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + * situation each iMCU row provides only one row group so the buffering logic + * must be different (eg, we must read two iMCU rows before we can emit the + * first row group). For now, we simply do not support providing context + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + * be worth providing --- if someone wants a 1/8th-size preview, they probably + * want it quick and dirty, so a context-free upsampler is sufficient. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_main_controller pub; /* public fields */ + + /* Pointer to allocated workspace (M or M+2 row groups). */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + + /* Remaining fields are only used in the context case. */ + + /* These are the master pointers to the funny-order pointer lists. */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + int whichptr; /* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + +/* context_state values: */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + +/* Forward declarations */ +METHODDEF(void) process_data_simple_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +METHODDEF(void) process_data_context_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF(void) process_data_crank_post + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#endif + + +LOCAL(void) +alloc_funny_pointers (j_decompress_ptr cinfo) +/* Allocate space for the funny pointer lists. + * This is done only once, not once per pass. + */ +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + int ci, rgroup; + int M = cinfo->min_DCT_v_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + /* Get top-level space for component array pointers. + * We alloc both arrays with one call to save a few cycles. + */ + main_ptr->xbuffer[0] = (JSAMPIMAGE) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); + main_ptr->xbuffer[1] = main_ptr->xbuffer[0] + cinfo->num_components; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_v_scaled_size) / + cinfo->min_DCT_v_scaled_size; /* height of a row group of component */ + /* Get space for pointer lists --- M+4 row groups in each list. + * We alloc both pointer lists with one call to save a few cycles. + */ + xbuf = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); + xbuf += rgroup; /* want one row group at negative offsets */ + main_ptr->xbuffer[0][ci] = xbuf; + xbuf += rgroup * (M + 4); + main_ptr->xbuffer[1][ci] = xbuf; + } +} + + +LOCAL(void) +make_funny_pointers (j_decompress_ptr cinfo) +/* Create the funny pointer lists discussed in the comments above. + * The actual workspace is already allocated (in main_ptr->buffer), + * and the space for the pointer lists is allocated too. + * This routine just fills in the curiously ordered lists. + * This will be repeated at the beginning of each pass. + */ +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_v_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY buf, xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_v_scaled_size) / + cinfo->min_DCT_v_scaled_size; /* height of a row group of component */ + xbuf0 = main_ptr->xbuffer[0][ci]; + xbuf1 = main_ptr->xbuffer[1][ci]; + /* First copy the workspace pointers as-is */ + buf = main_ptr->buffer[ci]; + for (i = 0; i < rgroup * (M + 2); i++) { + xbuf0[i] = xbuf1[i] = buf[i]; + } + /* In the second list, put the last four row groups in swapped order */ + for (i = 0; i < rgroup * 2; i++) { + xbuf1[rgroup*(M-2) + i] = buf[rgroup*M + i]; + xbuf1[rgroup*M + i] = buf[rgroup*(M-2) + i]; + } + /* The wraparound pointers at top and bottom will be filled later + * (see set_wraparound_pointers, below). Initially we want the "above" + * pointers to duplicate the first actual data line. This only needs + * to happen in xbuffer[0]. + */ + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[0]; + } + } +} + + +LOCAL(void) +set_wraparound_pointers (j_decompress_ptr cinfo) +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + * This changes the pointer list state from top-of-image to the normal state. + */ +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_v_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_v_scaled_size) / + cinfo->min_DCT_v_scaled_size; /* height of a row group of component */ + xbuf0 = main_ptr->xbuffer[0][ci]; + xbuf1 = main_ptr->xbuffer[1][ci]; + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i]; + xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i]; + xbuf0[rgroup*(M+2) + i] = xbuf0[i]; + xbuf1[rgroup*(M+2) + i] = xbuf1[i]; + } + } +} + + +LOCAL(void) +set_bottom_pointers (j_decompress_ptr cinfo) +/* Change the pointer lists to duplicate the last sample row at the bottom + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + */ +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + int ci, i, rgroup, iMCUheight, rows_left; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Count sample rows in one iMCU row and in one row group */ + iMCUheight = compptr->v_samp_factor * compptr->DCT_v_scaled_size; + rgroup = iMCUheight / cinfo->min_DCT_v_scaled_size; + /* Count nondummy sample rows remaining for this component */ + rows_left = (int) (compptr->downsampled_height % (JDIMENSION) iMCUheight); + if (rows_left == 0) rows_left = iMCUheight; + /* Count nondummy row groups. Should get same answer for each component, + * so we need only do it once. + */ + if (ci == 0) { + main_ptr->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); + } + /* Duplicate the last real sample row rgroup*2 times; this pads out the + * last partial rowgroup and ensures at least one full rowgroup of context. + */ + xbuf = main_ptr->xbuffer[main_ptr->whichptr][ci]; + for (i = 0; i < rgroup * 2; i++) { + xbuf[rows_left + i] = xbuf[rows_left-1]; + } + } +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->upsample->need_context_rows) { + main_ptr->pub.process_data = process_data_context_main; + make_funny_pointers(cinfo); /* Create the xbuffer[] lists */ + main_ptr->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + main_ptr->context_state = CTX_PREPARE_FOR_IMCU; + main_ptr->iMCU_row_ctr = 0; + } else { + /* Simple case with no context needed */ + main_ptr->pub.process_data = process_data_simple_main; + } + main_ptr->buffer_full = FALSE; /* Mark buffer empty */ + main_ptr->rowgroup_ctr = 0; + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_CRANK_DEST: + /* For last pass of 2-pass quantization, just crank the postprocessor */ + main_ptr->pub.process_data = process_data_crank_post; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This handles the simple case where no context is required. + */ + +METHODDEF(void) +process_data_simple_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + JDIMENSION rowgroups_avail; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main_ptr->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, main_ptr->buffer)) + return; /* suspension forced, can do nothing more */ + main_ptr->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + } + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_v_scaled_size; + /* Note: at the bottom of the image, we may pass extra garbage row groups + * to the postprocessor. The postprocessor has to check for bottom + * of image anyway (at row resolution), so no point in us doing it too. + */ + + /* Feed the postprocessor */ + (*cinfo->post->post_process_data) (cinfo, main_ptr->buffer, + &main_ptr->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + if (main_ptr->rowgroup_ctr >= rowgroups_avail) { + main_ptr->buffer_full = FALSE; + main_ptr->rowgroup_ctr = 0; + } +} + + +/* + * Process some data. + * This handles the case where context rows must be provided. + */ + +METHODDEF(void) +process_data_context_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr main_ptr = (my_main_ptr) cinfo->main; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main_ptr->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, + main_ptr->xbuffer[main_ptr->whichptr])) + return; /* suspension forced, can do nothing more */ + main_ptr->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + main_ptr->iMCU_row_ctr++; /* count rows received */ + } + + /* Postprocessor typically will not swallow all the input data it is handed + * in one call (due to filling the output buffer first). Must be prepared + * to exit and restart. This switch lets us keep track of how far we got. + * Note that each case falls through to the next on successful completion. + */ + switch (main_ptr->context_state) { + case CTX_POSTPONED_ROW: + /* Call postprocessor using previously set pointers for postponed row */ + (*cinfo->post->post_process_data) (cinfo, main_ptr->xbuffer[main_ptr->whichptr], + &main_ptr->rowgroup_ctr, main_ptr->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main_ptr->rowgroup_ctr < main_ptr->rowgroups_avail) + return; /* Need to suspend */ + main_ptr->context_state = CTX_PREPARE_FOR_IMCU; + if (*out_row_ctr >= out_rows_avail) + return; /* Postprocessor exactly filled output buf */ + /*FALLTHROUGH*/ + case CTX_PREPARE_FOR_IMCU: + /* Prepare to process first M-1 row groups of this iMCU row */ + main_ptr->rowgroup_ctr = 0; + main_ptr->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_v_scaled_size - 1); + /* Check for bottom of image: if so, tweak pointers to "duplicate" + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + */ + if (main_ptr->iMCU_row_ctr == cinfo->total_iMCU_rows) + set_bottom_pointers(cinfo); + main_ptr->context_state = CTX_PROCESS_IMCU; + /*FALLTHROUGH*/ + case CTX_PROCESS_IMCU: + /* Call postprocessor using previously set pointers */ + (*cinfo->post->post_process_data) (cinfo, main_ptr->xbuffer[main_ptr->whichptr], + &main_ptr->rowgroup_ctr, main_ptr->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main_ptr->rowgroup_ctr < main_ptr->rowgroups_avail) + return; /* Need to suspend */ + /* After the first iMCU, change wraparound pointers to normal state */ + if (main_ptr->iMCU_row_ctr == 1) + set_wraparound_pointers(cinfo); + /* Prepare to load new iMCU row using other xbuffer list */ + main_ptr->whichptr ^= 1; /* 0=>1 or 1=>0 */ + main_ptr->buffer_full = FALSE; + /* Still need to process last row group of this iMCU row, */ + /* which is saved at index M+1 of the other xbuffer */ + main_ptr->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_v_scaled_size + 1); + main_ptr->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_v_scaled_size + 2); + main_ptr->context_state = CTX_POSTPONED_ROW; + } +} + + +/* + * Process some data. + * Final pass of two-pass quantization: just call the postprocessor. + * Source data will be the postprocessor controller's internal buffer. + */ + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF(void) +process_data_crank_post (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL, + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail); +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL(void) +jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr main_ptr; + int ci, rgroup, ngroups; + jpeg_component_info *compptr; + + main_ptr = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_d_main_controller *) main_ptr; + main_ptr->pub.start_pass = start_pass_main; + + if (need_full_buffer) /* shouldn't happen */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Allocate the workspace. + * ngroups is the number of row groups we need. + */ + if (cinfo->upsample->need_context_rows) { + if (cinfo->min_DCT_v_scaled_size < 2) /* unsupported, see comments above */ + ERREXIT(cinfo, JERR_NOTIMPL); + alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */ + ngroups = cinfo->min_DCT_v_scaled_size + 2; + } else { + ngroups = cinfo->min_DCT_v_scaled_size; + } + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_v_scaled_size) / + cinfo->min_DCT_v_scaled_size; /* height of a row group of component */ + main_ptr->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_h_scaled_size, + (JDIMENSION) (rgroup * ngroups)); + } +} diff --git a/src/jpeg-8c/jdmarker.c b/src/jpeg-8c/jdmarker.c new file mode 100644 index 0000000..f2a9cc4 --- /dev/null +++ b/src/jpeg-8c/jdmarker.c @@ -0,0 +1,1406 @@ +/* + * jdmarker.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * Modified 2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* Private state */ + +typedef struct { + struct jpeg_marker_reader pub; /* public fields */ + + /* Application-overridable marker processing methods */ + jpeg_marker_parser_method process_COM; + jpeg_marker_parser_method process_APPn[16]; + + /* Limit on marker data length to save for each marker type */ + unsigned int length_limit_COM; + unsigned int length_limit_APPn[16]; + + /* Status of COM/APPn marker saving */ + jpeg_saved_marker_ptr cur_marker; /* NULL if not processing a marker */ + unsigned int bytes_read; /* data bytes read so far in marker */ + /* Note: cur_marker is not linked into marker_list until it's all read. */ +} my_marker_reader; + +typedef my_marker_reader * my_marker_ptr; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS(cinfo) \ + struct jpeg_source_mgr * datasrc = (cinfo)->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC(cinfo) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- used only in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD(cinfo) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL(cinfo,action) \ + if (bytes_in_buffer == 0) { \ + if (! (*datasrc->fill_input_buffer) (cinfo)) \ + { action; } \ + INPUT_RELOAD(cinfo); \ + } + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V = GETJOCTET(*next_input_byte++); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ + MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V += GETJOCTET(*next_input_byte++); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters + * can fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments + * that might not fit. If we are simply dropping such a marker, we use + * skip_input_data to get past it, and thereby put the problem on the + * source manager's shoulders. If we are saving the marker's contents + * into memory, we use a slightly different convention: when forced to + * suspend, the marker processor updates the restart point to the end of + * what it's consumed (ie, the end of the buffer) before returning FALSE. + * On resumption, cinfo->unread_marker still contains the marker code, + * but the data source will point to the next chunk of marker data. + * The marker processor must retain internal state to deal with this. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL(boolean) +get_soi (j_decompress_ptr cinfo) +/* Process an SOI marker */ +{ + int i; + + TRACEMS(cinfo, 1, JTRC_SOI); + + if (cinfo->marker->saw_SOI) + ERREXIT(cinfo, JERR_SOI_DUPLICATE); + + /* Reset all parameters that are defined to be reset by SOI */ + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->JFIF_major_version = 1; /* set default JFIF APP0 values */ + cinfo->JFIF_minor_version = 1; + cinfo->density_unit = 0; + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL(boolean) +get_sof (j_decompress_ptr cinfo, boolean is_baseline, boolean is_prog, + boolean is_arith) +/* Process a SOFn marker */ +{ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + cinfo->is_baseline = is_baseline; + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, cinfo->data_precision, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_height, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_width, return FALSE); + INPUT_BYTE(cinfo, cinfo->num_components, return FALSE); + + length -= 8; + + TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components); + + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_DUPLICATE); + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + if (length != (cinfo->num_components * 3)) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + if (cinfo->comp_info == NULL) /* do only once, even if suspend */ + cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF(jpeg_component_info)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->component_index = ci; + INPUT_BYTE(cinfo, compptr->component_id, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + compptr->h_samp_factor = (c >> 4) & 15; + compptr->v_samp_factor = (c ) & 15; + INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE); + + TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_sos (j_decompress_ptr cinfo) +/* Process a SOS marker */ +{ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + if (! cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOS_NO_SOF); + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, n, return FALSE); /* Number of components */ + + TRACEMS1(cinfo, 1, JTRC_SOS, n); + + if (length != (n * 2 + 6) || n > MAX_COMPS_IN_SCAN || + (n == 0 && !cinfo->progressive_mode)) + /* pseudo SOS marker only allowed in progressive mode */ + ERREXIT(cinfo, JERR_BAD_LENGTH); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for (i = 0; i < n; i++) { + INPUT_BYTE(cinfo, cc, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (cc == compptr->component_id) + goto id_found; + } + + ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc); + + id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = (c >> 4) & 15; + compptr->ac_tbl_no = (c ) & 15; + + TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ss = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Se = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ah = (c >> 4) & 15; + cinfo->Al = (c ) & 15; + + TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another (non-pseudo) SOS marker */ + if (n) cinfo->input_scan_number++; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +#ifdef D_ARITH_CODING_SUPPORTED + +LOCAL(boolean) +get_dac (j_decompress_ptr cinfo) +/* Process a DAC marker */ +{ + INT32 length; + int index, val; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + INPUT_BYTE(cinfo, val, return FALSE); + + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_DAC, index, val); + + if (index < 0 || index >= (2*NUM_ARITH_TBLS)) + ERREXIT1(cinfo, JERR_DAC_INDEX, index); + + if (index >= NUM_ARITH_TBLS) { /* define AC table */ + cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F); + cinfo->arith_dc_U[index] = (UINT8) (val >> 4); + if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index]) + ERREXIT1(cinfo, JERR_DAC_VALUE, val); + } + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + +#else /* ! D_ARITH_CODING_SUPPORTED */ + +#define get_dac(cinfo) skip_variable(cinfo) + +#endif /* D_ARITH_CODING_SUPPORTED */ + + +LOCAL(boolean) +get_dht (j_decompress_ptr cinfo) +/* Process a DHT marker */ +{ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL **htblptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 16) { + INPUT_BYTE(cinfo, index, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DHT, index); + + bits[0] = 0; + count = 0; + for (i = 1; i <= 16; i++) { + INPUT_BYTE(cinfo, bits[i], return FALSE); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8]); + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16]); + + /* Here we just do minimal validation of the counts to avoid walking + * off the end of our table space. jdhuff.c will check more carefully. + */ + if (count > 256 || ((INT32) count) > length) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + + for (i = 0; i < count; i++) + INPUT_BYTE(cinfo, huffval[i], return FALSE); + + length -= count; + + if (index & 0x10) { /* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if (index < 0 || index >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_DHT_INDEX, index); + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval)); + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_dqt (j_decompress_ptr cinfo) +/* Process a DQT marker */ +{ + INT32 length, count, i; + int n, prec; + unsigned int tmp; + JQUANT_TBL *quant_ptr; + const int *natural_order; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + length--; + INPUT_BYTE(cinfo, n, return FALSE); + prec = n >> 4; + n &= 0x0F; + + TRACEMS2(cinfo, 1, JTRC_DQT, n, prec); + + if (n >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, n); + + if (cinfo->quant_tbl_ptrs[n] == NULL) + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo); + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + if (prec) { + if (length < DCTSIZE2 * 2) { + /* Initialize full table for safety. */ + for (i = 0; i < DCTSIZE2; i++) { + quant_ptr->quantval[i] = 1; + } + count = length >> 1; + } else + count = DCTSIZE2; + } else { + if (length < DCTSIZE2) { + /* Initialize full table for safety. */ + for (i = 0; i < DCTSIZE2; i++) { + quant_ptr->quantval[i] = 1; + } + count = length; + } else + count = DCTSIZE2; + } + + switch (count) { + case (2*2): natural_order = jpeg_natural_order2; break; + case (3*3): natural_order = jpeg_natural_order3; break; + case (4*4): natural_order = jpeg_natural_order4; break; + case (5*5): natural_order = jpeg_natural_order5; break; + case (6*6): natural_order = jpeg_natural_order6; break; + case (7*7): natural_order = jpeg_natural_order7; break; + default: natural_order = jpeg_natural_order; break; + } + + for (i = 0; i < count; i++) { + if (prec) + INPUT_2BYTES(cinfo, tmp, return FALSE); + else + INPUT_BYTE(cinfo, tmp, return FALSE); + /* We convert the zigzag-order table to natural array order. */ + quant_ptr->quantval[natural_order[i]] = (UINT16) tmp; + } + + if (cinfo->err->trace_level >= 2) { + for (i = 0; i < DCTSIZE2; i += 8) { + TRACEMS8(cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i], quant_ptr->quantval[i+1], + quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], + quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], + quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); + } + } + + length -= count; + if (prec) length -= count; + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_dri (j_decompress_ptr cinfo) +/* Process a DRI marker */ +{ + INT32 length; + unsigned int tmp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + if (length != 4) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_2BYTES(cinfo, tmp, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DRI, tmp); + + cinfo->restart_interval = tmp; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Routines for processing APPn and COM markers. + * These are either saved in memory or discarded, per application request. + * APP0 and APP14 are specially checked to see if they are + * JFIF and Adobe markers, respectively. + */ + +#define APP0_DATA_LEN 14 /* Length of interesting data in APP0 */ +#define APP14_DATA_LEN 12 /* Length of interesting data in APP14 */ +#define APPN_DATA_LEN 14 /* Must be the largest of the above!! */ + + +LOCAL(void) +examine_app0 (j_decompress_ptr cinfo, JOCTET FAR * data, + unsigned int datalen, INT32 remaining) +/* Examine first few bytes from an APP0. + * Take appropriate action if it is a JFIF marker. + * datalen is # of bytes at data[], remaining is length of rest of marker data. + */ +{ + INT32 totallen = (INT32) datalen + remaining; + + if (datalen >= APP0_DATA_LEN && + GETJOCTET(data[0]) == 0x4A && + GETJOCTET(data[1]) == 0x46 && + GETJOCTET(data[2]) == 0x49 && + GETJOCTET(data[3]) == 0x46 && + GETJOCTET(data[4]) == 0) { + /* Found JFIF APP0 marker: save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->JFIF_major_version = GETJOCTET(data[5]); + cinfo->JFIF_minor_version = GETJOCTET(data[6]); + cinfo->density_unit = GETJOCTET(data[7]); + cinfo->X_density = (GETJOCTET(data[8]) << 8) + GETJOCTET(data[9]); + cinfo->Y_density = (GETJOCTET(data[10]) << 8) + GETJOCTET(data[11]); + /* Check version. + * Major version must be 1, anything else signals an incompatible change. + * (We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec.) + * Minor version should be 0..2, but process anyway if newer. + */ + if (cinfo->JFIF_major_version != 1) + WARNMS2(cinfo, JWRN_JFIF_MAJOR, + cinfo->JFIF_major_version, cinfo->JFIF_minor_version); + /* Generate trace messages */ + TRACEMS5(cinfo, 1, JTRC_JFIF, + cinfo->JFIF_major_version, cinfo->JFIF_minor_version, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit); + /* Validate thumbnail dimensions and issue appropriate messages */ + if (GETJOCTET(data[12]) | GETJOCTET(data[13])) + TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL, + GETJOCTET(data[12]), GETJOCTET(data[13])); + totallen -= APP0_DATA_LEN; + if (totallen != + ((INT32)GETJOCTET(data[12]) * (INT32)GETJOCTET(data[13]) * (INT32) 3)) + TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) totallen); + } else if (datalen >= 6 && + GETJOCTET(data[0]) == 0x4A && + GETJOCTET(data[1]) == 0x46 && + GETJOCTET(data[2]) == 0x58 && + GETJOCTET(data[3]) == 0x58 && + GETJOCTET(data[4]) == 0) { + /* Found JFIF "JFXX" extension APP0 marker */ + /* The library doesn't actually do anything with these, + * but we try to produce a helpful trace message. + */ + switch (GETJOCTET(data[5])) { + case 0x10: + TRACEMS1(cinfo, 1, JTRC_THUMB_JPEG, (int) totallen); + break; + case 0x11: + TRACEMS1(cinfo, 1, JTRC_THUMB_PALETTE, (int) totallen); + break; + case 0x13: + TRACEMS1(cinfo, 1, JTRC_THUMB_RGB, (int) totallen); + break; + default: + TRACEMS2(cinfo, 1, JTRC_JFIF_EXTENSION, + GETJOCTET(data[5]), (int) totallen); + break; + } + } else { + /* Start of APP0 does not match "JFIF" or "JFXX", or too short */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) totallen); + } +} + + +LOCAL(void) +examine_app14 (j_decompress_ptr cinfo, JOCTET FAR * data, + unsigned int datalen, INT32 remaining) +/* Examine first few bytes from an APP14. + * Take appropriate action if it is an Adobe marker. + * datalen is # of bytes at data[], remaining is length of rest of marker data. + */ +{ + unsigned int version, flags0, flags1, transform; + + if (datalen >= APP14_DATA_LEN && + GETJOCTET(data[0]) == 0x41 && + GETJOCTET(data[1]) == 0x64 && + GETJOCTET(data[2]) == 0x6F && + GETJOCTET(data[3]) == 0x62 && + GETJOCTET(data[4]) == 0x65) { + /* Found Adobe APP14 marker */ + version = (GETJOCTET(data[5]) << 8) + GETJOCTET(data[6]); + flags0 = (GETJOCTET(data[7]) << 8) + GETJOCTET(data[8]); + flags1 = (GETJOCTET(data[9]) << 8) + GETJOCTET(data[10]); + transform = GETJOCTET(data[11]); + TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe", or too short */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) (datalen + remaining)); + } +} + + +METHODDEF(boolean) +get_interesting_appn (j_decompress_ptr cinfo) +/* Process an APP0 or APP14 marker without saving it */ +{ + INT32 length; + JOCTET b[APPN_DATA_LEN]; + unsigned int i, numtoread; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* get the interesting part of the marker data */ + if (length >= APPN_DATA_LEN) + numtoread = APPN_DATA_LEN; + else if (length > 0) + numtoread = (unsigned int) length; + else + numtoread = 0; + for (i = 0; i < numtoread; i++) + INPUT_BYTE(cinfo, b[i], return FALSE); + length -= numtoread; + + /* process it */ + switch (cinfo->unread_marker) { + case M_APP0: + examine_app0(cinfo, (JOCTET FAR *) b, numtoread, length); + break; + case M_APP14: + examine_app14(cinfo, (JOCTET FAR *) b, numtoread, length); + break; + default: + /* can't get here unless jpeg_save_markers chooses wrong processor */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + + /* skip any remaining data -- could be lots */ + INPUT_SYNC(cinfo); + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +#ifdef SAVE_MARKERS_SUPPORTED + +METHODDEF(boolean) +save_marker (j_decompress_ptr cinfo) +/* Save an APPn or COM marker into the marker list */ +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + jpeg_saved_marker_ptr cur_marker = marker->cur_marker; + unsigned int bytes_read, data_length; + JOCTET FAR * data; + INT32 length = 0; + INPUT_VARS(cinfo); + + if (cur_marker == NULL) { + /* begin reading a marker */ + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + if (length >= 0) { /* watch out for bogus length word */ + /* figure out how much we want to save */ + unsigned int limit; + if (cinfo->unread_marker == (int) M_COM) + limit = marker->length_limit_COM; + else + limit = marker->length_limit_APPn[cinfo->unread_marker - (int) M_APP0]; + if ((unsigned int) length < limit) + limit = (unsigned int) length; + /* allocate and initialize the marker item */ + cur_marker = (jpeg_saved_marker_ptr) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(struct jpeg_marker_struct) + limit); + cur_marker->next = NULL; + cur_marker->marker = (UINT8) cinfo->unread_marker; + cur_marker->original_length = (unsigned int) length; + cur_marker->data_length = limit; + /* data area is just beyond the jpeg_marker_struct */ + data = cur_marker->data = (JOCTET FAR *) (cur_marker + 1); + marker->cur_marker = cur_marker; + marker->bytes_read = 0; + bytes_read = 0; + data_length = limit; + } else { + /* deal with bogus length word */ + bytes_read = data_length = 0; + data = NULL; + } + } else { + /* resume reading a marker */ + bytes_read = marker->bytes_read; + data_length = cur_marker->data_length; + data = cur_marker->data + bytes_read; + } + + while (bytes_read < data_length) { + INPUT_SYNC(cinfo); /* move the restart point to here */ + marker->bytes_read = bytes_read; + /* If there's not at least one byte in buffer, suspend */ + MAKE_BYTE_AVAIL(cinfo, return FALSE); + /* Copy bytes with reasonable rapidity */ + while (bytes_read < data_length && bytes_in_buffer > 0) { + *data++ = *next_input_byte++; + bytes_in_buffer--; + bytes_read++; + } + } + + /* Done reading what we want to read */ + if (cur_marker != NULL) { /* will be NULL if bogus length word */ + /* Add new marker to end of list */ + if (cinfo->marker_list == NULL) { + cinfo->marker_list = cur_marker; + } else { + jpeg_saved_marker_ptr prev = cinfo->marker_list; + while (prev->next != NULL) + prev = prev->next; + prev->next = cur_marker; + } + /* Reset pointer & calc remaining data length */ + data = cur_marker->data; + length = cur_marker->original_length - data_length; + } + /* Reset to initial state for next marker */ + marker->cur_marker = NULL; + + /* Process the marker if interesting; else just make a generic trace msg */ + switch (cinfo->unread_marker) { + case M_APP0: + examine_app0(cinfo, data, data_length, length); + break; + case M_APP14: + examine_app14(cinfo, data, data_length, length); + break; + default: + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, + (int) (data_length + length)); + break; + } + + /* skip any remaining data -- could be lots */ + INPUT_SYNC(cinfo); /* do before skip_input_data */ + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + +#endif /* SAVE_MARKERS_SUPPORTED */ + + +METHODDEF(boolean) +skip_variable (j_decompress_ptr cinfo) +/* Skip over an unknown or uninteresting variable-length marker */ +{ + INT32 length; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length); + + INPUT_SYNC(cinfo); /* do before skip_input_data */ + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL(boolean) +next_marker (j_decompress_ptr cinfo) +{ + int c; + INPUT_VARS(cinfo); + + for (;;) { + INPUT_BYTE(cinfo, c, return FALSE); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while (c != 0xFF) { + cinfo->marker->discarded_bytes++; + INPUT_SYNC(cinfo); + INPUT_BYTE(cinfo, c, return FALSE); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE(cinfo, c, return FALSE); + } while (c == 0xFF); + if (c != 0) + break; /* found a valid marker, exit loop */ + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC(cinfo); + } + + if (cinfo->marker->discarded_bytes != 0) { + WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +first_marker (j_decompress_ptr cinfo) +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ +{ + int c, c2; + INPUT_VARS(cinfo); + + INPUT_BYTE(cinfo, c, return FALSE); + INPUT_BYTE(cinfo, c2, return FALSE); + if (c != 0xFF || c2 != (int) M_SOI) + ERREXIT2(cinfo, JERR_NO_SOI, c, c2); + + cinfo->unread_marker = c2; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * Note: This function may return a pseudo SOS marker (with zero + * component number) for treat by input controller's consume_input. + * consume_input itself should filter out (skip) the pseudo marker + * after processing for the caller. + */ + +METHODDEF(int) +read_markers (j_decompress_ptr cinfo) +{ + /* Outer loop repeats once for each marker. */ + for (;;) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if (cinfo->unread_marker == 0) { + if (! cinfo->marker->saw_SOI) { + if (! first_marker(cinfo)) + return JPEG_SUSPENDED; + } else { + if (! next_marker(cinfo)) + return JPEG_SUSPENDED; + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch (cinfo->unread_marker) { + case M_SOI: + if (! get_soi(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_SOF0: /* Baseline */ + if (! get_sof(cinfo, TRUE, FALSE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF1: /* Extended sequential, Huffman */ + if (! get_sof(cinfo, FALSE, FALSE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF2: /* Progressive, Huffman */ + if (! get_sof(cinfo, FALSE, TRUE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF9: /* Extended sequential, arithmetic */ + if (! get_sof(cinfo, FALSE, FALSE, TRUE)) + return JPEG_SUSPENDED; + break; + + case M_SOF10: /* Progressive, arithmetic */ + if (! get_sof(cinfo, FALSE, TRUE, TRUE)) + return JPEG_SUSPENDED; + break; + + /* Currently unsupported SOFn types */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker); + break; + + case M_SOS: + if (! get_sos(cinfo)) + return JPEG_SUSPENDED; + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS(cinfo, 1, JTRC_EOI); + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if (! get_dac(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DHT: + if (! get_dht(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DQT: + if (! get_dqt(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DRI: + if (! get_dri(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if (! (*((my_marker_ptr) cinfo->marker)->process_APPn[ + cinfo->unread_marker - (int) M_APP0]) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_COM: + if (! (*((my_marker_ptr) cinfo->marker)->process_COM) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_RST0: /* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if (! skip_variable(cinfo)) + return JPEG_SUSPENDED; + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF(boolean) +read_restart_marker (j_decompress_ptr cinfo) +{ + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if (cinfo->unread_marker == 0) { + if (! next_marker(cinfo)) + return FALSE; + } + + if (cinfo->unread_marker == + ((int) M_RST0 + cinfo->marker->next_restart_num)) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1(cinfo, 3, JTRC_RST, cinfo->marker->next_restart_num); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if (! (*cinfo->src->resync_to_restart) (cinfo, + cinfo->marker->next_restart_num)) + return FALSE; + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = (cinfo->marker->next_restart_num + 1) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL(boolean) +jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) +{ + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;;) { + if (marker < (int) M_SOF0) + action = 2; /* invalid marker */ + else if (marker < (int) M_RST0 || marker > (int) M_RST7) + action = 3; /* valid non-restart marker */ + else { + if (marker == ((int) M_RST0 + ((desired+1) & 7)) || + marker == ((int) M_RST0 + ((desired+2) & 7))) + action = 3; /* one of the next two expected restarts */ + else if (marker == ((int) M_RST0 + ((desired-1) & 7)) || + marker == ((int) M_RST0 + ((desired-2) & 7))) + action = 2; /* a prior restart, so advance */ + else + action = 1; /* desired restart or too far away */ + } + TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action); + switch (action) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if (! next_marker(cinfo)) + return FALSE; + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF(void) +reset_marker_reader (j_decompress_ptr cinfo) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + marker->pub.saw_SOI = FALSE; /* set internal state too */ + marker->pub.saw_SOF = FALSE; + marker->pub.discarded_bytes = 0; + marker->cur_marker = NULL; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL(void) +jinit_marker_reader (j_decompress_ptr cinfo) +{ + my_marker_ptr marker; + int i; + + /* Create subobject in permanent pool */ + marker = (my_marker_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_marker_reader)); + cinfo->marker = (struct jpeg_marker_reader *) marker; + /* Initialize public method pointers */ + marker->pub.reset_marker_reader = reset_marker_reader; + marker->pub.read_markers = read_markers; + marker->pub.read_restart_marker = read_restart_marker; + /* Initialize COM/APPn processing. + * By default, we examine and then discard APP0 and APP14, + * but simply discard COM and all other APPn. + */ + marker->process_COM = skip_variable; + marker->length_limit_COM = 0; + for (i = 0; i < 16; i++) { + marker->process_APPn[i] = skip_variable; + marker->length_limit_APPn[i] = 0; + } + marker->process_APPn[0] = get_interesting_appn; + marker->process_APPn[14] = get_interesting_appn; + /* Reset marker processing state */ + reset_marker_reader(cinfo); +} + + +/* + * Control saving of COM and APPn markers into marker_list. + */ + +#ifdef SAVE_MARKERS_SUPPORTED + +GLOBAL(void) +jpeg_save_markers (j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + long maxlength; + jpeg_marker_parser_method processor; + + /* Length limit mustn't be larger than what we can allocate + * (should only be a concern in a 16-bit environment). + */ + maxlength = cinfo->mem->max_alloc_chunk - SIZEOF(struct jpeg_marker_struct); + if (((long) length_limit) > maxlength) + length_limit = (unsigned int) maxlength; + + /* Choose processor routine to use. + * APP0/APP14 have special requirements. + */ + if (length_limit) { + processor = save_marker; + /* If saving APP0/APP14, save at least enough for our internal use. */ + if (marker_code == (int) M_APP0 && length_limit < APP0_DATA_LEN) + length_limit = APP0_DATA_LEN; + else if (marker_code == (int) M_APP14 && length_limit < APP14_DATA_LEN) + length_limit = APP14_DATA_LEN; + } else { + processor = skip_variable; + /* If discarding APP0/APP14, use our regular on-the-fly processor. */ + if (marker_code == (int) M_APP0 || marker_code == (int) M_APP14) + processor = get_interesting_appn; + } + + if (marker_code == (int) M_COM) { + marker->process_COM = processor; + marker->length_limit_COM = length_limit; + } else if (marker_code >= (int) M_APP0 && marker_code <= (int) M_APP15) { + marker->process_APPn[marker_code - (int) M_APP0] = processor; + marker->length_limit_APPn[marker_code - (int) M_APP0] = length_limit; + } else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} + +#endif /* SAVE_MARKERS_SUPPORTED */ + + +/* + * Install a special processing method for COM or APPn markers. + */ + +GLOBAL(void) +jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + + if (marker_code == (int) M_COM) + marker->process_COM = routine; + else if (marker_code >= (int) M_APP0 && marker_code <= (int) M_APP15) + marker->process_APPn[marker_code - (int) M_APP0] = routine; + else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} diff --git a/src/jpeg-8c/jdmaster.c b/src/jpeg-8c/jdmaster.c new file mode 100644 index 0000000..8c1146e --- /dev/null +++ b/src/jpeg-8c/jdmaster.c @@ -0,0 +1,533 @@ +/* + * jdmaster.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 2002-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub; /* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL(boolean) +use_merged_upsample (j_decompress_ptr cinfo) +{ +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if (cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling) + return FALSE; + /* jdmerge.c only supports YCC=>RGB color conversion */ + if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 || + cinfo->out_color_space != JCS_RGB || + cinfo->out_color_components != RGB_PIXELSIZE) + return FALSE; + /* and it only handles 2h1v or 2h2v sampling ratios */ + if (cinfo->comp_info[0].h_samp_factor != 2 || + cinfo->comp_info[1].h_samp_factor != 1 || + cinfo->comp_info[2].h_samp_factor != 1 || + cinfo->comp_info[0].v_samp_factor > 2 || + cinfo->comp_info[1].v_samp_factor != 1 || + cinfo->comp_info[2].v_samp_factor != 1) + return FALSE; + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if (cinfo->comp_info[0].DCT_h_scaled_size != cinfo->min_DCT_h_scaled_size || + cinfo->comp_info[1].DCT_h_scaled_size != cinfo->min_DCT_h_scaled_size || + cinfo->comp_info[2].DCT_h_scaled_size != cinfo->min_DCT_h_scaled_size || + cinfo->comp_info[0].DCT_v_scaled_size != cinfo->min_DCT_v_scaled_size || + cinfo->comp_info[1].DCT_v_scaled_size != cinfo->min_DCT_v_scaled_size || + cinfo->comp_info[2].DCT_v_scaled_size != cinfo->min_DCT_v_scaled_size) + return FALSE; + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ +#else + return FALSE; +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL(void) +jpeg_calc_output_dimensions (j_decompress_ptr cinfo) +/* Do computations that are needed before master selection phase. + * This function is used for full decompression. + */ +{ +#ifdef IDCT_SCALING_SUPPORTED + int ci; + jpeg_component_info *compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_READY) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Compute core output image dimensions and DCT scaling choices. */ + jpeg_core_output_dimensions(cinfo); + +#ifdef IDCT_SCALING_SUPPORTED + + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code adapts subsampling ratios which are powers of 2. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + int ssize = 1; + while (cinfo->min_DCT_h_scaled_size * ssize <= + (cinfo->do_fancy_upsampling ? DCTSIZE : DCTSIZE / 2) && + (cinfo->max_h_samp_factor % (compptr->h_samp_factor * ssize * 2)) == 0) { + ssize = ssize * 2; + } + compptr->DCT_h_scaled_size = cinfo->min_DCT_h_scaled_size * ssize; + ssize = 1; + while (cinfo->min_DCT_v_scaled_size * ssize <= + (cinfo->do_fancy_upsampling ? DCTSIZE : DCTSIZE / 2) && + (cinfo->max_v_samp_factor % (compptr->v_samp_factor * ssize * 2)) == 0) { + ssize = ssize * 2; + } + compptr->DCT_v_scaled_size = cinfo->min_DCT_v_scaled_size * ssize; + + /* We don't support IDCT ratios larger than 2. */ + if (compptr->DCT_h_scaled_size > compptr->DCT_v_scaled_size * 2) + compptr->DCT_h_scaled_size = compptr->DCT_v_scaled_size * 2; + else if (compptr->DCT_v_scaled_size > compptr->DCT_h_scaled_size * 2) + compptr->DCT_v_scaled_size = compptr->DCT_h_scaled_size * 2; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * + (long) (compptr->h_samp_factor * compptr->DCT_h_scaled_size), + (long) (cinfo->max_h_samp_factor * cinfo->block_size)); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * + (long) (compptr->v_samp_factor * compptr->DCT_v_scaled_size), + (long) (cinfo->max_v_samp_factor * cinfo->block_size)); + } + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = (cinfo->quantize_colors ? 1 : + cinfo->out_color_components); + + /* See if upsampler will want to emit more than one row at a time */ + if (use_merged_upsample(cinfo)) + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + else + cinfo->rec_outbuf_height = 1; +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL(void) +prepare_range_limit_table (j_decompress_ptr cinfo) +/* Allocate and fill in the sample_range_limit table */ +{ + JSAMPLE * table; + int i; + + table = (JSAMPLE *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE)); + /* Main part of "simple" table: limit[x] = x */ + for (i = 0; i <= MAXJSAMPLE; i++) + table[i] = (JSAMPLE) i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + MEMZERO(table + (2 * (MAXJSAMPLE+1)), + (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL(void) +master_selection (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions(cinfo); + prepare_range_limit_table(cinfo); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample(cinfo); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if (! cinfo->quantize_colors || ! cinfo->buffered_image) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if (cinfo->quantize_colors) { + if (cinfo->raw_data_out) + ERREXIT(cinfo, JERR_NOTIMPL); + /* 2-pass quantizer only works in 3-component color space. */ + if (cinfo->out_color_components != 3) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if (cinfo->colormap != NULL) { + cinfo->enable_external_quant = TRUE; + } else if (cinfo->two_pass_quantize) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if (cinfo->enable_1pass_quant) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer(cinfo); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if (cinfo->enable_2pass_quant || cinfo->enable_external_quant) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer(cinfo); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if (! cinfo->raw_data_out) { + if (master->using_merged_upsample) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler(cinfo); /* does color conversion too */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + jinit_color_deconverter(cinfo); + jinit_upsampler(cinfo); + } + jinit_d_post_controller(cinfo, cinfo->enable_2pass_quant); + } + /* Inverse DCT */ + jinit_inverse_dct(cinfo); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) + jinit_arith_decoder(cinfo); + else { + jinit_huff_decoder(cinfo); + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller(cinfo, use_c_buffer); + + if (! cinfo->raw_data_out) + jinit_d_main_controller(cinfo, FALSE /* never need full buffer here */); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if (cinfo->progress != NULL && ! cinfo->buffered_image && + cinfo->inputctl->has_multiple_scans) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = (cinfo->enable_2pass_quant ? 3 : 2); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapistd.c will crank the pass to completion.) + */ + +METHODDEF(void) +prepare_for_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (master->pub.is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + (*cinfo->cquantize->start_pass) (cinfo, FALSE); + (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST); + (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if (cinfo->quantize_colors && cinfo->colormap == NULL) { + /* Select new quantization method */ + if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if (cinfo->enable_1pass_quant) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT(cinfo, JERR_MODE_CHANGE); + } + } + (*cinfo->idct->start_pass) (cinfo); + (*cinfo->coef->start_output_pass) (cinfo); + if (! cinfo->raw_data_out) { + if (! master->using_merged_upsample) + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->upsample->start_pass) (cinfo); + if (cinfo->quantize_colors) + (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); + (*cinfo->post->start_pass) (cinfo, + (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + } + } + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + (master->pub.is_dummy_pass ? 2 : 1); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if (cinfo->buffered_image && ! cinfo->inputctl->eoi_reached) { + cinfo->progress->total_passes += (cinfo->enable_2pass_quant ? 2 : 1); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF(void) +finish_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (cinfo->quantize_colors) + (*cinfo->cquantize->finish_pass) (cinfo); + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL(void) +jpeg_new_colormap (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_BUFIMAGE) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->quantize_colors && cinfo->enable_external_quant && + cinfo->colormap != NULL) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + (*cinfo->cquantize->new_color_map) (cinfo); + master->pub.is_dummy_pass = FALSE; /* just in case */ + } else + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL(void) +jinit_master_decompress (j_decompress_ptr cinfo) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_decomp_master)); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection(cinfo); +} diff --git a/src/jpeg-8c/jdmerge.c b/src/jpeg-8c/jdmerge.c new file mode 100644 index 0000000..3744446 --- /dev/null +++ b/src/jpeg-8c/jdmerge.c @@ -0,0 +1,400 @@ +/* + * jdmerge.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains code for merged upsampling/color conversion. + * + * This file combines functions from jdsample.c and jdcolor.c; + * read those files first to understand what's going on. + * + * When the chroma components are to be upsampled by simple replication + * (ie, box filtering), we can save some work in color conversion by + * calculating all the output pixels corresponding to a pair of chroma + * samples at one time. In the conversion equations + * R = Y + K1 * Cr + * G = Y + K2 * Cb + K3 * Cr + * B = Y + K4 * Cb + * only the Y term varies among the group of pixels corresponding to a pair + * of chroma samples, so the rest of the terms can be calculated just once. + * At typical sampling ratios, this eliminates half or three-quarters of the + * multiplications needed for color conversion. + * + * This file currently provides implementations for the following cases: + * YCbCr => RGB color conversion only. + * Sampling ratios of 2h1v or 2h2v. + * No scaling needed at upsample time. + * Corner-aligned (non-CCIR601) sampling alignment. + * Other special cases could be added, but in most applications these are + * the only common cases. (For uncommon cases we fall back on the more + * general code in jdsample.c and jdcolor.c.) + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef UPSAMPLE_MERGING_SUPPORTED + + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Pointer to routine to do actual upsampling/conversion of one row group */ + JMETHOD(void, upmethod, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf)); + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ + + /* For 2:1 vertical sampling, we produce two output rows at a time. + * We need a "spare" row buffer to hold the second output row if the + * application provides just a one-row buffer; we also use the spare + * to discard the dummy last row if the image height is odd. + */ + JSAMPROW spare_row; + boolean spare_full; /* T if spare buffer is occupied */ + + JDIMENSION out_row_width; /* samples per output row */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + * This is taken directly from jdcolor.c; see that file for more info. + */ + +LOCAL(void) +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int i; + INT32 x; + SHIFT_TEMPS + + upsample->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + upsample->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + upsample->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + upsample->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + upsample->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + upsample->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + upsample->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + upsample->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF(void) +start_pass_merged_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the spare buffer empty */ + upsample->spare_full = FALSE; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * The control routine just handles the row buffering considerations. + */ + +METHODDEF(void) +merged_2v_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +/* 2:1 vertical sampling case: may need a spare row. */ +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPROW work_ptrs[2]; + JDIMENSION num_rows; /* number of rows returned to caller */ + + if (upsample->spare_full) { + /* If we have a spare row saved from a previous cycle, just return it. */ + jcopy_sample_rows(& upsample->spare_row, 0, output_buf + *out_row_ctr, 0, + 1, upsample->out_row_width); + num_rows = 1; + upsample->spare_full = FALSE; + } else { + /* Figure number of rows to return to caller. */ + num_rows = 2; + /* Not more than the distance to the end of the image. */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + /* Create output pointer array for upsampler. */ + work_ptrs[0] = output_buf[*out_row_ctr]; + if (num_rows > 1) { + work_ptrs[1] = output_buf[*out_row_ctr + 1]; + } else { + work_ptrs[1] = upsample->spare_row; + upsample->spare_full = TRUE; + } + /* Now do the upsampling. */ + (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, work_ptrs); + } + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (! upsample->spare_full) + (*in_row_group_ctr)++; +} + + +METHODDEF(void) +merged_1v_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +/* 1:1 vertical sampling case: much easier, never need a spare row. */ +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Just do the upsampling. */ + (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, + output_buf + *out_row_ctr); + /* Adjust counts */ + (*out_row_ctr)++; + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by the control routines to do + * the actual upsampling/conversion. One row group is processed per call. + * + * Note: since we may be writing directly into application-supplied buffers, + * we have to be honest about the output width; we can't assume the buffer + * has been rounded up to an even width. + */ + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 1:1 vertical. + */ + +METHODDEF(void) +h2v1_merged_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr; + JSAMPROW inptr0, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr0 = input_buf[0][in_row_group_ctr]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr = output_buf[0]; + /* Loop for each pair of output pixels */ + for (col = cinfo->output_width >> 1; col > 0; col--) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE(*inptr1++); + cr = GETJSAMPLE(*inptr2++); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + /* Fetch 2 Y values and emit 2 pixels */ + y = GETJSAMPLE(*inptr0++); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr0++); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if (cinfo->output_width & 1) { + cb = GETJSAMPLE(*inptr1); + cr = GETJSAMPLE(*inptr2); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + y = GETJSAMPLE(*inptr0); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 2:1 vertical. + */ + +METHODDEF(void) +h2v2_merged_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr0, outptr1; + JSAMPROW inptr00, inptr01, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr00 = input_buf[0][in_row_group_ctr*2]; + inptr01 = input_buf[0][in_row_group_ctr*2 + 1]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr0 = output_buf[0]; + outptr1 = output_buf[1]; + /* Loop for each group of output pixels */ + for (col = cinfo->output_width >> 1; col > 0; col--) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE(*inptr1++); + cr = GETJSAMPLE(*inptr2++); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + /* Fetch 4 Y values and emit 4 pixels */ + y = GETJSAMPLE(*inptr00++); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr00++); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr01++); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr01++); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if (cinfo->output_width & 1) { + cb = GETJSAMPLE(*inptr1); + cr = GETJSAMPLE(*inptr2); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + y = GETJSAMPLE(*inptr00); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + y = GETJSAMPLE(*inptr01); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Module initialization routine for merged upsampling/color conversion. + * + * NB: this is called under the conditions determined by use_merged_upsample() + * in jdmaster.c. That routine MUST correspond to the actual capabilities + * of this module; no safety checks are made here. + */ + +GLOBAL(void) +jinit_merged_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_merged_upsample; + upsample->pub.need_context_rows = FALSE; + + upsample->out_row_width = cinfo->output_width * cinfo->out_color_components; + + if (cinfo->max_v_samp_factor == 2) { + upsample->pub.upsample = merged_2v_upsample; + upsample->upmethod = h2v2_merged_upsample; + /* Allocate a spare row buffer */ + upsample->spare_row = (JSAMPROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) (upsample->out_row_width * SIZEOF(JSAMPLE))); + } else { + upsample->pub.upsample = merged_1v_upsample; + upsample->upmethod = h2v1_merged_upsample; + /* No spare row needed */ + upsample->spare_row = NULL; + } + + build_ycc_rgb_table(cinfo); +} + +#endif /* UPSAMPLE_MERGING_SUPPORTED */ diff --git a/src/jpeg-8c/jdpostct.c b/src/jpeg-8c/jdpostct.c new file mode 100644 index 0000000..571563d --- /dev/null +++ b/src/jpeg-8c/jdpostct.c @@ -0,0 +1,290 @@ +/* + * jdpostct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the decompression postprocessing controller. + * This controller manages the upsampling, color conversion, and color + * quantization/reduction steps; specifically, it controls the buffering + * between upsample/color conversion and color quantization/reduction. + * + * If no color quantization/reduction is required, then this module has no + * work to do, and it just hands off to the upsample/color conversion code. + * An integrated upsample/convert/quantize process would replace this module + * entirely. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_post_controller pub; /* public fields */ + + /* Color quantization source buffer: this holds output data from + * the upsample/color conversion step to be passed to the quantizer. + * For two-pass color quantization, we need a full-image buffer; + * for one-pass operation, a strip buffer is sufficient. + */ + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ + /* for two-pass mode only: */ + JDIMENSION starting_row; /* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ +} my_post_controller; + +typedef my_post_controller * my_post_ptr; + + +/* Forward declarations */ +METHODDEF(void) post_process_1pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF(void) post_process_prepass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +METHODDEF(void) post_process_2pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->quantize_colors) { + /* Single-pass processing with color quantization. */ + post->pub.post_process_data = post_process_1pass; + /* We could be doing buffered-image output before starting a 2-pass + * color quantization; in that case, jinit_d_post_controller did not + * allocate a strip buffer. Use the virtual-array buffer as workspace. + */ + if (post->buffer == NULL) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE); + } + } else { + /* For single-pass processing without color quantization, + * I have no work to do; just call the upsampler directly. + */ + post->pub.post_process_data = cinfo->upsample->upsample; + } + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_SAVE_AND_PASS: + /* First pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_prepass; + break; + case JBUF_CRANK_DEST: + /* Second pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_2pass; + break; +#endif /* QUANT_2PASS_SUPPORTED */ + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } + post->starting_row = post->next_row = 0; +} + + +/* + * Process some data in the one-pass (strip buffer) case. + * This is used for color precision reduction as well as one-pass quantization. + */ + +METHODDEF(void) +post_process_1pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Fill the buffer, but not more than what we can dump out in one go. */ + /* Note we rely on the upsampler to detect bottom of image. */ + max_rows = out_rows_avail - *out_row_ctr; + if (max_rows > post->strip_height) + max_rows = post->strip_height; + num_rows = 0; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows); + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer, output_buf + *out_row_ctr, (int) num_rows); + *out_row_ctr += num_rows; +} + + +#ifdef QUANT_2PASS_SUPPORTED + +/* + * Process some data in the first pass of 2-pass quantization. + */ + +METHODDEF(void) +post_process_prepass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION old_next_row, num_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE); + } + + /* Upsample some data (up to a strip height's worth). */ + old_next_row = post->next_row; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height); + + /* Allow quantizer to scan new data. No data is emitted, */ + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + if (post->next_row > old_next_row) { + num_rows = post->next_row - old_next_row; + (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row, + (JSAMPARRAY) NULL, (int) num_rows); + *out_row_ctr += num_rows; + } + + /* Advance if we filled the strip. */ + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + + +/* + * Process some data in the second pass of 2-pass quantization. + */ + +METHODDEF(void) +post_process_2pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE); + } + + /* Determine number of rows to emit. */ + num_rows = post->strip_height - post->next_row; /* available in strip */ + max_rows = out_rows_avail - *out_row_ctr; /* available in output area */ + if (num_rows > max_rows) + num_rows = max_rows; + /* We have to check bottom of image here, can't depend on upsampler. */ + max_rows = cinfo->output_height - post->starting_row; + if (num_rows > max_rows) + num_rows = max_rows; + + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows); + *out_row_ctr += num_rows; + + /* Advance if we filled the strip. */ + post->next_row += num_rows; + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize postprocessing controller. + */ + +GLOBAL(void) +jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_post_ptr post; + + post = (my_post_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_post_controller)); + cinfo->post = (struct jpeg_d_post_controller *) post; + post->pub.start_pass = start_pass_dpost; + post->whole_image = NULL; /* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ + + /* Create the quantization buffer, if needed */ + if (cinfo->quantize_colors) { + /* The buffer strip height is max_v_samp_factor, which is typically + * an efficient number of rows for upsampling to return. + * (In the presence of output rescaling, we might want to be smarter?) + */ + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + if (need_full_buffer) { + /* Two-pass color quantization: need full-image storage. */ + /* We round up the number of rows to a multiple of the strip height. */ +#ifdef QUANT_2PASS_SUPPORTED + post->whole_image = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up((long) cinfo->output_height, + (long) post->strip_height), + post->strip_height); +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + /* One-pass color quantization: just make a strip buffer. */ + post->buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height); + } + } +} diff --git a/src/jpeg-8c/jdsample.c b/src/jpeg-8c/jdsample.c new file mode 100644 index 0000000..7bc8885 --- /dev/null +++ b/src/jpeg-8c/jdsample.c @@ -0,0 +1,361 @@ +/* + * jdsample.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * Modified 2002-2008 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains upsampling routines. + * + * Upsampling input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_v_scaled_size / min_DCT_v_scaled_size) + * sample rows of each component. Upsampling will normally produce + * max_v_samp_factor pixel rows from each row group (but this could vary + * if the upsampler is applying a scale factor of its own). + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to upsample a single component */ +typedef JMETHOD(void, upsample1_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Color conversion buffer. When using separate upsampling and color + * conversion steps, this buffer holds one upsampled row group until it + * has been color converted and output. + * Note: we do not allocate any storage for component(s) which are full-size, + * ie do not need rescaling. The corresponding entry of color_buf[] is + * simply set to point to the input data array, thereby avoiding copying. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + /* Per-component upsampling method pointers */ + upsample1_ptr methods[MAX_COMPONENTS]; + + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + /* Height of an input row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_expand need not + * recompute them each time. They are unused for other upsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF(void) +start_pass_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the conversion buffer empty */ + upsample->next_row_out = cinfo->max_v_samp_factor; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * In this version we upsample each component independently. + * We upsample one row group into the conversion buffer, then apply + * color conversion a row at a time. + */ + +METHODDEF(void) +sep_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int ci; + jpeg_component_info * compptr; + JDIMENSION num_rows; + + /* Fill the conversion buffer, if it's empty */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Invoke per-component upsample method. Notice we pass a POINTER + * to color_buf[ci], so that fullsize_upsample can change it. + */ + (*upsample->methods[ci]) (cinfo, compptr, + input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), + upsample->color_buf + ci); + } + upsample->next_row_out = 0; + } + + /* Color-convert and emit rows */ + + /* How many we have in the buffer: */ + num_rows = (JDIMENSION) (cinfo->max_v_samp_factor - upsample->next_row_out); + /* Not more than the distance to the end of the image. Need this test + * in case the image height is not a multiple of max_v_samp_factor: + */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + + (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf, + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows); + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + upsample->next_row_out += num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by sep_upsample to upsample pixel values + * of a single component. One row group is processed per call. + */ + + +/* + * For full-size components, we just make color_buf[ci] point at the + * input buffer, and thus avoid copying any data. Note that this is + * safe only because sep_upsample doesn't declare the input row group + * "consumed" until we are done color converting and emitting it. + */ + +METHODDEF(void) +fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = input_data; +} + + +/* + * This is a no-op version used for "uninteresting" components. + * These components will not be referenced by color conversion. + */ + +METHODDEF(void) +noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = NULL; /* safety check */ +} + + +/* + * This version handles any integral sampling ratios. + * This is not used for typical JPEG files, so it need not be fast. + * Nor, for that matter, is it particularly accurate: the algorithm is + * simple replication of the input pixel onto the corresponding output + * pixels. The hi-falutin sampling literature refers to this as a + * "box filter". A box filter tends to introduce visible artifacts, + * so if you are actually going to use 3:1 or 4:1 sampling ratios + * you would be well advised to improve this code. + */ + +METHODDEF(void) +int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + register int h; + JSAMPROW outend; + int h_expand, v_expand; + int inrow, outrow; + + h_expand = upsample->h_expand[compptr->component_index]; + v_expand = upsample->v_expand[compptr->component_index]; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + /* Generate one output row with proper horizontal expansion */ + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + for (h = h_expand; h > 0; h--) { + *outptr++ = invalue; + } + } + /* Generate any additional output rows by duplicating the first one */ + if (v_expand > 1) { + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + v_expand-1, cinfo->output_width); + } + inrow++; + outrow += v_expand; + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + * It's still a box filter. + */ + +METHODDEF(void) +h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int outrow; + + for (outrow = 0; outrow < cinfo->max_v_samp_factor; outrow++) { + inptr = input_data[outrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + * It's still a box filter. + */ + +METHODDEF(void) +h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow, outrow; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + 1, cinfo->output_width); + inrow++; + outrow += 2; + } +} + + +/* + * Module initialization routine for upsampling. + */ + +GLOBAL(void) +jinit_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + int ci; + jpeg_component_info * compptr; + boolean need_buffer; + int h_in_group, v_in_group, h_out_group, v_out_group; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_upsample; + upsample->pub.upsample = sep_upsample; + upsample->pub.need_context_rows = FALSE; /* until we find out differently */ + + if (cinfo->CCIR601_sampling) /* this isn't supported */ + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* Verify we can handle the sampling factors, select per-component methods, + * and create storage as needed. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Compute size of an "input group" after IDCT scaling. This many samples + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + */ + h_in_group = (compptr->h_samp_factor * compptr->DCT_h_scaled_size) / + cinfo->min_DCT_h_scaled_size; + v_in_group = (compptr->v_samp_factor * compptr->DCT_v_scaled_size) / + cinfo->min_DCT_v_scaled_size; + h_out_group = cinfo->max_h_samp_factor; + v_out_group = cinfo->max_v_samp_factor; + upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ + need_buffer = TRUE; + if (! compptr->component_needed) { + /* Don't bother to upsample an uninteresting component. */ + upsample->methods[ci] = noop_upsample; + need_buffer = FALSE; + } else if (h_in_group == h_out_group && v_in_group == v_out_group) { + /* Fullsize components can be processed without any work. */ + upsample->methods[ci] = fullsize_upsample; + need_buffer = FALSE; + } else if (h_in_group * 2 == h_out_group && + v_in_group == v_out_group) { + /* Special case for 2h1v upsampling */ + upsample->methods[ci] = h2v1_upsample; + } else if (h_in_group * 2 == h_out_group && + v_in_group * 2 == v_out_group) { + /* Special case for 2h2v upsampling */ + upsample->methods[ci] = h2v2_upsample; + } else if ((h_out_group % h_in_group) == 0 && + (v_out_group % v_in_group) == 0) { + /* Generic integral-factors upsampling method */ + upsample->methods[ci] = int_upsample; + upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group); + upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group); + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + if (need_buffer) { + upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up((long) cinfo->output_width, + (long) cinfo->max_h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/src/jpeg-8c/jdtrans.c b/src/jpeg-8c/jdtrans.c new file mode 100644 index 0000000..22dd47f --- /dev/null +++ b/src/jpeg-8c/jdtrans.c @@ -0,0 +1,140 @@ +/* + * jdtrans.c + * + * Copyright (C) 1995-1997, Thomas G. Lane. + * Modified 2000-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding decompression, + * that is, reading raw DCT coefficient arrays from an input JPEG file. + * The routines in jdapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(void) transdecode_master_selection JPP((j_decompress_ptr cinfo)); + + +/* + * Read the coefficient arrays from a JPEG file. + * jpeg_read_header must be completed before calling this. + * + * The entire image is read into a set of virtual coefficient-block arrays, + * one per component. The return value is a pointer to the array of + * virtual-array descriptors. These can be manipulated directly via the + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + * To release the memory occupied by the virtual arrays, call + * jpeg_finish_decompress() when done with the data. + * + * An alternative usage is to simply obtain access to the coefficient arrays + * during a buffered-image-mode decompression operation. This is allowed + * after any jpeg_finish_output() call. The arrays can be accessed until + * jpeg_finish_decompress() is called. (Note that any call to the library + * may reposition the arrays, so don't rely on access_virt_barray() results + * to stay valid across library calls.) + * + * Returns NULL if suspended. This case need be checked only if + * a suspending data source is used. + */ + +GLOBAL(jvirt_barray_ptr *) +jpeg_read_coefficients (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize active modules */ + transdecode_master_selection(cinfo); + cinfo->global_state = DSTATE_RDCOEFS; + } + if (cinfo->global_state == DSTATE_RDCOEFS) { + /* Absorb whole file into the coef buffer */ + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return NULL; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* startup underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } + /* Set state so that jpeg_finish_decompress does the right thing */ + cinfo->global_state = DSTATE_STOPPING; + } + /* At this point we should be in state DSTATE_STOPPING if being used + * standalone, or in state DSTATE_BUFIMAGE if being invoked to get access + * to the coefficients during a full buffered-image-mode decompression. + */ + if ((cinfo->global_state == DSTATE_STOPPING || + cinfo->global_state == DSTATE_BUFIMAGE) && cinfo->buffered_image) { + return cinfo->coef->coef_arrays; + } + /* Oops, improper usage */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return NULL; /* keep compiler happy */ +} + + +/* + * Master selection of decompression modules for transcoding. + * This substitutes for jdmaster.c's initialization of the full decompressor. + */ + +LOCAL(void) +transdecode_master_selection (j_decompress_ptr cinfo) +{ + /* This is effectively a buffered-image operation. */ + cinfo->buffered_image = TRUE; + + /* Compute output image dimensions and related values. */ + jpeg_core_output_dimensions(cinfo); + + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) + jinit_arith_decoder(cinfo); + else { + jinit_huff_decoder(cinfo); + } + + /* Always get a full-image coefficient buffer. */ + jinit_d_coef_controller(cinfo, TRUE); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + + /* Initialize progress monitoring. */ + if (cinfo->progress != NULL) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else if (cinfo->inputctl->has_multiple_scans) { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } else { + nscans = 1; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = 1; + } +} diff --git a/src/jpeg-8c/jerror.c b/src/jpeg-8c/jerror.c new file mode 100644 index 0000000..2860d3f --- /dev/null +++ b/src/jpeg-8c/jerror.c @@ -0,0 +1,254 @@ +/* + * jerror.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * If you define USE_WINDOWS_MESSAGEBOX in jconfig.h or in the makefile, + * you get a Windows-specific hack to display error messages in a dialog box. + * It ain't much, but it beats dropping error messages into the bit bucket, + * which is what happens to output to stderr under most Windows C compilers. + * + * These routines are used by both the compression and decompression code. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +#include + +#ifdef USE_WINDOWS_MESSAGEBOX +#include +#endif + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE(code,string) string , + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF(void) +error_exit (j_common_ptr cinfo) +{ + /* Always display the message */ + (*cinfo->err->output_message) (cinfo); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + exit(EXIT_FAILURE); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + * + * On Windows, printing to stderr is generally completely useless, + * so we provide optional code to produce an error-dialog popup. + * Most Windows applications will still prefer to override this routine, + * but if they don't, it'll do something at least marginally useful. + * + * NOTE: to use the library in an environment that doesn't support the + * C stdio library, you may have to delete the call to fprintf() entirely, + * not just not use this routine. + */ + +METHODDEF(void) +output_message (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + +#ifdef USE_WINDOWS_MESSAGEBOX + /* Display it in a message dialog box */ + MessageBox(GetActiveWindow(), buffer, "JPEG Library Error", + MB_OK | MB_ICONERROR); +#else + /* Send it to stderr, adding a newline */ + fprintf(stderr, "%s\n", buffer); +#endif +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF(void) +emit_message (j_common_ptr cinfo, int msg_level) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if (err->num_warnings == 0 || err->trace_level >= 3) + (*err->output_message) (cinfo); + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if (err->trace_level >= msg_level) + (*err->output_message) (cinfo); + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF(void) +format_message (j_common_ptr cinfo, char * buffer) +{ + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if (msg_code > 0 && msg_code <= err->last_jpeg_message) { + msgtext = err->jpeg_message_table[msg_code]; + } else if (err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if (msgtext == NULL) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ((ch = *msgptr++) != '\0') { + if (ch == '%') { + if (*msgptr == 's') isstring = TRUE; + break; + } + } + + /* Format the message into the passed buffer */ + if (isstring) + sprintf(buffer, msgtext, err->msg_parm.s); + else + sprintf(buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7]); +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF(void) +reset_error_mgr (j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL(struct jpeg_error_mgr *) +jpeg_std_error (struct jpeg_error_mgr * err) +{ + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0; /* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/src/jpeg-8c/jerror.h b/src/jpeg-8c/jerror.h new file mode 100644 index 0000000..1cfb2b1 --- /dev/null +++ b/src/jpeg-8c/jerror.h @@ -0,0 +1,304 @@ +/* + * jerror.h + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * Modified 1997-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +JMESSAGE(JERR_BAD_CROP_SPEC, "Invalid crop request") +JMESSAGE(JERR_BAD_DCT_COEF, "DCT coefficient out of range") +JMESSAGE(JERR_BAD_DCTSIZE, "DCT scaled block size %dx%d not supported") +JMESSAGE(JERR_BAD_DROP_SAMPLING, + "Component index %d: mismatching sampling ratio %d:%d, %d:%d, %c") +JMESSAGE(JERR_BAD_HUFF_TABLE, "Bogus Huffman table definition") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_LIB_VERSION, + "Wrong JPEG library version: library is %d, caller expects %d") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_STRUCT_SIZE, + "JPEG parameter struct mismatch: library thinks size is %u, caller expects %u") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +JMESSAGE(JERR_NO_ARITH_TABLE, "Arithmetic table 0x%02x was not defined") +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker: version %d.%02d, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_EXTENSION, + "JFIF extension marker: type 0x%02x, length %u") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Miscellaneous marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_THUMB_JPEG, + "JFIF extension marker: JPEG-compressed thumbnail image, length %u") +JMESSAGE(JTRC_THUMB_PALETTE, + "JFIF extension marker: palette thumbnail image, length %u") +JMESSAGE(JTRC_THUMB_RGB, + "JFIF extension marker: RGB thumbnail image, length %u") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +JMESSAGE(JWRN_ARITH_BAD_CODE, "Corrupt JPEG data: bad arithmetic code") +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + + +#ifndef JERROR_H +#define JERROR_H + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT3(cinfo,code,p1,p2,p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT4(cinfo,code,p1,p2,p3,p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT6(cinfo,code,p1,p2,p3,p4,p5,p6) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (cinfo)->err->msg_parm.i[4] = (p5), \ + (cinfo)->err->msg_parm.i[5] = (p6), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXITS(cinfo,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo,lvl,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS1(cinfo,lvl,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS2(cinfo,lvl,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS5(cinfo,lvl,code,p1,p2,p3,p4,p5) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMSS(cinfo,lvl,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/src/jpeg-8c/jfdctflt.c b/src/jpeg-8c/jfdctflt.c new file mode 100644 index 0000000..74d0d86 --- /dev/null +++ b/src/jpeg-8c/jfdctflt.c @@ -0,0 +1,174 @@ +/* + * jfdctflt.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * Modified 2003-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * forward DCT (Discrete Cosine Transform). + * + * This implementation should be more accurate than either of the integer + * DCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_float (FAST_FLOAT * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + FAST_FLOAT *dataptr; + JSAMPROW elemptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Load data into workspace */ + tmp0 = (FAST_FLOAT) (GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[7])); + tmp7 = (FAST_FLOAT) (GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[7])); + tmp1 = (FAST_FLOAT) (GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[6])); + tmp6 = (FAST_FLOAT) (GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[6])); + tmp2 = (FAST_FLOAT) (GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[5])); + tmp5 = (FAST_FLOAT) (GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[5])); + tmp3 = (FAST_FLOAT) (GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[4])); + tmp4 = (FAST_FLOAT) (GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[4])); + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Apply unsigned->signed conversion */ + dataptr[0] = tmp10 + tmp11 - 8 * CENTERJSAMPLE; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = 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) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = 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) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/src/jpeg-8c/jfdctfst.c b/src/jpeg-8c/jfdctfst.c new file mode 100644 index 0000000..8cad5f2 --- /dev/null +++ b/src/jpeg-8c/jfdctfst.c @@ -0,0 +1,230 @@ +/* + * jfdctfst.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * Modified 2003-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jfdctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * Again to save a few shifts, the intermediate results between pass 1 and + * pass 2 are not upscaled, but are represented only to integral precision. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#define CONST_BITS 8 + + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_0_382683433 ((INT32) 98) /* FIX(0.382683433) */ +#define FIX_0_541196100 ((INT32) 139) /* FIX(0.541196100) */ +#define FIX_0_707106781 ((INT32) 181) /* FIX(0.707106781) */ +#define FIX_1_306562965 ((INT32) 334) /* FIX(1.306562965) */ +#else +#define FIX_0_382683433 FIX(0.382683433) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_707106781 FIX(0.707106781) +#define FIX_1_306562965 FIX(1.306562965) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE(x,n) RIGHT_SHIFT(x, n) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var,const) ((DCTELEM) DESCALE((var) * (const), CONST_BITS)) + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_ifast (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z1, z2, z3, z4, z5, z11, z13; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Load data into workspace */ + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[7]); + tmp7 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[7]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[6]); + tmp6 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[6]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[5]); + tmp5 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[5]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[4]); + tmp4 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[4]); + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Apply unsigned->signed conversion */ + dataptr[0] = tmp10 + tmp11 - 8 * CENTERJSAMPLE; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = 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 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = 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 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/src/jpeg-8c/jfdctint.c b/src/jpeg-8c/jfdctint.c new file mode 100644 index 0000000..1dde58c --- /dev/null +++ b/src/jpeg-8c/jfdctint.c @@ -0,0 +1,4348 @@ +/* + * jfdctint.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * Modification developed 2003-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + * + * We also provide FDCT routines with various input sample block sizes for + * direct resolution reduction or enlargement and for direct resolving the + * common 2x1 and 1x2 subsampling cases without additional resampling: NxN + * (N=1...16), 2NxN, and Nx2N (N=1...8) pixels for one 8x8 output DCT block. + * + * For N<8 we fill the remaining block coefficients with zero. + * For N>8 we apply a partial N-point FDCT on the input samples, computing + * just the lower 8 frequency coefficients and discarding the rest. + * + * We must scale the output coefficients of the N-point FDCT appropriately + * to the standard 8-point FDCT level by 8/N per 1-D pass. This scaling + * is folded into the constant multipliers (pass 2) and/or final/initial + * shifting. + * + * CAUTION: We rely on the FIX() macro except for the N=1,2,4,8 cases + * since there would be too many additional constants to pre-calculate. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCT blocks. /* deliberate syntax err */ +#endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D DCT step produces outputs which are a factor of sqrt(N) + * larger than the true DCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D DCT, + * because the y0 and y4 outputs need not be divided by sqrt(N). + * In the IJG code, this factor of 8 is removed by the quantization step + * (in jcdctmgr.c), NOT in this module. + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (For 12-bit sample data, the intermediate + * array is INT32 anyway.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX(0.298631336) +#define FIX_0_390180644 FIX(0.390180644) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_175875602 FIX(1.175875602) +#define FIX_1_501321110 FIX(1.501321110) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_1_961570560 FIX(1.961570560) +#define FIX_2_053119869 FIX(2.053119869) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_072711026 FIX(3.072711026) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_islow (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + dataptr = data; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[7]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[6]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[5]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[4]); + + tmp10 = tmp0 + tmp3; + tmp12 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp13 = tmp1 - tmp2; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[7]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[6]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[5]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[4]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) ((tmp10 + tmp11 - 8 * CENTERJSAMPLE) << PASS1_BITS); + dataptr[4] = (DCTELEM) ((tmp10 - tmp11) << PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-1); + dataptr[2] = (DCTELEM) RIGHT_SHIFT(z1 + MULTIPLY(tmp12, FIX_0_765366865), + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) RIGHT_SHIFT(z1 - MULTIPLY(tmp13, FIX_1_847759065), + CONST_BITS-PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents sqrt(2) * cos(K*pi/16). + * i0..i3 in the paper are tmp0..tmp3 here. + */ + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = MULTIPLY(tmp12 + tmp13, FIX_1_175875602); /* c3 */ + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-1); + + tmp0 = MULTIPLY(tmp0, FIX_1_501321110); /* c1+c3-c5-c7 */ + tmp1 = MULTIPLY(tmp1, FIX_3_072711026); /* c1+c3+c5-c7 */ + tmp2 = MULTIPLY(tmp2, FIX_2_053119869); /* c1+c3-c5+c7 */ + tmp3 = MULTIPLY(tmp3, FIX_0_298631336); /* -c1+c3+c5-c7 */ + tmp10 = MULTIPLY(tmp10, - FIX_0_899976223); /* c7-c3 */ + tmp11 = MULTIPLY(tmp11, - FIX_2_562915447); /* -c1-c3 */ + tmp12 = MULTIPLY(tmp12, - FIX_0_390180644); /* c5-c3 */ + tmp13 = MULTIPLY(tmp13, - FIX_1_961570560); /* -c3-c5 */ + + tmp12 += z1; + tmp13 += z1; + + dataptr[1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + tmp10 + tmp12, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) + RIGHT_SHIFT(tmp1 + tmp11 + tmp13, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) + RIGHT_SHIFT(tmp2 + tmp11 + tmp12, CONST_BITS-PASS1_BITS); + dataptr[7] = (DCTELEM) + RIGHT_SHIFT(tmp3 + tmp10 + tmp13, CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + + /* Add fudge factor here for final descale. */ + tmp10 = tmp0 + tmp3 + (ONE << (PASS1_BITS-1)); + tmp12 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp13 = tmp1 - tmp2; + + tmp0 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + dataptr[DCTSIZE*0] = (DCTELEM) RIGHT_SHIFT(tmp10 + tmp11, PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) RIGHT_SHIFT(tmp10 - tmp11, PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS+PASS1_BITS-1); + dataptr[DCTSIZE*2] = (DCTELEM) + RIGHT_SHIFT(z1 + MULTIPLY(tmp12, FIX_0_765366865), CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) + RIGHT_SHIFT(z1 - MULTIPLY(tmp13, FIX_1_847759065), CONST_BITS+PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents sqrt(2) * cos(K*pi/16). + * i0..i3 in the paper are tmp0..tmp3 here. + */ + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = MULTIPLY(tmp12 + tmp13, FIX_1_175875602); /* c3 */ + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS+PASS1_BITS-1); + + tmp0 = MULTIPLY(tmp0, FIX_1_501321110); /* c1+c3-c5-c7 */ + tmp1 = MULTIPLY(tmp1, FIX_3_072711026); /* c1+c3+c5-c7 */ + tmp2 = MULTIPLY(tmp2, FIX_2_053119869); /* c1+c3-c5+c7 */ + tmp3 = MULTIPLY(tmp3, FIX_0_298631336); /* -c1+c3+c5-c7 */ + tmp10 = MULTIPLY(tmp10, - FIX_0_899976223); /* c7-c3 */ + tmp11 = MULTIPLY(tmp11, - FIX_2_562915447); /* -c1-c3 */ + tmp12 = MULTIPLY(tmp12, - FIX_0_390180644); /* c5-c3 */ + tmp13 = MULTIPLY(tmp13, - FIX_1_961570560); /* -c3-c5 */ + + tmp12 += z1; + tmp13 += z1; + + dataptr[DCTSIZE*1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + tmp10 + tmp12, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + RIGHT_SHIFT(tmp1 + tmp11 + tmp13, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) + RIGHT_SHIFT(tmp2 + tmp11 + tmp12, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*7] = (DCTELEM) + RIGHT_SHIFT(tmp3 + tmp10 + tmp13, CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + +#ifdef DCT_SCALING_SUPPORTED + + +/* + * Perform the forward DCT on a 7x7 sample block. + */ + +GLOBAL(void) +jpeg_fdct_7x7 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12; + INT32 z1, z2, z3; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* cK represents sqrt(2) * cos(K*pi/14). */ + + dataptr = data; + for (ctr = 0; ctr < 7; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[6]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[5]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[4]); + tmp3 = GETJSAMPLE(elemptr[3]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[6]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[5]); + tmp12 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[4]); + + z1 = tmp0 + tmp2; + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((z1 + tmp1 + tmp3 - 7 * CENTERJSAMPLE) << PASS1_BITS); + tmp3 += tmp3; + z1 -= tmp3; + z1 -= tmp3; + z1 = MULTIPLY(z1, FIX(0.353553391)); /* (c2+c6-c4)/2 */ + z2 = MULTIPLY(tmp0 - tmp2, FIX(0.920609002)); /* (c2+c4-c6)/2 */ + z3 = MULTIPLY(tmp1 - tmp2, FIX(0.314692123)); /* c6 */ + dataptr[2] = (DCTELEM) DESCALE(z1 + z2 + z3, CONST_BITS-PASS1_BITS); + z1 -= z2; + z2 = MULTIPLY(tmp0 - tmp1, FIX(0.881747734)); /* c4 */ + dataptr[4] = (DCTELEM) + DESCALE(z2 + z3 - MULTIPLY(tmp1 - tmp3, FIX(0.707106781)), /* c2+c6-c4 */ + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) DESCALE(z1 + z2, CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp1 = MULTIPLY(tmp10 + tmp11, FIX(0.935414347)); /* (c3+c1-c5)/2 */ + tmp2 = MULTIPLY(tmp10 - tmp11, FIX(0.170262339)); /* (c3+c5-c1)/2 */ + tmp0 = tmp1 - tmp2; + tmp1 += tmp2; + tmp2 = MULTIPLY(tmp11 + tmp12, - FIX(1.378756276)); /* -c1 */ + tmp1 += tmp2; + tmp3 = MULTIPLY(tmp10 + tmp12, FIX(0.613604268)); /* c5 */ + tmp0 += tmp3; + tmp2 += tmp3 + MULTIPLY(tmp12, FIX(1.870828693)); /* c3+c1-c5 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp0, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp1, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp2, CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/7)**2 = 64/49, which we fold + * into the constant multipliers: + * cK now represents sqrt(2) * cos(K*pi/14) * 64/49. + */ + + dataptr = data; + for (ctr = 0; ctr < 7; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*6]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*5]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*4]; + tmp3 = dataptr[DCTSIZE*3]; + + tmp10 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*6]; + tmp11 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*5]; + tmp12 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*4]; + + z1 = tmp0 + tmp2; + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(z1 + tmp1 + tmp3, FIX(1.306122449)), /* 64/49 */ + CONST_BITS+PASS1_BITS); + tmp3 += tmp3; + z1 -= tmp3; + z1 -= tmp3; + z1 = MULTIPLY(z1, FIX(0.461784020)); /* (c2+c6-c4)/2 */ + z2 = MULTIPLY(tmp0 - tmp2, FIX(1.202428084)); /* (c2+c4-c6)/2 */ + z3 = MULTIPLY(tmp1 - tmp2, FIX(0.411026446)); /* c6 */ + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(z1 + z2 + z3, CONST_BITS+PASS1_BITS); + z1 -= z2; + z2 = MULTIPLY(tmp0 - tmp1, FIX(1.151670509)); /* c4 */ + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(z2 + z3 - MULTIPLY(tmp1 - tmp3, FIX(0.923568041)), /* c2+c6-c4 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(z1 + z2, CONST_BITS+PASS1_BITS); + + /* Odd part */ + + tmp1 = MULTIPLY(tmp10 + tmp11, FIX(1.221765677)); /* (c3+c1-c5)/2 */ + tmp2 = MULTIPLY(tmp10 - tmp11, FIX(0.222383464)); /* (c3+c5-c1)/2 */ + tmp0 = tmp1 - tmp2; + tmp1 += tmp2; + tmp2 = MULTIPLY(tmp11 + tmp12, - FIX(1.800824523)); /* -c1 */ + tmp1 += tmp2; + tmp3 = MULTIPLY(tmp10 + tmp12, FIX(0.801442310)); /* c5 */ + tmp0 += tmp3; + tmp2 += tmp3 + MULTIPLY(tmp12, FIX(2.443531355)); /* c3+c1-c5 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp0, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp1, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp2, CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 6x6 sample block. + */ + +GLOBAL(void) +jpeg_fdct_6x6 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2; + INT32 tmp10, tmp11, tmp12; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* cK represents sqrt(2) * cos(K*pi/12). */ + + dataptr = data; + for (ctr = 0; ctr < 6; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[5]); + tmp11 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[4]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[3]); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[5]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[4]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[3]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 - 6 * CENTERJSAMPLE) << PASS1_BITS); + dataptr[2] = (DCTELEM) + DESCALE(MULTIPLY(tmp12, FIX(1.224744871)), /* c2 */ + CONST_BITS-PASS1_BITS); + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp11 - tmp11, FIX(0.707106781)), /* c4 */ + CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp10 = DESCALE(MULTIPLY(tmp0 + tmp2, FIX(0.366025404)), /* c5 */ + CONST_BITS-PASS1_BITS); + + dataptr[1] = (DCTELEM) (tmp10 + ((tmp0 + tmp1) << PASS1_BITS)); + dataptr[3] = (DCTELEM) ((tmp0 - tmp1 - tmp2) << PASS1_BITS); + dataptr[5] = (DCTELEM) (tmp10 + ((tmp2 - tmp1) << PASS1_BITS)); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/6)**2 = 16/9, which we fold + * into the constant multipliers: + * cK now represents sqrt(2) * cos(K*pi/12) * 16/9. + */ + + dataptr = data; + for (ctr = 0; ctr < 6; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*5]; + tmp11 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*3]; + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + tmp0 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*5]; + tmp1 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*3]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp12, FIX(2.177324216)), /* c2 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp11 - tmp11, FIX(1.257078722)), /* c4 */ + CONST_BITS+PASS1_BITS); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp0 + tmp2, FIX(0.650711829)); /* c5 */ + + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp0 + tmp1, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp1 - tmp2, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp2 - tmp1, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 5x5 sample block. + */ + +GLOBAL(void) +jpeg_fdct_5x5 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2; + INT32 tmp10, tmp11; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* We scale the results further by 2 as part of output adaption */ + /* scaling for different DCT size. */ + /* cK represents sqrt(2) * cos(K*pi/10). */ + + dataptr = data; + for (ctr = 0; ctr < 5; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[4]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[3]); + tmp2 = GETJSAMPLE(elemptr[2]); + + tmp10 = tmp0 + tmp1; + tmp11 = tmp0 - tmp1; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[4]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[3]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp2 - 5 * CENTERJSAMPLE) << (PASS1_BITS+1)); + tmp11 = MULTIPLY(tmp11, FIX(0.790569415)); /* (c2+c4)/2 */ + tmp10 -= tmp2 << 2; + tmp10 = MULTIPLY(tmp10, FIX(0.353553391)); /* (c2-c4)/2 */ + dataptr[2] = (DCTELEM) DESCALE(tmp11 + tmp10, CONST_BITS-PASS1_BITS-1); + dataptr[4] = (DCTELEM) DESCALE(tmp11 - tmp10, CONST_BITS-PASS1_BITS-1); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp0 + tmp1, FIX(0.831253876)); /* c3 */ + + dataptr[1] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp0, FIX(0.513743148)), /* c1-c3 */ + CONST_BITS-PASS1_BITS-1); + dataptr[3] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp1, FIX(2.176250899)), /* c1+c3 */ + CONST_BITS-PASS1_BITS-1); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/5)**2 = 64/25, which we partially + * fold into the constant multipliers (other part was done in pass 1): + * cK now represents sqrt(2) * cos(K*pi/10) * 32/25. + */ + + dataptr = data; + for (ctr = 0; ctr < 5; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*4]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*3]; + tmp2 = dataptr[DCTSIZE*2]; + + tmp10 = tmp0 + tmp1; + tmp11 = tmp0 - tmp1; + + tmp0 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*4]; + tmp1 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*3]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp2, FIX(1.28)), /* 32/25 */ + CONST_BITS+PASS1_BITS); + tmp11 = MULTIPLY(tmp11, FIX(1.011928851)); /* (c2+c4)/2 */ + tmp10 -= tmp2 << 2; + tmp10 = MULTIPLY(tmp10, FIX(0.452548340)); /* (c2-c4)/2 */ + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(tmp11 + tmp10, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(tmp11 - tmp10, CONST_BITS+PASS1_BITS); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp0 + tmp1, FIX(1.064004961)); /* c3 */ + + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp0, FIX(0.657591230)), /* c1-c3 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp1, FIX(2.785601151)), /* c1+c3 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 4x4 sample block. + */ + +GLOBAL(void) +jpeg_fdct_4x4 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1; + INT32 tmp10, tmp11; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* We must also scale the output by (8/4)**2 = 2**2, which we add here. */ + /* cK represents sqrt(2) * cos(K*pi/16) [refers to 8-point FDCT]. */ + + dataptr = data; + for (ctr = 0; ctr < 4; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[3]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[2]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[3]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[2]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp0 + tmp1 - 4 * CENTERJSAMPLE) << (PASS1_BITS+2)); + dataptr[2] = (DCTELEM) ((tmp0 - tmp1) << (PASS1_BITS+2)); + + /* Odd part */ + + tmp0 = MULTIPLY(tmp10 + tmp11, FIX_0_541196100); /* c6 */ + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-3); + + dataptr[1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + MULTIPLY(tmp10, FIX_0_765366865), /* c2-c6 */ + CONST_BITS-PASS1_BITS-2); + dataptr[3] = (DCTELEM) + RIGHT_SHIFT(tmp0 - MULTIPLY(tmp11, FIX_1_847759065), /* c2+c6 */ + CONST_BITS-PASS1_BITS-2); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + */ + + dataptr = data; + for (ctr = 0; ctr < 4; ctr++) { + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*3] + (ONE << (PASS1_BITS-1)); + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*2]; + + tmp10 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*3]; + tmp11 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*2]; + + dataptr[DCTSIZE*0] = (DCTELEM) RIGHT_SHIFT(tmp0 + tmp1, PASS1_BITS); + dataptr[DCTSIZE*2] = (DCTELEM) RIGHT_SHIFT(tmp0 - tmp1, PASS1_BITS); + + /* Odd part */ + + tmp0 = MULTIPLY(tmp10 + tmp11, FIX_0_541196100); /* c6 */ + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS+PASS1_BITS-1); + + dataptr[DCTSIZE*1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + MULTIPLY(tmp10, FIX_0_765366865), /* c2-c6 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + RIGHT_SHIFT(tmp0 - MULTIPLY(tmp11, FIX_1_847759065), /* c2+c6 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 3x3 sample block. + */ + +GLOBAL(void) +jpeg_fdct_3x3 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* We scale the results further by 2**2 as part of output adaption */ + /* scaling for different DCT size. */ + /* cK represents sqrt(2) * cos(K*pi/6). */ + + dataptr = data; + for (ctr = 0; ctr < 3; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[2]); + tmp1 = GETJSAMPLE(elemptr[1]); + + tmp2 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[2]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp0 + tmp1 - 3 * CENTERJSAMPLE) << (PASS1_BITS+2)); + dataptr[2] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp1 - tmp1, FIX(0.707106781)), /* c2 */ + CONST_BITS-PASS1_BITS-2); + + /* Odd part */ + + dataptr[1] = (DCTELEM) + DESCALE(MULTIPLY(tmp2, FIX(1.224744871)), /* c1 */ + CONST_BITS-PASS1_BITS-2); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/3)**2 = 64/9, which we partially + * fold into the constant multipliers (other part was done in pass 1): + * cK now represents sqrt(2) * cos(K*pi/6) * 16/9. + */ + + dataptr = data; + for (ctr = 0; ctr < 3; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*2]; + tmp1 = dataptr[DCTSIZE*1]; + + tmp2 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*2]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 + tmp1, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp1 - tmp1, FIX(1.257078722)), /* c2 */ + CONST_BITS+PASS1_BITS); + + /* Odd part */ + + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(MULTIPLY(tmp2, FIX(2.177324216)), /* c1 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 2x2 sample block. + */ + +GLOBAL(void) +jpeg_fdct_2x2 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + JSAMPROW elemptr; + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT. */ + + /* Row 0 */ + elemptr = sample_data[0] + start_col; + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[1]); + tmp1 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[1]); + + /* Row 1 */ + elemptr = sample_data[1] + start_col; + + tmp2 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[1]); + tmp3 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[1]); + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/2)**2 = 2**4. + */ + + /* Column 0 */ + /* Apply unsigned->signed conversion */ + data[DCTSIZE*0] = (DCTELEM) ((tmp0 + tmp2 - 4 * CENTERJSAMPLE) << 4); + data[DCTSIZE*1] = (DCTELEM) ((tmp0 - tmp2) << 4); + + /* Column 1 */ + data[DCTSIZE*0+1] = (DCTELEM) ((tmp1 + tmp3) << 4); + data[DCTSIZE*1+1] = (DCTELEM) ((tmp1 - tmp3) << 4); +} + + +/* + * Perform the forward DCT on a 1x1 sample block. + */ + +GLOBAL(void) +jpeg_fdct_1x1 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* We leave the result scaled up by an overall factor of 8. */ + /* We must also scale the output by (8/1)**2 = 2**6. */ + /* Apply unsigned->signed conversion */ + data[0] = (DCTELEM) + ((GETJSAMPLE(sample_data[0][start_col]) - CENTERJSAMPLE) << 6); +} + + +/* + * Perform the forward DCT on a 9x9 sample block. + */ + +GLOBAL(void) +jpeg_fdct_9x9 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2; + DCTELEM workspace[8]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* we scale the results further by 2 as part of output adaption */ + /* scaling for different DCT size. */ + /* cK represents sqrt(2) * cos(K*pi/18). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[8]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[7]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[6]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[5]); + tmp4 = GETJSAMPLE(elemptr[4]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[8]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[7]); + tmp12 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[6]); + tmp13 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[5]); + + z1 = tmp0 + tmp2 + tmp3; + z2 = tmp1 + tmp4; + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) ((z1 + z2 - 9 * CENTERJSAMPLE) << 1); + dataptr[6] = (DCTELEM) + DESCALE(MULTIPLY(z1 - z2 - z2, FIX(0.707106781)), /* c6 */ + CONST_BITS-1); + z1 = MULTIPLY(tmp0 - tmp2, FIX(1.328926049)); /* c2 */ + z2 = MULTIPLY(tmp1 - tmp4 - tmp4, FIX(0.707106781)); /* c6 */ + dataptr[2] = (DCTELEM) + DESCALE(MULTIPLY(tmp2 - tmp3, FIX(1.083350441)) /* c4 */ + + z1 + z2, CONST_BITS-1); + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp3 - tmp0, FIX(0.245575608)) /* c8 */ + + z1 - z2, CONST_BITS-1); + + /* Odd part */ + + dataptr[3] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12 - tmp13, FIX(1.224744871)), /* c3 */ + CONST_BITS-1); + + tmp11 = MULTIPLY(tmp11, FIX(1.224744871)); /* c3 */ + tmp0 = MULTIPLY(tmp10 + tmp12, FIX(0.909038955)); /* c5 */ + tmp1 = MULTIPLY(tmp10 + tmp13, FIX(0.483689525)); /* c7 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp11 + tmp0 + tmp1, CONST_BITS-1); + + tmp2 = MULTIPLY(tmp12 - tmp13, FIX(1.392728481)); /* c1 */ + + dataptr[5] = (DCTELEM) DESCALE(tmp0 - tmp11 - tmp2, CONST_BITS-1); + dataptr[7] = (DCTELEM) DESCALE(tmp1 - tmp11 + tmp2, CONST_BITS-1); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 9) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/9)**2 = 64/81, which we partially + * fold into the constant multipliers and final/initial shifting: + * cK now represents sqrt(2) * cos(K*pi/18) * 128/81. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*0]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*7]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*6]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*5]; + tmp4 = dataptr[DCTSIZE*4]; + + tmp10 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*0]; + tmp11 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*7]; + tmp12 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*6]; + tmp13 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*5]; + + z1 = tmp0 + tmp2 + tmp3; + z2 = tmp1 + tmp4; + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(z1 + z2, FIX(1.580246914)), /* 128/81 */ + CONST_BITS+2); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(MULTIPLY(z1 - z2 - z2, FIX(1.117403309)), /* c6 */ + CONST_BITS+2); + z1 = MULTIPLY(tmp0 - tmp2, FIX(2.100031287)); /* c2 */ + z2 = MULTIPLY(tmp1 - tmp4 - tmp4, FIX(1.117403309)); /* c6 */ + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp2 - tmp3, FIX(1.711961190)) /* c4 */ + + z1 + z2, CONST_BITS+2); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp3 - tmp0, FIX(0.388070096)) /* c8 */ + + z1 - z2, CONST_BITS+2); + + /* Odd part */ + + dataptr[DCTSIZE*3] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12 - tmp13, FIX(1.935399303)), /* c3 */ + CONST_BITS+2); + + tmp11 = MULTIPLY(tmp11, FIX(1.935399303)); /* c3 */ + tmp0 = MULTIPLY(tmp10 + tmp12, FIX(1.436506004)); /* c5 */ + tmp1 = MULTIPLY(tmp10 + tmp13, FIX(0.764348879)); /* c7 */ + + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(tmp11 + tmp0 + tmp1, CONST_BITS+2); + + tmp2 = MULTIPLY(tmp12 - tmp13, FIX(2.200854883)); /* c1 */ + + dataptr[DCTSIZE*5] = (DCTELEM) + DESCALE(tmp0 - tmp11 - tmp2, CONST_BITS+2); + dataptr[DCTSIZE*7] = (DCTELEM) + DESCALE(tmp1 - tmp11 + tmp2, CONST_BITS+2); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 10x10 sample block. + */ + +GLOBAL(void) +jpeg_fdct_10x10 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14; + DCTELEM workspace[8*2]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* we scale the results further by 2 as part of output adaption */ + /* scaling for different DCT size. */ + /* cK represents sqrt(2) * cos(K*pi/20). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[9]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[8]); + tmp12 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[7]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[6]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[5]); + + tmp10 = tmp0 + tmp4; + tmp13 = tmp0 - tmp4; + tmp11 = tmp1 + tmp3; + tmp14 = tmp1 - tmp3; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[9]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[8]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[7]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[6]); + tmp4 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[5]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 + tmp12 - 10 * CENTERJSAMPLE) << 1); + tmp12 += tmp12; + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12, FIX(1.144122806)) - /* c4 */ + MULTIPLY(tmp11 - tmp12, FIX(0.437016024)), /* c8 */ + CONST_BITS-1); + tmp10 = MULTIPLY(tmp13 + tmp14, FIX(0.831253876)); /* c6 */ + dataptr[2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp13, FIX(0.513743148)), /* c2-c6 */ + CONST_BITS-1); + dataptr[6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp14, FIX(2.176250899)), /* c2+c6 */ + CONST_BITS-1); + + /* Odd part */ + + tmp10 = tmp0 + tmp4; + tmp11 = tmp1 - tmp3; + dataptr[5] = (DCTELEM) ((tmp10 - tmp11 - tmp2) << 1); + tmp2 <<= CONST_BITS; + dataptr[1] = (DCTELEM) + DESCALE(MULTIPLY(tmp0, FIX(1.396802247)) + /* c1 */ + MULTIPLY(tmp1, FIX(1.260073511)) + tmp2 + /* c3 */ + MULTIPLY(tmp3, FIX(0.642039522)) + /* c7 */ + MULTIPLY(tmp4, FIX(0.221231742)), /* c9 */ + CONST_BITS-1); + tmp12 = MULTIPLY(tmp0 - tmp4, FIX(0.951056516)) - /* (c3+c7)/2 */ + MULTIPLY(tmp1 + tmp3, FIX(0.587785252)); /* (c1-c9)/2 */ + tmp13 = MULTIPLY(tmp10 + tmp11, FIX(0.309016994)) + /* (c3-c7)/2 */ + (tmp11 << (CONST_BITS - 1)) - tmp2; + dataptr[3] = (DCTELEM) DESCALE(tmp12 + tmp13, CONST_BITS-1); + dataptr[7] = (DCTELEM) DESCALE(tmp12 - tmp13, CONST_BITS-1); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 10) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/10)**2 = 16/25, which we partially + * fold into the constant multipliers and final/initial shifting: + * cK now represents sqrt(2) * cos(K*pi/20) * 32/25. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*1]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*0]; + tmp12 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*7]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*6]; + tmp4 = dataptr[DCTSIZE*4] + dataptr[DCTSIZE*5]; + + tmp10 = tmp0 + tmp4; + tmp13 = tmp0 - tmp4; + tmp11 = tmp1 + tmp3; + tmp14 = tmp1 - tmp3; + + tmp0 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*1]; + tmp1 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*0]; + tmp2 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*7]; + tmp3 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*6]; + tmp4 = dataptr[DCTSIZE*4] - dataptr[DCTSIZE*5]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11 + tmp12, FIX(1.28)), /* 32/25 */ + CONST_BITS+2); + tmp12 += tmp12; + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12, FIX(1.464477191)) - /* c4 */ + MULTIPLY(tmp11 - tmp12, FIX(0.559380511)), /* c8 */ + CONST_BITS+2); + tmp10 = MULTIPLY(tmp13 + tmp14, FIX(1.064004961)); /* c6 */ + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp13, FIX(0.657591230)), /* c2-c6 */ + CONST_BITS+2); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp14, FIX(2.785601151)), /* c2+c6 */ + CONST_BITS+2); + + /* Odd part */ + + tmp10 = tmp0 + tmp4; + tmp11 = tmp1 - tmp3; + dataptr[DCTSIZE*5] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp11 - tmp2, FIX(1.28)), /* 32/25 */ + CONST_BITS+2); + tmp2 = MULTIPLY(tmp2, FIX(1.28)); /* 32/25 */ + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(MULTIPLY(tmp0, FIX(1.787906876)) + /* c1 */ + MULTIPLY(tmp1, FIX(1.612894094)) + tmp2 + /* c3 */ + MULTIPLY(tmp3, FIX(0.821810588)) + /* c7 */ + MULTIPLY(tmp4, FIX(0.283176630)), /* c9 */ + CONST_BITS+2); + tmp12 = MULTIPLY(tmp0 - tmp4, FIX(1.217352341)) - /* (c3+c7)/2 */ + MULTIPLY(tmp1 + tmp3, FIX(0.752365123)); /* (c1-c9)/2 */ + tmp13 = MULTIPLY(tmp10 + tmp11, FIX(0.395541753)) + /* (c3-c7)/2 */ + MULTIPLY(tmp11, FIX(0.64)) - tmp2; /* 16/25 */ + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp12 + tmp13, CONST_BITS+2); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp12 - tmp13, CONST_BITS+2); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on an 11x11 sample block. + */ + +GLOBAL(void) +jpeg_fdct_11x11 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14; + INT32 z1, z2, z3; + DCTELEM workspace[8*3]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* we scale the results further by 2 as part of output adaption */ + /* scaling for different DCT size. */ + /* cK represents sqrt(2) * cos(K*pi/22). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[10]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[9]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[8]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[7]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[6]); + tmp5 = GETJSAMPLE(elemptr[5]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[10]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[9]); + tmp12 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[8]); + tmp13 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[7]); + tmp14 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[6]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp0 + tmp1 + tmp2 + tmp3 + tmp4 + tmp5 - 11 * CENTERJSAMPLE) << 1); + tmp5 += tmp5; + tmp0 -= tmp5; + tmp1 -= tmp5; + tmp2 -= tmp5; + tmp3 -= tmp5; + tmp4 -= tmp5; + z1 = MULTIPLY(tmp0 + tmp3, FIX(1.356927976)) + /* c2 */ + MULTIPLY(tmp2 + tmp4, FIX(0.201263574)); /* c10 */ + z2 = MULTIPLY(tmp1 - tmp3, FIX(0.926112931)); /* c6 */ + z3 = MULTIPLY(tmp0 - tmp1, FIX(1.189712156)); /* c4 */ + dataptr[2] = (DCTELEM) + DESCALE(z1 + z2 - MULTIPLY(tmp3, FIX(1.018300590)) /* c2+c8-c6 */ + - MULTIPLY(tmp4, FIX(1.390975730)), /* c4+c10 */ + CONST_BITS-1); + dataptr[4] = (DCTELEM) + DESCALE(z2 + z3 + MULTIPLY(tmp1, FIX(0.062335650)) /* c4-c6-c10 */ + - MULTIPLY(tmp2, FIX(1.356927976)) /* c2 */ + + MULTIPLY(tmp4, FIX(0.587485545)), /* c8 */ + CONST_BITS-1); + dataptr[6] = (DCTELEM) + DESCALE(z1 + z3 - MULTIPLY(tmp0, FIX(1.620527200)) /* c2+c4-c6 */ + - MULTIPLY(tmp2, FIX(0.788749120)), /* c8+c10 */ + CONST_BITS-1); + + /* Odd part */ + + tmp1 = MULTIPLY(tmp10 + tmp11, FIX(1.286413905)); /* c3 */ + tmp2 = MULTIPLY(tmp10 + tmp12, FIX(1.068791298)); /* c5 */ + tmp3 = MULTIPLY(tmp10 + tmp13, FIX(0.764581576)); /* c7 */ + tmp0 = tmp1 + tmp2 + tmp3 - MULTIPLY(tmp10, FIX(1.719967871)) /* c7+c5+c3-c1 */ + + MULTIPLY(tmp14, FIX(0.398430003)); /* c9 */ + tmp4 = MULTIPLY(tmp11 + tmp12, - FIX(0.764581576)); /* -c7 */ + tmp5 = MULTIPLY(tmp11 + tmp13, - FIX(1.399818907)); /* -c1 */ + tmp1 += tmp4 + tmp5 + MULTIPLY(tmp11, FIX(1.276416582)) /* c9+c7+c1-c3 */ + - MULTIPLY(tmp14, FIX(1.068791298)); /* c5 */ + tmp10 = MULTIPLY(tmp12 + tmp13, FIX(0.398430003)); /* c9 */ + tmp2 += tmp4 + tmp10 - MULTIPLY(tmp12, FIX(1.989053629)) /* c9+c5+c3-c7 */ + + MULTIPLY(tmp14, FIX(1.399818907)); /* c1 */ + tmp3 += tmp5 + tmp10 + MULTIPLY(tmp13, FIX(1.305598626)) /* c1+c5-c9-c7 */ + - MULTIPLY(tmp14, FIX(1.286413905)); /* c3 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp0, CONST_BITS-1); + dataptr[3] = (DCTELEM) DESCALE(tmp1, CONST_BITS-1); + dataptr[5] = (DCTELEM) DESCALE(tmp2, CONST_BITS-1); + dataptr[7] = (DCTELEM) DESCALE(tmp3, CONST_BITS-1); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 11) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/11)**2 = 64/121, which we partially + * fold into the constant multipliers and final/initial shifting: + * cK now represents sqrt(2) * cos(K*pi/22) * 128/121. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*2]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*1]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*0]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*7]; + tmp4 = dataptr[DCTSIZE*4] + dataptr[DCTSIZE*6]; + tmp5 = dataptr[DCTSIZE*5]; + + tmp10 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*2]; + tmp11 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*1]; + tmp12 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*0]; + tmp13 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*7]; + tmp14 = dataptr[DCTSIZE*4] - dataptr[DCTSIZE*6]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 + tmp1 + tmp2 + tmp3 + tmp4 + tmp5, + FIX(1.057851240)), /* 128/121 */ + CONST_BITS+2); + tmp5 += tmp5; + tmp0 -= tmp5; + tmp1 -= tmp5; + tmp2 -= tmp5; + tmp3 -= tmp5; + tmp4 -= tmp5; + z1 = MULTIPLY(tmp0 + tmp3, FIX(1.435427942)) + /* c2 */ + MULTIPLY(tmp2 + tmp4, FIX(0.212906922)); /* c10 */ + z2 = MULTIPLY(tmp1 - tmp3, FIX(0.979689713)); /* c6 */ + z3 = MULTIPLY(tmp0 - tmp1, FIX(1.258538479)); /* c4 */ + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(z1 + z2 - MULTIPLY(tmp3, FIX(1.077210542)) /* c2+c8-c6 */ + - MULTIPLY(tmp4, FIX(1.471445400)), /* c4+c10 */ + CONST_BITS+2); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(z2 + z3 + MULTIPLY(tmp1, FIX(0.065941844)) /* c4-c6-c10 */ + - MULTIPLY(tmp2, FIX(1.435427942)) /* c2 */ + + MULTIPLY(tmp4, FIX(0.621472312)), /* c8 */ + CONST_BITS+2); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(z1 + z3 - MULTIPLY(tmp0, FIX(1.714276708)) /* c2+c4-c6 */ + - MULTIPLY(tmp2, FIX(0.834379234)), /* c8+c10 */ + CONST_BITS+2); + + /* Odd part */ + + tmp1 = MULTIPLY(tmp10 + tmp11, FIX(1.360834544)); /* c3 */ + tmp2 = MULTIPLY(tmp10 + tmp12, FIX(1.130622199)); /* c5 */ + tmp3 = MULTIPLY(tmp10 + tmp13, FIX(0.808813568)); /* c7 */ + tmp0 = tmp1 + tmp2 + tmp3 - MULTIPLY(tmp10, FIX(1.819470145)) /* c7+c5+c3-c1 */ + + MULTIPLY(tmp14, FIX(0.421479672)); /* c9 */ + tmp4 = MULTIPLY(tmp11 + tmp12, - FIX(0.808813568)); /* -c7 */ + tmp5 = MULTIPLY(tmp11 + tmp13, - FIX(1.480800167)); /* -c1 */ + tmp1 += tmp4 + tmp5 + MULTIPLY(tmp11, FIX(1.350258864)) /* c9+c7+c1-c3 */ + - MULTIPLY(tmp14, FIX(1.130622199)); /* c5 */ + tmp10 = MULTIPLY(tmp12 + tmp13, FIX(0.421479672)); /* c9 */ + tmp2 += tmp4 + tmp10 - MULTIPLY(tmp12, FIX(2.104122847)) /* c9+c5+c3-c7 */ + + MULTIPLY(tmp14, FIX(1.480800167)); /* c1 */ + tmp3 += tmp5 + tmp10 + MULTIPLY(tmp13, FIX(1.381129125)) /* c1+c5-c9-c7 */ + - MULTIPLY(tmp14, FIX(1.360834544)); /* c3 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp0, CONST_BITS+2); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp1, CONST_BITS+2); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp2, CONST_BITS+2); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp3, CONST_BITS+2); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 12x12 sample block. + */ + +GLOBAL(void) +jpeg_fdct_12x12 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + DCTELEM workspace[8*4]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT. */ + /* cK represents sqrt(2) * cos(K*pi/24). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[11]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[10]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[9]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[8]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[7]); + tmp5 = GETJSAMPLE(elemptr[5]) + GETJSAMPLE(elemptr[6]); + + tmp10 = tmp0 + tmp5; + tmp13 = tmp0 - tmp5; + tmp11 = tmp1 + tmp4; + tmp14 = tmp1 - tmp4; + tmp12 = tmp2 + tmp3; + tmp15 = tmp2 - tmp3; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[11]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[10]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[9]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[8]); + tmp4 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[7]); + tmp5 = GETJSAMPLE(elemptr[5]) - GETJSAMPLE(elemptr[6]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) (tmp10 + tmp11 + tmp12 - 12 * CENTERJSAMPLE); + dataptr[6] = (DCTELEM) (tmp13 - tmp14 - tmp15); + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12, FIX(1.224744871)), /* c4 */ + CONST_BITS); + dataptr[2] = (DCTELEM) + DESCALE(tmp14 - tmp15 + MULTIPLY(tmp13 + tmp15, FIX(1.366025404)), /* c2 */ + CONST_BITS); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp1 + tmp4, FIX_0_541196100); /* c9 */ + tmp14 = tmp10 + MULTIPLY(tmp1, FIX_0_765366865); /* c3-c9 */ + tmp15 = tmp10 - MULTIPLY(tmp4, FIX_1_847759065); /* c3+c9 */ + tmp12 = MULTIPLY(tmp0 + tmp2, FIX(1.121971054)); /* c5 */ + tmp13 = MULTIPLY(tmp0 + tmp3, FIX(0.860918669)); /* c7 */ + tmp10 = tmp12 + tmp13 + tmp14 - MULTIPLY(tmp0, FIX(0.580774953)) /* c5+c7-c1 */ + + MULTIPLY(tmp5, FIX(0.184591911)); /* c11 */ + tmp11 = MULTIPLY(tmp2 + tmp3, - FIX(0.184591911)); /* -c11 */ + tmp12 += tmp11 - tmp15 - MULTIPLY(tmp2, FIX(2.339493912)) /* c1+c5-c11 */ + + MULTIPLY(tmp5, FIX(0.860918669)); /* c7 */ + tmp13 += tmp11 - tmp14 + MULTIPLY(tmp3, FIX(0.725788011)) /* c1+c11-c7 */ + - MULTIPLY(tmp5, FIX(1.121971054)); /* c5 */ + tmp11 = tmp15 + MULTIPLY(tmp0 - tmp3, FIX(1.306562965)) /* c3 */ + - MULTIPLY(tmp2 + tmp5, FIX_0_541196100); /* c9 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp10, CONST_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp11, CONST_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp12, CONST_BITS); + dataptr[7] = (DCTELEM) DESCALE(tmp13, CONST_BITS); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 12) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/12)**2 = 4/9, which we partially + * fold into the constant multipliers and final shifting: + * cK now represents sqrt(2) * cos(K*pi/24) * 8/9. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*3]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*2]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*1]; + tmp3 = dataptr[DCTSIZE*3] + wsptr[DCTSIZE*0]; + tmp4 = dataptr[DCTSIZE*4] + dataptr[DCTSIZE*7]; + tmp5 = dataptr[DCTSIZE*5] + dataptr[DCTSIZE*6]; + + tmp10 = tmp0 + tmp5; + tmp13 = tmp0 - tmp5; + tmp11 = tmp1 + tmp4; + tmp14 = tmp1 - tmp4; + tmp12 = tmp2 + tmp3; + tmp15 = tmp2 - tmp3; + + tmp0 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*3]; + tmp1 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*2]; + tmp2 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*1]; + tmp3 = dataptr[DCTSIZE*3] - wsptr[DCTSIZE*0]; + tmp4 = dataptr[DCTSIZE*4] - dataptr[DCTSIZE*7]; + tmp5 = dataptr[DCTSIZE*5] - dataptr[DCTSIZE*6]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11 + tmp12, FIX(0.888888889)), /* 8/9 */ + CONST_BITS+1); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(MULTIPLY(tmp13 - tmp14 - tmp15, FIX(0.888888889)), /* 8/9 */ + CONST_BITS+1); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12, FIX(1.088662108)), /* c4 */ + CONST_BITS+1); + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp14 - tmp15, FIX(0.888888889)) + /* 8/9 */ + MULTIPLY(tmp13 + tmp15, FIX(1.214244803)), /* c2 */ + CONST_BITS+1); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp1 + tmp4, FIX(0.481063200)); /* c9 */ + tmp14 = tmp10 + MULTIPLY(tmp1, FIX(0.680326102)); /* c3-c9 */ + tmp15 = tmp10 - MULTIPLY(tmp4, FIX(1.642452502)); /* c3+c9 */ + tmp12 = MULTIPLY(tmp0 + tmp2, FIX(0.997307603)); /* c5 */ + tmp13 = MULTIPLY(tmp0 + tmp3, FIX(0.765261039)); /* c7 */ + tmp10 = tmp12 + tmp13 + tmp14 - MULTIPLY(tmp0, FIX(0.516244403)) /* c5+c7-c1 */ + + MULTIPLY(tmp5, FIX(0.164081699)); /* c11 */ + tmp11 = MULTIPLY(tmp2 + tmp3, - FIX(0.164081699)); /* -c11 */ + tmp12 += tmp11 - tmp15 - MULTIPLY(tmp2, FIX(2.079550144)) /* c1+c5-c11 */ + + MULTIPLY(tmp5, FIX(0.765261039)); /* c7 */ + tmp13 += tmp11 - tmp14 + MULTIPLY(tmp3, FIX(0.645144899)) /* c1+c11-c7 */ + - MULTIPLY(tmp5, FIX(0.997307603)); /* c5 */ + tmp11 = tmp15 + MULTIPLY(tmp0 - tmp3, FIX(1.161389302)) /* c3 */ + - MULTIPLY(tmp2 + tmp5, FIX(0.481063200)); /* c9 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp10, CONST_BITS+1); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp11, CONST_BITS+1); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp12, CONST_BITS+1); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp13, CONST_BITS+1); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 13x13 sample block. + */ + +GLOBAL(void) +jpeg_fdct_13x13 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + INT32 z1, z2; + DCTELEM workspace[8*5]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT. */ + /* cK represents sqrt(2) * cos(K*pi/26). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[12]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[11]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[10]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[9]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[8]); + tmp5 = GETJSAMPLE(elemptr[5]) + GETJSAMPLE(elemptr[7]); + tmp6 = GETJSAMPLE(elemptr[6]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[12]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[11]); + tmp12 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[10]); + tmp13 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[9]); + tmp14 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[8]); + tmp15 = GETJSAMPLE(elemptr[5]) - GETJSAMPLE(elemptr[7]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + (tmp0 + tmp1 + tmp2 + tmp3 + tmp4 + tmp5 + tmp6 - 13 * CENTERJSAMPLE); + tmp6 += tmp6; + tmp0 -= tmp6; + tmp1 -= tmp6; + tmp2 -= tmp6; + tmp3 -= tmp6; + tmp4 -= tmp6; + tmp5 -= tmp6; + dataptr[2] = (DCTELEM) + DESCALE(MULTIPLY(tmp0, FIX(1.373119086)) + /* c2 */ + MULTIPLY(tmp1, FIX(1.058554052)) + /* c6 */ + MULTIPLY(tmp2, FIX(0.501487041)) - /* c10 */ + MULTIPLY(tmp3, FIX(0.170464608)) - /* c12 */ + MULTIPLY(tmp4, FIX(0.803364869)) - /* c8 */ + MULTIPLY(tmp5, FIX(1.252223920)), /* c4 */ + CONST_BITS); + z1 = MULTIPLY(tmp0 - tmp2, FIX(1.155388986)) - /* (c4+c6)/2 */ + MULTIPLY(tmp3 - tmp4, FIX(0.435816023)) - /* (c2-c10)/2 */ + MULTIPLY(tmp1 - tmp5, FIX(0.316450131)); /* (c8-c12)/2 */ + z2 = MULTIPLY(tmp0 + tmp2, FIX(0.096834934)) - /* (c4-c6)/2 */ + MULTIPLY(tmp3 + tmp4, FIX(0.937303064)) + /* (c2+c10)/2 */ + MULTIPLY(tmp1 + tmp5, FIX(0.486914739)); /* (c8+c12)/2 */ + + dataptr[4] = (DCTELEM) DESCALE(z1 + z2, CONST_BITS); + dataptr[6] = (DCTELEM) DESCALE(z1 - z2, CONST_BITS); + + /* Odd part */ + + tmp1 = MULTIPLY(tmp10 + tmp11, FIX(1.322312651)); /* c3 */ + tmp2 = MULTIPLY(tmp10 + tmp12, FIX(1.163874945)); /* c5 */ + tmp3 = MULTIPLY(tmp10 + tmp13, FIX(0.937797057)) + /* c7 */ + MULTIPLY(tmp14 + tmp15, FIX(0.338443458)); /* c11 */ + tmp0 = tmp1 + tmp2 + tmp3 - + MULTIPLY(tmp10, FIX(2.020082300)) + /* c3+c5+c7-c1 */ + MULTIPLY(tmp14, FIX(0.318774355)); /* c9-c11 */ + tmp4 = MULTIPLY(tmp14 - tmp15, FIX(0.937797057)) - /* c7 */ + MULTIPLY(tmp11 + tmp12, FIX(0.338443458)); /* c11 */ + tmp5 = MULTIPLY(tmp11 + tmp13, - FIX(1.163874945)); /* -c5 */ + tmp1 += tmp4 + tmp5 + + MULTIPLY(tmp11, FIX(0.837223564)) - /* c5+c9+c11-c3 */ + MULTIPLY(tmp14, FIX(2.341699410)); /* c1+c7 */ + tmp6 = MULTIPLY(tmp12 + tmp13, - FIX(0.657217813)); /* -c9 */ + tmp2 += tmp4 + tmp6 - + MULTIPLY(tmp12, FIX(1.572116027)) + /* c1+c5-c9-c11 */ + MULTIPLY(tmp15, FIX(2.260109708)); /* c3+c7 */ + tmp3 += tmp5 + tmp6 + + MULTIPLY(tmp13, FIX(2.205608352)) - /* c3+c5+c9-c7 */ + MULTIPLY(tmp15, FIX(1.742345811)); /* c1+c11 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp0, CONST_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp1, CONST_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp2, CONST_BITS); + dataptr[7] = (DCTELEM) DESCALE(tmp3, CONST_BITS); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 13) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/13)**2 = 64/169, which we partially + * fold into the constant multipliers and final shifting: + * cK now represents sqrt(2) * cos(K*pi/26) * 128/169. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*4]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*3]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*2]; + tmp3 = dataptr[DCTSIZE*3] + wsptr[DCTSIZE*1]; + tmp4 = dataptr[DCTSIZE*4] + wsptr[DCTSIZE*0]; + tmp5 = dataptr[DCTSIZE*5] + dataptr[DCTSIZE*7]; + tmp6 = dataptr[DCTSIZE*6]; + + tmp10 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*4]; + tmp11 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*3]; + tmp12 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*2]; + tmp13 = dataptr[DCTSIZE*3] - wsptr[DCTSIZE*1]; + tmp14 = dataptr[DCTSIZE*4] - wsptr[DCTSIZE*0]; + tmp15 = dataptr[DCTSIZE*5] - dataptr[DCTSIZE*7]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 + tmp1 + tmp2 + tmp3 + tmp4 + tmp5 + tmp6, + FIX(0.757396450)), /* 128/169 */ + CONST_BITS+1); + tmp6 += tmp6; + tmp0 -= tmp6; + tmp1 -= tmp6; + tmp2 -= tmp6; + tmp3 -= tmp6; + tmp4 -= tmp6; + tmp5 -= tmp6; + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp0, FIX(1.039995521)) + /* c2 */ + MULTIPLY(tmp1, FIX(0.801745081)) + /* c6 */ + MULTIPLY(tmp2, FIX(0.379824504)) - /* c10 */ + MULTIPLY(tmp3, FIX(0.129109289)) - /* c12 */ + MULTIPLY(tmp4, FIX(0.608465700)) - /* c8 */ + MULTIPLY(tmp5, FIX(0.948429952)), /* c4 */ + CONST_BITS+1); + z1 = MULTIPLY(tmp0 - tmp2, FIX(0.875087516)) - /* (c4+c6)/2 */ + MULTIPLY(tmp3 - tmp4, FIX(0.330085509)) - /* (c2-c10)/2 */ + MULTIPLY(tmp1 - tmp5, FIX(0.239678205)); /* (c8-c12)/2 */ + z2 = MULTIPLY(tmp0 + tmp2, FIX(0.073342435)) - /* (c4-c6)/2 */ + MULTIPLY(tmp3 + tmp4, FIX(0.709910013)) + /* (c2+c10)/2 */ + MULTIPLY(tmp1 + tmp5, FIX(0.368787494)); /* (c8+c12)/2 */ + + dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(z1 + z2, CONST_BITS+1); + dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(z1 - z2, CONST_BITS+1); + + /* Odd part */ + + tmp1 = MULTIPLY(tmp10 + tmp11, FIX(1.001514908)); /* c3 */ + tmp2 = MULTIPLY(tmp10 + tmp12, FIX(0.881514751)); /* c5 */ + tmp3 = MULTIPLY(tmp10 + tmp13, FIX(0.710284161)) + /* c7 */ + MULTIPLY(tmp14 + tmp15, FIX(0.256335874)); /* c11 */ + tmp0 = tmp1 + tmp2 + tmp3 - + MULTIPLY(tmp10, FIX(1.530003162)) + /* c3+c5+c7-c1 */ + MULTIPLY(tmp14, FIX(0.241438564)); /* c9-c11 */ + tmp4 = MULTIPLY(tmp14 - tmp15, FIX(0.710284161)) - /* c7 */ + MULTIPLY(tmp11 + tmp12, FIX(0.256335874)); /* c11 */ + tmp5 = MULTIPLY(tmp11 + tmp13, - FIX(0.881514751)); /* -c5 */ + tmp1 += tmp4 + tmp5 + + MULTIPLY(tmp11, FIX(0.634110155)) - /* c5+c9+c11-c3 */ + MULTIPLY(tmp14, FIX(1.773594819)); /* c1+c7 */ + tmp6 = MULTIPLY(tmp12 + tmp13, - FIX(0.497774438)); /* -c9 */ + tmp2 += tmp4 + tmp6 - + MULTIPLY(tmp12, FIX(1.190715098)) + /* c1+c5-c9-c11 */ + MULTIPLY(tmp15, FIX(1.711799069)); /* c3+c7 */ + tmp3 += tmp5 + tmp6 + + MULTIPLY(tmp13, FIX(1.670519935)) - /* c3+c5+c9-c7 */ + MULTIPLY(tmp15, FIX(1.319646532)); /* c1+c11 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp0, CONST_BITS+1); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp1, CONST_BITS+1); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp2, CONST_BITS+1); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp3, CONST_BITS+1); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 14x14 sample block. + */ + +GLOBAL(void) +jpeg_fdct_14x14 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16; + DCTELEM workspace[8*6]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT. */ + /* cK represents sqrt(2) * cos(K*pi/28). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[13]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[12]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[11]); + tmp13 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[10]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[9]); + tmp5 = GETJSAMPLE(elemptr[5]) + GETJSAMPLE(elemptr[8]); + tmp6 = GETJSAMPLE(elemptr[6]) + GETJSAMPLE(elemptr[7]); + + tmp10 = tmp0 + tmp6; + tmp14 = tmp0 - tmp6; + tmp11 = tmp1 + tmp5; + tmp15 = tmp1 - tmp5; + tmp12 = tmp2 + tmp4; + tmp16 = tmp2 - tmp4; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[13]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[12]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[11]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[10]); + tmp4 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[9]); + tmp5 = GETJSAMPLE(elemptr[5]) - GETJSAMPLE(elemptr[8]); + tmp6 = GETJSAMPLE(elemptr[6]) - GETJSAMPLE(elemptr[7]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + (tmp10 + tmp11 + tmp12 + tmp13 - 14 * CENTERJSAMPLE); + tmp13 += tmp13; + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp13, FIX(1.274162392)) + /* c4 */ + MULTIPLY(tmp11 - tmp13, FIX(0.314692123)) - /* c12 */ + MULTIPLY(tmp12 - tmp13, FIX(0.881747734)), /* c8 */ + CONST_BITS); + + tmp10 = MULTIPLY(tmp14 + tmp15, FIX(1.105676686)); /* c6 */ + + dataptr[2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp14, FIX(0.273079590)) /* c2-c6 */ + + MULTIPLY(tmp16, FIX(0.613604268)), /* c10 */ + CONST_BITS); + dataptr[6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp15, FIX(1.719280954)) /* c6+c10 */ + - MULTIPLY(tmp16, FIX(1.378756276)), /* c2 */ + CONST_BITS); + + /* Odd part */ + + tmp10 = tmp1 + tmp2; + tmp11 = tmp5 - tmp4; + dataptr[7] = (DCTELEM) (tmp0 - tmp10 + tmp3 - tmp11 - tmp6); + tmp3 <<= CONST_BITS; + tmp10 = MULTIPLY(tmp10, - FIX(0.158341681)); /* -c13 */ + tmp11 = MULTIPLY(tmp11, FIX(1.405321284)); /* c1 */ + tmp10 += tmp11 - tmp3; + tmp11 = MULTIPLY(tmp0 + tmp2, FIX(1.197448846)) + /* c5 */ + MULTIPLY(tmp4 + tmp6, FIX(0.752406978)); /* c9 */ + dataptr[5] = (DCTELEM) + DESCALE(tmp10 + tmp11 - MULTIPLY(tmp2, FIX(2.373959773)) /* c3+c5-c13 */ + + MULTIPLY(tmp4, FIX(1.119999435)), /* c1+c11-c9 */ + CONST_BITS); + tmp12 = MULTIPLY(tmp0 + tmp1, FIX(1.334852607)) + /* c3 */ + MULTIPLY(tmp5 - tmp6, FIX(0.467085129)); /* c11 */ + dataptr[3] = (DCTELEM) + DESCALE(tmp10 + tmp12 - MULTIPLY(tmp1, FIX(0.424103948)) /* c3-c9-c13 */ + - MULTIPLY(tmp5, FIX(3.069855259)), /* c1+c5+c11 */ + CONST_BITS); + dataptr[1] = (DCTELEM) + DESCALE(tmp11 + tmp12 + tmp3 + tmp6 - + MULTIPLY(tmp0 + tmp6, FIX(1.126980169)), /* c3+c5-c1 */ + CONST_BITS); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 14) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/14)**2 = 16/49, which we partially + * fold into the constant multipliers and final shifting: + * cK now represents sqrt(2) * cos(K*pi/28) * 32/49. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*5]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*3]; + tmp13 = dataptr[DCTSIZE*3] + wsptr[DCTSIZE*2]; + tmp4 = dataptr[DCTSIZE*4] + wsptr[DCTSIZE*1]; + tmp5 = dataptr[DCTSIZE*5] + wsptr[DCTSIZE*0]; + tmp6 = dataptr[DCTSIZE*6] + dataptr[DCTSIZE*7]; + + tmp10 = tmp0 + tmp6; + tmp14 = tmp0 - tmp6; + tmp11 = tmp1 + tmp5; + tmp15 = tmp1 - tmp5; + tmp12 = tmp2 + tmp4; + tmp16 = tmp2 - tmp4; + + tmp0 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*5]; + tmp1 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*3]; + tmp3 = dataptr[DCTSIZE*3] - wsptr[DCTSIZE*2]; + tmp4 = dataptr[DCTSIZE*4] - wsptr[DCTSIZE*1]; + tmp5 = dataptr[DCTSIZE*5] - wsptr[DCTSIZE*0]; + tmp6 = dataptr[DCTSIZE*6] - dataptr[DCTSIZE*7]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11 + tmp12 + tmp13, + FIX(0.653061224)), /* 32/49 */ + CONST_BITS+1); + tmp13 += tmp13; + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp13, FIX(0.832106052)) + /* c4 */ + MULTIPLY(tmp11 - tmp13, FIX(0.205513223)) - /* c12 */ + MULTIPLY(tmp12 - tmp13, FIX(0.575835255)), /* c8 */ + CONST_BITS+1); + + tmp10 = MULTIPLY(tmp14 + tmp15, FIX(0.722074570)); /* c6 */ + + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp14, FIX(0.178337691)) /* c2-c6 */ + + MULTIPLY(tmp16, FIX(0.400721155)), /* c10 */ + CONST_BITS+1); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp15, FIX(1.122795725)) /* c6+c10 */ + - MULTIPLY(tmp16, FIX(0.900412262)), /* c2 */ + CONST_BITS+1); + + /* Odd part */ + + tmp10 = tmp1 + tmp2; + tmp11 = tmp5 - tmp4; + dataptr[DCTSIZE*7] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp10 + tmp3 - tmp11 - tmp6, + FIX(0.653061224)), /* 32/49 */ + CONST_BITS+1); + tmp3 = MULTIPLY(tmp3 , FIX(0.653061224)); /* 32/49 */ + tmp10 = MULTIPLY(tmp10, - FIX(0.103406812)); /* -c13 */ + tmp11 = MULTIPLY(tmp11, FIX(0.917760839)); /* c1 */ + tmp10 += tmp11 - tmp3; + tmp11 = MULTIPLY(tmp0 + tmp2, FIX(0.782007410)) + /* c5 */ + MULTIPLY(tmp4 + tmp6, FIX(0.491367823)); /* c9 */ + dataptr[DCTSIZE*5] = (DCTELEM) + DESCALE(tmp10 + tmp11 - MULTIPLY(tmp2, FIX(1.550341076)) /* c3+c5-c13 */ + + MULTIPLY(tmp4, FIX(0.731428202)), /* c1+c11-c9 */ + CONST_BITS+1); + tmp12 = MULTIPLY(tmp0 + tmp1, FIX(0.871740478)) + /* c3 */ + MULTIPLY(tmp5 - tmp6, FIX(0.305035186)); /* c11 */ + dataptr[DCTSIZE*3] = (DCTELEM) + DESCALE(tmp10 + tmp12 - MULTIPLY(tmp1, FIX(0.276965844)) /* c3-c9-c13 */ + - MULTIPLY(tmp5, FIX(2.004803435)), /* c1+c5+c11 */ + CONST_BITS+1); + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(tmp11 + tmp12 + tmp3 + - MULTIPLY(tmp0, FIX(0.735987049)) /* c3+c5-c1 */ + - MULTIPLY(tmp6, FIX(0.082925825)), /* c9-c11-c13 */ + CONST_BITS+1); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 15x15 sample block. + */ + +GLOBAL(void) +jpeg_fdct_15x15 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16; + INT32 z1, z2, z3; + DCTELEM workspace[8*7]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT. */ + /* cK represents sqrt(2) * cos(K*pi/30). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[14]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[13]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[12]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[11]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[10]); + tmp5 = GETJSAMPLE(elemptr[5]) + GETJSAMPLE(elemptr[9]); + tmp6 = GETJSAMPLE(elemptr[6]) + GETJSAMPLE(elemptr[8]); + tmp7 = GETJSAMPLE(elemptr[7]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[14]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[13]); + tmp12 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[12]); + tmp13 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[11]); + tmp14 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[10]); + tmp15 = GETJSAMPLE(elemptr[5]) - GETJSAMPLE(elemptr[9]); + tmp16 = GETJSAMPLE(elemptr[6]) - GETJSAMPLE(elemptr[8]); + + z1 = tmp0 + tmp4 + tmp5; + z2 = tmp1 + tmp3 + tmp6; + z3 = tmp2 + tmp7; + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) (z1 + z2 + z3 - 15 * CENTERJSAMPLE); + z3 += z3; + dataptr[6] = (DCTELEM) + DESCALE(MULTIPLY(z1 - z3, FIX(1.144122806)) - /* c6 */ + MULTIPLY(z2 - z3, FIX(0.437016024)), /* c12 */ + CONST_BITS); + tmp2 += ((tmp1 + tmp4) >> 1) - tmp7 - tmp7; + z1 = MULTIPLY(tmp3 - tmp2, FIX(1.531135173)) - /* c2+c14 */ + MULTIPLY(tmp6 - tmp2, FIX(2.238241955)); /* c4+c8 */ + z2 = MULTIPLY(tmp5 - tmp2, FIX(0.798468008)) - /* c8-c14 */ + MULTIPLY(tmp0 - tmp2, FIX(0.091361227)); /* c2-c4 */ + z3 = MULTIPLY(tmp0 - tmp3, FIX(1.383309603)) + /* c2 */ + MULTIPLY(tmp6 - tmp5, FIX(0.946293579)) + /* c8 */ + MULTIPLY(tmp1 - tmp4, FIX(0.790569415)); /* (c6+c12)/2 */ + + dataptr[2] = (DCTELEM) DESCALE(z1 + z3, CONST_BITS); + dataptr[4] = (DCTELEM) DESCALE(z2 + z3, CONST_BITS); + + /* Odd part */ + + tmp2 = MULTIPLY(tmp10 - tmp12 - tmp13 + tmp15 + tmp16, + FIX(1.224744871)); /* c5 */ + tmp1 = MULTIPLY(tmp10 - tmp14 - tmp15, FIX(1.344997024)) + /* c3 */ + MULTIPLY(tmp11 - tmp13 - tmp16, FIX(0.831253876)); /* c9 */ + tmp12 = MULTIPLY(tmp12, FIX(1.224744871)); /* c5 */ + tmp4 = MULTIPLY(tmp10 - tmp16, FIX(1.406466353)) + /* c1 */ + MULTIPLY(tmp11 + tmp14, FIX(1.344997024)) + /* c3 */ + MULTIPLY(tmp13 + tmp15, FIX(0.575212477)); /* c11 */ + tmp0 = MULTIPLY(tmp13, FIX(0.475753014)) - /* c7-c11 */ + MULTIPLY(tmp14, FIX(0.513743148)) + /* c3-c9 */ + MULTIPLY(tmp16, FIX(1.700497885)) + tmp4 + tmp12; /* c1+c13 */ + tmp3 = MULTIPLY(tmp10, - FIX(0.355500862)) - /* -(c1-c7) */ + MULTIPLY(tmp11, FIX(2.176250899)) - /* c3+c9 */ + MULTIPLY(tmp15, FIX(0.869244010)) + tmp4 - tmp12; /* c11+c13 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp0, CONST_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp1, CONST_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp2, CONST_BITS); + dataptr[7] = (DCTELEM) DESCALE(tmp3, CONST_BITS); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 15) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/15)**2 = 64/225, which we partially + * fold into the constant multipliers and final shifting: + * cK now represents sqrt(2) * cos(K*pi/30) * 256/225. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*6]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*5]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*4]; + tmp3 = dataptr[DCTSIZE*3] + wsptr[DCTSIZE*3]; + tmp4 = dataptr[DCTSIZE*4] + wsptr[DCTSIZE*2]; + tmp5 = dataptr[DCTSIZE*5] + wsptr[DCTSIZE*1]; + tmp6 = dataptr[DCTSIZE*6] + wsptr[DCTSIZE*0]; + tmp7 = dataptr[DCTSIZE*7]; + + tmp10 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*6]; + tmp11 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*5]; + tmp12 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*4]; + tmp13 = dataptr[DCTSIZE*3] - wsptr[DCTSIZE*3]; + tmp14 = dataptr[DCTSIZE*4] - wsptr[DCTSIZE*2]; + tmp15 = dataptr[DCTSIZE*5] - wsptr[DCTSIZE*1]; + tmp16 = dataptr[DCTSIZE*6] - wsptr[DCTSIZE*0]; + + z1 = tmp0 + tmp4 + tmp5; + z2 = tmp1 + tmp3 + tmp6; + z3 = tmp2 + tmp7; + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(z1 + z2 + z3, FIX(1.137777778)), /* 256/225 */ + CONST_BITS+2); + z3 += z3; + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(MULTIPLY(z1 - z3, FIX(1.301757503)) - /* c6 */ + MULTIPLY(z2 - z3, FIX(0.497227121)), /* c12 */ + CONST_BITS+2); + tmp2 += ((tmp1 + tmp4) >> 1) - tmp7 - tmp7; + z1 = MULTIPLY(tmp3 - tmp2, FIX(1.742091575)) - /* c2+c14 */ + MULTIPLY(tmp6 - tmp2, FIX(2.546621957)); /* c4+c8 */ + z2 = MULTIPLY(tmp5 - tmp2, FIX(0.908479156)) - /* c8-c14 */ + MULTIPLY(tmp0 - tmp2, FIX(0.103948774)); /* c2-c4 */ + z3 = MULTIPLY(tmp0 - tmp3, FIX(1.573898926)) + /* c2 */ + MULTIPLY(tmp6 - tmp5, FIX(1.076671805)) + /* c8 */ + MULTIPLY(tmp1 - tmp4, FIX(0.899492312)); /* (c6+c12)/2 */ + + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(z1 + z3, CONST_BITS+2); + dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(z2 + z3, CONST_BITS+2); + + /* Odd part */ + + tmp2 = MULTIPLY(tmp10 - tmp12 - tmp13 + tmp15 + tmp16, + FIX(1.393487498)); /* c5 */ + tmp1 = MULTIPLY(tmp10 - tmp14 - tmp15, FIX(1.530307725)) + /* c3 */ + MULTIPLY(tmp11 - tmp13 - tmp16, FIX(0.945782187)); /* c9 */ + tmp12 = MULTIPLY(tmp12, FIX(1.393487498)); /* c5 */ + tmp4 = MULTIPLY(tmp10 - tmp16, FIX(1.600246161)) + /* c1 */ + MULTIPLY(tmp11 + tmp14, FIX(1.530307725)) + /* c3 */ + MULTIPLY(tmp13 + tmp15, FIX(0.654463974)); /* c11 */ + tmp0 = MULTIPLY(tmp13, FIX(0.541301207)) - /* c7-c11 */ + MULTIPLY(tmp14, FIX(0.584525538)) + /* c3-c9 */ + MULTIPLY(tmp16, FIX(1.934788705)) + tmp4 + tmp12; /* c1+c13 */ + tmp3 = MULTIPLY(tmp10, - FIX(0.404480980)) - /* -(c1-c7) */ + MULTIPLY(tmp11, FIX(2.476089912)) - /* c3+c9 */ + MULTIPLY(tmp15, FIX(0.989006518)) + tmp4 - tmp12; /* c11+c13 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp0, CONST_BITS+2); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp1, CONST_BITS+2); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp2, CONST_BITS+2); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp3, CONST_BITS+2); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 16x16 sample block. + */ + +GLOBAL(void) +jpeg_fdct_16x16 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17; + DCTELEM workspace[DCTSIZE2]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* cK represents sqrt(2) * cos(K*pi/32). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[15]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[14]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[13]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[12]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[11]); + tmp5 = GETJSAMPLE(elemptr[5]) + GETJSAMPLE(elemptr[10]); + tmp6 = GETJSAMPLE(elemptr[6]) + GETJSAMPLE(elemptr[9]); + tmp7 = GETJSAMPLE(elemptr[7]) + GETJSAMPLE(elemptr[8]); + + tmp10 = tmp0 + tmp7; + tmp14 = tmp0 - tmp7; + tmp11 = tmp1 + tmp6; + tmp15 = tmp1 - tmp6; + tmp12 = tmp2 + tmp5; + tmp16 = tmp2 - tmp5; + tmp13 = tmp3 + tmp4; + tmp17 = tmp3 - tmp4; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[15]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[14]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[13]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[12]); + tmp4 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[11]); + tmp5 = GETJSAMPLE(elemptr[5]) - GETJSAMPLE(elemptr[10]); + tmp6 = GETJSAMPLE(elemptr[6]) - GETJSAMPLE(elemptr[9]); + tmp7 = GETJSAMPLE(elemptr[7]) - GETJSAMPLE(elemptr[8]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 + tmp12 + tmp13 - 16 * CENTERJSAMPLE) << PASS1_BITS); + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp13, FIX(1.306562965)) + /* c4[16] = c2[8] */ + MULTIPLY(tmp11 - tmp12, FIX_0_541196100), /* c12[16] = c6[8] */ + CONST_BITS-PASS1_BITS); + + tmp10 = MULTIPLY(tmp17 - tmp15, FIX(0.275899379)) + /* c14[16] = c7[8] */ + MULTIPLY(tmp14 - tmp16, FIX(1.387039845)); /* c2[16] = c1[8] */ + + dataptr[2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp15, FIX(1.451774982)) /* c6+c14 */ + + MULTIPLY(tmp16, FIX(2.172734804)), /* c2+c10 */ + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp14, FIX(0.211164243)) /* c2-c6 */ + - MULTIPLY(tmp17, FIX(1.061594338)), /* c10+c14 */ + CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp11 = MULTIPLY(tmp0 + tmp1, FIX(1.353318001)) + /* c3 */ + MULTIPLY(tmp6 - tmp7, FIX(0.410524528)); /* c13 */ + tmp12 = MULTIPLY(tmp0 + tmp2, FIX(1.247225013)) + /* c5 */ + MULTIPLY(tmp5 + tmp7, FIX(0.666655658)); /* c11 */ + tmp13 = MULTIPLY(tmp0 + tmp3, FIX(1.093201867)) + /* c7 */ + MULTIPLY(tmp4 - tmp7, FIX(0.897167586)); /* c9 */ + tmp14 = MULTIPLY(tmp1 + tmp2, FIX(0.138617169)) + /* c15 */ + MULTIPLY(tmp6 - tmp5, FIX(1.407403738)); /* c1 */ + tmp15 = MULTIPLY(tmp1 + tmp3, - FIX(0.666655658)) + /* -c11 */ + MULTIPLY(tmp4 + tmp6, - FIX(1.247225013)); /* -c5 */ + tmp16 = MULTIPLY(tmp2 + tmp3, - FIX(1.353318001)) + /* -c3 */ + MULTIPLY(tmp5 - tmp4, FIX(0.410524528)); /* c13 */ + tmp10 = tmp11 + tmp12 + tmp13 - + MULTIPLY(tmp0, FIX(2.286341144)) + /* c7+c5+c3-c1 */ + MULTIPLY(tmp7, FIX(0.779653625)); /* c15+c13-c11+c9 */ + tmp11 += tmp14 + tmp15 + MULTIPLY(tmp1, FIX(0.071888074)) /* c9-c3-c15+c11 */ + - MULTIPLY(tmp6, FIX(1.663905119)); /* c7+c13+c1-c5 */ + tmp12 += tmp14 + tmp16 - MULTIPLY(tmp2, FIX(1.125726048)) /* c7+c5+c15-c3 */ + + MULTIPLY(tmp5, FIX(1.227391138)); /* c9-c11+c1-c13 */ + tmp13 += tmp15 + tmp16 + MULTIPLY(tmp3, FIX(1.065388962)) /* c15+c3+c11-c7 */ + + MULTIPLY(tmp4, FIX(2.167985692)); /* c1+c13+c5-c9 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp10, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp11, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp12, CONST_BITS-PASS1_BITS); + dataptr[7] = (DCTELEM) DESCALE(tmp13, CONST_BITS-PASS1_BITS); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == DCTSIZE * 2) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/16)**2 = 1/2**2. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + wsptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*4] + wsptr[DCTSIZE*3]; + tmp5 = dataptr[DCTSIZE*5] + wsptr[DCTSIZE*2]; + tmp6 = dataptr[DCTSIZE*6] + wsptr[DCTSIZE*1]; + tmp7 = dataptr[DCTSIZE*7] + wsptr[DCTSIZE*0]; + + tmp10 = tmp0 + tmp7; + tmp14 = tmp0 - tmp7; + tmp11 = tmp1 + tmp6; + tmp15 = tmp1 - tmp6; + tmp12 = tmp2 + tmp5; + tmp16 = tmp2 - tmp5; + tmp13 = tmp3 + tmp4; + tmp17 = tmp3 - tmp4; + + tmp0 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] - wsptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*4] - wsptr[DCTSIZE*3]; + tmp5 = dataptr[DCTSIZE*5] - wsptr[DCTSIZE*2]; + tmp6 = dataptr[DCTSIZE*6] - wsptr[DCTSIZE*1]; + tmp7 = dataptr[DCTSIZE*7] - wsptr[DCTSIZE*0]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(tmp10 + tmp11 + tmp12 + tmp13, PASS1_BITS+2); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp13, FIX(1.306562965)) + /* c4[16] = c2[8] */ + MULTIPLY(tmp11 - tmp12, FIX_0_541196100), /* c12[16] = c6[8] */ + CONST_BITS+PASS1_BITS+2); + + tmp10 = MULTIPLY(tmp17 - tmp15, FIX(0.275899379)) + /* c14[16] = c7[8] */ + MULTIPLY(tmp14 - tmp16, FIX(1.387039845)); /* c2[16] = c1[8] */ + + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp15, FIX(1.451774982)) /* c6+c14 */ + + MULTIPLY(tmp16, FIX(2.172734804)), /* c2+10 */ + CONST_BITS+PASS1_BITS+2); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp14, FIX(0.211164243)) /* c2-c6 */ + - MULTIPLY(tmp17, FIX(1.061594338)), /* c10+c14 */ + CONST_BITS+PASS1_BITS+2); + + /* Odd part */ + + tmp11 = MULTIPLY(tmp0 + tmp1, FIX(1.353318001)) + /* c3 */ + MULTIPLY(tmp6 - tmp7, FIX(0.410524528)); /* c13 */ + tmp12 = MULTIPLY(tmp0 + tmp2, FIX(1.247225013)) + /* c5 */ + MULTIPLY(tmp5 + tmp7, FIX(0.666655658)); /* c11 */ + tmp13 = MULTIPLY(tmp0 + tmp3, FIX(1.093201867)) + /* c7 */ + MULTIPLY(tmp4 - tmp7, FIX(0.897167586)); /* c9 */ + tmp14 = MULTIPLY(tmp1 + tmp2, FIX(0.138617169)) + /* c15 */ + MULTIPLY(tmp6 - tmp5, FIX(1.407403738)); /* c1 */ + tmp15 = MULTIPLY(tmp1 + tmp3, - FIX(0.666655658)) + /* -c11 */ + MULTIPLY(tmp4 + tmp6, - FIX(1.247225013)); /* -c5 */ + tmp16 = MULTIPLY(tmp2 + tmp3, - FIX(1.353318001)) + /* -c3 */ + MULTIPLY(tmp5 - tmp4, FIX(0.410524528)); /* c13 */ + tmp10 = tmp11 + tmp12 + tmp13 - + MULTIPLY(tmp0, FIX(2.286341144)) + /* c7+c5+c3-c1 */ + MULTIPLY(tmp7, FIX(0.779653625)); /* c15+c13-c11+c9 */ + tmp11 += tmp14 + tmp15 + MULTIPLY(tmp1, FIX(0.071888074)) /* c9-c3-c15+c11 */ + - MULTIPLY(tmp6, FIX(1.663905119)); /* c7+c13+c1-c5 */ + tmp12 += tmp14 + tmp16 - MULTIPLY(tmp2, FIX(1.125726048)) /* c7+c5+c15-c3 */ + + MULTIPLY(tmp5, FIX(1.227391138)); /* c9-c11+c1-c13 */ + tmp13 += tmp15 + tmp16 + MULTIPLY(tmp3, FIX(1.065388962)) /* c15+c3+c11-c7 */ + + MULTIPLY(tmp4, FIX(2.167985692)); /* c1+c13+c5-c9 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp10, CONST_BITS+PASS1_BITS+2); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp11, CONST_BITS+PASS1_BITS+2); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp12, CONST_BITS+PASS1_BITS+2); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp13, CONST_BITS+PASS1_BITS+2); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 16x8 sample block. + * + * 16-point FDCT in pass 1 (rows), 8-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_16x8 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17; + INT32 z1; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* 16-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/32). */ + + dataptr = data; + ctr = 0; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[15]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[14]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[13]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[12]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[11]); + tmp5 = GETJSAMPLE(elemptr[5]) + GETJSAMPLE(elemptr[10]); + tmp6 = GETJSAMPLE(elemptr[6]) + GETJSAMPLE(elemptr[9]); + tmp7 = GETJSAMPLE(elemptr[7]) + GETJSAMPLE(elemptr[8]); + + tmp10 = tmp0 + tmp7; + tmp14 = tmp0 - tmp7; + tmp11 = tmp1 + tmp6; + tmp15 = tmp1 - tmp6; + tmp12 = tmp2 + tmp5; + tmp16 = tmp2 - tmp5; + tmp13 = tmp3 + tmp4; + tmp17 = tmp3 - tmp4; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[15]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[14]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[13]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[12]); + tmp4 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[11]); + tmp5 = GETJSAMPLE(elemptr[5]) - GETJSAMPLE(elemptr[10]); + tmp6 = GETJSAMPLE(elemptr[6]) - GETJSAMPLE(elemptr[9]); + tmp7 = GETJSAMPLE(elemptr[7]) - GETJSAMPLE(elemptr[8]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 + tmp12 + tmp13 - 16 * CENTERJSAMPLE) << PASS1_BITS); + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp13, FIX(1.306562965)) + /* c4[16] = c2[8] */ + MULTIPLY(tmp11 - tmp12, FIX_0_541196100), /* c12[16] = c6[8] */ + CONST_BITS-PASS1_BITS); + + tmp10 = MULTIPLY(tmp17 - tmp15, FIX(0.275899379)) + /* c14[16] = c7[8] */ + MULTIPLY(tmp14 - tmp16, FIX(1.387039845)); /* c2[16] = c1[8] */ + + dataptr[2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp15, FIX(1.451774982)) /* c6+c14 */ + + MULTIPLY(tmp16, FIX(2.172734804)), /* c2+c10 */ + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp14, FIX(0.211164243)) /* c2-c6 */ + - MULTIPLY(tmp17, FIX(1.061594338)), /* c10+c14 */ + CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp11 = MULTIPLY(tmp0 + tmp1, FIX(1.353318001)) + /* c3 */ + MULTIPLY(tmp6 - tmp7, FIX(0.410524528)); /* c13 */ + tmp12 = MULTIPLY(tmp0 + tmp2, FIX(1.247225013)) + /* c5 */ + MULTIPLY(tmp5 + tmp7, FIX(0.666655658)); /* c11 */ + tmp13 = MULTIPLY(tmp0 + tmp3, FIX(1.093201867)) + /* c7 */ + MULTIPLY(tmp4 - tmp7, FIX(0.897167586)); /* c9 */ + tmp14 = MULTIPLY(tmp1 + tmp2, FIX(0.138617169)) + /* c15 */ + MULTIPLY(tmp6 - tmp5, FIX(1.407403738)); /* c1 */ + tmp15 = MULTIPLY(tmp1 + tmp3, - FIX(0.666655658)) + /* -c11 */ + MULTIPLY(tmp4 + tmp6, - FIX(1.247225013)); /* -c5 */ + tmp16 = MULTIPLY(tmp2 + tmp3, - FIX(1.353318001)) + /* -c3 */ + MULTIPLY(tmp5 - tmp4, FIX(0.410524528)); /* c13 */ + tmp10 = tmp11 + tmp12 + tmp13 - + MULTIPLY(tmp0, FIX(2.286341144)) + /* c7+c5+c3-c1 */ + MULTIPLY(tmp7, FIX(0.779653625)); /* c15+c13-c11+c9 */ + tmp11 += tmp14 + tmp15 + MULTIPLY(tmp1, FIX(0.071888074)) /* c9-c3-c15+c11 */ + - MULTIPLY(tmp6, FIX(1.663905119)); /* c7+c13+c1-c5 */ + tmp12 += tmp14 + tmp16 - MULTIPLY(tmp2, FIX(1.125726048)) /* c7+c5+c15-c3 */ + + MULTIPLY(tmp5, FIX(1.227391138)); /* c9-c11+c1-c13 */ + tmp13 += tmp15 + tmp16 + MULTIPLY(tmp3, FIX(1.065388962)) /* c15+c3+c11-c7 */ + + MULTIPLY(tmp4, FIX(2.167985692)); /* c1+c13+c5-c9 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp10, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp11, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp12, CONST_BITS-PASS1_BITS); + dataptr[7] = (DCTELEM) DESCALE(tmp13, CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by 8/16 = 1/2. + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + + tmp10 = tmp0 + tmp3; + tmp12 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp13 = tmp1 - tmp2; + + tmp0 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + dataptr[DCTSIZE*0] = (DCTELEM) DESCALE(tmp10 + tmp11, PASS1_BITS+1); + dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(tmp10 - tmp11, PASS1_BITS+1); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, FIX_0_765366865), + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(z1 - MULTIPLY(tmp13, FIX_1_847759065), + CONST_BITS+PASS1_BITS+1); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * 8-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/16). + * i0..i3 in the paper are tmp0..tmp3 here. + */ + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = MULTIPLY(tmp12 + tmp13, FIX_1_175875602); /* c3 */ + + tmp0 = MULTIPLY(tmp0, FIX_1_501321110); /* c1+c3-c5-c7 */ + tmp1 = MULTIPLY(tmp1, FIX_3_072711026); /* c1+c3+c5-c7 */ + tmp2 = MULTIPLY(tmp2, FIX_2_053119869); /* c1+c3-c5+c7 */ + tmp3 = MULTIPLY(tmp3, FIX_0_298631336); /* -c1+c3+c5-c7 */ + tmp10 = MULTIPLY(tmp10, - FIX_0_899976223); /* c7-c3 */ + tmp11 = MULTIPLY(tmp11, - FIX_2_562915447); /* -c1-c3 */ + tmp12 = MULTIPLY(tmp12, - FIX_0_390180644); /* c5-c3 */ + tmp13 = MULTIPLY(tmp13, - FIX_1_961570560); /* -c3-c5 */ + + tmp12 += z1; + tmp13 += z1; + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp0 + tmp10 + tmp12, + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp1 + tmp11 + tmp13, + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp2 + tmp11 + tmp12, + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp3 + tmp10 + tmp13, + CONST_BITS+PASS1_BITS+1); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 14x7 sample block. + * + * 14-point FDCT in pass 1 (rows), 7-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_14x7 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16; + INT32 z1, z2, z3; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Zero bottom row of output coefficient block. */ + MEMZERO(&data[DCTSIZE*7], SIZEOF(DCTELEM) * DCTSIZE); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* 14-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/28). */ + + dataptr = data; + for (ctr = 0; ctr < 7; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[13]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[12]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[11]); + tmp13 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[10]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[9]); + tmp5 = GETJSAMPLE(elemptr[5]) + GETJSAMPLE(elemptr[8]); + tmp6 = GETJSAMPLE(elemptr[6]) + GETJSAMPLE(elemptr[7]); + + tmp10 = tmp0 + tmp6; + tmp14 = tmp0 - tmp6; + tmp11 = tmp1 + tmp5; + tmp15 = tmp1 - tmp5; + tmp12 = tmp2 + tmp4; + tmp16 = tmp2 - tmp4; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[13]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[12]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[11]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[10]); + tmp4 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[9]); + tmp5 = GETJSAMPLE(elemptr[5]) - GETJSAMPLE(elemptr[8]); + tmp6 = GETJSAMPLE(elemptr[6]) - GETJSAMPLE(elemptr[7]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 + tmp12 + tmp13 - 14 * CENTERJSAMPLE) << PASS1_BITS); + tmp13 += tmp13; + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp13, FIX(1.274162392)) + /* c4 */ + MULTIPLY(tmp11 - tmp13, FIX(0.314692123)) - /* c12 */ + MULTIPLY(tmp12 - tmp13, FIX(0.881747734)), /* c8 */ + CONST_BITS-PASS1_BITS); + + tmp10 = MULTIPLY(tmp14 + tmp15, FIX(1.105676686)); /* c6 */ + + dataptr[2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp14, FIX(0.273079590)) /* c2-c6 */ + + MULTIPLY(tmp16, FIX(0.613604268)), /* c10 */ + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp15, FIX(1.719280954)) /* c6+c10 */ + - MULTIPLY(tmp16, FIX(1.378756276)), /* c2 */ + CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp10 = tmp1 + tmp2; + tmp11 = tmp5 - tmp4; + dataptr[7] = (DCTELEM) ((tmp0 - tmp10 + tmp3 - tmp11 - tmp6) << PASS1_BITS); + tmp3 <<= CONST_BITS; + tmp10 = MULTIPLY(tmp10, - FIX(0.158341681)); /* -c13 */ + tmp11 = MULTIPLY(tmp11, FIX(1.405321284)); /* c1 */ + tmp10 += tmp11 - tmp3; + tmp11 = MULTIPLY(tmp0 + tmp2, FIX(1.197448846)) + /* c5 */ + MULTIPLY(tmp4 + tmp6, FIX(0.752406978)); /* c9 */ + dataptr[5] = (DCTELEM) + DESCALE(tmp10 + tmp11 - MULTIPLY(tmp2, FIX(2.373959773)) /* c3+c5-c13 */ + + MULTIPLY(tmp4, FIX(1.119999435)), /* c1+c11-c9 */ + CONST_BITS-PASS1_BITS); + tmp12 = MULTIPLY(tmp0 + tmp1, FIX(1.334852607)) + /* c3 */ + MULTIPLY(tmp5 - tmp6, FIX(0.467085129)); /* c11 */ + dataptr[3] = (DCTELEM) + DESCALE(tmp10 + tmp12 - MULTIPLY(tmp1, FIX(0.424103948)) /* c3-c9-c13 */ + - MULTIPLY(tmp5, FIX(3.069855259)), /* c1+c5+c11 */ + CONST_BITS-PASS1_BITS); + dataptr[1] = (DCTELEM) + DESCALE(tmp11 + tmp12 + tmp3 + tmp6 - + MULTIPLY(tmp0 + tmp6, FIX(1.126980169)), /* c3+c5-c1 */ + CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/14)*(8/7) = 32/49, which we + * partially fold into the constant multipliers and final shifting: + * 7-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/14) * 64/49. + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*6]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*5]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*4]; + tmp3 = dataptr[DCTSIZE*3]; + + tmp10 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*6]; + tmp11 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*5]; + tmp12 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*4]; + + z1 = tmp0 + tmp2; + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(z1 + tmp1 + tmp3, FIX(1.306122449)), /* 64/49 */ + CONST_BITS+PASS1_BITS+1); + tmp3 += tmp3; + z1 -= tmp3; + z1 -= tmp3; + z1 = MULTIPLY(z1, FIX(0.461784020)); /* (c2+c6-c4)/2 */ + z2 = MULTIPLY(tmp0 - tmp2, FIX(1.202428084)); /* (c2+c4-c6)/2 */ + z3 = MULTIPLY(tmp1 - tmp2, FIX(0.411026446)); /* c6 */ + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(z1 + z2 + z3, CONST_BITS+PASS1_BITS+1); + z1 -= z2; + z2 = MULTIPLY(tmp0 - tmp1, FIX(1.151670509)); /* c4 */ + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(z2 + z3 - MULTIPLY(tmp1 - tmp3, FIX(0.923568041)), /* c2+c6-c4 */ + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(z1 + z2, CONST_BITS+PASS1_BITS+1); + + /* Odd part */ + + tmp1 = MULTIPLY(tmp10 + tmp11, FIX(1.221765677)); /* (c3+c1-c5)/2 */ + tmp2 = MULTIPLY(tmp10 - tmp11, FIX(0.222383464)); /* (c3+c5-c1)/2 */ + tmp0 = tmp1 - tmp2; + tmp1 += tmp2; + tmp2 = MULTIPLY(tmp11 + tmp12, - FIX(1.800824523)); /* -c1 */ + tmp1 += tmp2; + tmp3 = MULTIPLY(tmp10 + tmp12, FIX(0.801442310)); /* c5 */ + tmp0 += tmp3; + tmp2 += tmp3 + MULTIPLY(tmp12, FIX(2.443531355)); /* c3+c1-c5 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp0, CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp1, CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp2, CONST_BITS+PASS1_BITS+1); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 12x6 sample block. + * + * 12-point FDCT in pass 1 (rows), 6-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_12x6 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Zero 2 bottom rows of output coefficient block. */ + MEMZERO(&data[DCTSIZE*6], SIZEOF(DCTELEM) * DCTSIZE * 2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* 12-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/24). */ + + dataptr = data; + for (ctr = 0; ctr < 6; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[11]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[10]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[9]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[8]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[7]); + tmp5 = GETJSAMPLE(elemptr[5]) + GETJSAMPLE(elemptr[6]); + + tmp10 = tmp0 + tmp5; + tmp13 = tmp0 - tmp5; + tmp11 = tmp1 + tmp4; + tmp14 = tmp1 - tmp4; + tmp12 = tmp2 + tmp3; + tmp15 = tmp2 - tmp3; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[11]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[10]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[9]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[8]); + tmp4 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[7]); + tmp5 = GETJSAMPLE(elemptr[5]) - GETJSAMPLE(elemptr[6]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 + tmp12 - 12 * CENTERJSAMPLE) << PASS1_BITS); + dataptr[6] = (DCTELEM) ((tmp13 - tmp14 - tmp15) << PASS1_BITS); + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12, FIX(1.224744871)), /* c4 */ + CONST_BITS-PASS1_BITS); + dataptr[2] = (DCTELEM) + DESCALE(tmp14 - tmp15 + MULTIPLY(tmp13 + tmp15, FIX(1.366025404)), /* c2 */ + CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp1 + tmp4, FIX_0_541196100); /* c9 */ + tmp14 = tmp10 + MULTIPLY(tmp1, FIX_0_765366865); /* c3-c9 */ + tmp15 = tmp10 - MULTIPLY(tmp4, FIX_1_847759065); /* c3+c9 */ + tmp12 = MULTIPLY(tmp0 + tmp2, FIX(1.121971054)); /* c5 */ + tmp13 = MULTIPLY(tmp0 + tmp3, FIX(0.860918669)); /* c7 */ + tmp10 = tmp12 + tmp13 + tmp14 - MULTIPLY(tmp0, FIX(0.580774953)) /* c5+c7-c1 */ + + MULTIPLY(tmp5, FIX(0.184591911)); /* c11 */ + tmp11 = MULTIPLY(tmp2 + tmp3, - FIX(0.184591911)); /* -c11 */ + tmp12 += tmp11 - tmp15 - MULTIPLY(tmp2, FIX(2.339493912)) /* c1+c5-c11 */ + + MULTIPLY(tmp5, FIX(0.860918669)); /* c7 */ + tmp13 += tmp11 - tmp14 + MULTIPLY(tmp3, FIX(0.725788011)) /* c1+c11-c7 */ + - MULTIPLY(tmp5, FIX(1.121971054)); /* c5 */ + tmp11 = tmp15 + MULTIPLY(tmp0 - tmp3, FIX(1.306562965)) /* c3 */ + - MULTIPLY(tmp2 + tmp5, FIX_0_541196100); /* c9 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp10, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp11, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp12, CONST_BITS-PASS1_BITS); + dataptr[7] = (DCTELEM) DESCALE(tmp13, CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/12)*(8/6) = 8/9, which we + * partially fold into the constant multipliers and final shifting: + * 6-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/12) * 16/9. + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*5]; + tmp11 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*3]; + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + tmp0 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*5]; + tmp1 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*3]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp12, FIX(2.177324216)), /* c2 */ + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp11 - tmp11, FIX(1.257078722)), /* c4 */ + CONST_BITS+PASS1_BITS+1); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp0 + tmp2, FIX(0.650711829)); /* c5 */ + + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp0 + tmp1, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*3] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp1 - tmp2, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*5] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp2 - tmp1, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS+1); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 10x5 sample block. + * + * 10-point FDCT in pass 1 (rows), 5-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_10x5 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Zero 3 bottom rows of output coefficient block. */ + MEMZERO(&data[DCTSIZE*5], SIZEOF(DCTELEM) * DCTSIZE * 3); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* 10-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/20). */ + + dataptr = data; + for (ctr = 0; ctr < 5; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[9]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[8]); + tmp12 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[7]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[6]); + tmp4 = GETJSAMPLE(elemptr[4]) + GETJSAMPLE(elemptr[5]); + + tmp10 = tmp0 + tmp4; + tmp13 = tmp0 - tmp4; + tmp11 = tmp1 + tmp3; + tmp14 = tmp1 - tmp3; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[9]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[8]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[7]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[6]); + tmp4 = GETJSAMPLE(elemptr[4]) - GETJSAMPLE(elemptr[5]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 + tmp12 - 10 * CENTERJSAMPLE) << PASS1_BITS); + tmp12 += tmp12; + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12, FIX(1.144122806)) - /* c4 */ + MULTIPLY(tmp11 - tmp12, FIX(0.437016024)), /* c8 */ + CONST_BITS-PASS1_BITS); + tmp10 = MULTIPLY(tmp13 + tmp14, FIX(0.831253876)); /* c6 */ + dataptr[2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp13, FIX(0.513743148)), /* c2-c6 */ + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp14, FIX(2.176250899)), /* c2+c6 */ + CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp10 = tmp0 + tmp4; + tmp11 = tmp1 - tmp3; + dataptr[5] = (DCTELEM) ((tmp10 - tmp11 - tmp2) << PASS1_BITS); + tmp2 <<= CONST_BITS; + dataptr[1] = (DCTELEM) + DESCALE(MULTIPLY(tmp0, FIX(1.396802247)) + /* c1 */ + MULTIPLY(tmp1, FIX(1.260073511)) + tmp2 + /* c3 */ + MULTIPLY(tmp3, FIX(0.642039522)) + /* c7 */ + MULTIPLY(tmp4, FIX(0.221231742)), /* c9 */ + CONST_BITS-PASS1_BITS); + tmp12 = MULTIPLY(tmp0 - tmp4, FIX(0.951056516)) - /* (c3+c7)/2 */ + MULTIPLY(tmp1 + tmp3, FIX(0.587785252)); /* (c1-c9)/2 */ + tmp13 = MULTIPLY(tmp10 + tmp11, FIX(0.309016994)) + /* (c3-c7)/2 */ + (tmp11 << (CONST_BITS - 1)) - tmp2; + dataptr[3] = (DCTELEM) DESCALE(tmp12 + tmp13, CONST_BITS-PASS1_BITS); + dataptr[7] = (DCTELEM) DESCALE(tmp12 - tmp13, CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/10)*(8/5) = 32/25, which we + * fold into the constant multipliers: + * 5-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/10) * 32/25. + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*4]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*3]; + tmp2 = dataptr[DCTSIZE*2]; + + tmp10 = tmp0 + tmp1; + tmp11 = tmp0 - tmp1; + + tmp0 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*4]; + tmp1 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*3]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp2, FIX(1.28)), /* 32/25 */ + CONST_BITS+PASS1_BITS); + tmp11 = MULTIPLY(tmp11, FIX(1.011928851)); /* (c2+c4)/2 */ + tmp10 -= tmp2 << 2; + tmp10 = MULTIPLY(tmp10, FIX(0.452548340)); /* (c2-c4)/2 */ + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(tmp11 + tmp10, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(tmp11 - tmp10, CONST_BITS+PASS1_BITS); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp0 + tmp1, FIX(1.064004961)); /* c3 */ + + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp0, FIX(0.657591230)), /* c1-c3 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp1, FIX(2.785601151)), /* c1+c3 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on an 8x4 sample block. + * + * 8-point FDCT in pass 1 (rows), 4-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_8x4 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Zero 4 bottom rows of output coefficient block. */ + MEMZERO(&data[DCTSIZE*4], SIZEOF(DCTELEM) * DCTSIZE * 4); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* We must also scale the output by 8/4 = 2, which we add here. */ + + dataptr = data; + for (ctr = 0; ctr < 4; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[7]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[6]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[5]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[4]); + + tmp10 = tmp0 + tmp3; + tmp12 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp13 = tmp1 - tmp2; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[7]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[6]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[5]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[4]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 - 8 * CENTERJSAMPLE) << (PASS1_BITS+1)); + dataptr[4] = (DCTELEM) ((tmp10 - tmp11) << (PASS1_BITS+1)); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-2); + dataptr[2] = (DCTELEM) RIGHT_SHIFT(z1 + MULTIPLY(tmp12, FIX_0_765366865), + CONST_BITS-PASS1_BITS-1); + dataptr[6] = (DCTELEM) RIGHT_SHIFT(z1 - MULTIPLY(tmp13, FIX_1_847759065), + CONST_BITS-PASS1_BITS-1); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * 8-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/16). + * i0..i3 in the paper are tmp0..tmp3 here. + */ + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = MULTIPLY(tmp12 + tmp13, FIX_1_175875602); /* c3 */ + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-2); + + tmp0 = MULTIPLY(tmp0, FIX_1_501321110); /* c1+c3-c5-c7 */ + tmp1 = MULTIPLY(tmp1, FIX_3_072711026); /* c1+c3+c5-c7 */ + tmp2 = MULTIPLY(tmp2, FIX_2_053119869); /* c1+c3-c5+c7 */ + tmp3 = MULTIPLY(tmp3, FIX_0_298631336); /* -c1+c3+c5-c7 */ + tmp10 = MULTIPLY(tmp10, - FIX_0_899976223); /* c7-c3 */ + tmp11 = MULTIPLY(tmp11, - FIX_2_562915447); /* -c1-c3 */ + tmp12 = MULTIPLY(tmp12, - FIX_0_390180644); /* c5-c3 */ + tmp13 = MULTIPLY(tmp13, - FIX_1_961570560); /* -c3-c5 */ + + tmp12 += z1; + tmp13 += z1; + + dataptr[1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + tmp10 + tmp12, CONST_BITS-PASS1_BITS-1); + dataptr[3] = (DCTELEM) + RIGHT_SHIFT(tmp1 + tmp11 + tmp13, CONST_BITS-PASS1_BITS-1); + dataptr[5] = (DCTELEM) + RIGHT_SHIFT(tmp2 + tmp11 + tmp12, CONST_BITS-PASS1_BITS-1); + dataptr[7] = (DCTELEM) + RIGHT_SHIFT(tmp3 + tmp10 + tmp13, CONST_BITS-PASS1_BITS-1); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * 4-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/16). + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*3] + (ONE << (PASS1_BITS-1)); + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*2]; + + tmp10 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*3]; + tmp11 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*2]; + + dataptr[DCTSIZE*0] = (DCTELEM) RIGHT_SHIFT(tmp0 + tmp1, PASS1_BITS); + dataptr[DCTSIZE*2] = (DCTELEM) RIGHT_SHIFT(tmp0 - tmp1, PASS1_BITS); + + /* Odd part */ + + tmp0 = MULTIPLY(tmp10 + tmp11, FIX_0_541196100); /* c6 */ + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS+PASS1_BITS-1); + + dataptr[DCTSIZE*1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + MULTIPLY(tmp10, FIX_0_765366865), /* c2-c6 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + RIGHT_SHIFT(tmp0 - MULTIPLY(tmp11, FIX_1_847759065), /* c2+c6 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 6x3 sample block. + * + * 6-point FDCT in pass 1 (rows), 3-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_6x3 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2; + INT32 tmp10, tmp11, tmp12; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* We scale the results further by 2 as part of output adaption */ + /* scaling for different DCT size. */ + /* 6-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/12). */ + + dataptr = data; + for (ctr = 0; ctr < 3; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[5]); + tmp11 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[4]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[3]); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[5]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[4]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[3]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 - 6 * CENTERJSAMPLE) << (PASS1_BITS+1)); + dataptr[2] = (DCTELEM) + DESCALE(MULTIPLY(tmp12, FIX(1.224744871)), /* c2 */ + CONST_BITS-PASS1_BITS-1); + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp11 - tmp11, FIX(0.707106781)), /* c4 */ + CONST_BITS-PASS1_BITS-1); + + /* Odd part */ + + tmp10 = DESCALE(MULTIPLY(tmp0 + tmp2, FIX(0.366025404)), /* c5 */ + CONST_BITS-PASS1_BITS-1); + + dataptr[1] = (DCTELEM) (tmp10 + ((tmp0 + tmp1) << (PASS1_BITS+1))); + dataptr[3] = (DCTELEM) ((tmp0 - tmp1 - tmp2) << (PASS1_BITS+1)); + dataptr[5] = (DCTELEM) (tmp10 + ((tmp2 - tmp1) << (PASS1_BITS+1))); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/6)*(8/3) = 32/9, which we partially + * fold into the constant multipliers (other part was done in pass 1): + * 3-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/6) * 16/9. + */ + + dataptr = data; + for (ctr = 0; ctr < 6; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*2]; + tmp1 = dataptr[DCTSIZE*1]; + + tmp2 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*2]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 + tmp1, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp1 - tmp1, FIX(1.257078722)), /* c2 */ + CONST_BITS+PASS1_BITS); + + /* Odd part */ + + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(MULTIPLY(tmp2, FIX(2.177324216)), /* c1 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 4x2 sample block. + * + * 4-point FDCT in pass 1 (rows), 2-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_4x2 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1; + INT32 tmp10, tmp11; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* We must also scale the output by (8/4)*(8/2) = 2**3, which we add here. */ + /* 4-point FDCT kernel, */ + /* cK represents sqrt(2) * cos(K*pi/16) [refers to 8-point FDCT]. */ + + dataptr = data; + for (ctr = 0; ctr < 2; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[3]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[2]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[3]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[2]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp0 + tmp1 - 4 * CENTERJSAMPLE) << (PASS1_BITS+3)); + dataptr[2] = (DCTELEM) ((tmp0 - tmp1) << (PASS1_BITS+3)); + + /* Odd part */ + + tmp0 = MULTIPLY(tmp10 + tmp11, FIX_0_541196100); /* c6 */ + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-4); + + dataptr[1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + MULTIPLY(tmp10, FIX_0_765366865), /* c2-c6 */ + CONST_BITS-PASS1_BITS-3); + dataptr[3] = (DCTELEM) + RIGHT_SHIFT(tmp0 - MULTIPLY(tmp11, FIX_1_847759065), /* c2+c6 */ + CONST_BITS-PASS1_BITS-3); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + */ + + dataptr = data; + for (ctr = 0; ctr < 4; ctr++) { + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = dataptr[DCTSIZE*0] + (ONE << (PASS1_BITS-1)); + tmp1 = dataptr[DCTSIZE*1]; + + dataptr[DCTSIZE*0] = (DCTELEM) RIGHT_SHIFT(tmp0 + tmp1, PASS1_BITS); + + /* Odd part */ + + dataptr[DCTSIZE*1] = (DCTELEM) RIGHT_SHIFT(tmp0 - tmp1, PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 2x1 sample block. + * + * 2-point FDCT in pass 1 (rows), 1-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_2x1 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1; + JSAMPROW elemptr; + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + elemptr = sample_data[0] + start_col; + + tmp0 = GETJSAMPLE(elemptr[0]); + tmp1 = GETJSAMPLE(elemptr[1]); + + /* We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/2)*(8/1) = 2**5. + */ + + /* Even part */ + /* Apply unsigned->signed conversion */ + data[0] = (DCTELEM) ((tmp0 + tmp1 - 2 * CENTERJSAMPLE) << 5); + + /* Odd part */ + data[1] = (DCTELEM) ((tmp0 - tmp1) << 5); +} + + +/* + * Perform the forward DCT on an 8x16 sample block. + * + * 8-point FDCT in pass 1 (rows), 16-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_8x16 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17; + INT32 z1; + DCTELEM workspace[DCTSIZE2]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[7]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[6]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[5]); + tmp3 = GETJSAMPLE(elemptr[3]) + GETJSAMPLE(elemptr[4]); + + tmp10 = tmp0 + tmp3; + tmp12 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp13 = tmp1 - tmp2; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[7]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[6]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[5]); + tmp3 = GETJSAMPLE(elemptr[3]) - GETJSAMPLE(elemptr[4]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) ((tmp10 + tmp11 - 8 * CENTERJSAMPLE) << PASS1_BITS); + dataptr[4] = (DCTELEM) ((tmp10 - tmp11) << PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + dataptr[2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, FIX_0_765366865), + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) DESCALE(z1 - MULTIPLY(tmp13, FIX_1_847759065), + CONST_BITS-PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * 8-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/16). + * i0..i3 in the paper are tmp0..tmp3 here. + */ + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = MULTIPLY(tmp12 + tmp13, FIX_1_175875602); /* c3 */ + + tmp0 = MULTIPLY(tmp0, FIX_1_501321110); /* c1+c3-c5-c7 */ + tmp1 = MULTIPLY(tmp1, FIX_3_072711026); /* c1+c3+c5-c7 */ + tmp2 = MULTIPLY(tmp2, FIX_2_053119869); /* c1+c3-c5+c7 */ + tmp3 = MULTIPLY(tmp3, FIX_0_298631336); /* -c1+c3+c5-c7 */ + tmp10 = MULTIPLY(tmp10, - FIX_0_899976223); /* c7-c3 */ + tmp11 = MULTIPLY(tmp11, - FIX_2_562915447); /* -c1-c3 */ + tmp12 = MULTIPLY(tmp12, - FIX_0_390180644); /* c5-c3 */ + tmp13 = MULTIPLY(tmp13, - FIX_1_961570560); /* -c3-c5 */ + + tmp12 += z1; + tmp13 += z1; + + dataptr[1] = (DCTELEM) DESCALE(tmp0 + tmp10 + tmp12, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp1 + tmp11 + tmp13, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp2 + tmp11 + tmp12, CONST_BITS-PASS1_BITS); + dataptr[7] = (DCTELEM) DESCALE(tmp3 + tmp10 + tmp13, CONST_BITS-PASS1_BITS); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == DCTSIZE * 2) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by 8/16 = 1/2. + * 16-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/32). + */ + + dataptr = data; + wsptr = workspace; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + wsptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*4] + wsptr[DCTSIZE*3]; + tmp5 = dataptr[DCTSIZE*5] + wsptr[DCTSIZE*2]; + tmp6 = dataptr[DCTSIZE*6] + wsptr[DCTSIZE*1]; + tmp7 = dataptr[DCTSIZE*7] + wsptr[DCTSIZE*0]; + + tmp10 = tmp0 + tmp7; + tmp14 = tmp0 - tmp7; + tmp11 = tmp1 + tmp6; + tmp15 = tmp1 - tmp6; + tmp12 = tmp2 + tmp5; + tmp16 = tmp2 - tmp5; + tmp13 = tmp3 + tmp4; + tmp17 = tmp3 - tmp4; + + tmp0 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] - wsptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*4] - wsptr[DCTSIZE*3]; + tmp5 = dataptr[DCTSIZE*5] - wsptr[DCTSIZE*2]; + tmp6 = dataptr[DCTSIZE*6] - wsptr[DCTSIZE*1]; + tmp7 = dataptr[DCTSIZE*7] - wsptr[DCTSIZE*0]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(tmp10 + tmp11 + tmp12 + tmp13, PASS1_BITS+1); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp13, FIX(1.306562965)) + /* c4[16] = c2[8] */ + MULTIPLY(tmp11 - tmp12, FIX_0_541196100), /* c12[16] = c6[8] */ + CONST_BITS+PASS1_BITS+1); + + tmp10 = MULTIPLY(tmp17 - tmp15, FIX(0.275899379)) + /* c14[16] = c7[8] */ + MULTIPLY(tmp14 - tmp16, FIX(1.387039845)); /* c2[16] = c1[8] */ + + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp15, FIX(1.451774982)) /* c6+c14 */ + + MULTIPLY(tmp16, FIX(2.172734804)), /* c2+c10 */ + CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp14, FIX(0.211164243)) /* c2-c6 */ + - MULTIPLY(tmp17, FIX(1.061594338)), /* c10+c14 */ + CONST_BITS+PASS1_BITS+1); + + /* Odd part */ + + tmp11 = MULTIPLY(tmp0 + tmp1, FIX(1.353318001)) + /* c3 */ + MULTIPLY(tmp6 - tmp7, FIX(0.410524528)); /* c13 */ + tmp12 = MULTIPLY(tmp0 + tmp2, FIX(1.247225013)) + /* c5 */ + MULTIPLY(tmp5 + tmp7, FIX(0.666655658)); /* c11 */ + tmp13 = MULTIPLY(tmp0 + tmp3, FIX(1.093201867)) + /* c7 */ + MULTIPLY(tmp4 - tmp7, FIX(0.897167586)); /* c9 */ + tmp14 = MULTIPLY(tmp1 + tmp2, FIX(0.138617169)) + /* c15 */ + MULTIPLY(tmp6 - tmp5, FIX(1.407403738)); /* c1 */ + tmp15 = MULTIPLY(tmp1 + tmp3, - FIX(0.666655658)) + /* -c11 */ + MULTIPLY(tmp4 + tmp6, - FIX(1.247225013)); /* -c5 */ + tmp16 = MULTIPLY(tmp2 + tmp3, - FIX(1.353318001)) + /* -c3 */ + MULTIPLY(tmp5 - tmp4, FIX(0.410524528)); /* c13 */ + tmp10 = tmp11 + tmp12 + tmp13 - + MULTIPLY(tmp0, FIX(2.286341144)) + /* c7+c5+c3-c1 */ + MULTIPLY(tmp7, FIX(0.779653625)); /* c15+c13-c11+c9 */ + tmp11 += tmp14 + tmp15 + MULTIPLY(tmp1, FIX(0.071888074)) /* c9-c3-c15+c11 */ + - MULTIPLY(tmp6, FIX(1.663905119)); /* c7+c13+c1-c5 */ + tmp12 += tmp14 + tmp16 - MULTIPLY(tmp2, FIX(1.125726048)) /* c7+c5+c15-c3 */ + + MULTIPLY(tmp5, FIX(1.227391138)); /* c9-c11+c1-c13 */ + tmp13 += tmp15 + tmp16 + MULTIPLY(tmp3, FIX(1.065388962)) /* c15+c3+c11-c7 */ + + MULTIPLY(tmp4, FIX(2.167985692)); /* c1+c13+c5-c9 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp10, CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp11, CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp12, CONST_BITS+PASS1_BITS+1); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp13, CONST_BITS+PASS1_BITS+1); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 7x14 sample block. + * + * 7-point FDCT in pass 1 (rows), 14-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_7x14 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16; + INT32 z1, z2, z3; + DCTELEM workspace[8*6]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* 7-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/14). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[6]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[5]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[4]); + tmp3 = GETJSAMPLE(elemptr[3]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[6]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[5]); + tmp12 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[4]); + + z1 = tmp0 + tmp2; + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((z1 + tmp1 + tmp3 - 7 * CENTERJSAMPLE) << PASS1_BITS); + tmp3 += tmp3; + z1 -= tmp3; + z1 -= tmp3; + z1 = MULTIPLY(z1, FIX(0.353553391)); /* (c2+c6-c4)/2 */ + z2 = MULTIPLY(tmp0 - tmp2, FIX(0.920609002)); /* (c2+c4-c6)/2 */ + z3 = MULTIPLY(tmp1 - tmp2, FIX(0.314692123)); /* c6 */ + dataptr[2] = (DCTELEM) DESCALE(z1 + z2 + z3, CONST_BITS-PASS1_BITS); + z1 -= z2; + z2 = MULTIPLY(tmp0 - tmp1, FIX(0.881747734)); /* c4 */ + dataptr[4] = (DCTELEM) + DESCALE(z2 + z3 - MULTIPLY(tmp1 - tmp3, FIX(0.707106781)), /* c2+c6-c4 */ + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) DESCALE(z1 + z2, CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp1 = MULTIPLY(tmp10 + tmp11, FIX(0.935414347)); /* (c3+c1-c5)/2 */ + tmp2 = MULTIPLY(tmp10 - tmp11, FIX(0.170262339)); /* (c3+c5-c1)/2 */ + tmp0 = tmp1 - tmp2; + tmp1 += tmp2; + tmp2 = MULTIPLY(tmp11 + tmp12, - FIX(1.378756276)); /* -c1 */ + tmp1 += tmp2; + tmp3 = MULTIPLY(tmp10 + tmp12, FIX(0.613604268)); /* c5 */ + tmp0 += tmp3; + tmp2 += tmp3 + MULTIPLY(tmp12, FIX(1.870828693)); /* c3+c1-c5 */ + + dataptr[1] = (DCTELEM) DESCALE(tmp0, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp1, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp2, CONST_BITS-PASS1_BITS); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 14) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/7)*(8/14) = 32/49, which we + * fold into the constant multipliers: + * 14-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/28) * 32/49. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = 0; ctr < 7; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*5]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*3]; + tmp13 = dataptr[DCTSIZE*3] + wsptr[DCTSIZE*2]; + tmp4 = dataptr[DCTSIZE*4] + wsptr[DCTSIZE*1]; + tmp5 = dataptr[DCTSIZE*5] + wsptr[DCTSIZE*0]; + tmp6 = dataptr[DCTSIZE*6] + dataptr[DCTSIZE*7]; + + tmp10 = tmp0 + tmp6; + tmp14 = tmp0 - tmp6; + tmp11 = tmp1 + tmp5; + tmp15 = tmp1 - tmp5; + tmp12 = tmp2 + tmp4; + tmp16 = tmp2 - tmp4; + + tmp0 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*5]; + tmp1 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*3]; + tmp3 = dataptr[DCTSIZE*3] - wsptr[DCTSIZE*2]; + tmp4 = dataptr[DCTSIZE*4] - wsptr[DCTSIZE*1]; + tmp5 = dataptr[DCTSIZE*5] - wsptr[DCTSIZE*0]; + tmp6 = dataptr[DCTSIZE*6] - dataptr[DCTSIZE*7]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11 + tmp12 + tmp13, + FIX(0.653061224)), /* 32/49 */ + CONST_BITS+PASS1_BITS); + tmp13 += tmp13; + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp13, FIX(0.832106052)) + /* c4 */ + MULTIPLY(tmp11 - tmp13, FIX(0.205513223)) - /* c12 */ + MULTIPLY(tmp12 - tmp13, FIX(0.575835255)), /* c8 */ + CONST_BITS+PASS1_BITS); + + tmp10 = MULTIPLY(tmp14 + tmp15, FIX(0.722074570)); /* c6 */ + + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp14, FIX(0.178337691)) /* c2-c6 */ + + MULTIPLY(tmp16, FIX(0.400721155)), /* c10 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp15, FIX(1.122795725)) /* c6+c10 */ + - MULTIPLY(tmp16, FIX(0.900412262)), /* c2 */ + CONST_BITS+PASS1_BITS); + + /* Odd part */ + + tmp10 = tmp1 + tmp2; + tmp11 = tmp5 - tmp4; + dataptr[DCTSIZE*7] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp10 + tmp3 - tmp11 - tmp6, + FIX(0.653061224)), /* 32/49 */ + CONST_BITS+PASS1_BITS); + tmp3 = MULTIPLY(tmp3 , FIX(0.653061224)); /* 32/49 */ + tmp10 = MULTIPLY(tmp10, - FIX(0.103406812)); /* -c13 */ + tmp11 = MULTIPLY(tmp11, FIX(0.917760839)); /* c1 */ + tmp10 += tmp11 - tmp3; + tmp11 = MULTIPLY(tmp0 + tmp2, FIX(0.782007410)) + /* c5 */ + MULTIPLY(tmp4 + tmp6, FIX(0.491367823)); /* c9 */ + dataptr[DCTSIZE*5] = (DCTELEM) + DESCALE(tmp10 + tmp11 - MULTIPLY(tmp2, FIX(1.550341076)) /* c3+c5-c13 */ + + MULTIPLY(tmp4, FIX(0.731428202)), /* c1+c11-c9 */ + CONST_BITS+PASS1_BITS); + tmp12 = MULTIPLY(tmp0 + tmp1, FIX(0.871740478)) + /* c3 */ + MULTIPLY(tmp5 - tmp6, FIX(0.305035186)); /* c11 */ + dataptr[DCTSIZE*3] = (DCTELEM) + DESCALE(tmp10 + tmp12 - MULTIPLY(tmp1, FIX(0.276965844)) /* c3-c9-c13 */ + - MULTIPLY(tmp5, FIX(2.004803435)), /* c1+c5+c11 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(tmp11 + tmp12 + tmp3 + - MULTIPLY(tmp0, FIX(0.735987049)) /* c3+c5-c1 */ + - MULTIPLY(tmp6, FIX(0.082925825)), /* c9-c11-c13 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 6x12 sample block. + * + * 6-point FDCT in pass 1 (rows), 12-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_6x12 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + DCTELEM workspace[8*4]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* 6-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/12). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[5]); + tmp11 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[4]); + tmp2 = GETJSAMPLE(elemptr[2]) + GETJSAMPLE(elemptr[3]); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[5]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[4]); + tmp2 = GETJSAMPLE(elemptr[2]) - GETJSAMPLE(elemptr[3]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp11 - 6 * CENTERJSAMPLE) << PASS1_BITS); + dataptr[2] = (DCTELEM) + DESCALE(MULTIPLY(tmp12, FIX(1.224744871)), /* c2 */ + CONST_BITS-PASS1_BITS); + dataptr[4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp11 - tmp11, FIX(0.707106781)), /* c4 */ + CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp10 = DESCALE(MULTIPLY(tmp0 + tmp2, FIX(0.366025404)), /* c5 */ + CONST_BITS-PASS1_BITS); + + dataptr[1] = (DCTELEM) (tmp10 + ((tmp0 + tmp1) << PASS1_BITS)); + dataptr[3] = (DCTELEM) ((tmp0 - tmp1 - tmp2) << PASS1_BITS); + dataptr[5] = (DCTELEM) (tmp10 + ((tmp2 - tmp1) << PASS1_BITS)); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 12) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/6)*(8/12) = 8/9, which we + * fold into the constant multipliers: + * 12-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/24) * 8/9. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = 0; ctr < 6; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*3]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*2]; + tmp2 = dataptr[DCTSIZE*2] + wsptr[DCTSIZE*1]; + tmp3 = dataptr[DCTSIZE*3] + wsptr[DCTSIZE*0]; + tmp4 = dataptr[DCTSIZE*4] + dataptr[DCTSIZE*7]; + tmp5 = dataptr[DCTSIZE*5] + dataptr[DCTSIZE*6]; + + tmp10 = tmp0 + tmp5; + tmp13 = tmp0 - tmp5; + tmp11 = tmp1 + tmp4; + tmp14 = tmp1 - tmp4; + tmp12 = tmp2 + tmp3; + tmp15 = tmp2 - tmp3; + + tmp0 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*3]; + tmp1 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*2]; + tmp2 = dataptr[DCTSIZE*2] - wsptr[DCTSIZE*1]; + tmp3 = dataptr[DCTSIZE*3] - wsptr[DCTSIZE*0]; + tmp4 = dataptr[DCTSIZE*4] - dataptr[DCTSIZE*7]; + tmp5 = dataptr[DCTSIZE*5] - dataptr[DCTSIZE*6]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11 + tmp12, FIX(0.888888889)), /* 8/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(MULTIPLY(tmp13 - tmp14 - tmp15, FIX(0.888888889)), /* 8/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12, FIX(1.088662108)), /* c4 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp14 - tmp15, FIX(0.888888889)) + /* 8/9 */ + MULTIPLY(tmp13 + tmp15, FIX(1.214244803)), /* c2 */ + CONST_BITS+PASS1_BITS); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp1 + tmp4, FIX(0.481063200)); /* c9 */ + tmp14 = tmp10 + MULTIPLY(tmp1, FIX(0.680326102)); /* c3-c9 */ + tmp15 = tmp10 - MULTIPLY(tmp4, FIX(1.642452502)); /* c3+c9 */ + tmp12 = MULTIPLY(tmp0 + tmp2, FIX(0.997307603)); /* c5 */ + tmp13 = MULTIPLY(tmp0 + tmp3, FIX(0.765261039)); /* c7 */ + tmp10 = tmp12 + tmp13 + tmp14 - MULTIPLY(tmp0, FIX(0.516244403)) /* c5+c7-c1 */ + + MULTIPLY(tmp5, FIX(0.164081699)); /* c11 */ + tmp11 = MULTIPLY(tmp2 + tmp3, - FIX(0.164081699)); /* -c11 */ + tmp12 += tmp11 - tmp15 - MULTIPLY(tmp2, FIX(2.079550144)) /* c1+c5-c11 */ + + MULTIPLY(tmp5, FIX(0.765261039)); /* c7 */ + tmp13 += tmp11 - tmp14 + MULTIPLY(tmp3, FIX(0.645144899)) /* c1+c11-c7 */ + - MULTIPLY(tmp5, FIX(0.997307603)); /* c5 */ + tmp11 = tmp15 + MULTIPLY(tmp0 - tmp3, FIX(1.161389302)) /* c3 */ + - MULTIPLY(tmp2 + tmp5, FIX(0.481063200)); /* c9 */ + + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp10, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp11, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp12, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp13, CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 5x10 sample block. + * + * 5-point FDCT in pass 1 (rows), 10-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_5x10 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4; + INT32 tmp10, tmp11, tmp12, tmp13, tmp14; + DCTELEM workspace[8*2]; + DCTELEM *dataptr; + DCTELEM *wsptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* 5-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/10). */ + + dataptr = data; + ctr = 0; + for (;;) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[4]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[3]); + tmp2 = GETJSAMPLE(elemptr[2]); + + tmp10 = tmp0 + tmp1; + tmp11 = tmp0 - tmp1; + + tmp0 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[4]); + tmp1 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[3]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp10 + tmp2 - 5 * CENTERJSAMPLE) << PASS1_BITS); + tmp11 = MULTIPLY(tmp11, FIX(0.790569415)); /* (c2+c4)/2 */ + tmp10 -= tmp2 << 2; + tmp10 = MULTIPLY(tmp10, FIX(0.353553391)); /* (c2-c4)/2 */ + dataptr[2] = (DCTELEM) DESCALE(tmp11 + tmp10, CONST_BITS-PASS1_BITS); + dataptr[4] = (DCTELEM) DESCALE(tmp11 - tmp10, CONST_BITS-PASS1_BITS); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp0 + tmp1, FIX(0.831253876)); /* c3 */ + + dataptr[1] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp0, FIX(0.513743148)), /* c1-c3 */ + CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp1, FIX(2.176250899)), /* c1+c3 */ + CONST_BITS-PASS1_BITS); + + ctr++; + + if (ctr != DCTSIZE) { + if (ctr == 10) + break; /* Done. */ + dataptr += DCTSIZE; /* advance pointer to next row */ + } else + dataptr = workspace; /* switch pointer to extended workspace */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/5)*(8/10) = 32/25, which we + * fold into the constant multipliers: + * 10-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/20) * 32/25. + */ + + dataptr = data; + wsptr = workspace; + for (ctr = 0; ctr < 5; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + wsptr[DCTSIZE*1]; + tmp1 = dataptr[DCTSIZE*1] + wsptr[DCTSIZE*0]; + tmp12 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*7]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*6]; + tmp4 = dataptr[DCTSIZE*4] + dataptr[DCTSIZE*5]; + + tmp10 = tmp0 + tmp4; + tmp13 = tmp0 - tmp4; + tmp11 = tmp1 + tmp3; + tmp14 = tmp1 - tmp3; + + tmp0 = dataptr[DCTSIZE*0] - wsptr[DCTSIZE*1]; + tmp1 = dataptr[DCTSIZE*1] - wsptr[DCTSIZE*0]; + tmp2 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*7]; + tmp3 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*6]; + tmp4 = dataptr[DCTSIZE*4] - dataptr[DCTSIZE*5]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11 + tmp12, FIX(1.28)), /* 32/25 */ + CONST_BITS+PASS1_BITS); + tmp12 += tmp12; + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp12, FIX(1.464477191)) - /* c4 */ + MULTIPLY(tmp11 - tmp12, FIX(0.559380511)), /* c8 */ + CONST_BITS+PASS1_BITS); + tmp10 = MULTIPLY(tmp13 + tmp14, FIX(1.064004961)); /* c6 */ + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp13, FIX(0.657591230)), /* c2-c6 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) + DESCALE(tmp10 - MULTIPLY(tmp14, FIX(2.785601151)), /* c2+c6 */ + CONST_BITS+PASS1_BITS); + + /* Odd part */ + + tmp10 = tmp0 + tmp4; + tmp11 = tmp1 - tmp3; + dataptr[DCTSIZE*5] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp11 - tmp2, FIX(1.28)), /* 32/25 */ + CONST_BITS+PASS1_BITS); + tmp2 = MULTIPLY(tmp2, FIX(1.28)); /* 32/25 */ + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(MULTIPLY(tmp0, FIX(1.787906876)) + /* c1 */ + MULTIPLY(tmp1, FIX(1.612894094)) + tmp2 + /* c3 */ + MULTIPLY(tmp3, FIX(0.821810588)) + /* c7 */ + MULTIPLY(tmp4, FIX(0.283176630)), /* c9 */ + CONST_BITS+PASS1_BITS); + tmp12 = MULTIPLY(tmp0 - tmp4, FIX(1.217352341)) - /* (c3+c7)/2 */ + MULTIPLY(tmp1 + tmp3, FIX(0.752365123)); /* (c1-c9)/2 */ + tmp13 = MULTIPLY(tmp10 + tmp11, FIX(0.395541753)) + /* (c3-c7)/2 */ + MULTIPLY(tmp11, FIX(0.64)) - tmp2; /* 16/25 */ + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp12 + tmp13, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp12 - tmp13, CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + wsptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 4x8 sample block. + * + * 4-point FDCT in pass 1 (rows), 8-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_4x8 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* We must also scale the output by 8/4 = 2, which we add here. */ + /* 4-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/16). */ + + dataptr = data; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[3]); + tmp1 = GETJSAMPLE(elemptr[1]) + GETJSAMPLE(elemptr[2]); + + tmp10 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[3]); + tmp11 = GETJSAMPLE(elemptr[1]) - GETJSAMPLE(elemptr[2]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp0 + tmp1 - 4 * CENTERJSAMPLE) << (PASS1_BITS+1)); + dataptr[2] = (DCTELEM) ((tmp0 - tmp1) << (PASS1_BITS+1)); + + /* Odd part */ + + tmp0 = MULTIPLY(tmp10 + tmp11, FIX_0_541196100); /* c6 */ + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-2); + + dataptr[1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + MULTIPLY(tmp10, FIX_0_765366865), /* c2-c6 */ + CONST_BITS-PASS1_BITS-1); + dataptr[3] = (DCTELEM) + RIGHT_SHIFT(tmp0 - MULTIPLY(tmp11, FIX_1_847759065), /* c2+c6 */ + CONST_BITS-PASS1_BITS-1); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + */ + + dataptr = data; + for (ctr = 0; ctr < 4; ctr++) { + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + + /* Add fudge factor here for final descale. */ + tmp10 = tmp0 + tmp3 + (ONE << (PASS1_BITS-1)); + tmp12 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp13 = tmp1 - tmp2; + + tmp0 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + dataptr[DCTSIZE*0] = (DCTELEM) RIGHT_SHIFT(tmp10 + tmp11, PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) RIGHT_SHIFT(tmp10 - tmp11, PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS+PASS1_BITS-1); + dataptr[DCTSIZE*2] = (DCTELEM) + RIGHT_SHIFT(z1 + MULTIPLY(tmp12, FIX_0_765366865), CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) + RIGHT_SHIFT(z1 - MULTIPLY(tmp13, FIX_1_847759065), CONST_BITS+PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * 8-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/16). + * i0..i3 in the paper are tmp0..tmp3 here. + */ + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = MULTIPLY(tmp12 + tmp13, FIX_1_175875602); /* c3 */ + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS+PASS1_BITS-1); + + tmp0 = MULTIPLY(tmp0, FIX_1_501321110); /* c1+c3-c5-c7 */ + tmp1 = MULTIPLY(tmp1, FIX_3_072711026); /* c1+c3+c5-c7 */ + tmp2 = MULTIPLY(tmp2, FIX_2_053119869); /* c1+c3-c5+c7 */ + tmp3 = MULTIPLY(tmp3, FIX_0_298631336); /* -c1+c3+c5-c7 */ + tmp10 = MULTIPLY(tmp10, - FIX_0_899976223); /* c7-c3 */ + tmp11 = MULTIPLY(tmp11, - FIX_2_562915447); /* -c1-c3 */ + tmp12 = MULTIPLY(tmp12, - FIX_0_390180644); /* c5-c3 */ + tmp13 = MULTIPLY(tmp13, - FIX_1_961570560); /* -c3-c5 */ + + tmp12 += z1; + tmp13 += z1; + + dataptr[DCTSIZE*1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + tmp10 + tmp12, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + RIGHT_SHIFT(tmp1 + tmp11 + tmp13, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) + RIGHT_SHIFT(tmp2 + tmp11 + tmp12, CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*7] = (DCTELEM) + RIGHT_SHIFT(tmp3 + tmp10 + tmp13, CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 3x6 sample block. + * + * 3-point FDCT in pass 1 (rows), 6-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_3x6 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1, tmp2; + INT32 tmp10, tmp11, tmp12; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + /* We scale the results further by 2 as part of output adaption */ + /* scaling for different DCT size. */ + /* 3-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/6). */ + + dataptr = data; + for (ctr = 0; ctr < 6; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]) + GETJSAMPLE(elemptr[2]); + tmp1 = GETJSAMPLE(elemptr[1]); + + tmp2 = GETJSAMPLE(elemptr[0]) - GETJSAMPLE(elemptr[2]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) + ((tmp0 + tmp1 - 3 * CENTERJSAMPLE) << (PASS1_BITS+1)); + dataptr[2] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp1 - tmp1, FIX(0.707106781)), /* c2 */ + CONST_BITS-PASS1_BITS-1); + + /* Odd part */ + + dataptr[1] = (DCTELEM) + DESCALE(MULTIPLY(tmp2, FIX(1.224744871)), /* c1 */ + CONST_BITS-PASS1_BITS-1); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + * We must also scale the output by (8/6)*(8/3) = 32/9, which we partially + * fold into the constant multipliers (other part was done in pass 1): + * 6-point FDCT kernel, cK represents sqrt(2) * cos(K*pi/12) * 16/9. + */ + + dataptr = data; + for (ctr = 0; ctr < 3; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*5]; + tmp11 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*3]; + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + tmp0 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*5]; + tmp1 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*4]; + tmp2 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*3]; + + dataptr[DCTSIZE*0] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 + tmp11, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*2] = (DCTELEM) + DESCALE(MULTIPLY(tmp12, FIX(2.177324216)), /* c2 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) + DESCALE(MULTIPLY(tmp10 - tmp11 - tmp11, FIX(1.257078722)), /* c4 */ + CONST_BITS+PASS1_BITS); + + /* Odd part */ + + tmp10 = MULTIPLY(tmp0 + tmp2, FIX(0.650711829)); /* c5 */ + + dataptr[DCTSIZE*1] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp0 + tmp1, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + DESCALE(MULTIPLY(tmp0 - tmp1 - tmp2, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) + DESCALE(tmp10 + MULTIPLY(tmp2 - tmp1, FIX(1.777777778)), /* 16/9 */ + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 2x4 sample block. + * + * 2-point FDCT in pass 1 (rows), 4-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_2x4 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1; + INT32 tmp10, tmp11; + DCTELEM *dataptr; + JSAMPROW elemptr; + int ctr; + SHIFT_TEMPS + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT. */ + /* We must also scale the output by (8/2)*(8/4) = 2**3, which we add here. */ + + dataptr = data; + for (ctr = 0; ctr < 4; ctr++) { + elemptr = sample_data[ctr] + start_col; + + /* Even part */ + + tmp0 = GETJSAMPLE(elemptr[0]); + tmp1 = GETJSAMPLE(elemptr[1]); + + /* Apply unsigned->signed conversion */ + dataptr[0] = (DCTELEM) ((tmp0 + tmp1 - 2 * CENTERJSAMPLE) << 3); + + /* Odd part */ + + dataptr[1] = (DCTELEM) ((tmp0 - tmp1) << 3); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We leave the results scaled up by an overall factor of 8. + * 4-point FDCT kernel, + * cK represents sqrt(2) * cos(K*pi/16) [refers to 8-point FDCT]. + */ + + dataptr = data; + for (ctr = 0; ctr < 2; ctr++) { + /* Even part */ + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*3]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*2]; + + tmp10 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*3]; + tmp11 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*2]; + + dataptr[DCTSIZE*0] = (DCTELEM) (tmp0 + tmp1); + dataptr[DCTSIZE*2] = (DCTELEM) (tmp0 - tmp1); + + /* Odd part */ + + tmp0 = MULTIPLY(tmp10 + tmp11, FIX_0_541196100); /* c6 */ + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-1); + + dataptr[DCTSIZE*1] = (DCTELEM) + RIGHT_SHIFT(tmp0 + MULTIPLY(tmp10, FIX_0_765366865), /* c2-c6 */ + CONST_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) + RIGHT_SHIFT(tmp0 - MULTIPLY(tmp11, FIX_1_847759065), /* c2+c6 */ + CONST_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + + +/* + * Perform the forward DCT on a 1x2 sample block. + * + * 1-point FDCT in pass 1 (rows), 2-point in pass 2 (columns). + */ + +GLOBAL(void) +jpeg_fdct_1x2 (DCTELEM * data, JSAMPARRAY sample_data, JDIMENSION start_col) +{ + INT32 tmp0, tmp1; + + /* Pre-zero output coefficient block. */ + MEMZERO(data, SIZEOF(DCTELEM) * DCTSIZE2); + + tmp0 = GETJSAMPLE(sample_data[0][start_col]); + tmp1 = GETJSAMPLE(sample_data[1][start_col]); + + /* We leave the results scaled up by an overall factor of 8. + * We must also scale the output by (8/1)*(8/2) = 2**5. + */ + + /* Even part */ + /* Apply unsigned->signed conversion */ + data[DCTSIZE*0] = (DCTELEM) ((tmp0 + tmp1 - 2 * CENTERJSAMPLE) << 5); + + /* Odd part */ + data[DCTSIZE*1] = (DCTELEM) ((tmp0 - tmp1) << 5); +} + +#endif /* DCT_SCALING_SUPPORTED */ +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/src/jpeg-8c/jidctflt.c b/src/jpeg-8c/jidctflt.c new file mode 100644 index 0000000..23ae9d3 --- /dev/null +++ b/src/jpeg-8c/jidctflt.c @@ -0,0 +1,235 @@ +/* + * jidctflt.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * Modified 2010 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * This implementation should be more accurate than either of the integer + * IDCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a float result. + */ + +#define DEQUANTIZE(coef,quantval) (((FAST_FLOAT) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z5, z10, z11, z12, z13; + JCOEFPTR inptr; + FLOAT_MULT_TYPE * quantptr; + FAST_FLOAT * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = cinfo->sample_range_limit; + int ctr; + FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */ + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + FAST_FLOAT dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */ + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = z5 - z12 * ((FAST_FLOAT) 1.082392200); /* 2*(c2-c6) */ + tmp12 = z5 - z10 * ((FAST_FLOAT) 2.613125930); /* 2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 - tmp5; + + wsptr[DCTSIZE*0] = tmp0 + tmp7; + wsptr[DCTSIZE*7] = tmp0 - tmp7; + wsptr[DCTSIZE*1] = tmp1 + tmp6; + wsptr[DCTSIZE*6] = tmp1 - tmp6; + wsptr[DCTSIZE*2] = tmp2 + tmp5; + wsptr[DCTSIZE*5] = tmp2 - tmp5; + wsptr[DCTSIZE*3] = tmp3 + tmp4; + wsptr[DCTSIZE*4] = tmp3 - tmp4; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * And testing floats for zero is relatively expensive, so we don't bother. + */ + + /* Even part */ + + /* Apply signed->unsigned and prepare float->int conversion */ + z5 = wsptr[0] + ((FAST_FLOAT) CENTERJSAMPLE + (FAST_FLOAT) 0.5); + tmp10 = z5 + wsptr[4]; + tmp11 = z5 - wsptr[4]; + + tmp13 = wsptr[2] + wsptr[6]; + tmp12 = (wsptr[2] - wsptr[6]) * ((FAST_FLOAT) 1.414213562) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = wsptr[5] + wsptr[3]; + z10 = wsptr[5] - wsptr[3]; + z11 = wsptr[1] + wsptr[7]; + z12 = wsptr[1] - wsptr[7]; + + tmp7 = z11 + z13; + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = z5 - z12 * ((FAST_FLOAT) 1.082392200); /* 2*(c2-c6) */ + tmp12 = z5 - z10 * ((FAST_FLOAT) 2.613125930); /* 2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 - tmp5; + + /* Final output stage: float->int conversion and range-limit */ + + outptr[0] = range_limit[((int) (tmp0 + tmp7)) & RANGE_MASK]; + outptr[7] = range_limit[((int) (tmp0 - tmp7)) & RANGE_MASK]; + outptr[1] = range_limit[((int) (tmp1 + tmp6)) & RANGE_MASK]; + outptr[6] = range_limit[((int) (tmp1 - tmp6)) & RANGE_MASK]; + outptr[2] = range_limit[((int) (tmp2 + tmp5)) & RANGE_MASK]; + outptr[5] = range_limit[((int) (tmp2 - tmp5)) & RANGE_MASK]; + outptr[3] = range_limit[((int) (tmp3 + tmp4)) & RANGE_MASK]; + outptr[4] = range_limit[((int) (tmp3 - tmp4)) & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/src/jpeg-8c/jidctfst.c b/src/jpeg-8c/jidctfst.c new file mode 100644 index 0000000..dba4216 --- /dev/null +++ b/src/jpeg-8c/jidctfst.c @@ -0,0 +1,368 @@ +/* + * jidctfst.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jidctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * The dequantized coefficients are not integers because the AA&N scaling + * factors have been incorporated. We represent them scaled up by PASS1_BITS, + * so that the first and second IDCT rounds have the same input scaling. + * For 8-bit JSAMPLEs, we choose IFAST_SCALE_BITS = PASS1_BITS so as to + * avoid a descaling shift; this compromises accuracy rather drastically + * for small quantization table entries, but it saves a lot of shifts. + * For 12-bit JSAMPLEs, there's no hope of using 16x16 multiplies anyway, + * so we use a much larger scaling factor to preserve accuracy. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 8 +#define PASS1_BITS 2 +#else +#define CONST_BITS 8 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_1_082392200 ((INT32) 277) /* FIX(1.082392200) */ +#define FIX_1_414213562 ((INT32) 362) /* FIX(1.414213562) */ +#define FIX_1_847759065 ((INT32) 473) /* FIX(1.847759065) */ +#define FIX_2_613125930 ((INT32) 669) /* FIX(2.613125930) */ +#else +#define FIX_1_082392200 FIX(1.082392200) +#define FIX_1_414213562 FIX(1.414213562) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_2_613125930 FIX(2.613125930) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE(x,n) RIGHT_SHIFT(x, n) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var,const) ((DCTELEM) DESCALE((var) * (const), CONST_BITS)) + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a DCTELEM result. For 8-bit data a 16x16->16 + * multiplication will do. For 12-bit data, the multiplier table is + * declared INT32, so a 32-bit multiply will be used. + */ + +#if BITS_IN_JSAMPLE == 8 +#define DEQUANTIZE(coef,quantval) (((IFAST_MULT_TYPE) (coef)) * (quantval)) +#else +#define DEQUANTIZE(coef,quantval) \ + DESCALE((coef)*(quantval), IFAST_SCALE_BITS-PASS1_BITS) +#endif + + +/* Like DESCALE, but applies to a DCTELEM and produces an int. + * We assume that int right shift is unsigned if INT32 right shift is. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS DCTELEM ishift_temp; +#if BITS_IN_JSAMPLE == 8 +#define DCTELEMBITS 16 /* DCTELEM may be 16 or 32 bits */ +#else +#define DCTELEMBITS 32 /* DCTELEM must be 32 bits */ +#endif +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~((DCTELEM) 0)) << (DCTELEMBITS-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +#ifdef USE_ACCURATE_ROUNDING +#define IDESCALE(x,n) ((int) IRIGHT_SHIFT((x) + (1 << ((n)-1)), n)) +#else +#define IDESCALE(x,n) ((int) IRIGHT_SHIFT(x, n)) +#endif + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z5, z10, z11, z12, z13; + JCOEFPTR inptr; + IFAST_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS /* for DESCALE */ + ISHIFT_TEMPS /* for IDESCALE */ + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (IFAST_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + int dcval = (int) DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = (int) (tmp0 + tmp7); + wsptr[DCTSIZE*7] = (int) (tmp0 - tmp7); + wsptr[DCTSIZE*1] = (int) (tmp1 + tmp6); + wsptr[DCTSIZE*6] = (int) (tmp1 - tmp6); + wsptr[DCTSIZE*2] = (int) (tmp2 + tmp5); + wsptr[DCTSIZE*5] = (int) (tmp2 - tmp5); + wsptr[DCTSIZE*4] = (int) (tmp3 + tmp4); + wsptr[DCTSIZE*3] = (int) (tmp3 - tmp4); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[IDESCALE(wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ((DCTELEM) wsptr[0] + (DCTELEM) wsptr[4]); + tmp11 = ((DCTELEM) wsptr[0] - (DCTELEM) wsptr[4]); + + tmp13 = ((DCTELEM) wsptr[2] + (DCTELEM) wsptr[6]); + tmp12 = MULTIPLY((DCTELEM) wsptr[2] - (DCTELEM) wsptr[6], FIX_1_414213562) + - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = (DCTELEM) wsptr[5] + (DCTELEM) wsptr[3]; + z10 = (DCTELEM) wsptr[5] - (DCTELEM) wsptr[3]; + z11 = (DCTELEM) wsptr[1] + (DCTELEM) wsptr[7]; + z12 = (DCTELEM) wsptr[1] - (DCTELEM) wsptr[7]; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[IDESCALE(tmp0 + tmp7, PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[IDESCALE(tmp0 - tmp7, PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[IDESCALE(tmp1 + tmp6, PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[IDESCALE(tmp1 - tmp6, PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[IDESCALE(tmp2 + tmp5, PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[IDESCALE(tmp2 - tmp5, PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[IDESCALE(tmp3 + tmp4, PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[IDESCALE(tmp3 - tmp4, PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/src/jpeg-8c/jidctint.c b/src/jpeg-8c/jidctint.c new file mode 100644 index 0000000..dcdf7ce --- /dev/null +++ b/src/jpeg-8c/jidctint.c @@ -0,0 +1,5137 @@ +/* + * jidctint.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * Modification developed 2002-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + * + * We also provide IDCT routines with various output sample block sizes for + * direct resolution reduction or enlargement and for direct resolving the + * common 2x1 and 1x2 subsampling cases without additional resampling: NxN + * (N=1...16), 2NxN, and Nx2N (N=1...8) pixels for one 8x8 input DCT block. + * + * For N<8 we simply take the corresponding low-frequency coefficients of + * the 8x8 input DCT block and apply an NxN point IDCT on the sub-block + * to yield the downscaled outputs. + * This can be seen as direct low-pass downsampling from the DCT domain + * point of view rather than the usual spatial domain point of view, + * yielding significant computational savings and results at least + * as good as common bilinear (averaging) spatial downsampling. + * + * For N>8 we apply a partial NxN IDCT on the 8 input coefficients as + * lower frequencies and higher frequencies assumed to be zero. + * It turns out that the computational effort is similar to the 8x8 IDCT + * regarding the output size. + * Furthermore, the scaling and descaling is the same for all IDCT sizes. + * + * CAUTION: We rely on the FIX() macro except for the N=1,2,4,8 cases + * since there would be too many additional constants to pre-calculate. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCT blocks. /* deliberate syntax err */ +#endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D IDCT step produces outputs which are a factor of sqrt(N) + * larger than the true IDCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D IDCT, + * because the y0 and y4 inputs need not be divided by sqrt(N). + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (To scale up 12-bit sample data further, an + * intermediate INT32 array would be needed.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX(0.298631336) +#define FIX_0_390180644 FIX(0.390180644) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_175875602 FIX(1.175875602) +#define FIX_1_501321110 FIX(1.501321110) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_1_961570560 FIX(1.961570560) +#define FIX_2_053119869 FIX(2.053119869) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_072711026 FIX(3.072711026) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE(coef,quantval) (((ISLOW_MULT_TYPE) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + /* Note results are scaled up by sqrt(8) compared to a true IDCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z2, FIX_0_765366865); + tmp3 = z1 - MULTIPLY(z3, FIX_1_847759065); + + z2 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z2 <<= CONST_BITS; + z3 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z2 += ONE << (CONST_BITS-PASS1_BITS-1); + + tmp0 = z2 + z3; + tmp1 = z2 - z3; + + tmp10 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + tmp11 = tmp1 + tmp3; + tmp12 = tmp1 - tmp3; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + z2 = tmp0 + tmp2; + z3 = tmp1 + tmp3; + + z1 = MULTIPLY(z2 + z3, FIX_1_175875602); /* sqrt(2) * c3 */ + z2 = MULTIPLY(z2, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z3 = MULTIPLY(z3, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + z2 += z1; + z3 += z1; + + z1 = MULTIPLY(tmp0 + tmp3, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + tmp0 += z1 + z2; + tmp3 += z1 + z3; + + z1 = MULTIPLY(tmp1 + tmp2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp1 += z1 + z3; + tmp2 += z1 + z2; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + wsptr[DCTSIZE*0] = (int) RIGHT_SHIFT(tmp10 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*7] = (int) RIGHT_SHIFT(tmp10 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*1] = (int) RIGHT_SHIFT(tmp11 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*6] = (int) RIGHT_SHIFT(tmp11 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*2] = (int) RIGHT_SHIFT(tmp12 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*5] = (int) RIGHT_SHIFT(tmp12 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*3] = (int) RIGHT_SHIFT(tmp13 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*4] = (int) RIGHT_SHIFT(tmp13 - tmp0, CONST_BITS-PASS1_BITS); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z2, FIX_0_765366865); + tmp3 = z1 - MULTIPLY(z3, FIX_1_847759065); + + /* Add fudge factor here for final descale. */ + z2 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z3 = (INT32) wsptr[4]; + + tmp0 = (z2 + z3) << CONST_BITS; + tmp1 = (z2 - z3) << CONST_BITS; + + tmp10 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + tmp11 = tmp1 + tmp3; + tmp12 = tmp1 - tmp3; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = (INT32) wsptr[7]; + tmp1 = (INT32) wsptr[5]; + tmp2 = (INT32) wsptr[3]; + tmp3 = (INT32) wsptr[1]; + + z2 = tmp0 + tmp2; + z3 = tmp1 + tmp3; + + z1 = MULTIPLY(z2 + z3, FIX_1_175875602); /* sqrt(2) * c3 */ + z2 = MULTIPLY(z2, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z3 = MULTIPLY(z3, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + z2 += z1; + z3 += z1; + + z1 = MULTIPLY(tmp0 + tmp3, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + tmp0 += z1 + z2; + tmp3 += z1 + z3; + + z1 = MULTIPLY(tmp1 + tmp2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp1 += z1 + z3; + tmp2 += z1 + z2; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp13 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp13 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#ifdef IDCT_SCALING_SUPPORTED + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 7x7 output block. + * + * Optimized algorithm with 12 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/14). + */ + +GLOBAL(void) +jpeg_idct_7x7 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[7*7]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 7; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp13 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp13 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp13 += ONE << (CONST_BITS-PASS1_BITS-1); + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = MULTIPLY(z2 - z3, FIX(0.881747734)); /* c4 */ + tmp12 = MULTIPLY(z1 - z2, FIX(0.314692123)); /* c6 */ + tmp11 = tmp10 + tmp12 + tmp13 - MULTIPLY(z2, FIX(1.841218003)); /* c2+c4-c6 */ + tmp0 = z1 + z3; + z2 -= tmp0; + tmp0 = MULTIPLY(tmp0, FIX(1.274162392)) + tmp13; /* c2 */ + tmp10 += tmp0 - MULTIPLY(z3, FIX(0.077722536)); /* c2-c4-c6 */ + tmp12 += tmp0 - MULTIPLY(z1, FIX(2.470602249)); /* c2+c4+c6 */ + tmp13 += MULTIPLY(z2, FIX(1.414213562)); /* c0 */ + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + + tmp1 = MULTIPLY(z1 + z2, FIX(0.935414347)); /* (c3+c1-c5)/2 */ + tmp2 = MULTIPLY(z1 - z2, FIX(0.170262339)); /* (c3+c5-c1)/2 */ + tmp0 = tmp1 - tmp2; + tmp1 += tmp2; + tmp2 = MULTIPLY(z2 + z3, - FIX(1.378756276)); /* -c1 */ + tmp1 += tmp2; + z2 = MULTIPLY(z1 + z3, FIX(0.613604268)); /* c5 */ + tmp0 += z2; + tmp2 += z2 + MULTIPLY(z3, FIX(1.870828693)); /* c3+c1-c5 */ + + /* Final output stage */ + + wsptr[7*0] = (int) RIGHT_SHIFT(tmp10 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[7*6] = (int) RIGHT_SHIFT(tmp10 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[7*1] = (int) RIGHT_SHIFT(tmp11 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[7*5] = (int) RIGHT_SHIFT(tmp11 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[7*2] = (int) RIGHT_SHIFT(tmp12 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[7*4] = (int) RIGHT_SHIFT(tmp12 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[7*3] = (int) RIGHT_SHIFT(tmp13, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 7 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 7; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp13 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp13 <<= CONST_BITS; + + z1 = (INT32) wsptr[2]; + z2 = (INT32) wsptr[4]; + z3 = (INT32) wsptr[6]; + + tmp10 = MULTIPLY(z2 - z3, FIX(0.881747734)); /* c4 */ + tmp12 = MULTIPLY(z1 - z2, FIX(0.314692123)); /* c6 */ + tmp11 = tmp10 + tmp12 + tmp13 - MULTIPLY(z2, FIX(1.841218003)); /* c2+c4-c6 */ + tmp0 = z1 + z3; + z2 -= tmp0; + tmp0 = MULTIPLY(tmp0, FIX(1.274162392)) + tmp13; /* c2 */ + tmp10 += tmp0 - MULTIPLY(z3, FIX(0.077722536)); /* c2-c4-c6 */ + tmp12 += tmp0 - MULTIPLY(z1, FIX(2.470602249)); /* c2+c4+c6 */ + tmp13 += MULTIPLY(z2, FIX(1.414213562)); /* c0 */ + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + + tmp1 = MULTIPLY(z1 + z2, FIX(0.935414347)); /* (c3+c1-c5)/2 */ + tmp2 = MULTIPLY(z1 - z2, FIX(0.170262339)); /* (c3+c5-c1)/2 */ + tmp0 = tmp1 - tmp2; + tmp1 += tmp2; + tmp2 = MULTIPLY(z2 + z3, - FIX(1.378756276)); /* -c1 */ + tmp1 += tmp2; + z2 = MULTIPLY(z1 + z3, FIX(0.613604268)); /* c5 */ + tmp0 += z2; + tmp2 += z2 + MULTIPLY(z3, FIX(1.870828693)); /* c3+c1-c5 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 7; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 6x6 output block. + * + * Optimized algorithm with 3 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/12). + */ + +GLOBAL(void) +jpeg_idct_6x6 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp10, tmp11, tmp12; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[6*6]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 6; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp10 = MULTIPLY(tmp2, FIX(0.707106781)); /* c4 */ + tmp1 = tmp0 + tmp10; + tmp11 = RIGHT_SHIFT(tmp0 - tmp10 - tmp10, CONST_BITS-PASS1_BITS); + tmp10 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp0 = MULTIPLY(tmp10, FIX(1.224744871)); /* c2 */ + tmp10 = tmp1 + tmp0; + tmp12 = tmp1 - tmp0; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp1 = MULTIPLY(z1 + z3, FIX(0.366025404)); /* c5 */ + tmp0 = tmp1 + ((z1 + z2) << CONST_BITS); + tmp2 = tmp1 + ((z3 - z2) << CONST_BITS); + tmp1 = (z1 - z2 - z3) << PASS1_BITS; + + /* Final output stage */ + + wsptr[6*0] = (int) RIGHT_SHIFT(tmp10 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[6*5] = (int) RIGHT_SHIFT(tmp10 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[6*1] = (int) (tmp11 + tmp1); + wsptr[6*4] = (int) (tmp11 - tmp1); + wsptr[6*2] = (int) RIGHT_SHIFT(tmp12 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[6*3] = (int) RIGHT_SHIFT(tmp12 - tmp2, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 6 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 6; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp0 <<= CONST_BITS; + tmp2 = (INT32) wsptr[4]; + tmp10 = MULTIPLY(tmp2, FIX(0.707106781)); /* c4 */ + tmp1 = tmp0 + tmp10; + tmp11 = tmp0 - tmp10 - tmp10; + tmp10 = (INT32) wsptr[2]; + tmp0 = MULTIPLY(tmp10, FIX(1.224744871)); /* c2 */ + tmp10 = tmp1 + tmp0; + tmp12 = tmp1 - tmp0; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + tmp1 = MULTIPLY(z1 + z3, FIX(0.366025404)); /* c5 */ + tmp0 = tmp1 + ((z1 + z2) << CONST_BITS); + tmp2 = tmp1 + ((z3 - z2) << CONST_BITS); + tmp1 = (z1 - z2 - z3) << CONST_BITS; + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 6; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 5x5 output block. + * + * Optimized algorithm with 5 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/10). + */ + +GLOBAL(void) +jpeg_idct_5x5 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp10, tmp11, tmp12; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[5*5]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 5; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp12 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp12 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp12 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp0 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z1 = MULTIPLY(tmp0 + tmp1, FIX(0.790569415)); /* (c2+c4)/2 */ + z2 = MULTIPLY(tmp0 - tmp1, FIX(0.353553391)); /* (c2-c4)/2 */ + z3 = tmp12 + z2; + tmp10 = z3 + z1; + tmp11 = z3 - z1; + tmp12 -= z2 << 2; + + /* Odd part */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + + z1 = MULTIPLY(z2 + z3, FIX(0.831253876)); /* c3 */ + tmp0 = z1 + MULTIPLY(z2, FIX(0.513743148)); /* c1-c3 */ + tmp1 = z1 - MULTIPLY(z3, FIX(2.176250899)); /* c1+c3 */ + + /* Final output stage */ + + wsptr[5*0] = (int) RIGHT_SHIFT(tmp10 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[5*4] = (int) RIGHT_SHIFT(tmp10 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[5*1] = (int) RIGHT_SHIFT(tmp11 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[5*3] = (int) RIGHT_SHIFT(tmp11 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[5*2] = (int) RIGHT_SHIFT(tmp12, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 5 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 5; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp12 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp12 <<= CONST_BITS; + tmp0 = (INT32) wsptr[2]; + tmp1 = (INT32) wsptr[4]; + z1 = MULTIPLY(tmp0 + tmp1, FIX(0.790569415)); /* (c2+c4)/2 */ + z2 = MULTIPLY(tmp0 - tmp1, FIX(0.353553391)); /* (c2-c4)/2 */ + z3 = tmp12 + z2; + tmp10 = z3 + z1; + tmp11 = z3 - z1; + tmp12 -= z2 << 2; + + /* Odd part */ + + z2 = (INT32) wsptr[1]; + z3 = (INT32) wsptr[3]; + + z1 = MULTIPLY(z2 + z3, FIX(0.831253876)); /* c3 */ + tmp0 = z1 + MULTIPLY(z2, FIX(0.513743148)); /* c1-c3 */ + tmp1 = z1 - MULTIPLY(z3, FIX(2.176250899)); /* c1+c3 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 5; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 4x4 output block. + * + * Optimized algorithm with 3 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/16) [refers to 8-point IDCT]. + */ + +GLOBAL(void) +jpeg_idct_4x4 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp2, tmp10, tmp12; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[4*4]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 4; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + + tmp10 = (tmp0 + tmp2) << PASS1_BITS; + tmp12 = (tmp0 - tmp2) << PASS1_BITS; + + /* Odd part */ + /* Same rotation as in the even part of the 8x8 LL&M IDCT */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); /* c6 */ + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp0 = RIGHT_SHIFT(z1 + MULTIPLY(z2, FIX_0_765366865), /* c2-c6 */ + CONST_BITS-PASS1_BITS); + tmp2 = RIGHT_SHIFT(z1 - MULTIPLY(z3, FIX_1_847759065), /* c2+c6 */ + CONST_BITS-PASS1_BITS); + + /* Final output stage */ + + wsptr[4*0] = (int) (tmp10 + tmp0); + wsptr[4*3] = (int) (tmp10 - tmp0); + wsptr[4*1] = (int) (tmp12 + tmp2); + wsptr[4*2] = (int) (tmp12 - tmp2); + } + + /* Pass 2: process 4 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 4; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp2 = (INT32) wsptr[2]; + + tmp10 = (tmp0 + tmp2) << CONST_BITS; + tmp12 = (tmp0 - tmp2) << CONST_BITS; + + /* Odd part */ + /* Same rotation as in the even part of the 8x8 LL&M IDCT */ + + z2 = (INT32) wsptr[1]; + z3 = (INT32) wsptr[3]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); /* c6 */ + tmp0 = z1 + MULTIPLY(z2, FIX_0_765366865); /* c2-c6 */ + tmp2 = z1 - MULTIPLY(z3, FIX_1_847759065); /* c2+c6 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 4; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 3x3 output block. + * + * Optimized algorithm with 2 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/6). + */ + +GLOBAL(void) +jpeg_idct_3x3 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp2, tmp10, tmp12; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[3*3]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 3; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp12 = MULTIPLY(tmp2, FIX(0.707106781)); /* c2 */ + tmp10 = tmp0 + tmp12; + tmp2 = tmp0 - tmp12 - tmp12; + + /* Odd part */ + + tmp12 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp0 = MULTIPLY(tmp12, FIX(1.224744871)); /* c1 */ + + /* Final output stage */ + + wsptr[3*0] = (int) RIGHT_SHIFT(tmp10 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[3*2] = (int) RIGHT_SHIFT(tmp10 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[3*1] = (int) RIGHT_SHIFT(tmp2, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 3 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 3; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp0 <<= CONST_BITS; + tmp2 = (INT32) wsptr[2]; + tmp12 = MULTIPLY(tmp2, FIX(0.707106781)); /* c2 */ + tmp10 = tmp0 + tmp12; + tmp2 = tmp0 - tmp12 - tmp12; + + /* Odd part */ + + tmp12 = (INT32) wsptr[1]; + tmp0 = MULTIPLY(tmp12, FIX(1.224744871)); /* c1 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 3; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 2x2 output block. + * + * Multiplication-less algorithm. + */ + +GLOBAL(void) +jpeg_idct_2x2 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + ISLOW_MULT_TYPE * quantptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + SHIFT_TEMPS + + /* Pass 1: process columns from input. */ + + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + + /* Column 0 */ + tmp4 = DEQUANTIZE(coef_block[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp5 = DEQUANTIZE(coef_block[DCTSIZE*1], quantptr[DCTSIZE*1]); + /* Add fudge factor here for final descale. */ + tmp4 += ONE << 2; + + tmp0 = tmp4 + tmp5; + tmp2 = tmp4 - tmp5; + + /* Column 1 */ + tmp4 = DEQUANTIZE(coef_block[DCTSIZE*0+1], quantptr[DCTSIZE*0+1]); + tmp5 = DEQUANTIZE(coef_block[DCTSIZE*1+1], quantptr[DCTSIZE*1+1]); + + tmp1 = tmp4 + tmp5; + tmp3 = tmp4 - tmp5; + + /* Pass 2: process 2 rows, store into output array. */ + + /* Row 0 */ + outptr = output_buf[0] + output_col; + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp0 + tmp1, 3) & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp0 - tmp1, 3) & RANGE_MASK]; + + /* Row 1 */ + outptr = output_buf[1] + output_col; + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp2 + tmp3, 3) & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp2 - tmp3, 3) & RANGE_MASK]; +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 1x1 output block. + * + * We hardly need an inverse DCT routine for this: just take the + * average pixel value, which is one-eighth of the DC coefficient. + */ + +GLOBAL(void) +jpeg_idct_1x1 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + int dcval; + ISLOW_MULT_TYPE * quantptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + SHIFT_TEMPS + + /* 1x1 is trivial: just take the DC coefficient divided by 8. */ + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + dcval = DEQUANTIZE(coef_block[0], quantptr[0]); + dcval = (int) DESCALE((INT32) dcval, 3); + + output_buf[0][output_col] = range_limit[dcval & RANGE_MASK]; +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 9x9 output block. + * + * Optimized algorithm with 10 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/18). + */ + +GLOBAL(void) +jpeg_idct_9x9 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp10, tmp11, tmp12, tmp13, tmp14; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*9]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-1); + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp3 = MULTIPLY(z3, FIX(0.707106781)); /* c6 */ + tmp1 = tmp0 + tmp3; + tmp2 = tmp0 - tmp3 - tmp3; + + tmp0 = MULTIPLY(z1 - z2, FIX(0.707106781)); /* c6 */ + tmp11 = tmp2 + tmp0; + tmp14 = tmp2 - tmp0 - tmp0; + + tmp0 = MULTIPLY(z1 + z2, FIX(1.328926049)); /* c2 */ + tmp2 = MULTIPLY(z1, FIX(1.083350441)); /* c4 */ + tmp3 = MULTIPLY(z2, FIX(0.245575608)); /* c8 */ + + tmp10 = tmp1 + tmp0 - tmp3; + tmp12 = tmp1 - tmp0 + tmp2; + tmp13 = tmp1 - tmp2 + tmp3; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z2 = MULTIPLY(z2, - FIX(1.224744871)); /* -c3 */ + + tmp2 = MULTIPLY(z1 + z3, FIX(0.909038955)); /* c5 */ + tmp3 = MULTIPLY(z1 + z4, FIX(0.483689525)); /* c7 */ + tmp0 = tmp2 + tmp3 - z2; + tmp1 = MULTIPLY(z3 - z4, FIX(1.392728481)); /* c1 */ + tmp2 += z2 - tmp1; + tmp3 += z2 + tmp1; + tmp1 = MULTIPLY(z1 - z3 - z4, FIX(1.224744871)); /* c3 */ + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp10 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp10 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp11 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[8*7] = (int) RIGHT_SHIFT(tmp11 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp12 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp12 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp13 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp13 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp14, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 9 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 9; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp0 <<= CONST_BITS; + + z1 = (INT32) wsptr[2]; + z2 = (INT32) wsptr[4]; + z3 = (INT32) wsptr[6]; + + tmp3 = MULTIPLY(z3, FIX(0.707106781)); /* c6 */ + tmp1 = tmp0 + tmp3; + tmp2 = tmp0 - tmp3 - tmp3; + + tmp0 = MULTIPLY(z1 - z2, FIX(0.707106781)); /* c6 */ + tmp11 = tmp2 + tmp0; + tmp14 = tmp2 - tmp0 - tmp0; + + tmp0 = MULTIPLY(z1 + z2, FIX(1.328926049)); /* c2 */ + tmp2 = MULTIPLY(z1, FIX(1.083350441)); /* c4 */ + tmp3 = MULTIPLY(z2, FIX(0.245575608)); /* c8 */ + + tmp10 = tmp1 + tmp0 - tmp3; + tmp12 = tmp1 - tmp0 + tmp2; + tmp13 = tmp1 - tmp2 + tmp3; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + + z2 = MULTIPLY(z2, - FIX(1.224744871)); /* -c3 */ + + tmp2 = MULTIPLY(z1 + z3, FIX(0.909038955)); /* c5 */ + tmp3 = MULTIPLY(z1 + z4, FIX(0.483689525)); /* c7 */ + tmp0 = tmp2 + tmp3 - z2; + tmp1 = MULTIPLY(z3 - z4, FIX(1.392728481)); /* c1 */ + tmp2 += z2 - tmp1; + tmp3 += z2 + tmp1; + tmp1 = MULTIPLY(z1 - z3 - z4, FIX(1.224744871)); /* c3 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp13 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp13 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 10x10 output block. + * + * Optimized algorithm with 12 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/20). + */ + +GLOBAL(void) +jpeg_idct_10x10 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24; + INT32 z1, z2, z3, z4, z5; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*10]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + z3 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z3 += ONE << (CONST_BITS-PASS1_BITS-1); + z4 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z1 = MULTIPLY(z4, FIX(1.144122806)); /* c4 */ + z2 = MULTIPLY(z4, FIX(0.437016024)); /* c8 */ + tmp10 = z3 + z1; + tmp11 = z3 - z2; + + tmp22 = RIGHT_SHIFT(z3 - ((z1 - z2) << 1), /* c0 = (c4-c8)*2 */ + CONST_BITS-PASS1_BITS); + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z1 = MULTIPLY(z2 + z3, FIX(0.831253876)); /* c6 */ + tmp12 = z1 + MULTIPLY(z2, FIX(0.513743148)); /* c2-c6 */ + tmp13 = z1 - MULTIPLY(z3, FIX(2.176250899)); /* c2+c6 */ + + tmp20 = tmp10 + tmp12; + tmp24 = tmp10 - tmp12; + tmp21 = tmp11 + tmp13; + tmp23 = tmp11 - tmp13; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp11 = z2 + z4; + tmp13 = z2 - z4; + + tmp12 = MULTIPLY(tmp13, FIX(0.309016994)); /* (c3-c7)/2 */ + z5 = z3 << CONST_BITS; + + z2 = MULTIPLY(tmp11, FIX(0.951056516)); /* (c3+c7)/2 */ + z4 = z5 + tmp12; + + tmp10 = MULTIPLY(z1, FIX(1.396802247)) + z2 + z4; /* c1 */ + tmp14 = MULTIPLY(z1, FIX(0.221231742)) - z2 + z4; /* c9 */ + + z2 = MULTIPLY(tmp11, FIX(0.587785252)); /* (c1-c9)/2 */ + z4 = z5 - tmp12 - (tmp13 << (CONST_BITS - 1)); + + tmp12 = (z1 - tmp13 - z3) << PASS1_BITS; + + tmp11 = MULTIPLY(z1, FIX(1.260073511)) - z2 - z4; /* c3 */ + tmp13 = MULTIPLY(z1, FIX(0.642039522)) - z2 + z4; /* c7 */ + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*9] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) (tmp22 + tmp12); + wsptr[8*7] = (int) (tmp22 - tmp12); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp23 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp23 - tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 10 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 10; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + z3 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z3 <<= CONST_BITS; + z4 = (INT32) wsptr[4]; + z1 = MULTIPLY(z4, FIX(1.144122806)); /* c4 */ + z2 = MULTIPLY(z4, FIX(0.437016024)); /* c8 */ + tmp10 = z3 + z1; + tmp11 = z3 - z2; + + tmp22 = z3 - ((z1 - z2) << 1); /* c0 = (c4-c8)*2 */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY(z2 + z3, FIX(0.831253876)); /* c6 */ + tmp12 = z1 + MULTIPLY(z2, FIX(0.513743148)); /* c2-c6 */ + tmp13 = z1 - MULTIPLY(z3, FIX(2.176250899)); /* c2+c6 */ + + tmp20 = tmp10 + tmp12; + tmp24 = tmp10 - tmp12; + tmp21 = tmp11 + tmp13; + tmp23 = tmp11 - tmp13; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z3 <<= CONST_BITS; + z4 = (INT32) wsptr[7]; + + tmp11 = z2 + z4; + tmp13 = z2 - z4; + + tmp12 = MULTIPLY(tmp13, FIX(0.309016994)); /* (c3-c7)/2 */ + + z2 = MULTIPLY(tmp11, FIX(0.951056516)); /* (c3+c7)/2 */ + z4 = z3 + tmp12; + + tmp10 = MULTIPLY(z1, FIX(1.396802247)) + z2 + z4; /* c1 */ + tmp14 = MULTIPLY(z1, FIX(0.221231742)) - z2 + z4; /* c9 */ + + z2 = MULTIPLY(tmp11, FIX(0.587785252)); /* (c1-c9)/2 */ + z4 = z3 - tmp12 - (tmp13 << (CONST_BITS - 1)); + + tmp12 = ((z1 - tmp13) << CONST_BITS) - z3; + + tmp11 = MULTIPLY(z1, FIX(1.260073511)) - z2 - z4; /* c3 */ + tmp13 = MULTIPLY(z1, FIX(0.642039522)) - z2 + z4; /* c7 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 11x11 output block. + * + * Optimized algorithm with 24 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/22). + */ + +GLOBAL(void) +jpeg_idct_11x11 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*11]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp10 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp10 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp10 += ONE << (CONST_BITS-PASS1_BITS-1); + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp20 = MULTIPLY(z2 - z3, FIX(2.546640132)); /* c2+c4 */ + tmp23 = MULTIPLY(z2 - z1, FIX(0.430815045)); /* c2-c6 */ + z4 = z1 + z3; + tmp24 = MULTIPLY(z4, - FIX(1.155664402)); /* -(c2-c10) */ + z4 -= z2; + tmp25 = tmp10 + MULTIPLY(z4, FIX(1.356927976)); /* c2 */ + tmp21 = tmp20 + tmp23 + tmp25 - + MULTIPLY(z2, FIX(1.821790775)); /* c2+c4+c10-c6 */ + tmp20 += tmp25 + MULTIPLY(z3, FIX(2.115825087)); /* c4+c6 */ + tmp23 += tmp25 - MULTIPLY(z1, FIX(1.513598477)); /* c6+c8 */ + tmp24 += tmp25; + tmp22 = tmp24 - MULTIPLY(z3, FIX(0.788749120)); /* c8+c10 */ + tmp24 += MULTIPLY(z2, FIX(1.944413522)) - /* c2+c8 */ + MULTIPLY(z1, FIX(1.390975730)); /* c4+c10 */ + tmp25 = tmp10 - MULTIPLY(z4, FIX(1.414213562)); /* c0 */ + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp11 = z1 + z2; + tmp14 = MULTIPLY(tmp11 + z3 + z4, FIX(0.398430003)); /* c9 */ + tmp11 = MULTIPLY(tmp11, FIX(0.887983902)); /* c3-c9 */ + tmp12 = MULTIPLY(z1 + z3, FIX(0.670361295)); /* c5-c9 */ + tmp13 = tmp14 + MULTIPLY(z1 + z4, FIX(0.366151574)); /* c7-c9 */ + tmp10 = tmp11 + tmp12 + tmp13 - + MULTIPLY(z1, FIX(0.923107866)); /* c7+c5+c3-c1-2*c9 */ + z1 = tmp14 - MULTIPLY(z2 + z3, FIX(1.163011579)); /* c7+c9 */ + tmp11 += z1 + MULTIPLY(z2, FIX(2.073276588)); /* c1+c7+3*c9-c3 */ + tmp12 += z1 - MULTIPLY(z3, FIX(1.192193623)); /* c3+c5-c7-c9 */ + z1 = MULTIPLY(z2 + z4, - FIX(1.798248910)); /* -(c1+c9) */ + tmp11 += z1; + tmp13 += z1 + MULTIPLY(z4, FIX(2.102458632)); /* c1+c5+c9-c7 */ + tmp14 += MULTIPLY(z2, - FIX(1.467221301)) + /* -(c5+c9) */ + MULTIPLY(z3, FIX(1.001388905)) - /* c1-c9 */ + MULTIPLY(z4, FIX(1.684843907)); /* c3+c9 */ + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*10] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*9] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp23 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*7] = (int) RIGHT_SHIFT(tmp23 - tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp25, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 11 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 11; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp10 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp10 <<= CONST_BITS; + + z1 = (INT32) wsptr[2]; + z2 = (INT32) wsptr[4]; + z3 = (INT32) wsptr[6]; + + tmp20 = MULTIPLY(z2 - z3, FIX(2.546640132)); /* c2+c4 */ + tmp23 = MULTIPLY(z2 - z1, FIX(0.430815045)); /* c2-c6 */ + z4 = z1 + z3; + tmp24 = MULTIPLY(z4, - FIX(1.155664402)); /* -(c2-c10) */ + z4 -= z2; + tmp25 = tmp10 + MULTIPLY(z4, FIX(1.356927976)); /* c2 */ + tmp21 = tmp20 + tmp23 + tmp25 - + MULTIPLY(z2, FIX(1.821790775)); /* c2+c4+c10-c6 */ + tmp20 += tmp25 + MULTIPLY(z3, FIX(2.115825087)); /* c4+c6 */ + tmp23 += tmp25 - MULTIPLY(z1, FIX(1.513598477)); /* c6+c8 */ + tmp24 += tmp25; + tmp22 = tmp24 - MULTIPLY(z3, FIX(0.788749120)); /* c8+c10 */ + tmp24 += MULTIPLY(z2, FIX(1.944413522)) - /* c2+c8 */ + MULTIPLY(z1, FIX(1.390975730)); /* c4+c10 */ + tmp25 = tmp10 - MULTIPLY(z4, FIX(1.414213562)); /* c0 */ + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + + tmp11 = z1 + z2; + tmp14 = MULTIPLY(tmp11 + z3 + z4, FIX(0.398430003)); /* c9 */ + tmp11 = MULTIPLY(tmp11, FIX(0.887983902)); /* c3-c9 */ + tmp12 = MULTIPLY(z1 + z3, FIX(0.670361295)); /* c5-c9 */ + tmp13 = tmp14 + MULTIPLY(z1 + z4, FIX(0.366151574)); /* c7-c9 */ + tmp10 = tmp11 + tmp12 + tmp13 - + MULTIPLY(z1, FIX(0.923107866)); /* c7+c5+c3-c1-2*c9 */ + z1 = tmp14 - MULTIPLY(z2 + z3, FIX(1.163011579)); /* c7+c9 */ + tmp11 += z1 + MULTIPLY(z2, FIX(2.073276588)); /* c1+c7+3*c9-c3 */ + tmp12 += z1 - MULTIPLY(z3, FIX(1.192193623)); /* c3+c5-c7-c9 */ + z1 = MULTIPLY(z2 + z4, - FIX(1.798248910)); /* -(c1+c9) */ + tmp11 += z1; + tmp13 += z1 + MULTIPLY(z4, FIX(2.102458632)); /* c1+c5+c9-c7 */ + tmp14 += MULTIPLY(z2, - FIX(1.467221301)) + /* -(c5+c9) */ + MULTIPLY(z3, FIX(1.001388905)) - /* c1-c9 */ + MULTIPLY(z4, FIX(1.684843907)); /* c3+c9 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 12x12 output block. + * + * Optimized algorithm with 15 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/24). + */ + +GLOBAL(void) +jpeg_idct_12x12 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*12]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + z3 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z3 += ONE << (CONST_BITS-PASS1_BITS-1); + + z4 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z4 = MULTIPLY(z4, FIX(1.224744871)); /* c4 */ + + tmp10 = z3 + z4; + tmp11 = z3 - z4; + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z4 = MULTIPLY(z1, FIX(1.366025404)); /* c2 */ + z1 <<= CONST_BITS; + z2 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + z2 <<= CONST_BITS; + + tmp12 = z1 - z2; + + tmp21 = z3 + tmp12; + tmp24 = z3 - tmp12; + + tmp12 = z4 + z2; + + tmp20 = tmp10 + tmp12; + tmp25 = tmp10 - tmp12; + + tmp12 = z4 - z1 - z2; + + tmp22 = tmp11 + tmp12; + tmp23 = tmp11 - tmp12; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp11 = MULTIPLY(z2, FIX(1.306562965)); /* c3 */ + tmp14 = MULTIPLY(z2, - FIX_0_541196100); /* -c9 */ + + tmp10 = z1 + z3; + tmp15 = MULTIPLY(tmp10 + z4, FIX(0.860918669)); /* c7 */ + tmp12 = tmp15 + MULTIPLY(tmp10, FIX(0.261052384)); /* c5-c7 */ + tmp10 = tmp12 + tmp11 + MULTIPLY(z1, FIX(0.280143716)); /* c1-c5 */ + tmp13 = MULTIPLY(z3 + z4, - FIX(1.045510580)); /* -(c7+c11) */ + tmp12 += tmp13 + tmp14 - MULTIPLY(z3, FIX(1.478575242)); /* c1+c5-c7-c11 */ + tmp13 += tmp15 - tmp11 + MULTIPLY(z4, FIX(1.586706681)); /* c1+c11 */ + tmp15 += tmp14 - MULTIPLY(z1, FIX(0.676326758)) - /* c7-c11 */ + MULTIPLY(z4, FIX(1.982889723)); /* c5+c7 */ + + z1 -= z4; + z2 -= z3; + z3 = MULTIPLY(z1 + z2, FIX_0_541196100); /* c9 */ + tmp11 = z3 + MULTIPLY(z1, FIX_0_765366865); /* c3-c9 */ + tmp14 = z3 - MULTIPLY(z2, FIX_1_847759065); /* c3+c9 */ + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*11] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*10] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*9] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp23 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp23 - tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*7] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp25 + tmp15, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp25 - tmp15, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 12 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 12; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + z3 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z3 <<= CONST_BITS; + + z4 = (INT32) wsptr[4]; + z4 = MULTIPLY(z4, FIX(1.224744871)); /* c4 */ + + tmp10 = z3 + z4; + tmp11 = z3 - z4; + + z1 = (INT32) wsptr[2]; + z4 = MULTIPLY(z1, FIX(1.366025404)); /* c2 */ + z1 <<= CONST_BITS; + z2 = (INT32) wsptr[6]; + z2 <<= CONST_BITS; + + tmp12 = z1 - z2; + + tmp21 = z3 + tmp12; + tmp24 = z3 - tmp12; + + tmp12 = z4 + z2; + + tmp20 = tmp10 + tmp12; + tmp25 = tmp10 - tmp12; + + tmp12 = z4 - z1 - z2; + + tmp22 = tmp11 + tmp12; + tmp23 = tmp11 - tmp12; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + + tmp11 = MULTIPLY(z2, FIX(1.306562965)); /* c3 */ + tmp14 = MULTIPLY(z2, - FIX_0_541196100); /* -c9 */ + + tmp10 = z1 + z3; + tmp15 = MULTIPLY(tmp10 + z4, FIX(0.860918669)); /* c7 */ + tmp12 = tmp15 + MULTIPLY(tmp10, FIX(0.261052384)); /* c5-c7 */ + tmp10 = tmp12 + tmp11 + MULTIPLY(z1, FIX(0.280143716)); /* c1-c5 */ + tmp13 = MULTIPLY(z3 + z4, - FIX(1.045510580)); /* -(c7+c11) */ + tmp12 += tmp13 + tmp14 - MULTIPLY(z3, FIX(1.478575242)); /* c1+c5-c7-c11 */ + tmp13 += tmp15 - tmp11 + MULTIPLY(z4, FIX(1.586706681)); /* c1+c11 */ + tmp15 += tmp14 - MULTIPLY(z1, FIX(0.676326758)) - /* c7-c11 */ + MULTIPLY(z4, FIX(1.982889723)); /* c5+c7 */ + + z1 -= z4; + z2 -= z3; + z3 = MULTIPLY(z1 + z2, FIX_0_541196100); /* c9 */ + tmp11 = z3 + MULTIPLY(z1, FIX_0_765366865); /* c3-c9 */ + tmp14 = z3 - MULTIPLY(z2, FIX_1_847759065); /* c3+c9 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[11] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25 + tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp25 - tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 13x13 output block. + * + * Optimized algorithm with 29 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/26). + */ + +GLOBAL(void) +jpeg_idct_13x13 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*13]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z1 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-1); + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z4 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = z3 + z4; + tmp11 = z3 - z4; + + tmp12 = MULTIPLY(tmp10, FIX(1.155388986)); /* (c4+c6)/2 */ + tmp13 = MULTIPLY(tmp11, FIX(0.096834934)) + z1; /* (c4-c6)/2 */ + + tmp20 = MULTIPLY(z2, FIX(1.373119086)) + tmp12 + tmp13; /* c2 */ + tmp22 = MULTIPLY(z2, FIX(0.501487041)) - tmp12 + tmp13; /* c10 */ + + tmp12 = MULTIPLY(tmp10, FIX(0.316450131)); /* (c8-c12)/2 */ + tmp13 = MULTIPLY(tmp11, FIX(0.486914739)) + z1; /* (c8+c12)/2 */ + + tmp21 = MULTIPLY(z2, FIX(1.058554052)) - tmp12 + tmp13; /* c6 */ + tmp25 = MULTIPLY(z2, - FIX(1.252223920)) + tmp12 + tmp13; /* c4 */ + + tmp12 = MULTIPLY(tmp10, FIX(0.435816023)); /* (c2-c10)/2 */ + tmp13 = MULTIPLY(tmp11, FIX(0.937303064)) - z1; /* (c2+c10)/2 */ + + tmp23 = MULTIPLY(z2, - FIX(0.170464608)) - tmp12 - tmp13; /* c12 */ + tmp24 = MULTIPLY(z2, - FIX(0.803364869)) + tmp12 - tmp13; /* c8 */ + + tmp26 = MULTIPLY(tmp11 - z2, FIX(1.414213562)) + z1; /* c0 */ + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp11 = MULTIPLY(z1 + z2, FIX(1.322312651)); /* c3 */ + tmp12 = MULTIPLY(z1 + z3, FIX(1.163874945)); /* c5 */ + tmp15 = z1 + z4; + tmp13 = MULTIPLY(tmp15, FIX(0.937797057)); /* c7 */ + tmp10 = tmp11 + tmp12 + tmp13 - + MULTIPLY(z1, FIX(2.020082300)); /* c7+c5+c3-c1 */ + tmp14 = MULTIPLY(z2 + z3, - FIX(0.338443458)); /* -c11 */ + tmp11 += tmp14 + MULTIPLY(z2, FIX(0.837223564)); /* c5+c9+c11-c3 */ + tmp12 += tmp14 - MULTIPLY(z3, FIX(1.572116027)); /* c1+c5-c9-c11 */ + tmp14 = MULTIPLY(z2 + z4, - FIX(1.163874945)); /* -c5 */ + tmp11 += tmp14; + tmp13 += tmp14 + MULTIPLY(z4, FIX(2.205608352)); /* c3+c5+c9-c7 */ + tmp14 = MULTIPLY(z3 + z4, - FIX(0.657217813)); /* -c9 */ + tmp12 += tmp14; + tmp13 += tmp14; + tmp15 = MULTIPLY(tmp15, FIX(0.338443458)); /* c11 */ + tmp14 = tmp15 + MULTIPLY(z1, FIX(0.318774355)) - /* c9-c11 */ + MULTIPLY(z2, FIX(0.466105296)); /* c1-c7 */ + z1 = MULTIPLY(z3 - z2, FIX(0.937797057)); /* c7 */ + tmp14 += z1; + tmp15 += z1 + MULTIPLY(z3, FIX(0.384515595)) - /* c3-c7 */ + MULTIPLY(z4, FIX(1.742345811)); /* c1+c11 */ + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*12] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*11] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*10] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp23 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*9] = (int) RIGHT_SHIFT(tmp23 - tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp25 + tmp15, CONST_BITS-PASS1_BITS); + wsptr[8*7] = (int) RIGHT_SHIFT(tmp25 - tmp15, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp26, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 13 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 13; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + z1 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z1 <<= CONST_BITS; + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[4]; + z4 = (INT32) wsptr[6]; + + tmp10 = z3 + z4; + tmp11 = z3 - z4; + + tmp12 = MULTIPLY(tmp10, FIX(1.155388986)); /* (c4+c6)/2 */ + tmp13 = MULTIPLY(tmp11, FIX(0.096834934)) + z1; /* (c4-c6)/2 */ + + tmp20 = MULTIPLY(z2, FIX(1.373119086)) + tmp12 + tmp13; /* c2 */ + tmp22 = MULTIPLY(z2, FIX(0.501487041)) - tmp12 + tmp13; /* c10 */ + + tmp12 = MULTIPLY(tmp10, FIX(0.316450131)); /* (c8-c12)/2 */ + tmp13 = MULTIPLY(tmp11, FIX(0.486914739)) + z1; /* (c8+c12)/2 */ + + tmp21 = MULTIPLY(z2, FIX(1.058554052)) - tmp12 + tmp13; /* c6 */ + tmp25 = MULTIPLY(z2, - FIX(1.252223920)) + tmp12 + tmp13; /* c4 */ + + tmp12 = MULTIPLY(tmp10, FIX(0.435816023)); /* (c2-c10)/2 */ + tmp13 = MULTIPLY(tmp11, FIX(0.937303064)) - z1; /* (c2+c10)/2 */ + + tmp23 = MULTIPLY(z2, - FIX(0.170464608)) - tmp12 - tmp13; /* c12 */ + tmp24 = MULTIPLY(z2, - FIX(0.803364869)) + tmp12 - tmp13; /* c8 */ + + tmp26 = MULTIPLY(tmp11 - z2, FIX(1.414213562)) + z1; /* c0 */ + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + + tmp11 = MULTIPLY(z1 + z2, FIX(1.322312651)); /* c3 */ + tmp12 = MULTIPLY(z1 + z3, FIX(1.163874945)); /* c5 */ + tmp15 = z1 + z4; + tmp13 = MULTIPLY(tmp15, FIX(0.937797057)); /* c7 */ + tmp10 = tmp11 + tmp12 + tmp13 - + MULTIPLY(z1, FIX(2.020082300)); /* c7+c5+c3-c1 */ + tmp14 = MULTIPLY(z2 + z3, - FIX(0.338443458)); /* -c11 */ + tmp11 += tmp14 + MULTIPLY(z2, FIX(0.837223564)); /* c5+c9+c11-c3 */ + tmp12 += tmp14 - MULTIPLY(z3, FIX(1.572116027)); /* c1+c5-c9-c11 */ + tmp14 = MULTIPLY(z2 + z4, - FIX(1.163874945)); /* -c5 */ + tmp11 += tmp14; + tmp13 += tmp14 + MULTIPLY(z4, FIX(2.205608352)); /* c3+c5+c9-c7 */ + tmp14 = MULTIPLY(z3 + z4, - FIX(0.657217813)); /* -c9 */ + tmp12 += tmp14; + tmp13 += tmp14; + tmp15 = MULTIPLY(tmp15, FIX(0.338443458)); /* c11 */ + tmp14 = tmp15 + MULTIPLY(z1, FIX(0.318774355)) - /* c9-c11 */ + MULTIPLY(z2, FIX(0.466105296)); /* c1-c7 */ + z1 = MULTIPLY(z3 - z2, FIX(0.937797057)); /* c7 */ + tmp14 += z1; + tmp15 += z1 + MULTIPLY(z3, FIX(0.384515595)) - /* c3-c7 */ + MULTIPLY(z4, FIX(1.742345811)); /* c1+c11 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[12] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[11] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25 + tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp25 - tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp26, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 14x14 output block. + * + * Optimized algorithm with 20 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/28). + */ + +GLOBAL(void) +jpeg_idct_14x14 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*14]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z1 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-1); + z4 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z2 = MULTIPLY(z4, FIX(1.274162392)); /* c4 */ + z3 = MULTIPLY(z4, FIX(0.314692123)); /* c12 */ + z4 = MULTIPLY(z4, FIX(0.881747734)); /* c8 */ + + tmp10 = z1 + z2; + tmp11 = z1 + z3; + tmp12 = z1 - z4; + + tmp23 = RIGHT_SHIFT(z1 - ((z2 + z3 - z4) << 1), /* c0 = (c4+c12-c8)*2 */ + CONST_BITS-PASS1_BITS); + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z2 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z3 = MULTIPLY(z1 + z2, FIX(1.105676686)); /* c6 */ + + tmp13 = z3 + MULTIPLY(z1, FIX(0.273079590)); /* c2-c6 */ + tmp14 = z3 - MULTIPLY(z2, FIX(1.719280954)); /* c6+c10 */ + tmp15 = MULTIPLY(z1, FIX(0.613604268)) - /* c10 */ + MULTIPLY(z2, FIX(1.378756276)); /* c2 */ + + tmp20 = tmp10 + tmp13; + tmp26 = tmp10 - tmp13; + tmp21 = tmp11 + tmp14; + tmp25 = tmp11 - tmp14; + tmp22 = tmp12 + tmp15; + tmp24 = tmp12 - tmp15; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp13 = z4 << CONST_BITS; + + tmp14 = z1 + z3; + tmp11 = MULTIPLY(z1 + z2, FIX(1.334852607)); /* c3 */ + tmp12 = MULTIPLY(tmp14, FIX(1.197448846)); /* c5 */ + tmp10 = tmp11 + tmp12 + tmp13 - MULTIPLY(z1, FIX(1.126980169)); /* c3+c5-c1 */ + tmp14 = MULTIPLY(tmp14, FIX(0.752406978)); /* c9 */ + tmp16 = tmp14 - MULTIPLY(z1, FIX(1.061150426)); /* c9+c11-c13 */ + z1 -= z2; + tmp15 = MULTIPLY(z1, FIX(0.467085129)) - tmp13; /* c11 */ + tmp16 += tmp15; + z1 += z4; + z4 = MULTIPLY(z2 + z3, - FIX(0.158341681)) - tmp13; /* -c13 */ + tmp11 += z4 - MULTIPLY(z2, FIX(0.424103948)); /* c3-c9-c13 */ + tmp12 += z4 - MULTIPLY(z3, FIX(2.373959773)); /* c3+c5-c13 */ + z4 = MULTIPLY(z3 - z2, FIX(1.405321284)); /* c1 */ + tmp14 += z4 + tmp13 - MULTIPLY(z3, FIX(1.6906431334)); /* c1+c9-c11 */ + tmp15 += z4 + MULTIPLY(z2, FIX(0.674957567)); /* c1+c11-c5 */ + + tmp13 = (z1 - z3) << PASS1_BITS; + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*13] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*12] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*11] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) (tmp23 + tmp13); + wsptr[8*10] = (int) (tmp23 - tmp13); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*9] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp25 + tmp15, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp25 - tmp15, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp26 + tmp16, CONST_BITS-PASS1_BITS); + wsptr[8*7] = (int) RIGHT_SHIFT(tmp26 - tmp16, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 14 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 14; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + z1 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z1 <<= CONST_BITS; + z4 = (INT32) wsptr[4]; + z2 = MULTIPLY(z4, FIX(1.274162392)); /* c4 */ + z3 = MULTIPLY(z4, FIX(0.314692123)); /* c12 */ + z4 = MULTIPLY(z4, FIX(0.881747734)); /* c8 */ + + tmp10 = z1 + z2; + tmp11 = z1 + z3; + tmp12 = z1 - z4; + + tmp23 = z1 - ((z2 + z3 - z4) << 1); /* c0 = (c4+c12-c8)*2 */ + + z1 = (INT32) wsptr[2]; + z2 = (INT32) wsptr[6]; + + z3 = MULTIPLY(z1 + z2, FIX(1.105676686)); /* c6 */ + + tmp13 = z3 + MULTIPLY(z1, FIX(0.273079590)); /* c2-c6 */ + tmp14 = z3 - MULTIPLY(z2, FIX(1.719280954)); /* c6+c10 */ + tmp15 = MULTIPLY(z1, FIX(0.613604268)) - /* c10 */ + MULTIPLY(z2, FIX(1.378756276)); /* c2 */ + + tmp20 = tmp10 + tmp13; + tmp26 = tmp10 - tmp13; + tmp21 = tmp11 + tmp14; + tmp25 = tmp11 - tmp14; + tmp22 = tmp12 + tmp15; + tmp24 = tmp12 - tmp15; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + z4 <<= CONST_BITS; + + tmp14 = z1 + z3; + tmp11 = MULTIPLY(z1 + z2, FIX(1.334852607)); /* c3 */ + tmp12 = MULTIPLY(tmp14, FIX(1.197448846)); /* c5 */ + tmp10 = tmp11 + tmp12 + z4 - MULTIPLY(z1, FIX(1.126980169)); /* c3+c5-c1 */ + tmp14 = MULTIPLY(tmp14, FIX(0.752406978)); /* c9 */ + tmp16 = tmp14 - MULTIPLY(z1, FIX(1.061150426)); /* c9+c11-c13 */ + z1 -= z2; + tmp15 = MULTIPLY(z1, FIX(0.467085129)) - z4; /* c11 */ + tmp16 += tmp15; + tmp13 = MULTIPLY(z2 + z3, - FIX(0.158341681)) - z4; /* -c13 */ + tmp11 += tmp13 - MULTIPLY(z2, FIX(0.424103948)); /* c3-c9-c13 */ + tmp12 += tmp13 - MULTIPLY(z3, FIX(2.373959773)); /* c3+c5-c13 */ + tmp13 = MULTIPLY(z3 - z2, FIX(1.405321284)); /* c1 */ + tmp14 += tmp13 + z4 - MULTIPLY(z3, FIX(1.6906431334)); /* c1+c9-c11 */ + tmp15 += tmp13 + MULTIPLY(z2, FIX(0.674957567)); /* c1+c11-c5 */ + + tmp13 = ((z1 - z3) << CONST_BITS) + z4; + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[13] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[12] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[11] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25 + tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp25 - tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp26 + tmp16, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp26 - tmp16, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 15x15 output block. + * + * Optimized algorithm with 22 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/30). + */ + +GLOBAL(void) +jpeg_idct_15x15 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26, tmp27; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*15]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z1 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-1); + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z4 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = MULTIPLY(z4, FIX(0.437016024)); /* c12 */ + tmp11 = MULTIPLY(z4, FIX(1.144122806)); /* c6 */ + + tmp12 = z1 - tmp10; + tmp13 = z1 + tmp11; + z1 -= (tmp11 - tmp10) << 1; /* c0 = (c6-c12)*2 */ + + z4 = z2 - z3; + z3 += z2; + tmp10 = MULTIPLY(z3, FIX(1.337628990)); /* (c2+c4)/2 */ + tmp11 = MULTIPLY(z4, FIX(0.045680613)); /* (c2-c4)/2 */ + z2 = MULTIPLY(z2, FIX(1.439773946)); /* c4+c14 */ + + tmp20 = tmp13 + tmp10 + tmp11; + tmp23 = tmp12 - tmp10 + tmp11 + z2; + + tmp10 = MULTIPLY(z3, FIX(0.547059574)); /* (c8+c14)/2 */ + tmp11 = MULTIPLY(z4, FIX(0.399234004)); /* (c8-c14)/2 */ + + tmp25 = tmp13 - tmp10 - tmp11; + tmp26 = tmp12 + tmp10 - tmp11 - z2; + + tmp10 = MULTIPLY(z3, FIX(0.790569415)); /* (c6+c12)/2 */ + tmp11 = MULTIPLY(z4, FIX(0.353553391)); /* (c6-c12)/2 */ + + tmp21 = tmp12 + tmp10 + tmp11; + tmp24 = tmp13 - tmp10 + tmp11; + tmp11 += tmp11; + tmp22 = z1 + tmp11; /* c10 = c6-c12 */ + tmp27 = z1 - tmp11 - tmp11; /* c0 = (c6-c12)*2 */ + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z4 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z3 = MULTIPLY(z4, FIX(1.224744871)); /* c5 */ + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp13 = z2 - z4; + tmp15 = MULTIPLY(z1 + tmp13, FIX(0.831253876)); /* c9 */ + tmp11 = tmp15 + MULTIPLY(z1, FIX(0.513743148)); /* c3-c9 */ + tmp14 = tmp15 - MULTIPLY(tmp13, FIX(2.176250899)); /* c3+c9 */ + + tmp13 = MULTIPLY(z2, - FIX(0.831253876)); /* -c9 */ + tmp15 = MULTIPLY(z2, - FIX(1.344997024)); /* -c3 */ + z2 = z1 - z4; + tmp12 = z3 + MULTIPLY(z2, FIX(1.406466353)); /* c1 */ + + tmp10 = tmp12 + MULTIPLY(z4, FIX(2.457431844)) - tmp15; /* c1+c7 */ + tmp16 = tmp12 - MULTIPLY(z1, FIX(1.112434820)) + tmp13; /* c1-c13 */ + tmp12 = MULTIPLY(z2, FIX(1.224744871)) - z3; /* c5 */ + z2 = MULTIPLY(z1 + z4, FIX(0.575212477)); /* c11 */ + tmp13 += z2 + MULTIPLY(z1, FIX(0.475753014)) - z3; /* c7-c11 */ + tmp15 += z2 - MULTIPLY(z4, FIX(0.869244010)) + z3; /* c11+c13 */ + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*14] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*13] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*12] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp23 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*11] = (int) RIGHT_SHIFT(tmp23 - tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*10] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp25 + tmp15, CONST_BITS-PASS1_BITS); + wsptr[8*9] = (int) RIGHT_SHIFT(tmp25 - tmp15, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp26 + tmp16, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp26 - tmp16, CONST_BITS-PASS1_BITS); + wsptr[8*7] = (int) RIGHT_SHIFT(tmp27, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 15 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 15; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + z1 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z1 <<= CONST_BITS; + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[4]; + z4 = (INT32) wsptr[6]; + + tmp10 = MULTIPLY(z4, FIX(0.437016024)); /* c12 */ + tmp11 = MULTIPLY(z4, FIX(1.144122806)); /* c6 */ + + tmp12 = z1 - tmp10; + tmp13 = z1 + tmp11; + z1 -= (tmp11 - tmp10) << 1; /* c0 = (c6-c12)*2 */ + + z4 = z2 - z3; + z3 += z2; + tmp10 = MULTIPLY(z3, FIX(1.337628990)); /* (c2+c4)/2 */ + tmp11 = MULTIPLY(z4, FIX(0.045680613)); /* (c2-c4)/2 */ + z2 = MULTIPLY(z2, FIX(1.439773946)); /* c4+c14 */ + + tmp20 = tmp13 + tmp10 + tmp11; + tmp23 = tmp12 - tmp10 + tmp11 + z2; + + tmp10 = MULTIPLY(z3, FIX(0.547059574)); /* (c8+c14)/2 */ + tmp11 = MULTIPLY(z4, FIX(0.399234004)); /* (c8-c14)/2 */ + + tmp25 = tmp13 - tmp10 - tmp11; + tmp26 = tmp12 + tmp10 - tmp11 - z2; + + tmp10 = MULTIPLY(z3, FIX(0.790569415)); /* (c6+c12)/2 */ + tmp11 = MULTIPLY(z4, FIX(0.353553391)); /* (c6-c12)/2 */ + + tmp21 = tmp12 + tmp10 + tmp11; + tmp24 = tmp13 - tmp10 + tmp11; + tmp11 += tmp11; + tmp22 = z1 + tmp11; /* c10 = c6-c12 */ + tmp27 = z1 - tmp11 - tmp11; /* c0 = (c6-c12)*2 */ + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z4 = (INT32) wsptr[5]; + z3 = MULTIPLY(z4, FIX(1.224744871)); /* c5 */ + z4 = (INT32) wsptr[7]; + + tmp13 = z2 - z4; + tmp15 = MULTIPLY(z1 + tmp13, FIX(0.831253876)); /* c9 */ + tmp11 = tmp15 + MULTIPLY(z1, FIX(0.513743148)); /* c3-c9 */ + tmp14 = tmp15 - MULTIPLY(tmp13, FIX(2.176250899)); /* c3+c9 */ + + tmp13 = MULTIPLY(z2, - FIX(0.831253876)); /* -c9 */ + tmp15 = MULTIPLY(z2, - FIX(1.344997024)); /* -c3 */ + z2 = z1 - z4; + tmp12 = z3 + MULTIPLY(z2, FIX(1.406466353)); /* c1 */ + + tmp10 = tmp12 + MULTIPLY(z4, FIX(2.457431844)) - tmp15; /* c1+c7 */ + tmp16 = tmp12 - MULTIPLY(z1, FIX(1.112434820)) + tmp13; /* c1-c13 */ + tmp12 = MULTIPLY(z2, FIX(1.224744871)) - z3; /* c5 */ + z2 = MULTIPLY(z1 + z4, FIX(0.575212477)); /* c11 */ + tmp13 += z2 + MULTIPLY(z1, FIX(0.475753014)) - z3; /* c7-c11 */ + tmp15 += z2 - MULTIPLY(z4, FIX(0.869244010)) + z3; /* c11+c13 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[14] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[13] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[12] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[11] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25 + tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp25 - tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp26 + tmp16, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp26 - tmp16, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp27, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 16x16 output block. + * + * Optimized algorithm with 28 multiplications in the 1-D kernel. + * cK represents sqrt(2) * cos(K*pi/32). + */ + +GLOBAL(void) +jpeg_idct_16x16 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp10, tmp11, tmp12, tmp13; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26, tmp27; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*16]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp0 += 1 << (CONST_BITS-PASS1_BITS-1); + + z1 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp1 = MULTIPLY(z1, FIX(1.306562965)); /* c4[16] = c2[8] */ + tmp2 = MULTIPLY(z1, FIX_0_541196100); /* c12[16] = c6[8] */ + + tmp10 = tmp0 + tmp1; + tmp11 = tmp0 - tmp1; + tmp12 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z2 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + z3 = z1 - z2; + z4 = MULTIPLY(z3, FIX(0.275899379)); /* c14[16] = c7[8] */ + z3 = MULTIPLY(z3, FIX(1.387039845)); /* c2[16] = c1[8] */ + + tmp0 = z3 + MULTIPLY(z2, FIX_2_562915447); /* (c6+c2)[16] = (c3+c1)[8] */ + tmp1 = z4 + MULTIPLY(z1, FIX_0_899976223); /* (c6-c14)[16] = (c3-c7)[8] */ + tmp2 = z3 - MULTIPLY(z1, FIX(0.601344887)); /* (c2-c10)[16] = (c1-c5)[8] */ + tmp3 = z4 - MULTIPLY(z2, FIX(0.509795579)); /* (c10-c14)[16] = (c5-c7)[8] */ + + tmp20 = tmp10 + tmp0; + tmp27 = tmp10 - tmp0; + tmp21 = tmp12 + tmp1; + tmp26 = tmp12 - tmp1; + tmp22 = tmp13 + tmp2; + tmp25 = tmp13 - tmp2; + tmp23 = tmp11 + tmp3; + tmp24 = tmp11 - tmp3; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp11 = z1 + z3; + + tmp1 = MULTIPLY(z1 + z2, FIX(1.353318001)); /* c3 */ + tmp2 = MULTIPLY(tmp11, FIX(1.247225013)); /* c5 */ + tmp3 = MULTIPLY(z1 + z4, FIX(1.093201867)); /* c7 */ + tmp10 = MULTIPLY(z1 - z4, FIX(0.897167586)); /* c9 */ + tmp11 = MULTIPLY(tmp11, FIX(0.666655658)); /* c11 */ + tmp12 = MULTIPLY(z1 - z2, FIX(0.410524528)); /* c13 */ + tmp0 = tmp1 + tmp2 + tmp3 - + MULTIPLY(z1, FIX(2.286341144)); /* c7+c5+c3-c1 */ + tmp13 = tmp10 + tmp11 + tmp12 - + MULTIPLY(z1, FIX(1.835730603)); /* c9+c11+c13-c15 */ + z1 = MULTIPLY(z2 + z3, FIX(0.138617169)); /* c15 */ + tmp1 += z1 + MULTIPLY(z2, FIX(0.071888074)); /* c9+c11-c3-c15 */ + tmp2 += z1 - MULTIPLY(z3, FIX(1.125726048)); /* c5+c7+c15-c3 */ + z1 = MULTIPLY(z3 - z2, FIX(1.407403738)); /* c1 */ + tmp11 += z1 - MULTIPLY(z3, FIX(0.766367282)); /* c1+c11-c9-c13 */ + tmp12 += z1 + MULTIPLY(z2, FIX(1.971951411)); /* c1+c5+c13-c7 */ + z2 += z4; + z1 = MULTIPLY(z2, - FIX(0.666655658)); /* -c11 */ + tmp1 += z1; + tmp3 += z1 + MULTIPLY(z4, FIX(1.065388962)); /* c3+c11+c15-c7 */ + z2 = MULTIPLY(z2, - FIX(1.247225013)); /* -c5 */ + tmp10 += z2 + MULTIPLY(z4, FIX(3.141271809)); /* c1+c5+c9-c13 */ + tmp12 += z2; + z2 = MULTIPLY(z3 + z4, - FIX(1.353318001)); /* -c3 */ + tmp2 += z2; + tmp3 += z2; + z2 = MULTIPLY(z4 - z3, FIX(0.410524528)); /* c13 */ + tmp10 += z2; + tmp11 += z2; + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[8*15] = (int) RIGHT_SHIFT(tmp20 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[8*14] = (int) RIGHT_SHIFT(tmp21 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[8*13] = (int) RIGHT_SHIFT(tmp22 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp23 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[8*12] = (int) RIGHT_SHIFT(tmp23 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp24 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*11] = (int) RIGHT_SHIFT(tmp24 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp25 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*10] = (int) RIGHT_SHIFT(tmp25 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp26 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*9] = (int) RIGHT_SHIFT(tmp26 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*7] = (int) RIGHT_SHIFT(tmp27 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp27 - tmp13, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 16 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 16; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp0 <<= CONST_BITS; + + z1 = (INT32) wsptr[4]; + tmp1 = MULTIPLY(z1, FIX(1.306562965)); /* c4[16] = c2[8] */ + tmp2 = MULTIPLY(z1, FIX_0_541196100); /* c12[16] = c6[8] */ + + tmp10 = tmp0 + tmp1; + tmp11 = tmp0 - tmp1; + tmp12 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + + z1 = (INT32) wsptr[2]; + z2 = (INT32) wsptr[6]; + z3 = z1 - z2; + z4 = MULTIPLY(z3, FIX(0.275899379)); /* c14[16] = c7[8] */ + z3 = MULTIPLY(z3, FIX(1.387039845)); /* c2[16] = c1[8] */ + + tmp0 = z3 + MULTIPLY(z2, FIX_2_562915447); /* (c6+c2)[16] = (c3+c1)[8] */ + tmp1 = z4 + MULTIPLY(z1, FIX_0_899976223); /* (c6-c14)[16] = (c3-c7)[8] */ + tmp2 = z3 - MULTIPLY(z1, FIX(0.601344887)); /* (c2-c10)[16] = (c1-c5)[8] */ + tmp3 = z4 - MULTIPLY(z2, FIX(0.509795579)); /* (c10-c14)[16] = (c5-c7)[8] */ + + tmp20 = tmp10 + tmp0; + tmp27 = tmp10 - tmp0; + tmp21 = tmp12 + tmp1; + tmp26 = tmp12 - tmp1; + tmp22 = tmp13 + tmp2; + tmp25 = tmp13 - tmp2; + tmp23 = tmp11 + tmp3; + tmp24 = tmp11 - tmp3; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + + tmp11 = z1 + z3; + + tmp1 = MULTIPLY(z1 + z2, FIX(1.353318001)); /* c3 */ + tmp2 = MULTIPLY(tmp11, FIX(1.247225013)); /* c5 */ + tmp3 = MULTIPLY(z1 + z4, FIX(1.093201867)); /* c7 */ + tmp10 = MULTIPLY(z1 - z4, FIX(0.897167586)); /* c9 */ + tmp11 = MULTIPLY(tmp11, FIX(0.666655658)); /* c11 */ + tmp12 = MULTIPLY(z1 - z2, FIX(0.410524528)); /* c13 */ + tmp0 = tmp1 + tmp2 + tmp3 - + MULTIPLY(z1, FIX(2.286341144)); /* c7+c5+c3-c1 */ + tmp13 = tmp10 + tmp11 + tmp12 - + MULTIPLY(z1, FIX(1.835730603)); /* c9+c11+c13-c15 */ + z1 = MULTIPLY(z2 + z3, FIX(0.138617169)); /* c15 */ + tmp1 += z1 + MULTIPLY(z2, FIX(0.071888074)); /* c9+c11-c3-c15 */ + tmp2 += z1 - MULTIPLY(z3, FIX(1.125726048)); /* c5+c7+c15-c3 */ + z1 = MULTIPLY(z3 - z2, FIX(1.407403738)); /* c1 */ + tmp11 += z1 - MULTIPLY(z3, FIX(0.766367282)); /* c1+c11-c9-c13 */ + tmp12 += z1 + MULTIPLY(z2, FIX(1.971951411)); /* c1+c5+c13-c7 */ + z2 += z4; + z1 = MULTIPLY(z2, - FIX(0.666655658)); /* -c11 */ + tmp1 += z1; + tmp3 += z1 + MULTIPLY(z4, FIX(1.065388962)); /* c3+c11+c15-c7 */ + z2 = MULTIPLY(z2, - FIX(1.247225013)); /* -c5 */ + tmp10 += z2 + MULTIPLY(z4, FIX(3.141271809)); /* c1+c5+c9-c13 */ + tmp12 += z2; + z2 = MULTIPLY(z3 + z4, - FIX(1.353318001)); /* -c3 */ + tmp2 += z2; + tmp3 += z2; + z2 = MULTIPLY(z4 - z3, FIX(0.410524528)); /* c13 */ + tmp10 += z2; + tmp11 += z2; + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[15] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[14] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[13] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[12] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[11] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp25 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp26 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp26 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp27 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp27 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 16x8 output block. + * + * 8-point IDCT in pass 1 (columns), 16-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_16x8 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp10, tmp11, tmp12, tmp13; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26, tmp27; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*8]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + /* Note results are scaled up by sqrt(8) compared to a true IDCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z2, FIX_0_765366865); + tmp3 = z1 - MULTIPLY(z3, FIX_1_847759065); + + z2 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z2 <<= CONST_BITS; + z3 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z2 += ONE << (CONST_BITS-PASS1_BITS-1); + + tmp0 = z2 + z3; + tmp1 = z2 - z3; + + tmp10 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + tmp11 = tmp1 + tmp3; + tmp12 = tmp1 - tmp3; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + z2 = tmp0 + tmp2; + z3 = tmp1 + tmp3; + + z1 = MULTIPLY(z2 + z3, FIX_1_175875602); /* sqrt(2) * c3 */ + z2 = MULTIPLY(z2, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z3 = MULTIPLY(z3, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + z2 += z1; + z3 += z1; + + z1 = MULTIPLY(tmp0 + tmp3, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + tmp0 += z1 + z2; + tmp3 += z1 + z3; + + z1 = MULTIPLY(tmp1 + tmp2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp1 += z1 + z3; + tmp2 += z1 + z2; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + wsptr[DCTSIZE*0] = (int) RIGHT_SHIFT(tmp10 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*7] = (int) RIGHT_SHIFT(tmp10 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*1] = (int) RIGHT_SHIFT(tmp11 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*6] = (int) RIGHT_SHIFT(tmp11 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*2] = (int) RIGHT_SHIFT(tmp12 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*5] = (int) RIGHT_SHIFT(tmp12 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*3] = (int) RIGHT_SHIFT(tmp13 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*4] = (int) RIGHT_SHIFT(tmp13 - tmp0, CONST_BITS-PASS1_BITS); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process 8 rows from work array, store into output array. + * 16-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/32). + */ + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp0 <<= CONST_BITS; + + z1 = (INT32) wsptr[4]; + tmp1 = MULTIPLY(z1, FIX(1.306562965)); /* c4[16] = c2[8] */ + tmp2 = MULTIPLY(z1, FIX_0_541196100); /* c12[16] = c6[8] */ + + tmp10 = tmp0 + tmp1; + tmp11 = tmp0 - tmp1; + tmp12 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + + z1 = (INT32) wsptr[2]; + z2 = (INT32) wsptr[6]; + z3 = z1 - z2; + z4 = MULTIPLY(z3, FIX(0.275899379)); /* c14[16] = c7[8] */ + z3 = MULTIPLY(z3, FIX(1.387039845)); /* c2[16] = c1[8] */ + + tmp0 = z3 + MULTIPLY(z2, FIX_2_562915447); /* (c6+c2)[16] = (c3+c1)[8] */ + tmp1 = z4 + MULTIPLY(z1, FIX_0_899976223); /* (c6-c14)[16] = (c3-c7)[8] */ + tmp2 = z3 - MULTIPLY(z1, FIX(0.601344887)); /* (c2-c10)[16] = (c1-c5)[8] */ + tmp3 = z4 - MULTIPLY(z2, FIX(0.509795579)); /* (c10-c14)[16] = (c5-c7)[8] */ + + tmp20 = tmp10 + tmp0; + tmp27 = tmp10 - tmp0; + tmp21 = tmp12 + tmp1; + tmp26 = tmp12 - tmp1; + tmp22 = tmp13 + tmp2; + tmp25 = tmp13 - tmp2; + tmp23 = tmp11 + tmp3; + tmp24 = tmp11 - tmp3; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + + tmp11 = z1 + z3; + + tmp1 = MULTIPLY(z1 + z2, FIX(1.353318001)); /* c3 */ + tmp2 = MULTIPLY(tmp11, FIX(1.247225013)); /* c5 */ + tmp3 = MULTIPLY(z1 + z4, FIX(1.093201867)); /* c7 */ + tmp10 = MULTIPLY(z1 - z4, FIX(0.897167586)); /* c9 */ + tmp11 = MULTIPLY(tmp11, FIX(0.666655658)); /* c11 */ + tmp12 = MULTIPLY(z1 - z2, FIX(0.410524528)); /* c13 */ + tmp0 = tmp1 + tmp2 + tmp3 - + MULTIPLY(z1, FIX(2.286341144)); /* c7+c5+c3-c1 */ + tmp13 = tmp10 + tmp11 + tmp12 - + MULTIPLY(z1, FIX(1.835730603)); /* c9+c11+c13-c15 */ + z1 = MULTIPLY(z2 + z3, FIX(0.138617169)); /* c15 */ + tmp1 += z1 + MULTIPLY(z2, FIX(0.071888074)); /* c9+c11-c3-c15 */ + tmp2 += z1 - MULTIPLY(z3, FIX(1.125726048)); /* c5+c7+c15-c3 */ + z1 = MULTIPLY(z3 - z2, FIX(1.407403738)); /* c1 */ + tmp11 += z1 - MULTIPLY(z3, FIX(0.766367282)); /* c1+c11-c9-c13 */ + tmp12 += z1 + MULTIPLY(z2, FIX(1.971951411)); /* c1+c5+c13-c7 */ + z2 += z4; + z1 = MULTIPLY(z2, - FIX(0.666655658)); /* -c11 */ + tmp1 += z1; + tmp3 += z1 + MULTIPLY(z4, FIX(1.065388962)); /* c3+c11+c15-c7 */ + z2 = MULTIPLY(z2, - FIX(1.247225013)); /* -c5 */ + tmp10 += z2 + MULTIPLY(z4, FIX(3.141271809)); /* c1+c5+c9-c13 */ + tmp12 += z2; + z2 = MULTIPLY(z3 + z4, - FIX(1.353318001)); /* -c3 */ + tmp2 += z2; + tmp3 += z2; + z2 = MULTIPLY(z4 - z3, FIX(0.410524528)); /* c13 */ + tmp10 += z2; + tmp11 += z2; + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[15] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[14] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[13] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[12] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[11] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp25 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp26 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp26 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp27 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp27 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 14x7 output block. + * + * 7-point IDCT in pass 1 (columns), 14-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_14x7 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*7]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 7-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/14). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp23 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp23 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp23 += ONE << (CONST_BITS-PASS1_BITS-1); + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp20 = MULTIPLY(z2 - z3, FIX(0.881747734)); /* c4 */ + tmp22 = MULTIPLY(z1 - z2, FIX(0.314692123)); /* c6 */ + tmp21 = tmp20 + tmp22 + tmp23 - MULTIPLY(z2, FIX(1.841218003)); /* c2+c4-c6 */ + tmp10 = z1 + z3; + z2 -= tmp10; + tmp10 = MULTIPLY(tmp10, FIX(1.274162392)) + tmp23; /* c2 */ + tmp20 += tmp10 - MULTIPLY(z3, FIX(0.077722536)); /* c2-c4-c6 */ + tmp22 += tmp10 - MULTIPLY(z1, FIX(2.470602249)); /* c2+c4+c6 */ + tmp23 += MULTIPLY(z2, FIX(1.414213562)); /* c0 */ + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + + tmp11 = MULTIPLY(z1 + z2, FIX(0.935414347)); /* (c3+c1-c5)/2 */ + tmp12 = MULTIPLY(z1 - z2, FIX(0.170262339)); /* (c3+c5-c1)/2 */ + tmp10 = tmp11 - tmp12; + tmp11 += tmp12; + tmp12 = MULTIPLY(z2 + z3, - FIX(1.378756276)); /* -c1 */ + tmp11 += tmp12; + z2 = MULTIPLY(z1 + z3, FIX(0.613604268)); /* c5 */ + tmp10 += z2; + tmp12 += z2 + MULTIPLY(z3, FIX(1.870828693)); /* c3+c1-c5 */ + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp23, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 7 rows from work array, store into output array. + * 14-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/28). + */ + wsptr = workspace; + for (ctr = 0; ctr < 7; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + z1 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z1 <<= CONST_BITS; + z4 = (INT32) wsptr[4]; + z2 = MULTIPLY(z4, FIX(1.274162392)); /* c4 */ + z3 = MULTIPLY(z4, FIX(0.314692123)); /* c12 */ + z4 = MULTIPLY(z4, FIX(0.881747734)); /* c8 */ + + tmp10 = z1 + z2; + tmp11 = z1 + z3; + tmp12 = z1 - z4; + + tmp23 = z1 - ((z2 + z3 - z4) << 1); /* c0 = (c4+c12-c8)*2 */ + + z1 = (INT32) wsptr[2]; + z2 = (INT32) wsptr[6]; + + z3 = MULTIPLY(z1 + z2, FIX(1.105676686)); /* c6 */ + + tmp13 = z3 + MULTIPLY(z1, FIX(0.273079590)); /* c2-c6 */ + tmp14 = z3 - MULTIPLY(z2, FIX(1.719280954)); /* c6+c10 */ + tmp15 = MULTIPLY(z1, FIX(0.613604268)) - /* c10 */ + MULTIPLY(z2, FIX(1.378756276)); /* c2 */ + + tmp20 = tmp10 + tmp13; + tmp26 = tmp10 - tmp13; + tmp21 = tmp11 + tmp14; + tmp25 = tmp11 - tmp14; + tmp22 = tmp12 + tmp15; + tmp24 = tmp12 - tmp15; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + z4 <<= CONST_BITS; + + tmp14 = z1 + z3; + tmp11 = MULTIPLY(z1 + z2, FIX(1.334852607)); /* c3 */ + tmp12 = MULTIPLY(tmp14, FIX(1.197448846)); /* c5 */ + tmp10 = tmp11 + tmp12 + z4 - MULTIPLY(z1, FIX(1.126980169)); /* c3+c5-c1 */ + tmp14 = MULTIPLY(tmp14, FIX(0.752406978)); /* c9 */ + tmp16 = tmp14 - MULTIPLY(z1, FIX(1.061150426)); /* c9+c11-c13 */ + z1 -= z2; + tmp15 = MULTIPLY(z1, FIX(0.467085129)) - z4; /* c11 */ + tmp16 += tmp15; + tmp13 = MULTIPLY(z2 + z3, - FIX(0.158341681)) - z4; /* -c13 */ + tmp11 += tmp13 - MULTIPLY(z2, FIX(0.424103948)); /* c3-c9-c13 */ + tmp12 += tmp13 - MULTIPLY(z3, FIX(2.373959773)); /* c3+c5-c13 */ + tmp13 = MULTIPLY(z3 - z2, FIX(1.405321284)); /* c1 */ + tmp14 += tmp13 + z4 - MULTIPLY(z3, FIX(1.6906431334)); /* c1+c9-c11 */ + tmp15 += tmp13 + MULTIPLY(z2, FIX(0.674957567)); /* c1+c11-c5 */ + + tmp13 = ((z1 - z3) << CONST_BITS) + z4; + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[13] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[12] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[11] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25 + tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp25 - tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp26 + tmp16, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp26 - tmp16, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 12x6 output block. + * + * 6-point IDCT in pass 1 (columns), 12-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_12x6 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*6]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 6-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/12). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp10 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp10 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp10 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp12 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp20 = MULTIPLY(tmp12, FIX(0.707106781)); /* c4 */ + tmp11 = tmp10 + tmp20; + tmp21 = RIGHT_SHIFT(tmp10 - tmp20 - tmp20, CONST_BITS-PASS1_BITS); + tmp20 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp10 = MULTIPLY(tmp20, FIX(1.224744871)); /* c2 */ + tmp20 = tmp11 + tmp10; + tmp22 = tmp11 - tmp10; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp11 = MULTIPLY(z1 + z3, FIX(0.366025404)); /* c5 */ + tmp10 = tmp11 + ((z1 + z2) << CONST_BITS); + tmp12 = tmp11 + ((z3 - z2) << CONST_BITS); + tmp11 = (z1 - z2 - z3) << PASS1_BITS; + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) (tmp21 + tmp11); + wsptr[8*4] = (int) (tmp21 - tmp11); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 6 rows from work array, store into output array. + * 12-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/24). + */ + wsptr = workspace; + for (ctr = 0; ctr < 6; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + z3 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z3 <<= CONST_BITS; + + z4 = (INT32) wsptr[4]; + z4 = MULTIPLY(z4, FIX(1.224744871)); /* c4 */ + + tmp10 = z3 + z4; + tmp11 = z3 - z4; + + z1 = (INT32) wsptr[2]; + z4 = MULTIPLY(z1, FIX(1.366025404)); /* c2 */ + z1 <<= CONST_BITS; + z2 = (INT32) wsptr[6]; + z2 <<= CONST_BITS; + + tmp12 = z1 - z2; + + tmp21 = z3 + tmp12; + tmp24 = z3 - tmp12; + + tmp12 = z4 + z2; + + tmp20 = tmp10 + tmp12; + tmp25 = tmp10 - tmp12; + + tmp12 = z4 - z1 - z2; + + tmp22 = tmp11 + tmp12; + tmp23 = tmp11 - tmp12; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z4 = (INT32) wsptr[7]; + + tmp11 = MULTIPLY(z2, FIX(1.306562965)); /* c3 */ + tmp14 = MULTIPLY(z2, - FIX_0_541196100); /* -c9 */ + + tmp10 = z1 + z3; + tmp15 = MULTIPLY(tmp10 + z4, FIX(0.860918669)); /* c7 */ + tmp12 = tmp15 + MULTIPLY(tmp10, FIX(0.261052384)); /* c5-c7 */ + tmp10 = tmp12 + tmp11 + MULTIPLY(z1, FIX(0.280143716)); /* c1-c5 */ + tmp13 = MULTIPLY(z3 + z4, - FIX(1.045510580)); /* -(c7+c11) */ + tmp12 += tmp13 + tmp14 - MULTIPLY(z3, FIX(1.478575242)); /* c1+c5-c7-c11 */ + tmp13 += tmp15 - tmp11 + MULTIPLY(z4, FIX(1.586706681)); /* c1+c11 */ + tmp15 += tmp14 - MULTIPLY(z1, FIX(0.676326758)) - /* c7-c11 */ + MULTIPLY(z4, FIX(1.982889723)); /* c5+c7 */ + + z1 -= z4; + z2 -= z3; + z3 = MULTIPLY(z1 + z2, FIX_0_541196100); /* c9 */ + tmp11 = z3 + MULTIPLY(z1, FIX_0_765366865); /* c3-c9 */ + tmp14 = z3 - MULTIPLY(z2, FIX_1_847759065); /* c3+c9 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[11] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[10] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp25 + tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp25 - tmp15, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 10x5 output block. + * + * 5-point IDCT in pass 1 (columns), 10-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_10x5 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*5]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 5-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/10). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp12 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp12 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp12 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp13 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp14 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z1 = MULTIPLY(tmp13 + tmp14, FIX(0.790569415)); /* (c2+c4)/2 */ + z2 = MULTIPLY(tmp13 - tmp14, FIX(0.353553391)); /* (c2-c4)/2 */ + z3 = tmp12 + z2; + tmp10 = z3 + z1; + tmp11 = z3 - z1; + tmp12 -= z2 << 2; + + /* Odd part */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + + z1 = MULTIPLY(z2 + z3, FIX(0.831253876)); /* c3 */ + tmp13 = z1 + MULTIPLY(z2, FIX(0.513743148)); /* c1-c3 */ + tmp14 = z1 - MULTIPLY(z3, FIX(2.176250899)); /* c1+c3 */ + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp10 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp10 - tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp11 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp11 - tmp14, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp12, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 5 rows from work array, store into output array. + * 10-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/20). + */ + wsptr = workspace; + for (ctr = 0; ctr < 5; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + z3 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z3 <<= CONST_BITS; + z4 = (INT32) wsptr[4]; + z1 = MULTIPLY(z4, FIX(1.144122806)); /* c4 */ + z2 = MULTIPLY(z4, FIX(0.437016024)); /* c8 */ + tmp10 = z3 + z1; + tmp11 = z3 - z2; + + tmp22 = z3 - ((z1 - z2) << 1); /* c0 = (c4-c8)*2 */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY(z2 + z3, FIX(0.831253876)); /* c6 */ + tmp12 = z1 + MULTIPLY(z2, FIX(0.513743148)); /* c2-c6 */ + tmp13 = z1 - MULTIPLY(z3, FIX(2.176250899)); /* c2+c6 */ + + tmp20 = tmp10 + tmp12; + tmp24 = tmp10 - tmp12; + tmp21 = tmp11 + tmp13; + tmp23 = tmp11 - tmp13; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + z3 <<= CONST_BITS; + z4 = (INT32) wsptr[7]; + + tmp11 = z2 + z4; + tmp13 = z2 - z4; + + tmp12 = MULTIPLY(tmp13, FIX(0.309016994)); /* (c3-c7)/2 */ + + z2 = MULTIPLY(tmp11, FIX(0.951056516)); /* (c3+c7)/2 */ + z4 = z3 + tmp12; + + tmp10 = MULTIPLY(z1, FIX(1.396802247)) + z2 + z4; /* c1 */ + tmp14 = MULTIPLY(z1, FIX(0.221231742)) - z2 + z4; /* c9 */ + + z2 = MULTIPLY(tmp11, FIX(0.587785252)); /* (c1-c9)/2 */ + z4 = z3 - tmp12 - (tmp13 << (CONST_BITS - 1)); + + tmp12 = ((z1 - tmp13) << CONST_BITS) - z3; + + tmp11 = MULTIPLY(z1, FIX(1.260073511)) - z2 - z4; /* c3 */ + tmp13 = MULTIPLY(z1, FIX(0.642039522)) - z2 + z4; /* c7 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[9] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[8] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp23 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp24 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp24 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 8; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 8x4 output block. + * + * 4-point IDCT in pass 1 (columns), 8-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_8x4 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*4]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 4-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/16). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + + tmp10 = (tmp0 + tmp2) << PASS1_BITS; + tmp12 = (tmp0 - tmp2) << PASS1_BITS; + + /* Odd part */ + /* Same rotation as in the even part of the 8x8 LL&M IDCT */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); /* c6 */ + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp0 = RIGHT_SHIFT(z1 + MULTIPLY(z2, FIX_0_765366865), /* c2-c6 */ + CONST_BITS-PASS1_BITS); + tmp2 = RIGHT_SHIFT(z1 - MULTIPLY(z3, FIX_1_847759065), /* c2+c6 */ + CONST_BITS-PASS1_BITS); + + /* Final output stage */ + + wsptr[8*0] = (int) (tmp10 + tmp0); + wsptr[8*3] = (int) (tmp10 - tmp0); + wsptr[8*1] = (int) (tmp12 + tmp2); + wsptr[8*2] = (int) (tmp12 - tmp2); + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < 4; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z2, FIX_0_765366865); + tmp3 = z1 - MULTIPLY(z3, FIX_1_847759065); + + /* Add fudge factor here for final descale. */ + z2 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z3 = (INT32) wsptr[4]; + + tmp0 = (z2 + z3) << CONST_BITS; + tmp1 = (z2 - z3) << CONST_BITS; + + tmp10 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + tmp11 = tmp1 + tmp3; + tmp12 = tmp1 - tmp3; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = (INT32) wsptr[7]; + tmp1 = (INT32) wsptr[5]; + tmp2 = (INT32) wsptr[3]; + tmp3 = (INT32) wsptr[1]; + + z2 = tmp0 + tmp2; + z3 = tmp1 + tmp3; + + z1 = MULTIPLY(z2 + z3, FIX_1_175875602); /* sqrt(2) * c3 */ + z2 = MULTIPLY(z2, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z3 = MULTIPLY(z3, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + z2 += z1; + z3 += z1; + + z1 = MULTIPLY(tmp0 + tmp3, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + tmp0 += z1 + z2; + tmp3 += z1 + z3; + + z1 = MULTIPLY(tmp1 + tmp2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp1 += z1 + z3; + tmp2 += z1 + z2; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp13 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp13 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 6x3 output block. + * + * 3-point IDCT in pass 1 (columns), 6-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_6x3 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp10, tmp11, tmp12; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[6*3]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 3-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/6). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 6; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp12 = MULTIPLY(tmp2, FIX(0.707106781)); /* c2 */ + tmp10 = tmp0 + tmp12; + tmp2 = tmp0 - tmp12 - tmp12; + + /* Odd part */ + + tmp12 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp0 = MULTIPLY(tmp12, FIX(1.224744871)); /* c1 */ + + /* Final output stage */ + + wsptr[6*0] = (int) RIGHT_SHIFT(tmp10 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[6*2] = (int) RIGHT_SHIFT(tmp10 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[6*1] = (int) RIGHT_SHIFT(tmp2, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 3 rows from work array, store into output array. + * 6-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/12). + */ + wsptr = workspace; + for (ctr = 0; ctr < 3; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp0 <<= CONST_BITS; + tmp2 = (INT32) wsptr[4]; + tmp10 = MULTIPLY(tmp2, FIX(0.707106781)); /* c4 */ + tmp1 = tmp0 + tmp10; + tmp11 = tmp0 - tmp10 - tmp10; + tmp10 = (INT32) wsptr[2]; + tmp0 = MULTIPLY(tmp10, FIX(1.224744871)); /* c2 */ + tmp10 = tmp1 + tmp0; + tmp12 = tmp1 - tmp0; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + tmp1 = MULTIPLY(z1 + z3, FIX(0.366025404)); /* c5 */ + tmp0 = tmp1 + ((z1 + z2) << CONST_BITS); + tmp2 = tmp1 + ((z3 - z2) << CONST_BITS); + tmp1 = (z1 - z2 - z3) << CONST_BITS; + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 6; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 4x2 output block. + * + * 2-point IDCT in pass 1 (columns), 4-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_4x2 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp2, tmp10, tmp12; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + INT32 * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + INT32 workspace[4*2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 4; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp10 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + /* Odd part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + /* Final output stage */ + + wsptr[4*0] = tmp10 + tmp0; + wsptr[4*1] = tmp10 - tmp0; + } + + /* Pass 2: process 2 rows from work array, store into output array. + * 4-point IDCT kernel, + * cK represents sqrt(2) * cos(K*pi/16) [refers to 8-point IDCT]. + */ + wsptr = workspace; + for (ctr = 0; ctr < 2; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = wsptr[0] + (ONE << 2); + tmp2 = wsptr[2]; + + tmp10 = (tmp0 + tmp2) << CONST_BITS; + tmp12 = (tmp0 - tmp2) << CONST_BITS; + + /* Odd part */ + /* Same rotation as in the even part of the 8x8 LL&M IDCT */ + + z2 = wsptr[1]; + z3 = wsptr[3]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); /* c6 */ + tmp0 = z1 + MULTIPLY(z2, FIX_0_765366865); /* c2-c6 */ + tmp2 = z1 - MULTIPLY(z3, FIX_1_847759065); /* c2+c6 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp2, + CONST_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp2, + CONST_BITS+3) + & RANGE_MASK]; + + wsptr += 4; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 2x1 output block. + * + * 1-point IDCT in pass 1 (columns), 2-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_2x1 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp10; + ISLOW_MULT_TYPE * quantptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + SHIFT_TEMPS + + /* Pass 1: empty. */ + + /* Pass 2: process 1 row from input, store into output array. */ + + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + outptr = output_buf[0] + output_col; + + /* Even part */ + + tmp10 = DEQUANTIZE(coef_block[0], quantptr[0]); + /* Add fudge factor here for final descale. */ + tmp10 += ONE << 2; + + /* Odd part */ + + tmp0 = DEQUANTIZE(coef_block[1], quantptr[1]); + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, 3) & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, 3) & RANGE_MASK]; +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 8x16 output block. + * + * 16-point IDCT in pass 1 (columns), 8-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_8x16 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp10, tmp11, tmp12, tmp13; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26, tmp27; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[8*16]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 16-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/32). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-1); + + z1 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp1 = MULTIPLY(z1, FIX(1.306562965)); /* c4[16] = c2[8] */ + tmp2 = MULTIPLY(z1, FIX_0_541196100); /* c12[16] = c6[8] */ + + tmp10 = tmp0 + tmp1; + tmp11 = tmp0 - tmp1; + tmp12 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z2 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + z3 = z1 - z2; + z4 = MULTIPLY(z3, FIX(0.275899379)); /* c14[16] = c7[8] */ + z3 = MULTIPLY(z3, FIX(1.387039845)); /* c2[16] = c1[8] */ + + tmp0 = z3 + MULTIPLY(z2, FIX_2_562915447); /* (c6+c2)[16] = (c3+c1)[8] */ + tmp1 = z4 + MULTIPLY(z1, FIX_0_899976223); /* (c6-c14)[16] = (c3-c7)[8] */ + tmp2 = z3 - MULTIPLY(z1, FIX(0.601344887)); /* (c2-c10)[16] = (c1-c5)[8] */ + tmp3 = z4 - MULTIPLY(z2, FIX(0.509795579)); /* (c10-c14)[16] = (c5-c7)[8] */ + + tmp20 = tmp10 + tmp0; + tmp27 = tmp10 - tmp0; + tmp21 = tmp12 + tmp1; + tmp26 = tmp12 - tmp1; + tmp22 = tmp13 + tmp2; + tmp25 = tmp13 - tmp2; + tmp23 = tmp11 + tmp3; + tmp24 = tmp11 - tmp3; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp11 = z1 + z3; + + tmp1 = MULTIPLY(z1 + z2, FIX(1.353318001)); /* c3 */ + tmp2 = MULTIPLY(tmp11, FIX(1.247225013)); /* c5 */ + tmp3 = MULTIPLY(z1 + z4, FIX(1.093201867)); /* c7 */ + tmp10 = MULTIPLY(z1 - z4, FIX(0.897167586)); /* c9 */ + tmp11 = MULTIPLY(tmp11, FIX(0.666655658)); /* c11 */ + tmp12 = MULTIPLY(z1 - z2, FIX(0.410524528)); /* c13 */ + tmp0 = tmp1 + tmp2 + tmp3 - + MULTIPLY(z1, FIX(2.286341144)); /* c7+c5+c3-c1 */ + tmp13 = tmp10 + tmp11 + tmp12 - + MULTIPLY(z1, FIX(1.835730603)); /* c9+c11+c13-c15 */ + z1 = MULTIPLY(z2 + z3, FIX(0.138617169)); /* c15 */ + tmp1 += z1 + MULTIPLY(z2, FIX(0.071888074)); /* c9+c11-c3-c15 */ + tmp2 += z1 - MULTIPLY(z3, FIX(1.125726048)); /* c5+c7+c15-c3 */ + z1 = MULTIPLY(z3 - z2, FIX(1.407403738)); /* c1 */ + tmp11 += z1 - MULTIPLY(z3, FIX(0.766367282)); /* c1+c11-c9-c13 */ + tmp12 += z1 + MULTIPLY(z2, FIX(1.971951411)); /* c1+c5+c13-c7 */ + z2 += z4; + z1 = MULTIPLY(z2, - FIX(0.666655658)); /* -c11 */ + tmp1 += z1; + tmp3 += z1 + MULTIPLY(z4, FIX(1.065388962)); /* c3+c11+c15-c7 */ + z2 = MULTIPLY(z2, - FIX(1.247225013)); /* -c5 */ + tmp10 += z2 + MULTIPLY(z4, FIX(3.141271809)); /* c1+c5+c9-c13 */ + tmp12 += z2; + z2 = MULTIPLY(z3 + z4, - FIX(1.353318001)); /* -c3 */ + tmp2 += z2; + tmp3 += z2; + z2 = MULTIPLY(z4 - z3, FIX(0.410524528)); /* c13 */ + tmp10 += z2; + tmp11 += z2; + + /* Final output stage */ + + wsptr[8*0] = (int) RIGHT_SHIFT(tmp20 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[8*15] = (int) RIGHT_SHIFT(tmp20 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[8*1] = (int) RIGHT_SHIFT(tmp21 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[8*14] = (int) RIGHT_SHIFT(tmp21 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[8*2] = (int) RIGHT_SHIFT(tmp22 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[8*13] = (int) RIGHT_SHIFT(tmp22 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[8*3] = (int) RIGHT_SHIFT(tmp23 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[8*12] = (int) RIGHT_SHIFT(tmp23 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[8*4] = (int) RIGHT_SHIFT(tmp24 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*11] = (int) RIGHT_SHIFT(tmp24 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[8*5] = (int) RIGHT_SHIFT(tmp25 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*10] = (int) RIGHT_SHIFT(tmp25 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[8*6] = (int) RIGHT_SHIFT(tmp26 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*9] = (int) RIGHT_SHIFT(tmp26 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[8*7] = (int) RIGHT_SHIFT(tmp27 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[8*8] = (int) RIGHT_SHIFT(tmp27 - tmp13, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < 16; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z2, FIX_0_765366865); + tmp3 = z1 - MULTIPLY(z3, FIX_1_847759065); + + /* Add fudge factor here for final descale. */ + z2 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + z3 = (INT32) wsptr[4]; + + tmp0 = (z2 + z3) << CONST_BITS; + tmp1 = (z2 - z3) << CONST_BITS; + + tmp10 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + tmp11 = tmp1 + tmp3; + tmp12 = tmp1 - tmp3; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = (INT32) wsptr[7]; + tmp1 = (INT32) wsptr[5]; + tmp2 = (INT32) wsptr[3]; + tmp3 = (INT32) wsptr[1]; + + z2 = tmp0 + tmp2; + z3 = tmp1 + tmp3; + + z1 = MULTIPLY(z2 + z3, FIX_1_175875602); /* sqrt(2) * c3 */ + z2 = MULTIPLY(z2, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z3 = MULTIPLY(z3, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + z2 += z1; + z3 += z1; + + z1 = MULTIPLY(tmp0 + tmp3, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + tmp0 += z1 + z2; + tmp3 += z1 + z3; + + z1 = MULTIPLY(tmp1 + tmp2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp1 += z1 + z3; + tmp2 += z1 + z2; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp13 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp13 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 7x14 output block. + * + * 14-point IDCT in pass 1 (columns), 7-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_7x14 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[7*14]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 14-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/28). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 7; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z1 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z1 += ONE << (CONST_BITS-PASS1_BITS-1); + z4 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z2 = MULTIPLY(z4, FIX(1.274162392)); /* c4 */ + z3 = MULTIPLY(z4, FIX(0.314692123)); /* c12 */ + z4 = MULTIPLY(z4, FIX(0.881747734)); /* c8 */ + + tmp10 = z1 + z2; + tmp11 = z1 + z3; + tmp12 = z1 - z4; + + tmp23 = RIGHT_SHIFT(z1 - ((z2 + z3 - z4) << 1), /* c0 = (c4+c12-c8)*2 */ + CONST_BITS-PASS1_BITS); + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z2 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z3 = MULTIPLY(z1 + z2, FIX(1.105676686)); /* c6 */ + + tmp13 = z3 + MULTIPLY(z1, FIX(0.273079590)); /* c2-c6 */ + tmp14 = z3 - MULTIPLY(z2, FIX(1.719280954)); /* c6+c10 */ + tmp15 = MULTIPLY(z1, FIX(0.613604268)) - /* c10 */ + MULTIPLY(z2, FIX(1.378756276)); /* c2 */ + + tmp20 = tmp10 + tmp13; + tmp26 = tmp10 - tmp13; + tmp21 = tmp11 + tmp14; + tmp25 = tmp11 - tmp14; + tmp22 = tmp12 + tmp15; + tmp24 = tmp12 - tmp15; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp13 = z4 << CONST_BITS; + + tmp14 = z1 + z3; + tmp11 = MULTIPLY(z1 + z2, FIX(1.334852607)); /* c3 */ + tmp12 = MULTIPLY(tmp14, FIX(1.197448846)); /* c5 */ + tmp10 = tmp11 + tmp12 + tmp13 - MULTIPLY(z1, FIX(1.126980169)); /* c3+c5-c1 */ + tmp14 = MULTIPLY(tmp14, FIX(0.752406978)); /* c9 */ + tmp16 = tmp14 - MULTIPLY(z1, FIX(1.061150426)); /* c9+c11-c13 */ + z1 -= z2; + tmp15 = MULTIPLY(z1, FIX(0.467085129)) - tmp13; /* c11 */ + tmp16 += tmp15; + z1 += z4; + z4 = MULTIPLY(z2 + z3, - FIX(0.158341681)) - tmp13; /* -c13 */ + tmp11 += z4 - MULTIPLY(z2, FIX(0.424103948)); /* c3-c9-c13 */ + tmp12 += z4 - MULTIPLY(z3, FIX(2.373959773)); /* c3+c5-c13 */ + z4 = MULTIPLY(z3 - z2, FIX(1.405321284)); /* c1 */ + tmp14 += z4 + tmp13 - MULTIPLY(z3, FIX(1.6906431334)); /* c1+c9-c11 */ + tmp15 += z4 + MULTIPLY(z2, FIX(0.674957567)); /* c1+c11-c5 */ + + tmp13 = (z1 - z3) << PASS1_BITS; + + /* Final output stage */ + + wsptr[7*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[7*13] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[7*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[7*12] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[7*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[7*11] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[7*3] = (int) (tmp23 + tmp13); + wsptr[7*10] = (int) (tmp23 - tmp13); + wsptr[7*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[7*9] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + wsptr[7*5] = (int) RIGHT_SHIFT(tmp25 + tmp15, CONST_BITS-PASS1_BITS); + wsptr[7*8] = (int) RIGHT_SHIFT(tmp25 - tmp15, CONST_BITS-PASS1_BITS); + wsptr[7*6] = (int) RIGHT_SHIFT(tmp26 + tmp16, CONST_BITS-PASS1_BITS); + wsptr[7*7] = (int) RIGHT_SHIFT(tmp26 - tmp16, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 14 rows from work array, store into output array. + * 7-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/14). + */ + wsptr = workspace; + for (ctr = 0; ctr < 14; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp23 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp23 <<= CONST_BITS; + + z1 = (INT32) wsptr[2]; + z2 = (INT32) wsptr[4]; + z3 = (INT32) wsptr[6]; + + tmp20 = MULTIPLY(z2 - z3, FIX(0.881747734)); /* c4 */ + tmp22 = MULTIPLY(z1 - z2, FIX(0.314692123)); /* c6 */ + tmp21 = tmp20 + tmp22 + tmp23 - MULTIPLY(z2, FIX(1.841218003)); /* c2+c4-c6 */ + tmp10 = z1 + z3; + z2 -= tmp10; + tmp10 = MULTIPLY(tmp10, FIX(1.274162392)) + tmp23; /* c2 */ + tmp20 += tmp10 - MULTIPLY(z3, FIX(0.077722536)); /* c2-c4-c6 */ + tmp22 += tmp10 - MULTIPLY(z1, FIX(2.470602249)); /* c2+c4+c6 */ + tmp23 += MULTIPLY(z2, FIX(1.414213562)); /* c0 */ + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + + tmp11 = MULTIPLY(z1 + z2, FIX(0.935414347)); /* (c3+c1-c5)/2 */ + tmp12 = MULTIPLY(z1 - z2, FIX(0.170262339)); /* (c3+c5-c1)/2 */ + tmp10 = tmp11 - tmp12; + tmp11 += tmp12; + tmp12 = MULTIPLY(z2 + z3, - FIX(1.378756276)); /* -c1 */ + tmp11 += tmp12; + z2 = MULTIPLY(z1 + z3, FIX(0.613604268)); /* c5 */ + tmp10 += z2; + tmp12 += z2 + MULTIPLY(z3, FIX(1.870828693)); /* c3+c1-c5 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp23, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 7; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 6x12 output block. + * + * 12-point IDCT in pass 1 (columns), 6-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_6x12 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14, tmp15; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24, tmp25; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[6*12]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 12-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/24). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 6; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + z3 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z3 += ONE << (CONST_BITS-PASS1_BITS-1); + + z4 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z4 = MULTIPLY(z4, FIX(1.224744871)); /* c4 */ + + tmp10 = z3 + z4; + tmp11 = z3 - z4; + + z1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z4 = MULTIPLY(z1, FIX(1.366025404)); /* c2 */ + z1 <<= CONST_BITS; + z2 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + z2 <<= CONST_BITS; + + tmp12 = z1 - z2; + + tmp21 = z3 + tmp12; + tmp24 = z3 - tmp12; + + tmp12 = z4 + z2; + + tmp20 = tmp10 + tmp12; + tmp25 = tmp10 - tmp12; + + tmp12 = z4 - z1 - z2; + + tmp22 = tmp11 + tmp12; + tmp23 = tmp11 - tmp12; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp11 = MULTIPLY(z2, FIX(1.306562965)); /* c3 */ + tmp14 = MULTIPLY(z2, - FIX_0_541196100); /* -c9 */ + + tmp10 = z1 + z3; + tmp15 = MULTIPLY(tmp10 + z4, FIX(0.860918669)); /* c7 */ + tmp12 = tmp15 + MULTIPLY(tmp10, FIX(0.261052384)); /* c5-c7 */ + tmp10 = tmp12 + tmp11 + MULTIPLY(z1, FIX(0.280143716)); /* c1-c5 */ + tmp13 = MULTIPLY(z3 + z4, - FIX(1.045510580)); /* -(c7+c11) */ + tmp12 += tmp13 + tmp14 - MULTIPLY(z3, FIX(1.478575242)); /* c1+c5-c7-c11 */ + tmp13 += tmp15 - tmp11 + MULTIPLY(z4, FIX(1.586706681)); /* c1+c11 */ + tmp15 += tmp14 - MULTIPLY(z1, FIX(0.676326758)) - /* c7-c11 */ + MULTIPLY(z4, FIX(1.982889723)); /* c5+c7 */ + + z1 -= z4; + z2 -= z3; + z3 = MULTIPLY(z1 + z2, FIX_0_541196100); /* c9 */ + tmp11 = z3 + MULTIPLY(z1, FIX_0_765366865); /* c3-c9 */ + tmp14 = z3 - MULTIPLY(z2, FIX_1_847759065); /* c3+c9 */ + + /* Final output stage */ + + wsptr[6*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[6*11] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[6*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[6*10] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[6*2] = (int) RIGHT_SHIFT(tmp22 + tmp12, CONST_BITS-PASS1_BITS); + wsptr[6*9] = (int) RIGHT_SHIFT(tmp22 - tmp12, CONST_BITS-PASS1_BITS); + wsptr[6*3] = (int) RIGHT_SHIFT(tmp23 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[6*8] = (int) RIGHT_SHIFT(tmp23 - tmp13, CONST_BITS-PASS1_BITS); + wsptr[6*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[6*7] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + wsptr[6*5] = (int) RIGHT_SHIFT(tmp25 + tmp15, CONST_BITS-PASS1_BITS); + wsptr[6*6] = (int) RIGHT_SHIFT(tmp25 - tmp15, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 12 rows from work array, store into output array. + * 6-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/12). + */ + wsptr = workspace; + for (ctr = 0; ctr < 12; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp10 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp10 <<= CONST_BITS; + tmp12 = (INT32) wsptr[4]; + tmp20 = MULTIPLY(tmp12, FIX(0.707106781)); /* c4 */ + tmp11 = tmp10 + tmp20; + tmp21 = tmp10 - tmp20 - tmp20; + tmp20 = (INT32) wsptr[2]; + tmp10 = MULTIPLY(tmp20, FIX(1.224744871)); /* c2 */ + tmp20 = tmp11 + tmp10; + tmp22 = tmp11 - tmp10; + + /* Odd part */ + + z1 = (INT32) wsptr[1]; + z2 = (INT32) wsptr[3]; + z3 = (INT32) wsptr[5]; + tmp11 = MULTIPLY(z1 + z3, FIX(0.366025404)); /* c5 */ + tmp10 = tmp11 + ((z1 + z2) << CONST_BITS); + tmp12 = tmp11 + ((z3 - z2) << CONST_BITS); + tmp11 = (z1 - z2 - z3) << CONST_BITS; + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp20 + tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) RIGHT_SHIFT(tmp20 - tmp10, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp21 + tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp21 - tmp11, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp22 + tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp22 - tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 6; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 5x10 output block. + * + * 10-point IDCT in pass 1 (columns), 5-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_5x10 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp10, tmp11, tmp12, tmp13, tmp14; + INT32 tmp20, tmp21, tmp22, tmp23, tmp24; + INT32 z1, z2, z3, z4, z5; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[5*10]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 10-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/20). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 5; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + z3 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z3 += ONE << (CONST_BITS-PASS1_BITS-1); + z4 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z1 = MULTIPLY(z4, FIX(1.144122806)); /* c4 */ + z2 = MULTIPLY(z4, FIX(0.437016024)); /* c8 */ + tmp10 = z3 + z1; + tmp11 = z3 - z2; + + tmp22 = RIGHT_SHIFT(z3 - ((z1 - z2) << 1), /* c0 = (c4-c8)*2 */ + CONST_BITS-PASS1_BITS); + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z1 = MULTIPLY(z2 + z3, FIX(0.831253876)); /* c6 */ + tmp12 = z1 + MULTIPLY(z2, FIX(0.513743148)); /* c2-c6 */ + tmp13 = z1 - MULTIPLY(z3, FIX(2.176250899)); /* c2+c6 */ + + tmp20 = tmp10 + tmp12; + tmp24 = tmp10 - tmp12; + tmp21 = tmp11 + tmp13; + tmp23 = tmp11 - tmp13; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z4 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + tmp11 = z2 + z4; + tmp13 = z2 - z4; + + tmp12 = MULTIPLY(tmp13, FIX(0.309016994)); /* (c3-c7)/2 */ + z5 = z3 << CONST_BITS; + + z2 = MULTIPLY(tmp11, FIX(0.951056516)); /* (c3+c7)/2 */ + z4 = z5 + tmp12; + + tmp10 = MULTIPLY(z1, FIX(1.396802247)) + z2 + z4; /* c1 */ + tmp14 = MULTIPLY(z1, FIX(0.221231742)) - z2 + z4; /* c9 */ + + z2 = MULTIPLY(tmp11, FIX(0.587785252)); /* (c1-c9)/2 */ + z4 = z5 - tmp12 - (tmp13 << (CONST_BITS - 1)); + + tmp12 = (z1 - tmp13 - z3) << PASS1_BITS; + + tmp11 = MULTIPLY(z1, FIX(1.260073511)) - z2 - z4; /* c3 */ + tmp13 = MULTIPLY(z1, FIX(0.642039522)) - z2 + z4; /* c7 */ + + /* Final output stage */ + + wsptr[5*0] = (int) RIGHT_SHIFT(tmp20 + tmp10, CONST_BITS-PASS1_BITS); + wsptr[5*9] = (int) RIGHT_SHIFT(tmp20 - tmp10, CONST_BITS-PASS1_BITS); + wsptr[5*1] = (int) RIGHT_SHIFT(tmp21 + tmp11, CONST_BITS-PASS1_BITS); + wsptr[5*8] = (int) RIGHT_SHIFT(tmp21 - tmp11, CONST_BITS-PASS1_BITS); + wsptr[5*2] = (int) (tmp22 + tmp12); + wsptr[5*7] = (int) (tmp22 - tmp12); + wsptr[5*3] = (int) RIGHT_SHIFT(tmp23 + tmp13, CONST_BITS-PASS1_BITS); + wsptr[5*6] = (int) RIGHT_SHIFT(tmp23 - tmp13, CONST_BITS-PASS1_BITS); + wsptr[5*4] = (int) RIGHT_SHIFT(tmp24 + tmp14, CONST_BITS-PASS1_BITS); + wsptr[5*5] = (int) RIGHT_SHIFT(tmp24 - tmp14, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 10 rows from work array, store into output array. + * 5-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/10). + */ + wsptr = workspace; + for (ctr = 0; ctr < 10; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp12 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp12 <<= CONST_BITS; + tmp13 = (INT32) wsptr[2]; + tmp14 = (INT32) wsptr[4]; + z1 = MULTIPLY(tmp13 + tmp14, FIX(0.790569415)); /* (c2+c4)/2 */ + z2 = MULTIPLY(tmp13 - tmp14, FIX(0.353553391)); /* (c2-c4)/2 */ + z3 = tmp12 + z2; + tmp10 = z3 + z1; + tmp11 = z3 - z1; + tmp12 -= z2 << 2; + + /* Odd part */ + + z2 = (INT32) wsptr[1]; + z3 = (INT32) wsptr[3]; + + z1 = MULTIPLY(z2 + z3, FIX(0.831253876)); /* c3 */ + tmp13 = z1 + MULTIPLY(z2, FIX(0.513743148)); /* c1-c3 */ + tmp14 = z1 - MULTIPLY(z3, FIX(2.176250899)); /* c1+c3 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp13, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp11 + tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp11 - tmp14, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 5; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 4x8 output block. + * + * 8-point IDCT in pass 1 (columns), 4-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_4x8 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[4*8]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + /* Note results are scaled up by sqrt(8) compared to a true IDCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 4; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[4*0] = dcval; + wsptr[4*1] = dcval; + wsptr[4*2] = dcval; + wsptr[4*3] = dcval; + wsptr[4*4] = dcval; + wsptr[4*5] = dcval; + wsptr[4*6] = dcval; + wsptr[4*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z2, FIX_0_765366865); + tmp3 = z1 - MULTIPLY(z3, FIX_1_847759065); + + z2 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + z2 <<= CONST_BITS; + z3 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + z2 += ONE << (CONST_BITS-PASS1_BITS-1); + + tmp0 = z2 + z3; + tmp1 = z2 - z3; + + tmp10 = tmp0 + tmp2; + tmp13 = tmp0 - tmp2; + tmp11 = tmp1 + tmp3; + tmp12 = tmp1 - tmp3; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + z2 = tmp0 + tmp2; + z3 = tmp1 + tmp3; + + z1 = MULTIPLY(z2 + z3, FIX_1_175875602); /* sqrt(2) * c3 */ + z2 = MULTIPLY(z2, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z3 = MULTIPLY(z3, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + z2 += z1; + z3 += z1; + + z1 = MULTIPLY(tmp0 + tmp3, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + tmp0 += z1 + z2; + tmp3 += z1 + z3; + + z1 = MULTIPLY(tmp1 + tmp2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp1 += z1 + z3; + tmp2 += z1 + z2; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + wsptr[4*0] = (int) RIGHT_SHIFT(tmp10 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[4*7] = (int) RIGHT_SHIFT(tmp10 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[4*1] = (int) RIGHT_SHIFT(tmp11 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[4*6] = (int) RIGHT_SHIFT(tmp11 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[4*2] = (int) RIGHT_SHIFT(tmp12 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[4*5] = (int) RIGHT_SHIFT(tmp12 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[4*3] = (int) RIGHT_SHIFT(tmp13 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[4*4] = (int) RIGHT_SHIFT(tmp13 - tmp0, CONST_BITS-PASS1_BITS); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process 8 rows from work array, store into output array. + * 4-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/16). + */ + wsptr = workspace; + for (ctr = 0; ctr < 8; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp2 = (INT32) wsptr[2]; + + tmp10 = (tmp0 + tmp2) << CONST_BITS; + tmp12 = (tmp0 - tmp2) << CONST_BITS; + + /* Odd part */ + /* Same rotation as in the even part of the 8x8 LL&M IDCT */ + + z2 = (INT32) wsptr[1]; + z3 = (INT32) wsptr[3]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); /* c6 */ + tmp0 = z1 + MULTIPLY(z2, FIX_0_765366865); /* c2-c6 */ + tmp2 = z1 - MULTIPLY(z3, FIX_1_847759065); /* c2+c6 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp12 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp12 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 4; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 3x6 output block. + * + * 6-point IDCT in pass 1 (columns), 3-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_3x6 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp10, tmp11, tmp12; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[3*6]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 6-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/12). + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 3; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= CONST_BITS; + /* Add fudge factor here for final descale. */ + tmp0 += ONE << (CONST_BITS-PASS1_BITS-1); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp10 = MULTIPLY(tmp2, FIX(0.707106781)); /* c4 */ + tmp1 = tmp0 + tmp10; + tmp11 = RIGHT_SHIFT(tmp0 - tmp10 - tmp10, CONST_BITS-PASS1_BITS); + tmp10 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp0 = MULTIPLY(tmp10, FIX(1.224744871)); /* c2 */ + tmp10 = tmp1 + tmp0; + tmp12 = tmp1 - tmp0; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z3 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp1 = MULTIPLY(z1 + z3, FIX(0.366025404)); /* c5 */ + tmp0 = tmp1 + ((z1 + z2) << CONST_BITS); + tmp2 = tmp1 + ((z3 - z2) << CONST_BITS); + tmp1 = (z1 - z2 - z3) << PASS1_BITS; + + /* Final output stage */ + + wsptr[3*0] = (int) RIGHT_SHIFT(tmp10 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[3*5] = (int) RIGHT_SHIFT(tmp10 - tmp0, CONST_BITS-PASS1_BITS); + wsptr[3*1] = (int) (tmp11 + tmp1); + wsptr[3*4] = (int) (tmp11 - tmp1); + wsptr[3*2] = (int) RIGHT_SHIFT(tmp12 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[3*3] = (int) RIGHT_SHIFT(tmp12 - tmp2, CONST_BITS-PASS1_BITS); + } + + /* Pass 2: process 6 rows from work array, store into output array. + * 3-point IDCT kernel, cK represents sqrt(2) * cos(K*pi/6). + */ + wsptr = workspace; + for (ctr = 0; ctr < 6; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp0 = (INT32) wsptr[0] + (ONE << (PASS1_BITS+2)); + tmp0 <<= CONST_BITS; + tmp2 = (INT32) wsptr[2]; + tmp12 = MULTIPLY(tmp2, FIX(0.707106781)); /* c2 */ + tmp10 = tmp0 + tmp12; + tmp2 = tmp0 - tmp12 - tmp12; + + /* Odd part */ + + tmp12 = (INT32) wsptr[1]; + tmp0 = MULTIPLY(tmp12, FIX(1.224744871)); /* c1 */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += 3; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 2x4 output block. + * + * 4-point IDCT in pass 1 (columns), 2-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_2x4 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp2, tmp10, tmp12; + INT32 z1, z2, z3; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + INT32 * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + INT32 workspace[2*4]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. + * 4-point IDCT kernel, + * cK represents sqrt(2) * cos(K*pi/16) [refers to 8-point IDCT]. + */ + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = 0; ctr < 2; ctr++, inptr++, quantptr++, wsptr++) { + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + + tmp10 = (tmp0 + tmp2) << CONST_BITS; + tmp12 = (tmp0 - tmp2) << CONST_BITS; + + /* Odd part */ + /* Same rotation as in the even part of the 8x8 LL&M IDCT */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); /* c6 */ + tmp0 = z1 + MULTIPLY(z2, FIX_0_765366865); /* c2-c6 */ + tmp2 = z1 - MULTIPLY(z3, FIX_1_847759065); /* c2+c6 */ + + /* Final output stage */ + + wsptr[2*0] = tmp10 + tmp0; + wsptr[2*3] = tmp10 - tmp0; + wsptr[2*1] = tmp12 + tmp2; + wsptr[2*2] = tmp12 - tmp2; + } + + /* Pass 2: process 4 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 4; ctr++) { + outptr = output_buf[ctr] + output_col; + + /* Even part */ + + /* Add fudge factor here for final descale. */ + tmp10 = wsptr[0] + (ONE << (CONST_BITS+2)); + + /* Odd part */ + + tmp0 = wsptr[1]; + + /* Final output stage */ + + outptr[0] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, CONST_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, CONST_BITS+3) + & RANGE_MASK]; + + wsptr += 2; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a 1x2 output block. + * + * 2-point IDCT in pass 1 (columns), 1-point in pass 2 (rows). + */ + +GLOBAL(void) +jpeg_idct_1x2 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp10; + ISLOW_MULT_TYPE * quantptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + SHIFT_TEMPS + + /* Process 1 column from input, store into output array. */ + + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + + /* Even part */ + + tmp10 = DEQUANTIZE(coef_block[DCTSIZE*0], quantptr[DCTSIZE*0]); + /* Add fudge factor here for final descale. */ + tmp10 += ONE << 2; + + /* Odd part */ + + tmp0 = DEQUANTIZE(coef_block[DCTSIZE*1], quantptr[DCTSIZE*1]); + + /* Final output stage */ + + output_buf[0][output_col] = range_limit[(int) RIGHT_SHIFT(tmp10 + tmp0, 3) + & RANGE_MASK]; + output_buf[1][output_col] = range_limit[(int) RIGHT_SHIFT(tmp10 - tmp0, 3) + & RANGE_MASK]; +} + +#endif /* IDCT_SCALING_SUPPORTED */ +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/src/jpeg-8c/jinclude.h b/src/jpeg-8c/jinclude.h new file mode 100644 index 0000000..0a4f151 --- /dev/null +++ b/src/jpeg-8c/jinclude.h @@ -0,0 +1,91 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/src/jpeg-8c/jmemmgr.c b/src/jpeg-8c/jmemmgr.c new file mode 100644 index 0000000..d801b32 --- /dev/null +++ b/src/jpeg-8c/jmemmgr.c @@ -0,0 +1,1118 @@ +/* + * jmemmgr.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the JPEG system-independent memory management + * routines. This code is usable across a wide variety of machines; most + * of the system dependencies have been isolated in a separate file. + * The major functions provided here are: + * * pool-based allocation and freeing of memory; + * * policy decisions about how to divide available memory among the + * virtual arrays; + * * control logic for swapping virtual arrays between main memory and + * backing storage. + * The separate system-dependent file provides the actual backing-storage + * access code, and it contains the policy decision about how much total + * main memory to use. + * This file is system-dependent in the sense that some of its functions + * are unnecessary in some systems. For example, if there is enough virtual + * memory so that backing storage will never be used, much of the virtual + * array control logic could be removed. (Of course, if you have that much + * memory then you shouldn't care about a little bit of unused code...) + */ + +#define JPEG_INTERNALS +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef NO_GETENV +#ifndef HAVE_STDLIB_H /* should declare getenv() */ +extern char * getenv JPP((const char * name)); +#endif +#endif + + +/* + * Some important notes: + * The allocation routines provided here must never return NULL. + * They should exit to error_exit if unsuccessful. + * + * It's not a good idea to try to merge the sarray and barray routines, + * even though they are textually almost the same, because samples are + * usually stored as bytes while coefficients are shorts or ints. Thus, + * in machines where byte pointers have a different representation from + * word pointers, the resulting machine code could not be the same. + */ + + +/* + * Many machines require storage alignment: longs must start on 4-byte + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + * always returns pointers that are multiples of the worst-case alignment + * requirement, and we had better do so too. + * There isn't any really portable way to determine the worst-case alignment + * requirement. This module assumes that the alignment requirement is + * multiples of sizeof(ALIGN_TYPE). + * By default, we define ALIGN_TYPE as double. This is necessary on some + * workstations (where doubles really do need 8-byte alignment) and will work + * fine on nearly everything. If your machine has lesser alignment needs, + * you can save a few bytes by making ALIGN_TYPE smaller. + * The only place I know of where this will NOT work is certain Macintosh + * 680x0 compilers that define double as a 10-byte IEEE extended float. + * Doing 10-byte alignment is counterproductive because longwords won't be + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + * such a compiler. + */ + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#define ALIGN_TYPE double +#endif + + +/* + * We allocate objects from "pools", where each pool is gotten with a single + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + * overhead within a pool, except for alignment padding. Each pool has a + * header with a link to the next pool of the same class. + * Small and large pool headers are identical except that the latter's + * link pointer must be FAR on 80x86 machines. + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + * of the alignment requirement of ALIGN_TYPE. + */ + +typedef union small_pool_struct * small_pool_ptr; + +typedef union small_pool_struct { + struct { + small_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} small_pool_hdr; + +typedef union large_pool_struct FAR * large_pool_ptr; + +typedef union large_pool_struct { + struct { + large_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} large_pool_hdr; + + +/* + * Here is the full definition of a memory manager object. + */ + +typedef struct { + struct jpeg_memory_mgr pub; /* public fields */ + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + /* Since we only have one lifetime class of virtual arrays, only one + * linked list is necessary (for each datatype). Note that the virtual + * array control blocks being linked together are actually stored somewhere + * in the small-pool list. + */ + jvirt_sarray_ptr virt_sarray_list; + jvirt_barray_ptr virt_barray_list; + + /* This counts total space obtained from jpeg_get_small/large */ + long total_space_allocated; + + /* alloc_sarray and alloc_barray set this value for use by virtual + * array routines. + */ + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ +} my_memory_mgr; + +typedef my_memory_mgr * my_mem_ptr; + + +/* + * The control blocks for virtual arrays. + * Note that these blocks are allocated in the "small" pool area. + * System-dependent info for the associated backing store (if any) is hidden + * inside the backing_store_info struct. + */ + +struct jvirt_sarray_control { + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + +struct jvirt_barray_control { + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next; /* link to next virtual barray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + +LOCAL(void) +print_mem_stats (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + + /* Since this is only a debugging stub, we can cheat a little by using + * fprintf directly rather than going through the trace message code. + * This is helpful because message parm array can't handle longs. + */ + fprintf(stderr, "Freeing pool %d, total space = %ld\n", + pool_id, mem->total_space_allocated); + + for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + lhdr_ptr = lhdr_ptr->hdr.next) { + fprintf(stderr, " Large chunk used %ld\n", + (long) lhdr_ptr->hdr.bytes_used); + } + + for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + shdr_ptr = shdr_ptr->hdr.next) { + fprintf(stderr, " Small chunk used %ld free %ld\n", + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left); + } +} + +#endif /* MEM_STATS */ + + +LOCAL(void) +out_of_memory (j_common_ptr cinfo, int which) +/* Report an out-of-memory error and stop execution */ +/* If we compiled MEM_STATS support, report alloc requests before dying */ +{ +#ifdef MEM_STATS + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ +#endif + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which); +} + + +/* + * Allocation of "small" objects. + * + * For these, we use pooled storage. When a new pool must be created, + * we try to get enough space for the current request plus a "slop" factor, + * where the slop will be the amount of leftover space in the new pool. + * The speed vs. space tradeoff is largely determined by the slop values. + * A different slop value is provided for each pool class (lifetime), + * and we also distinguish the first pool of a class from later ones. + * NOTE: the values given work fairly well on both 16- and 32-bit-int + * machines, but may be too small if longs are 64 bits or more. + */ + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = +{ + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ +}; + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = +{ + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ +}; + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + +METHODDEF(void *) +alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "small" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr hdr_ptr, prev_hdr_ptr; + char * data_ptr; + size_t odd_bytes, min_request, slop; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr))) + out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* See if space is available in any existing pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + prev_hdr_ptr = NULL; + hdr_ptr = mem->small_list[pool_id]; + while (hdr_ptr != NULL) { + if (hdr_ptr->hdr.bytes_left >= sizeofobject) + break; /* found pool with enough space */ + prev_hdr_ptr = hdr_ptr; + hdr_ptr = hdr_ptr->hdr.next; + } + + /* Time to make a new pool? */ + if (hdr_ptr == NULL) { + /* min_request is what we need now, slop is what will be leftover */ + min_request = sizeofobject + SIZEOF(small_pool_hdr); + if (prev_hdr_ptr == NULL) /* first pool in class? */ + slop = first_pool_slop[pool_id]; + else + slop = extra_pool_slop[pool_id]; + /* Don't ask for more than MAX_ALLOC_CHUNK */ + if (slop > (size_t) (MAX_ALLOC_CHUNK-min_request)) + slop = (size_t) (MAX_ALLOC_CHUNK-min_request); + /* Try to get space, if fail reduce slop and try again */ + for (;;) { + hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop); + if (hdr_ptr != NULL) + break; + slop /= 2; + if (slop < MIN_SLOP) /* give up when it gets real small */ + out_of_memory(cinfo, 2); /* jpeg_get_small failed */ + } + mem->total_space_allocated += min_request + slop; + /* Success, initialize the new pool header and add to end of list */ + hdr_ptr->hdr.next = NULL; + hdr_ptr->hdr.bytes_used = 0; + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + if (prev_hdr_ptr == NULL) /* first pool in class? */ + mem->small_list[pool_id] = hdr_ptr; + else + prev_hdr_ptr->hdr.next = hdr_ptr; + } + + /* OK, allocate the object from the current pool */ + data_ptr = (char *) (hdr_ptr + 1); /* point to first data byte in pool */ + data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */ + hdr_ptr->hdr.bytes_used += sizeofobject; + hdr_ptr->hdr.bytes_left -= sizeofobject; + + return (void *) data_ptr; +} + + +/* + * Allocation of "large" objects. + * + * The external semantics of these are the same as "small" objects, + * except that FAR pointers are used on 80x86. However the pool + * management heuristics are quite different. We assume that each + * request is large enough that it may as well be passed directly to + * jpeg_get_large; the pool management just links everything together + * so that we can free it all on demand. + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + * structures. The routines that create these structures (see below) + * deliberately bunch rows together to ensure a large request size. + */ + +METHODDEF(void FAR *) +alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "large" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + large_pool_ptr hdr_ptr; + size_t odd_bytes; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr))) + out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* Always make a new pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject + + SIZEOF(large_pool_hdr)); + if (hdr_ptr == NULL) + out_of_memory(cinfo, 4); /* jpeg_get_large failed */ + mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr); + + /* Success, initialize the new pool header and add to list */ + hdr_ptr->hdr.next = mem->large_list[pool_id]; + /* We maintain space counts in each pool header for statistical purposes, + * even though they are not needed for allocation. + */ + hdr_ptr->hdr.bytes_used = sizeofobject; + hdr_ptr->hdr.bytes_left = 0; + mem->large_list[pool_id] = hdr_ptr; + + return (void FAR *) (hdr_ptr + 1); /* point to first data byte in pool */ +} + + +/* + * Creation of 2-D sample arrays. + * The pointers are in near heap, the samples themselves in FAR heap. + * + * To minimize allocation overhead and to allow I/O of large contiguous + * blocks, we allocate the sample rows in groups of as many rows as possible + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + * NB: the virtual array control routines, later in this file, know about + * this chunking of rows. The rowsperchunk value is left in the mem manager + * object so that it can be saved away if this sarray is the workspace for + * a virtual array. + */ + +METHODDEF(JSAMPARRAY) +alloc_sarray (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows) +/* Allocate a 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JSAMPARRAY result; + JSAMPROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) samplesperrow * SIZEOF(JSAMPLE)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JSAMPARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JSAMPROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JSAMPROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF(JSAMPLE))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += samplesperrow; + } + } + + return result; +} + + +/* + * Creation of 2-D coefficient-block arrays. + * This is essentially the same as the code for sample arrays, above. + */ + +METHODDEF(JBLOCKARRAY) +alloc_barray (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows) +/* Allocate a 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JBLOCKARRAY result; + JBLOCKROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) blocksperrow * SIZEOF(JBLOCK)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JBLOCKARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JBLOCKROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JBLOCKROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF(JBLOCK))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += blocksperrow; + } + } + + return result; +} + + +/* + * About virtual array management: + * + * The above "normal" array routines are only used to allocate strip buffers + * (as wide as the image, but just a few rows high). Full-image-sized buffers + * are handled as "virtual" arrays. The array is still accessed a strip at a + * time, but the memory manager must save the whole array for repeated + * accesses. The intended implementation is that there is a strip buffer in + * memory (as high as is possible given the desired memory limit), plus a + * backing file that holds the rest of the array. + * + * The request_virt_array routines are told the total size of the image and + * the maximum number of rows that will be accessed at once. The in-memory + * buffer must be at least as large as the maxaccess value. + * + * The request routines create control blocks but not the in-memory buffers. + * That is postponed until realize_virt_arrays is called. At that time the + * total amount of space needed is known (approximately, anyway), so free + * memory can be divided up fairly. + * + * The access_virt_array routines are responsible for making a specific strip + * area accessible (after reading or writing the backing file, if necessary). + * Note that the access routines are told whether the caller intends to modify + * the accessed strip; during a read-only pass this saves having to rewrite + * data to disk. The access routines are also responsible for pre-zeroing + * any newly accessed rows, if pre-zeroing was requested. + * + * In current usage, the access requests are usually for nonoverlapping + * strips; that is, successive access start_row numbers differ by exactly + * num_rows = maxaccess. This means we can get good performance with simple + * buffer dump/reload logic, by making the in-memory buffer be a multiple + * of the access height; then there will never be accesses across bufferload + * boundaries. The code will still work with overlapping access requests, + * but it doesn't handle bufferload overlaps very efficiently. + */ + + +METHODDEF(jvirt_sarray_ptr) +request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_sarray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_sarray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->samplesperrow = samplesperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ + mem->virt_sarray_list = result; + + return result; +} + + +METHODDEF(jvirt_barray_ptr) +request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_barray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_barray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->blocksperrow = blocksperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_barray_list; /* add to list of virtual arrays */ + mem->virt_barray_list = result; + + return result; +} + + +METHODDEF(void) +realize_virt_arrays (j_common_ptr cinfo) +/* Allocate the in-memory buffers for any unrealized virtual arrays */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + long space_per_minheight, maximum_space, avail_mem; + long minheights, max_minheights; + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + /* Compute the minimum space needed (maxaccess rows in each buffer) + * and the maximum space needed (full image height in each buffer). + * These may be of use to the system-dependent jpeg_mem_available routine. + */ + space_per_minheight = 0; + maximum_space = 0; + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) sptr->maxaccess * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + maximum_space += (long) sptr->rows_in_array * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + } + } + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) bptr->maxaccess * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + maximum_space += (long) bptr->rows_in_array * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + } + } + + if (space_per_minheight <= 0) + return; /* no unrealized arrays, no work */ + + /* Determine amount of memory to actually use; this is system-dependent. */ + avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space, + mem->total_space_allocated); + + /* If the maximum space needed is available, make all the buffers full + * height; otherwise parcel it out with the same number of minheights + * in each buffer. + */ + if (avail_mem >= maximum_space) + max_minheights = 1000000000L; + else { + max_minheights = avail_mem / space_per_minheight; + /* If there doesn't seem to be enough space, try to get the minimum + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + */ + if (max_minheights <= 0) + max_minheights = 1; + } + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); + jpeg_open_backing_store(cinfo, & sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF(JSAMPLE)); + sptr->b_s_open = TRUE; + } + sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE, + sptr->samplesperrow, sptr->rows_in_mem); + sptr->rowsperchunk = mem->last_rowsperchunk; + sptr->cur_start_row = 0; + sptr->first_undef_row = 0; + sptr->dirty = FALSE; + } + } + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); + jpeg_open_backing_store(cinfo, & bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF(JBLOCK)); + bptr->b_s_open = TRUE; + } + bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE, + bptr->blocksperrow, bptr->rows_in_mem); + bptr->rowsperchunk = mem->last_rowsperchunk; + bptr->cur_start_row = 0; + bptr->first_undef_row = 0; + bptr->dirty = FALSE; + } + } +} + + +LOCAL(void) +do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual sample array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->samplesperrow * SIZEOF(JSAMPLE); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +LOCAL(void) +do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual coefficient-block array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->blocksperrow * SIZEOF(JBLOCK); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +METHODDEF(JSAMPARRAY) +access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual sample array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_sarray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_sarray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF(JSAMPLE); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +METHODDEF(JBLOCKARRAY) +access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual block array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_barray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_barray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF(JBLOCK); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +/* + * Release all objects belonging to a specified pool. + */ + +METHODDEF(void) +free_pool (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + size_t space_freed; + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + +#ifdef MEM_STATS + if (cinfo->err->trace_level > 1) + print_mem_stats(cinfo, pool_id); /* print pool's memory usage statistics */ +#endif + + /* If freeing IMAGE pool, close any virtual arrays first */ + if (pool_id == JPOOL_IMAGE) { + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->b_s_open) { /* there may be no backing store */ + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); + } + } + mem->virt_sarray_list = NULL; + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->b_s_open) { /* there may be no backing store */ + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); + } + } + mem->virt_barray_list = NULL; + } + + /* Release large objects */ + lhdr_ptr = mem->large_list[pool_id]; + mem->large_list[pool_id] = NULL; + + while (lhdr_ptr != NULL) { + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + space_freed = lhdr_ptr->hdr.bytes_used + + lhdr_ptr->hdr.bytes_left + + SIZEOF(large_pool_hdr); + jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + lhdr_ptr = next_lhdr_ptr; + } + + /* Release small objects */ + shdr_ptr = mem->small_list[pool_id]; + mem->small_list[pool_id] = NULL; + + while (shdr_ptr != NULL) { + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + space_freed = shdr_ptr->hdr.bytes_used + + shdr_ptr->hdr.bytes_left + + SIZEOF(small_pool_hdr); + jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + shdr_ptr = next_shdr_ptr; + } +} + + +/* + * Close up shop entirely. + * Note that this cannot be called unless cinfo->mem is non-NULL. + */ + +METHODDEF(void) +self_destruct (j_common_ptr cinfo) +{ + int pool; + + /* Close all backing store, release all memory. + * Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + free_pool(cinfo, pool); + } + + /* Release the memory manager control block too. */ + jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr)); + cinfo->mem = NULL; /* ensures I will be called only once */ + + jpeg_mem_term(cinfo); /* system-dependent cleanup */ +} + + +/* + * Memory manager initialization. + * When this is called, only the error manager pointer is valid in cinfo! + */ + +GLOBAL(void) +jinit_memory_mgr (j_common_ptr cinfo) +{ + my_mem_ptr mem; + long max_to_use; + int pool; + size_t test_mac; + + cinfo->mem = NULL; /* for safety if init fails */ + + /* Check for configuration errors. + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + * doesn't reflect any real hardware alignment requirement. + * The test is a little tricky: for X>0, X and X-1 have no one-bits + * in common if and only if X is a power of 2, ie has only one one-bit. + * Some compilers may give an "unreachable code" warning here; ignore it. + */ + if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0) + ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE); + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + * a multiple of SIZEOF(ALIGN_TYPE). + * Again, an "unreachable code" warning may be ignored here. + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + */ + test_mac = (size_t) MAX_ALLOC_CHUNK; + if ((long) test_mac != MAX_ALLOC_CHUNK || + (MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0) + ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK); + + max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */ + + /* Attempt to allocate memory manager's control block */ + mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr)); + + if (mem == NULL) { + jpeg_mem_term(cinfo); /* system-dependent cleanup */ + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0); + } + + /* OK, fill in the method pointers */ + mem->pub.alloc_small = alloc_small; + mem->pub.alloc_large = alloc_large; + mem->pub.alloc_sarray = alloc_sarray; + mem->pub.alloc_barray = alloc_barray; + mem->pub.request_virt_sarray = request_virt_sarray; + mem->pub.request_virt_barray = request_virt_barray; + mem->pub.realize_virt_arrays = realize_virt_arrays; + mem->pub.access_virt_sarray = access_virt_sarray; + mem->pub.access_virt_barray = access_virt_barray; + mem->pub.free_pool = free_pool; + mem->pub.self_destruct = self_destruct; + + /* Make MAX_ALLOC_CHUNK accessible to other modules */ + mem->pub.max_alloc_chunk = MAX_ALLOC_CHUNK; + + /* Initialize working state */ + mem->pub.max_memory_to_use = max_to_use; + + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + mem->small_list[pool] = NULL; + mem->large_list[pool] = NULL; + } + mem->virt_sarray_list = NULL; + mem->virt_barray_list = NULL; + + mem->total_space_allocated = SIZEOF(my_memory_mgr); + + /* Declare ourselves open for business */ + cinfo->mem = & mem->pub; + + /* Check for an environment variable JPEGMEM; if found, override the + * default max_memory setting from jpeg_mem_init. Note that the + * surrounding application may again override this value. + * If your system doesn't support getenv(), define NO_GETENV to disable + * this feature. + */ +#ifndef NO_GETENV + { char * memenv; + + if ((memenv = getenv("JPEGMEM")) != NULL) { + char ch = 'x'; + + if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) { + if (ch == 'm' || ch == 'M') + max_to_use *= 1000L; + mem->pub.max_memory_to_use = max_to_use * 1000L; + } + } + } +#endif + +} diff --git a/src/jpeg-8c/jmemnobs.c b/src/jpeg-8c/jmemnobs.c new file mode 100644 index 0000000..eb8c337 --- /dev/null +++ b/src/jpeg-8c/jmemnobs.c @@ -0,0 +1,109 @@ +/* + * jmemnobs.c + * + * Copyright (C) 1992-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a really simple implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that no backing-store files are needed: all required space + * can be obtained from malloc(). + * This is very portable in the sense that it'll compile on almost anything, + * but you'd better have lots of main memory (or virtual memory) if you want + * to process big images. + * Note that the max_memory_to_use option is ignored by this implementation. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ +extern void * malloc JPP((size_t size)); +extern void free JPP((void *ptr)); +#endif + + +/* + * Memory allocation and freeing are controlled by the regular library + * routines malloc() and free(). + */ + +GLOBAL(void *) +jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void *) malloc(sizeofobject); +} + +GLOBAL(void) +jpeg_free_small (j_common_ptr cinfo, void * object, size_t sizeofobject) +{ + free(object); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL(void FAR *) +jpeg_get_large (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void FAR *) malloc(sizeofobject); +} + +GLOBAL(void) +jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject) +{ + free(object); +} + + +/* + * This routine computes the total memory space available for allocation. + * Here we always say, "we got all you want bud!" + */ + +GLOBAL(long) +jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated) +{ + return max_bytes_needed; +} + + +/* + * Backing store (temporary file) management. + * Since jpeg_mem_available always promised the moon, + * this should never be called and we can just error out. + */ + +GLOBAL(void) +jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed) +{ + ERREXIT(cinfo, JERR_NO_BACKING_STORE); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. Here, there isn't any. + */ + +GLOBAL(long) +jpeg_mem_init (j_common_ptr cinfo) +{ + return 0; /* just set max_memory_to_use to 0 */ +} + +GLOBAL(void) +jpeg_mem_term (j_common_ptr cinfo) +{ + /* no work */ +} diff --git a/src/jpeg-8c/jmemsys.h b/src/jpeg-8c/jmemsys.h new file mode 100644 index 0000000..6c3c6d3 --- /dev/null +++ b/src/jpeg-8c/jmemsys.h @@ -0,0 +1,198 @@ +/* + * jmemsys.h + * + * Copyright (C) 1992-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file defines the interface between the system-independent + * and system-dependent portions of the JPEG memory manager. No other + * modules need include it. (The system-independent portion is jmemmgr.c; + * there are several different versions of the system-dependent portion.) + * + * This file works as-is for the system-dependent memory managers supplied + * in the IJG distribution. You may need to modify it if you write a + * custom memory manager. If system-dependent changes are needed in + * this file, the best method is to #ifdef them based on a configuration + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR + * and USE_MAC_MEMMGR. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * These two functions are used to allocate and release small chunks of + * memory. (Typically the total amount requested through jpeg_get_small is + * no more than 20K or so; this will be requested in chunks of a few K each.) + * Behavior should be the same as for the standard library functions malloc + * and free; in particular, jpeg_get_small must return NULL on failure. + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + * size of the object being freed, just in case it's needed. + * On an 80x86 machine using small-data memory model, these manage near heap. + */ + +EXTERN(void *) jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); +EXTERN(void) jpeg_free_small JPP((j_common_ptr cinfo, void * object, + size_t sizeofobject)); + +/* + * These two functions are used to allocate and release large chunks of + * memory (up to the total free space designated by jpeg_mem_available). + * The interface is the same as above, except that on an 80x86 machine, + * far pointers are used. On most other machines these are identical to + * the jpeg_get/free_small routines; but we keep them separate anyway, + * in case a different allocation strategy is desirable for large chunks. + */ + +EXTERN(void FAR *) jpeg_get_large JPP((j_common_ptr cinfo, + size_t sizeofobject)); +EXTERN(void) jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, + size_t sizeofobject)); + +/* + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + * matter, but that case should never come into play). This macro is needed + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + * On those machines, we expect that jconfig.h will provide a proper value. + * On machines with 32-bit flat address spaces, any large constant may be used. + * + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + * size_t and will be a multiple of sizeof(align_type). + */ + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#define MAX_ALLOC_CHUNK 1000000000L +#endif + +/* + * This routine computes the total space still available for allocation by + * jpeg_get_large. If more space than this is needed, backing store will be + * used. NOTE: any memory already allocated must not be counted. + * + * There is a minimum space requirement, corresponding to the minimum + * feasible buffer sizes; jmemmgr.c will request that much space even if + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + * all working storage in memory, is also passed in case it is useful. + * Finally, the total space already allocated is passed. If no better + * method is available, cinfo->mem->max_memory_to_use - already_allocated + * is often a suitable calculation. + * + * It is OK for jpeg_mem_available to underestimate the space available + * (that'll just lead to more backing-store access than is really necessary). + * However, an overestimate will lead to failure. Hence it's wise to subtract + * a slop factor from the true available space. 5% should be enough. + * + * On machines with lots of virtual memory, any large constant may be returned. + * Conversely, zero may be returned to always use the minimum amount of memory. + */ + +EXTERN(long) jpeg_mem_available JPP((j_common_ptr cinfo, + long min_bytes_needed, + long max_bytes_needed, + long already_allocated)); + + +/* + * This structure holds whatever state is needed to access a single + * backing-store object. The read/write/close method pointers are called + * by jmemmgr.c to manipulate the backing-store object; all other fields + * are private to the system-dependent backing store routines. + */ + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ + +typedef union { + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ +} handle_union; + +#endif /* USE_MSDOS_MEMMGR */ + +#ifdef USE_MAC_MEMMGR /* Mac-specific junk */ +#include +#endif /* USE_MAC_MEMMGR */ + + +typedef struct backing_store_struct * backing_store_ptr; + +typedef struct backing_store_struct { + /* Methods for reading/writing/closing this backing-store object */ + JMETHOD(void, read_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, write_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, close_backing_store, (j_common_ptr cinfo, + backing_store_ptr info)); + + /* Private fields for system-dependent backing-store management */ +#ifdef USE_MSDOS_MEMMGR + /* For the MS-DOS manager (jmemdos.c), we need: */ + handle_union handle; /* reference to backing-store storage object */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else +#ifdef USE_MAC_MEMMGR + /* For the Mac manager (jmemmac.c), we need: */ + short temp_file; /* file reference number to temp file */ + FSSpec tempSpec; /* the FSSpec for the temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else + /* For a typical implementation with temp files, we need: */ + FILE * temp_file; /* stdio reference to temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ +#endif +#endif +} backing_store_info; + + +/* + * Initial opening of a backing-store object. This must fill in the + * read/write/close pointers in the object. The read/write routines + * may take an error exit if the specified maximum file size is exceeded. + * (If jpeg_mem_available always returns a large value, this routine can + * just take an error exit.) + */ + +EXTERN(void) jpeg_open_backing_store JPP((j_common_ptr cinfo, + backing_store_ptr info, + long total_bytes_needed)); + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. jpeg_mem_init will be called before anything is + * allocated (and, therefore, nothing in cinfo is of use except the error + * manager pointer). It should return a suitable default value for + * max_memory_to_use; this may subsequently be overridden by the surrounding + * application. (Note that max_memory_to_use is only important if + * jpeg_mem_available chooses to consult it ... no one else will.) + * jpeg_mem_term may assume that all requested memory has been freed and that + * all opened backing-store objects have been closed. + */ + +EXTERN(long) jpeg_mem_init JPP((j_common_ptr cinfo)); +EXTERN(void) jpeg_mem_term JPP((j_common_ptr cinfo)); diff --git a/src/jpeg-8c/jmorecfg.h b/src/jpeg-8c/jmorecfg.h new file mode 100644 index 0000000..928d052 --- /dev/null +++ b/src/jpeg-8c/jmorecfg.h @@ -0,0 +1,371 @@ +/* + * jmorecfg.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 1997-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of the JPEG spec, set this to 255. However, darn + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef CHAR_IS_UNSIGNED +#define GETJSAMPLE(value) ((int) (value)) +#else +#define GETJSAMPLE(value) ((int) (value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef CHAR_IS_UNSIGNED +#define GETJOCTET(value) (value) +#else +#define GETJOCTET(value) ((value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef CHAR_IS_UNSIGNED +typedef char UINT8; +#else /* not CHAR_IS_UNSIGNED */ +typedef short UINT8; +#endif /* CHAR_IS_UNSIGNED */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +#ifndef _BASETSD_H_ /* Microsoft defines it in basetsd.h */ +#ifndef _BASETSD_H /* MinGW is slightly different */ +#ifndef QGLOBAL_H /* Qt defines it in qglobal.h */ +typedef long INT32; +#endif +#endif +#endif +#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These macros are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions; + * in particular, you'll need to do that to make the library a Windows DLL. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +/* a function called through method pointers: */ +#define METHODDEF(type) static type +/* a function used only in its module: */ +#define LOCAL(type) static type +/* a function referenced thru EXTERNs: */ +#define GLOBAL(type) type +/* a reference to a GLOBAL function: */ +#define EXTERN(type) extern type + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + * Again, you can customize this if you need special linkage keywords. + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD(type,methodname,arglist) type (*methodname) arglist +#else +#define JMETHOD(type,methodname,arglist) type (*methodname) () +#endif + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + * by just saying "FAR *" where such a pointer is needed. In a few places + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + */ + +#ifndef FAR +#ifdef NEED_FAR_POINTERS +#define FAR far +#else +#define FAR +#endif +#endif + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +#ifndef HAVE_BOOLEAN +typedef int boolean; +#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Capability options common to encoder and decoder: */ + +#define DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#define DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#define C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define DCT_SCALING_SUPPORTED /* Input rescaling via DCT? (Requires DCT_ISLOW)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#define D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#define SAVE_MARKERS_SUPPORTED /* jpeg_save_markers() needed? */ +#define BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#define UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#define QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#define QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * Ordering of RGB data in scanlines passed to or from the application. + * If your application wants to deal with data in the order B,G,R, just + * change these macros. You can also deal with formats such as R,G,B,X + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + * the offsets will also change the order in which colormap data is organized. + * RESTRICTIONS: + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + * is not 3 (they don't understand about dummy color components!). So you + * can't use color quantization if you change that value. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 3 /* JSAMPLEs per RGB scanline element */ + + +/* Definitions for speed-related optimizations. */ + + +/* If your compiler supports inline functions, define INLINE + * as the inline keyword; otherwise define it as empty. + */ + +#ifndef INLINE +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#define INLINE __inline__ +#endif +#ifndef INLINE +#define INLINE /* default is to define it as empty */ +#endif +#endif + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#define MULTIPLIER int /* type for fastest integer multiply */ +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + * Typically, float is faster in ANSI C compilers, while double is faster in + * pre-ANSI compilers (because they insist on converting to double anyway). + * The code below therefore chooses float if we have ANSI-style prototypes. + */ + +#ifndef FAST_FLOAT +#ifdef HAVE_PROTOTYPES +#define FAST_FLOAT float +#else +#define FAST_FLOAT double +#endif +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/src/jpeg-8c/jpegint.h b/src/jpeg-8c/jpegint.h new file mode 100644 index 0000000..0c27a4e --- /dev/null +++ b/src/jpeg-8c/jpegint.h @@ -0,0 +1,407 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 1997-2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +typedef JMETHOD(void, forward_DCT_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); + +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* It is useful to allow each component to have a separate FDCT method. */ + forward_DCT_ptr forward_DCT[MAX_COMPONENTS]; +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_arith_encoder jIAEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_arith_decoder jIADecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#define jpeg_natural_order7 jZAGTable7 +#define jpeg_natural_order6 jZAGTable6 +#define jpeg_natural_order5 jZAGTable5 +#define jpeg_natural_order4 jZAGTable4 +#define jpeg_natural_order3 jZAGTable3 +#define jpeg_natural_order2 jZAGTable2 +#define jpeg_aritab jAriTab +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_arith_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_arith_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ +extern const int jpeg_natural_order7[]; /* zz to natural order for 7x7 block */ +extern const int jpeg_natural_order6[]; /* zz to natural order for 6x6 block */ +extern const int jpeg_natural_order5[]; /* zz to natural order for 5x5 block */ +extern const int jpeg_natural_order4[]; /* zz to natural order for 4x4 block */ +extern const int jpeg_natural_order3[]; /* zz to natural order for 3x3 block */ +extern const int jpeg_natural_order2[]; /* zz to natural order for 2x2 block */ + +/* Arithmetic coding probability estimation tables in jaricom.c */ +extern const INT32 jpeg_aritab[]; + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/src/jpeg-8c/jpeglib.h b/src/jpeg-8c/jpeglib.h new file mode 100644 index 0000000..1eb1fac --- /dev/null +++ b/src/jpeg-8c/jpeglib.h @@ -0,0 +1,1160 @@ +/* + * jpeglib.h + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * Modified 2002-2010 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "jconfig.h" /* widely used configuration options */ +#endif +#include "jmorecfg.h" /* seldom changed options */ + + +#ifdef __cplusplus +#ifndef DONT_USE_EXTERN_C +extern "C" { +#endif +#endif + +/* Version IDs for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 80". + */ + +#define JPEG_LIB_VERSION 80 /* Compatibility version 8.0 */ +#define JPEG_LIB_VERSION_MAJOR 8 +#define JPEG_LIB_VERSION_MINOR 3 + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This array gives the coefficient quantizers in natural array order + * (not the zigzag order in which they are stored in a JPEG DQT marker). + * CAUTION: IJG versions prior to v6a kept this array in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples, + * reflecting any scaling we choose to apply during the DCT step. + * Values from 1 to 16 are supported. + * Note that different components may receive different DCT scalings. + */ + int DCT_h_scaled_size; + int DCT_v_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface); + * DCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_h_scaled_size/DCTSIZE) + * and similarly for height. + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples: MCU_width * DCT_h_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is currently used only for decompression. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + +/* The decompressor can save APPn and COM markers in a list of these: */ + +typedef struct jpeg_marker_struct FAR * jpeg_saved_marker_ptr; + +struct jpeg_marker_struct { + jpeg_saved_marker_ptr next; /* next in list, or NULL */ + UINT8 marker; /* marker code: JPEG_COM, or JPEG_APP0+n */ + unsigned int original_length; /* # bytes of data in the file */ + unsigned int data_length; /* # bytes of data saved at data[] */ + JOCTET FAR * data; /* the data contained in the marker */ + /* the marker length word is not counted in data_length or original_length */ +}; + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ + void * client_data; /* Available for use by application */\ + boolean is_decompressor; /* So common code can tell which is which */\ + int global_state /* For checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + JDIMENSION jpeg_width; /* scaled JPEG image width */ + JDIMENSION jpeg_height; /* scaled JPEG image height */ + /* Dimensions of actual JPEG image that will be written to file, + * derived from input dimensions by scaling factors above. + * These fields are computed by jpeg_start_compress(). + * You can also use jpeg_calc_jpeg_dimensions() to determine these values + * in advance of calling jpeg_start_compress(). + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + int q_scale_factor[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined, + * and corresponding scale factors (percentage, initialized 100). + */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + boolean do_fancy_downsampling; /* TRUE=apply fancy downsampling */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + UINT8 JFIF_major_version; /* What to write for the JFIF version number */ + UINT8 JFIF_minor_version; + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_h_scaled_size; /* smallest DCT_h_scaled_size of any component */ + int min_DCT_v_scaled_size; /* smallest DCT_v_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + int block_size; /* the basic DCT block size: 1..16 */ + const int * natural_order; /* natural-order position array */ + int lim_Se; /* min( Se, DCTSIZE2-1 ) */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; + jpeg_scan_info * script_space; /* workspace for jpeg_simple_progression */ + int script_space_size; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean is_baseline; /* TRUE if Baseline SOF0 encountered */ + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker; only valid if saw_JFIF_marker is TRUE: */ + UINT8 JFIF_major_version; /* JFIF version number */ + UINT8 JFIF_minor_version; + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Aside from the specific data retained from APPn markers known to the + * library, the uninterpreted contents of any or all APPn and COM markers + * can be saved in a list for examination by the application. + */ + jpeg_saved_marker_ptr marker_list; /* Head of list of saved markers */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_h_scaled_size; /* smallest DCT_h_scaled_size of any component */ + int min_DCT_v_scaled_size; /* smallest DCT_v_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_v_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* These fields are derived from Se of first SOS marker. + */ + int block_size; /* the basic DCT block size: 1..16 */ + const int * natural_order; /* natural-order position array for entropy decode */ + int lim_Se; /* min( Se, DCTSIZE2-1 ) for entropy decode */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD(void, error_exit, (j_common_ptr cinfo)); + /* Conditionally emit a trace or warning message */ + JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level)); + /* Routine that actually outputs a trace or error message */ + JMETHOD(void, output_message, (j_common_ptr cinfo)); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD(void, init_destination, (j_compress_ptr cinfo)); + JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); + JMETHOD(void, term_destination, (j_compress_ptr cinfo)); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD(void, init_source, (j_decompress_ptr cinfo)); + JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); + JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); + JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); + JMETHOD(void, term_source, (j_decompress_ptr cinfo)); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows)); + JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows)); + JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); + JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); + JMETHOD(void, self_destruct, (j_common_ptr cinfo)); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; + + /* Maximum allocation request accepted by alloc_large. */ + long max_alloc_chunk; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP(arglist) arglist +#else +#define JPP(arglist) () +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_CreateCompress jCreaCompress +#define jpeg_CreateDecompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_mem_dest jMemDest +#define jpeg_mem_src jMemSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_default_qtables jDefQTables +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_calc_jpeg_dimensions jCjpegDimensions +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_m_header jWrtMHeader +#define jpeg_write_m_byte jWrtMByte +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_core_output_dimensions jCoreDimensions +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_save_markers jSaveMarkers +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN(struct jpeg_error_mgr *) jpeg_std_error + JPP((struct jpeg_error_mgr * err)); + +/* Initialization of JPEG compression objects. + * jpeg_create_compress() and jpeg_create_decompress() are the exported + * names that applications should call. These expand to calls on + * jpeg_CreateCompress and jpeg_CreateDecompress with additional information + * passed for version mismatch checking. + * NB: you must set up the error-manager BEFORE calling jpeg_create_xxx. + */ +#define jpeg_create_compress(cinfo) \ + jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \ + (size_t) sizeof(struct jpeg_compress_struct)) +#define jpeg_create_decompress(cinfo) \ + jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \ + (size_t) sizeof(struct jpeg_decompress_struct)) +EXTERN(void) jpeg_CreateCompress JPP((j_compress_ptr cinfo, + int version, size_t structsize)); +EXTERN(void) jpeg_CreateDecompress JPP((j_decompress_ptr cinfo, + int version, size_t structsize)); +/* Destruction of JPEG compression objects */ +EXTERN(void) jpeg_destroy_compress JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile)); + +/* Data source and destination managers: memory buffers. */ +EXTERN(void) jpeg_mem_dest JPP((j_compress_ptr cinfo, + unsigned char ** outbuffer, + unsigned long * outsize)); +EXTERN(void) jpeg_mem_src JPP((j_decompress_ptr cinfo, + unsigned char * inbuffer, + unsigned long insize)); + +/* Default parameter setup for compression */ +EXTERN(void) jpeg_set_defaults JPP((j_compress_ptr cinfo)); +/* Compression parameter setup aids */ +EXTERN(void) jpeg_set_colorspace JPP((j_compress_ptr cinfo, + J_COLOR_SPACE colorspace)); +EXTERN(void) jpeg_default_colorspace JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, + boolean force_baseline)); +EXTERN(void) jpeg_set_linear_quality JPP((j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline)); +EXTERN(void) jpeg_default_qtables JPP((j_compress_ptr cinfo, + boolean force_baseline)); +EXTERN(void) jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); +EXTERN(int) jpeg_quality_scaling JPP((int quality)); +EXTERN(void) jpeg_simple_progression JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_suppress_tables JPP((j_compress_ptr cinfo, + boolean suppress)); +EXTERN(JQUANT_TBL *) jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); +EXTERN(JHUFF_TBL *) jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); + +/* Main entry points for compression */ +EXTERN(void) jpeg_start_compress JPP((j_compress_ptr cinfo, + boolean write_all_tables)); +EXTERN(JDIMENSION) jpeg_write_scanlines JPP((j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines)); +EXTERN(void) jpeg_finish_compress JPP((j_compress_ptr cinfo)); + +/* Precalculate JPEG dimensions for current compression parameters. */ +EXTERN(void) jpeg_calc_jpeg_dimensions JPP((j_compress_ptr cinfo)); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_write_raw_data JPP((j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines)); + +/* Write a special marker. See libjpeg.txt concerning safe usage. */ +EXTERN(void) jpeg_write_marker + JPP((j_compress_ptr cinfo, int marker, + const JOCTET * dataptr, unsigned int datalen)); +/* Same, but piecemeal. */ +EXTERN(void) jpeg_write_m_header + JPP((j_compress_ptr cinfo, int marker, unsigned int datalen)); +EXTERN(void) jpeg_write_m_byte + JPP((j_compress_ptr cinfo, int val)); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN(void) jpeg_write_tables JPP((j_compress_ptr cinfo)); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN(int) jpeg_read_header JPP((j_decompress_ptr cinfo, + boolean require_image)); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN(boolean) jpeg_start_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(JDIMENSION) jpeg_read_scanlines JPP((j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines)); +EXTERN(boolean) jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_read_raw_data JPP((j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines)); + +/* Additional entry points for buffered-image mode. */ +EXTERN(boolean) jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); +EXTERN(boolean) jpeg_start_output JPP((j_decompress_ptr cinfo, + int scan_number)); +EXTERN(boolean) jpeg_finish_output JPP((j_decompress_ptr cinfo)); +EXTERN(boolean) jpeg_input_complete JPP((j_decompress_ptr cinfo)); +EXTERN(void) jpeg_new_colormap JPP((j_decompress_ptr cinfo)); +EXTERN(int) jpeg_consume_input JPP((j_decompress_ptr cinfo)); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN(void) jpeg_core_output_dimensions JPP((j_decompress_ptr cinfo)); +EXTERN(void) jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); + +/* Control saving of COM and APPn markers into marker_list. */ +EXTERN(void) jpeg_save_markers + JPP((j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit)); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN(void) jpeg_set_marker_processor + JPP((j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine)); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN(jvirt_barray_ptr *) jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); +EXTERN(void) jpeg_write_coefficients JPP((j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays)); +EXTERN(void) jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, + j_compress_ptr dstinfo)); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN(void) jpeg_abort_compress JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_abort_decompress JPP((j_decompress_ptr cinfo)); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN(void) jpeg_abort JPP((j_common_ptr cinfo)); +EXTERN(void) jpeg_destroy JPP((j_common_ptr cinfo)); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN(boolean) jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, + int desired)); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "jpegint.h" /* fetch private declarations */ +#include "jerror.h" /* fetch error codes too */ +#endif + +#ifdef __cplusplus +#ifndef DONT_USE_EXTERN_C +} +#endif +#endif + +#endif /* JPEGLIB_H */ diff --git a/src/jpeg-8c/jquant1.c b/src/jpeg-8c/jquant1.c new file mode 100644 index 0000000..b2f96aa --- /dev/null +++ b/src/jpeg-8c/jquant1.c @@ -0,0 +1,856 @@ +/* + * jquant1.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 1-pass color quantization (color mapping) routines. + * These routines provide mapping to a fixed color map using equally spaced + * color values. Optional Floyd-Steinberg or ordered dithering is available. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_1PASS_SUPPORTED + + +/* + * The main purpose of 1-pass quantization is to provide a fast, if not very + * high quality, colormapped output capability. A 2-pass quantizer usually + * gives better visual quality; however, for quantized grayscale output this + * quantizer is perfectly adequate. Dithering is highly recommended with this + * quantizer, though you can turn it off if you really want to. + * + * In 1-pass quantization the colormap must be chosen in advance of seeing the + * image. We use a map consisting of all combinations of Ncolors[i] color + * values for the i'th component. The Ncolors[] values are chosen so that + * their product, the total number of colors, is no more than that requested. + * (In most cases, the product will be somewhat less.) + * + * Since the colormap is orthogonal, the representative value for each color + * component can be determined without considering the other components; + * then these indexes can be combined into a colormap index by a standard + * N-dimensional-array-subscript calculation. Most of the arithmetic involved + * can be precalculated and stored in the lookup table colorindex[]. + * colorindex[i][j] maps pixel value j in component i to the nearest + * representative value (grid plane) for that component; this index is + * multiplied by the array stride for component i, so that the + * index of the colormap entry closest to a given pixel value is just + * sum( colorindex[component-number][pixel-component-value] ) + * Aside from being fast, this scheme allows for variable spacing between + * representative values with no additional lookup cost. + * + * If gamma correction has been applied in color conversion, it might be wise + * to adjust the color grid spacing so that the representative colors are + * equidistant in linear space. At this writing, gamma correction is not + * implemented by jdcolor, so nothing is done here. + */ + + +/* Declarations for ordered dithering. + * + * We use a standard 16x16 ordered dither array. The basic concept of ordered + * dithering is described in many references, for instance Dale Schumacher's + * chapter II.2 of Graphics Gems II (James Arvo, ed. Academic Press, 1991). + * In place of Schumacher's comparisons against a "threshold" value, we add a + * "dither" value to the input pixel and then round the result to the nearest + * output value. The dither value is equivalent to (0.5 - threshold) times + * the distance between output values. For ordered dithering, we assume that + * the output colors are equally spaced; if not, results will probably be + * worse, since the dither may be too much or too little at a given point. + * + * The normal calculation would be to form pixel value + dither, range-limit + * this to 0..MAXJSAMPLE, and then index into the colorindex table as usual. + * We can skip the separate range-limiting step by extending the colorindex + * table in both directions. + */ + +#define ODITHER_SIZE 16 /* dimension of dither matrix */ +/* NB: if ODITHER_SIZE is not a power of 2, ODITHER_MASK uses will break */ +#define ODITHER_CELLS (ODITHER_SIZE*ODITHER_SIZE) /* # cells in matrix */ +#define ODITHER_MASK (ODITHER_SIZE-1) /* mask for wrapping around counters */ + +typedef int ODITHER_MATRIX[ODITHER_SIZE][ODITHER_SIZE]; +typedef int (*ODITHER_MATRIX_PTR)[ODITHER_SIZE]; + +static const UINT8 base_dither_matrix[ODITHER_SIZE][ODITHER_SIZE] = { + /* Bayer's order-4 dither array. Generated by the code given in + * Stephen Hawley's article "Ordered Dithering" in Graphics Gems I. + * The values in this array must range from 0 to ODITHER_CELLS-1. + */ + { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, + { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, + { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, + { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, + { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, + { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, + { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, + { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, + { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, + { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, + { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, + { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, + { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, + { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, + { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, + { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } +}; + + +/* Declarations for Floyd-Steinberg dithering. + * + * Errors are accumulated into the array fserrors[], at a resolution of + * 1/16th of a pixel count. The error at a given pixel is propagated + * to its not-yet-processed neighbors using the standard F-S fractions, + * ... (here) 7/16 + * 3/16 5/16 1/16 + * We work left-to-right on even rows, right-to-left on odd rows. + * + * We can get away with a single array (holding one row's worth of errors) + * by using it to store the current row's errors at pixel columns not yet + * processed, but the next row's errors at columns already processed. We + * need only a few extra variables to hold the errors immediately around the + * current column. (If we are lucky, those variables are in registers, but + * even if not, they're probably cheaper to access than array elements are.) + * + * The fserrors[] array is indexed [component#][position]. + * We provide (#columns + 2) entries per component; the extra entry at each + * end saves us from special-casing the first and last pixels. + * + * Note: on a wide image, we might not have enough room in a PC's near data + * segment to hold the error array; so it is allocated with alloc_large. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef INT16 FSERROR; /* 16 bits should be enough */ +typedef int LOCFSERROR; /* use 'int' for calculation temps */ +#else +typedef INT32 FSERROR; /* may need more than 16 bits */ +typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ +#endif + +typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */ + + +/* Private subobject */ + +#define MAX_Q_COMPS 4 /* max components I can handle */ + +typedef struct { + struct jpeg_color_quantizer pub; /* public fields */ + + /* Initially allocated colormap is saved here */ + JSAMPARRAY sv_colormap; /* The color map as a 2-D pixel array */ + int sv_actual; /* number of entries in use */ + + JSAMPARRAY colorindex; /* Precomputed mapping for speed */ + /* colorindex[i][j] = index of color closest to pixel value j in component i, + * premultiplied as described above. Since colormap indexes must fit into + * JSAMPLEs, the entries of this array will too. + */ + boolean is_padded; /* is the colorindex padded for odither? */ + + int Ncolors[MAX_Q_COMPS]; /* # of values alloced to each component */ + + /* Variables for ordered dithering */ + int row_index; /* cur row's vertical index in dither matrix */ + ODITHER_MATRIX_PTR odither[MAX_Q_COMPS]; /* one dither array per component */ + + /* Variables for Floyd-Steinberg dithering */ + FSERRPTR fserrors[MAX_Q_COMPS]; /* accumulated errors */ + boolean on_odd_row; /* flag to remember which row we are on */ +} my_cquantizer; + +typedef my_cquantizer * my_cquantize_ptr; + + +/* + * Policy-making subroutines for create_colormap and create_colorindex. + * These routines determine the colormap to be used. The rest of the module + * only assumes that the colormap is orthogonal. + * + * * select_ncolors decides how to divvy up the available colors + * among the components. + * * output_value defines the set of representative values for a component. + * * largest_input_value defines the mapping from input values to + * representative values for a component. + * Note that the latter two routines may impose different policies for + * different components, though this is not currently done. + */ + + +LOCAL(int) +select_ncolors (j_decompress_ptr cinfo, int Ncolors[]) +/* Determine allocation of desired colors to components, */ +/* and fill in Ncolors[] array to indicate choice. */ +/* Return value is total number of colors (product of Ncolors[] values). */ +{ + int nc = cinfo->out_color_components; /* number of color components */ + int max_colors = cinfo->desired_number_of_colors; + int total_colors, iroot, i, j; + boolean changed; + long temp; + static const int RGB_order[3] = { RGB_GREEN, RGB_RED, RGB_BLUE }; + + /* We can allocate at least the nc'th root of max_colors per component. */ + /* Compute floor(nc'th root of max_colors). */ + iroot = 1; + do { + iroot++; + temp = iroot; /* set temp = iroot ** nc */ + for (i = 1; i < nc; i++) + temp *= iroot; + } while (temp <= (long) max_colors); /* repeat till iroot exceeds root */ + iroot--; /* now iroot = floor(root) */ + + /* Must have at least 2 color values per component */ + if (iroot < 2) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, (int) temp); + + /* Initialize to iroot color values for each component */ + total_colors = 1; + for (i = 0; i < nc; i++) { + Ncolors[i] = iroot; + total_colors *= iroot; + } + /* We may be able to increment the count for one or more components without + * exceeding max_colors, though we know not all can be incremented. + * Sometimes, the first component can be incremented more than once! + * (Example: for 16 colors, we start at 2*2*2, go to 3*2*2, then 4*2*2.) + * In RGB colorspace, try to increment G first, then R, then B. + */ + do { + changed = FALSE; + for (i = 0; i < nc; i++) { + j = (cinfo->out_color_space == JCS_RGB ? RGB_order[i] : i); + /* calculate new total_colors if Ncolors[j] is incremented */ + temp = total_colors / Ncolors[j]; + temp *= Ncolors[j]+1; /* done in long arith to avoid oflo */ + if (temp > (long) max_colors) + break; /* won't fit, done with this pass */ + Ncolors[j]++; /* OK, apply the increment */ + total_colors = (int) temp; + changed = TRUE; + } + } while (changed); + + return total_colors; +} + + +LOCAL(int) +output_value (j_decompress_ptr cinfo, int ci, int j, int maxj) +/* Return j'th output value, where j will range from 0 to maxj */ +/* The output values must fall in 0..MAXJSAMPLE in increasing order */ +{ + /* We always provide values 0 and MAXJSAMPLE for each component; + * any additional values are equally spaced between these limits. + * (Forcing the upper and lower values to the limits ensures that + * dithering can't produce a color outside the selected gamut.) + */ + return (int) (((INT32) j * MAXJSAMPLE + maxj/2) / maxj); +} + + +LOCAL(int) +largest_input_value (j_decompress_ptr cinfo, int ci, int j, int maxj) +/* Return largest input value that should map to j'th output value */ +/* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */ +{ + /* Breakpoints are halfway between values returned by output_value */ + return (int) (((INT32) (2*j + 1) * MAXJSAMPLE + maxj) / (2*maxj)); +} + + +/* + * Create the colormap. + */ + +LOCAL(void) +create_colormap (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colormap; /* Created colormap */ + int total_colors; /* Number of distinct output colors */ + int i,j,k, nci, blksize, blkdist, ptr, val; + + /* Select number of colors for each component */ + total_colors = select_ncolors(cinfo, cquantize->Ncolors); + + /* Report selected color counts */ + if (cinfo->out_color_components == 3) + TRACEMS4(cinfo, 1, JTRC_QUANT_3_NCOLORS, + total_colors, cquantize->Ncolors[0], + cquantize->Ncolors[1], cquantize->Ncolors[2]); + else + TRACEMS1(cinfo, 1, JTRC_QUANT_NCOLORS, total_colors); + + /* Allocate and fill in the colormap. */ + /* The colors are ordered in the map in standard row-major order, */ + /* i.e. rightmost (highest-indexed) color changes most rapidly. */ + + colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) total_colors, (JDIMENSION) cinfo->out_color_components); + + /* blksize is number of adjacent repeated entries for a component */ + /* blkdist is distance between groups of identical entries for a component */ + blkdist = total_colors; + + for (i = 0; i < cinfo->out_color_components; i++) { + /* fill in colormap entries for i'th color component */ + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + blksize = blkdist / nci; + for (j = 0; j < nci; j++) { + /* Compute j'th output value (out of nci) for component */ + val = output_value(cinfo, i, j, nci-1); + /* Fill in all colormap entries that have this value of this component */ + for (ptr = j * blksize; ptr < total_colors; ptr += blkdist) { + /* fill in blksize entries beginning at ptr */ + for (k = 0; k < blksize; k++) + colormap[i][ptr+k] = (JSAMPLE) val; + } + } + blkdist = blksize; /* blksize of this color is blkdist of next */ + } + + /* Save the colormap in private storage, + * where it will survive color quantization mode changes. + */ + cquantize->sv_colormap = colormap; + cquantize->sv_actual = total_colors; +} + + +/* + * Create the color index table. + */ + +LOCAL(void) +create_colorindex (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPROW indexptr; + int i,j,k, nci, blksize, val, pad; + + /* For ordered dither, we pad the color index tables by MAXJSAMPLE in + * each direction (input index values can be -MAXJSAMPLE .. 2*MAXJSAMPLE). + * This is not necessary in the other dithering modes. However, we + * flag whether it was done in case user changes dithering mode. + */ + if (cinfo->dither_mode == JDITHER_ORDERED) { + pad = MAXJSAMPLE*2; + cquantize->is_padded = TRUE; + } else { + pad = 0; + cquantize->is_padded = FALSE; + } + + cquantize->colorindex = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (MAXJSAMPLE+1 + pad), + (JDIMENSION) cinfo->out_color_components); + + /* blksize is number of adjacent repeated entries for a component */ + blksize = cquantize->sv_actual; + + for (i = 0; i < cinfo->out_color_components; i++) { + /* fill in colorindex entries for i'th color component */ + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + blksize = blksize / nci; + + /* adjust colorindex pointers to provide padding at negative indexes. */ + if (pad) + cquantize->colorindex[i] += MAXJSAMPLE; + + /* in loop, val = index of current output value, */ + /* and k = largest j that maps to current val */ + indexptr = cquantize->colorindex[i]; + val = 0; + k = largest_input_value(cinfo, i, 0, nci-1); + for (j = 0; j <= MAXJSAMPLE; j++) { + while (j > k) /* advance val if past boundary */ + k = largest_input_value(cinfo, i, ++val, nci-1); + /* premultiply so that no multiplication needed in main processing */ + indexptr[j] = (JSAMPLE) (val * blksize); + } + /* Pad at both ends if necessary */ + if (pad) + for (j = 1; j <= MAXJSAMPLE; j++) { + indexptr[-j] = indexptr[0]; + indexptr[MAXJSAMPLE+j] = indexptr[MAXJSAMPLE]; + } + } +} + + +/* + * Create an ordered-dither array for a component having ncolors + * distinct output values. + */ + +LOCAL(ODITHER_MATRIX_PTR) +make_odither_array (j_decompress_ptr cinfo, int ncolors) +{ + ODITHER_MATRIX_PTR odither; + int j,k; + INT32 num,den; + + odither = (ODITHER_MATRIX_PTR) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(ODITHER_MATRIX)); + /* The inter-value distance for this color is MAXJSAMPLE/(ncolors-1). + * Hence the dither value for the matrix cell with fill order f + * (f=0..N-1) should be (N-1-2*f)/(2*N) * MAXJSAMPLE/(ncolors-1). + * On 16-bit-int machine, be careful to avoid overflow. + */ + den = 2 * ODITHER_CELLS * ((INT32) (ncolors - 1)); + for (j = 0; j < ODITHER_SIZE; j++) { + for (k = 0; k < ODITHER_SIZE; k++) { + num = ((INT32) (ODITHER_CELLS-1 - 2*((int)base_dither_matrix[j][k]))) + * MAXJSAMPLE; + /* Ensure round towards zero despite C's lack of consistency + * about rounding negative values in integer division... + */ + odither[j][k] = (int) (num<0 ? -((-num)/den) : num/den); + } + } + return odither; +} + + +/* + * Create the ordered-dither tables. + * Components having the same number of representative colors may + * share a dither table. + */ + +LOCAL(void) +create_odither_tables (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + ODITHER_MATRIX_PTR odither; + int i, j, nci; + + for (i = 0; i < cinfo->out_color_components; i++) { + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + odither = NULL; /* search for matching prior component */ + for (j = 0; j < i; j++) { + if (nci == cquantize->Ncolors[j]) { + odither = cquantize->odither[j]; + break; + } + } + if (odither == NULL) /* need a new table? */ + odither = make_odither_array(cinfo, nci); + cquantize->odither[i] = odither; + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF(void) +color_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colorindex = cquantize->colorindex; + register int pixcode, ci; + register JSAMPROW ptrin, ptrout; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + register int nc = cinfo->out_color_components; + + for (row = 0; row < num_rows; row++) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for (col = width; col > 0; col--) { + pixcode = 0; + for (ci = 0; ci < nc; ci++) { + pixcode += GETJSAMPLE(colorindex[ci][GETJSAMPLE(*ptrin++)]); + } + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF(void) +color_quantize3 (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* Fast path for out_color_components==3, no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW ptrin, ptrout; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for (col = width; col > 0; col--) { + pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*ptrin++)]); + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF(void) +quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, with ordered dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + int * dither; /* points to active row of dither matrix */ + int row_index, col_index; /* current indexes into dither matrix */ + int nc = cinfo->out_color_components; + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + /* Initialize output values to 0 so can process components separately */ + jzero_far((void FAR *) output_buf[row], + (size_t) (width * SIZEOF(JSAMPLE))); + row_index = cquantize->row_index; + for (ci = 0; ci < nc; ci++) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + colorindex_ci = cquantize->colorindex[ci]; + dither = cquantize->odither[ci][row_index]; + col_index = 0; + + for (col = width; col > 0; col--) { + /* Form pixel value + dither, range-limit to 0..MAXJSAMPLE, + * select output value, accumulate into output code for this pixel. + * Range-limiting need not be done explicitly, as we have extended + * the colorindex table to produce the right answers for out-of-range + * inputs. The maximum dither is +- MAXJSAMPLE; this sets the + * required amount of padding. + */ + *output_ptr += colorindex_ci[GETJSAMPLE(*input_ptr)+dither[col_index]]; + input_ptr += nc; + output_ptr++; + col_index = (col_index + 1) & ODITHER_MASK; + } + } + /* Advance row index for next row */ + row_index = (row_index + 1) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF(void) +quantize3_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* Fast path for out_color_components==3, with ordered dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int * dither0; /* points to active row of dither matrix */ + int * dither1; + int * dither2; + int row_index, col_index; /* current indexes into dither matrix */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + row_index = cquantize->row_index; + input_ptr = input_buf[row]; + output_ptr = output_buf[row]; + dither0 = cquantize->odither[0][row_index]; + dither1 = cquantize->odither[1][row_index]; + dither2 = cquantize->odither[2][row_index]; + col_index = 0; + + for (col = width; col > 0; col--) { + pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*input_ptr++) + + dither0[col_index]]); + pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*input_ptr++) + + dither1[col_index]]); + pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*input_ptr++) + + dither2[col_index]]); + *output_ptr++ = (JSAMPLE) pixcode; + col_index = (col_index + 1) & ODITHER_MASK; + } + row_index = (row_index + 1) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF(void) +quantize_fs_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, with Floyd-Steinberg dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register LOCFSERROR cur; /* current error or pixel value */ + LOCFSERROR belowerr; /* error for pixel below cur */ + LOCFSERROR bpreverr; /* error for below/prev col */ + LOCFSERROR bnexterr; /* error for below/next col */ + LOCFSERROR delta; + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + JSAMPROW colormap_ci; + int pixcode; + int nc = cinfo->out_color_components; + int dir; /* 1 for left-to-right, -1 for right-to-left */ + int dirnc; /* dir * nc */ + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE *range_limit = cinfo->sample_range_limit; + SHIFT_TEMPS + + for (row = 0; row < num_rows; row++) { + /* Initialize output values to 0 so can process components separately */ + jzero_far((void FAR *) output_buf[row], + (size_t) (width * SIZEOF(JSAMPLE))); + for (ci = 0; ci < nc; ci++) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + if (cquantize->on_odd_row) { + /* work right to left in this row */ + input_ptr += (width-1) * nc; /* so point to rightmost pixel */ + output_ptr += width-1; + dir = -1; + dirnc = -nc; + errorptr = cquantize->fserrors[ci] + (width+1); /* => entry after last column */ + } else { + /* work left to right in this row */ + dir = 1; + dirnc = nc; + errorptr = cquantize->fserrors[ci]; /* => entry before first column */ + } + colorindex_ci = cquantize->colorindex[ci]; + colormap_ci = cquantize->sv_colormap[ci]; + /* Preset error values: no error propagated to first pixel from left */ + cur = 0; + /* and no error propagated to row below yet */ + belowerr = bpreverr = 0; + + for (col = width; col > 0; col--) { + /* cur holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur = RIGHT_SHIFT(cur + errorptr[dir] + 8, 4); + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE; this sets the required size + * of the range_limit array. + */ + cur += GETJSAMPLE(*input_ptr); + cur = GETJSAMPLE(range_limit[cur]); + /* Select output value, accumulate into output code for this pixel */ + pixcode = GETJSAMPLE(colorindex_ci[cur]); + *output_ptr += (JSAMPLE) pixcode; + /* Compute actual representation error at this pixel */ + /* Note: we can do this even though we don't have the final */ + /* pixel code, because the colormap is orthogonal. */ + cur -= GETJSAMPLE(colormap_ci[pixcode]); + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + bnexterr = cur; + delta = cur * 2; + cur += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr + cur); + cur += delta; /* form error * 5 */ + bpreverr = belowerr + cur; + belowerr = bnexterr; + cur += delta; /* form error * 7 */ + /* At this point cur contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + input_ptr += dirnc; /* advance input ptr to next column */ + output_ptr += dir; /* advance output ptr to next column */ + errorptr += dir; /* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error value into the + * final fserrors[] entry. Note we need not unload belowerr because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr; /* unload prev err into array */ + } + cquantize->on_odd_row = (cquantize->on_odd_row ? FALSE : TRUE); + } +} + + +/* + * Allocate workspace for Floyd-Steinberg errors. + */ + +LOCAL(void) +alloc_fs_workspace (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR)); + for (i = 0; i < cinfo->out_color_components; i++) { + cquantize->fserrors[i] = (FSERRPTR) + (*cinfo->mem->alloc_large)((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize); + } +} + + +/* + * Initialize for one-pass color quantization. + */ + +METHODDEF(void) +start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + /* Install my colormap. */ + cinfo->colormap = cquantize->sv_colormap; + cinfo->actual_number_of_colors = cquantize->sv_actual; + + /* Initialize for desired dithering mode. */ + switch (cinfo->dither_mode) { + case JDITHER_NONE: + if (cinfo->out_color_components == 3) + cquantize->pub.color_quantize = color_quantize3; + else + cquantize->pub.color_quantize = color_quantize; + break; + case JDITHER_ORDERED: + if (cinfo->out_color_components == 3) + cquantize->pub.color_quantize = quantize3_ord_dither; + else + cquantize->pub.color_quantize = quantize_ord_dither; + cquantize->row_index = 0; /* initialize state for ordered dither */ + /* If user changed to ordered dither from another mode, + * we must recreate the color index table with padding. + * This will cost extra space, but probably isn't very likely. + */ + if (! cquantize->is_padded) + create_colorindex(cinfo); + /* Create ordered-dither tables if we didn't already. */ + if (cquantize->odither[0] == NULL) + create_odither_tables(cinfo); + break; + case JDITHER_FS: + cquantize->pub.color_quantize = quantize_fs_dither; + cquantize->on_odd_row = FALSE; /* initialize state for F-S dither */ + /* Allocate Floyd-Steinberg workspace if didn't already. */ + if (cquantize->fserrors[0] == NULL) + alloc_fs_workspace(cinfo); + /* Initialize the propagated errors to zero. */ + arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR)); + for (i = 0; i < cinfo->out_color_components; i++) + jzero_far((void FAR *) cquantize->fserrors[i], arraysize); + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } +} + + +/* + * Finish up at the end of the pass. + */ + +METHODDEF(void) +finish_pass_1_quant (j_decompress_ptr cinfo) +{ + /* no work in 1-pass case */ +} + + +/* + * Switch to a new external colormap between output passes. + * Shouldn't get to this module! + */ + +METHODDEF(void) +new_color_map_1_quant (j_decompress_ptr cinfo) +{ + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + + +/* + * Module initialization routine for 1-pass color quantization. + */ + +GLOBAL(void) +jinit_1pass_quantizer (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize; + + cquantize = (my_cquantize_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_cquantizer)); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_1_quant; + cquantize->pub.finish_pass = finish_pass_1_quant; + cquantize->pub.new_color_map = new_color_map_1_quant; + cquantize->fserrors[0] = NULL; /* Flag FS workspace not allocated */ + cquantize->odither[0] = NULL; /* Also flag odither arrays not allocated */ + + /* Make sure my internal arrays won't overflow */ + if (cinfo->out_color_components > MAX_Q_COMPS) + ERREXIT1(cinfo, JERR_QUANT_COMPONENTS, MAX_Q_COMPS); + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if (cinfo->desired_number_of_colors > (MAXJSAMPLE+1)) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXJSAMPLE+1); + + /* Create the colormap and color index table. */ + create_colormap(cinfo); + create_colorindex(cinfo); + + /* Allocate Floyd-Steinberg workspace now if requested. + * We do this now since it is FAR storage and may affect the memory + * manager's space calculations. If the user changes to FS dither + * mode in a later pass, we will allocate the space then, and will + * possibly overrun the max_memory_to_use setting. + */ + if (cinfo->dither_mode == JDITHER_FS) + alloc_fs_workspace(cinfo); +} + +#endif /* QUANT_1PASS_SUPPORTED */ diff --git a/src/jpeg-8c/jquant2.c b/src/jpeg-8c/jquant2.c new file mode 100644 index 0000000..af601e3 --- /dev/null +++ b/src/jpeg-8c/jquant2.c @@ -0,0 +1,1310 @@ +/* + * jquant2.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 2-pass color quantization (color mapping) routines. + * These routines provide selection of a custom color map for an image, + * followed by mapping of the image to that color map, with optional + * Floyd-Steinberg dithering. + * It is also possible to use just the second pass to map to an arbitrary + * externally-given color map. + * + * Note: ordered dithering is not supported, since there isn't any fast + * way to compute intercolor distances; it's unclear that ordered dither's + * fundamental assumptions even hold with an irregularly spaced color map. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_2PASS_SUPPORTED + + +/* + * This module implements the well-known Heckbert paradigm for color + * quantization. Most of the ideas used here can be traced back to + * Heckbert's seminal paper + * Heckbert, Paul. "Color Image Quantization for Frame Buffer Display", + * Proc. SIGGRAPH '82, Computer Graphics v.16 #3 (July 1982), pp 297-304. + * + * In the first pass over the image, we accumulate a histogram showing the + * usage count of each possible color. To keep the histogram to a reasonable + * size, we reduce the precision of the input; typical practice is to retain + * 5 or 6 bits per color, so that 8 or 4 different input values are counted + * in the same histogram cell. + * + * Next, the color-selection step begins with a box representing the whole + * color space, and repeatedly splits the "largest" remaining box until we + * have as many boxes as desired colors. Then the mean color in each + * remaining box becomes one of the possible output colors. + * + * The second pass over the image maps each input pixel to the closest output + * color (optionally after applying a Floyd-Steinberg dithering correction). + * This mapping is logically trivial, but making it go fast enough requires + * considerable care. + * + * Heckbert-style quantizers vary a good deal in their policies for choosing + * the "largest" box and deciding where to cut it. The particular policies + * used here have proved out well in experimental comparisons, but better ones + * may yet be found. + * + * In earlier versions of the IJG code, this module quantized in YCbCr color + * space, processing the raw upsampled data without a color conversion step. + * This allowed the color conversion math to be done only once per colormap + * entry, not once per pixel. However, that optimization precluded other + * useful optimizations (such as merging color conversion with upsampling) + * and it also interfered with desired capabilities such as quantizing to an + * externally-supplied colormap. We have therefore abandoned that approach. + * The present code works in the post-conversion color space, typically RGB. + * + * To improve the visual quality of the results, we actually work in scaled + * RGB space, giving G distances more weight than R, and R in turn more than + * B. To do everything in integer math, we must use integer scale factors. + * The 2/3/1 scale factors used here correspond loosely to the relative + * weights of the colors in the NTSC grayscale equation. + * If you want to use this code to quantize a non-RGB color space, you'll + * probably need to change these scale factors. + */ + +#define R_SCALE 2 /* scale R distances by this much */ +#define G_SCALE 3 /* scale G distances by this much */ +#define B_SCALE 1 /* and B by this much */ + +/* Relabel R/G/B as components 0/1/2, respecting the RGB ordering defined + * in jmorecfg.h. As the code stands, it will do the right thing for R,G,B + * and B,G,R orders. If you define some other weird order in jmorecfg.h, + * you'll get compile errors until you extend this logic. In that case + * you'll probably want to tweak the histogram sizes too. + */ + +#if RGB_RED == 0 +#define C0_SCALE R_SCALE +#endif +#if RGB_BLUE == 0 +#define C0_SCALE B_SCALE +#endif +#if RGB_GREEN == 1 +#define C1_SCALE G_SCALE +#endif +#if RGB_RED == 2 +#define C2_SCALE R_SCALE +#endif +#if RGB_BLUE == 2 +#define C2_SCALE B_SCALE +#endif + + +/* + * First we have the histogram data structure and routines for creating it. + * + * The number of bits of precision can be adjusted by changing these symbols. + * We recommend keeping 6 bits for G and 5 each for R and B. + * If you have plenty of memory and cycles, 6 bits all around gives marginally + * better results; if you are short of memory, 5 bits all around will save + * some space but degrade the results. + * To maintain a fully accurate histogram, we'd need to allocate a "long" + * (preferably unsigned long) for each cell. In practice this is overkill; + * we can get by with 16 bits per cell. Few of the cell counts will overflow, + * and clamping those that do overflow to the maximum value will give close- + * enough results. This reduces the recommended histogram size from 256Kb + * to 128Kb, which is a useful savings on PC-class machines. + * (In the second pass the histogram space is re-used for pixel mapping data; + * in that capacity, each cell must be able to store zero to the number of + * desired colors. 16 bits/cell is plenty for that too.) + * Since the JPEG code is intended to run in small memory model on 80x86 + * machines, we can't just allocate the histogram in one chunk. Instead + * of a true 3-D array, we use a row of pointers to 2-D arrays. Each + * pointer corresponds to a C0 value (typically 2^5 = 32 pointers) and + * each 2-D array has 2^6*2^5 = 2048 or 2^6*2^6 = 4096 entries. Note that + * on 80x86 machines, the pointer row is in near memory but the actual + * arrays are in far memory (same arrangement as we use for image arrays). + */ + +#define MAXNUMCOLORS (MAXJSAMPLE+1) /* maximum size of colormap */ + +/* These will do the right thing for either R,G,B or B,G,R color order, + * but you may not like the results for other color orders. + */ +#define HIST_C0_BITS 5 /* bits of precision in R/B histogram */ +#define HIST_C1_BITS 6 /* bits of precision in G histogram */ +#define HIST_C2_BITS 5 /* bits of precision in B/R histogram */ + +/* Number of elements along histogram axes. */ +#define HIST_C0_ELEMS (1<cquantize; + register JSAMPROW ptr; + register histptr histp; + register hist3d histogram = cquantize->histogram; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + ptr = input_buf[row]; + for (col = width; col > 0; col--) { + /* get pixel value and index into the histogram */ + histp = & histogram[GETJSAMPLE(ptr[0]) >> C0_SHIFT] + [GETJSAMPLE(ptr[1]) >> C1_SHIFT] + [GETJSAMPLE(ptr[2]) >> C2_SHIFT]; + /* increment, check for overflow and undo increment if so. */ + if (++(*histp) <= 0) + (*histp)--; + ptr += 3; + } + } +} + + +/* + * Next we have the really interesting routines: selection of a colormap + * given the completed histogram. + * These routines work with a list of "boxes", each representing a rectangular + * subset of the input color space (to histogram precision). + */ + +typedef struct { + /* The bounds of the box (inclusive); expressed as histogram indexes */ + int c0min, c0max; + int c1min, c1max; + int c2min, c2max; + /* The volume (actually 2-norm) of the box */ + INT32 volume; + /* The number of nonzero histogram cells within this box */ + long colorcount; +} box; + +typedef box * boxptr; + + +LOCAL(boxptr) +find_biggest_color_pop (boxptr boxlist, int numboxes) +/* Find the splittable box with the largest color population */ +/* Returns NULL if no splittable boxes remain */ +{ + register boxptr boxp; + register int i; + register long maxc = 0; + boxptr which = NULL; + + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->colorcount > maxc && boxp->volume > 0) { + which = boxp; + maxc = boxp->colorcount; + } + } + return which; +} + + +LOCAL(boxptr) +find_biggest_volume (boxptr boxlist, int numboxes) +/* Find the splittable box with the largest (scaled) volume */ +/* Returns NULL if no splittable boxes remain */ +{ + register boxptr boxp; + register int i; + register INT32 maxv = 0; + boxptr which = NULL; + + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->volume > maxv) { + which = boxp; + maxv = boxp->volume; + } + } + return which; +} + + +LOCAL(void) +update_box (j_decompress_ptr cinfo, boxptr boxp) +/* Shrink the min/max bounds of a box to enclose only nonzero elements, */ +/* and recompute its volume and population */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0,c1,c2; + int c0min,c0max,c1min,c1max,c2min,c2max; + INT32 dist0,dist1,dist2; + long ccount; + + c0min = boxp->c0min; c0max = boxp->c0max; + c1min = boxp->c1min; c1max = boxp->c1max; + c2min = boxp->c2min; c2max = boxp->c2max; + + if (c0max > c0min) + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c0min = c0min = c0; + goto have_c0min; + } + } + have_c0min: + if (c0max > c0min) + for (c0 = c0max; c0 >= c0min; c0--) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c0max = c0max = c0; + goto have_c0max; + } + } + have_c0max: + if (c1max > c1min) + for (c1 = c1min; c1 <= c1max; c1++) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c1min = c1min = c1; + goto have_c1min; + } + } + have_c1min: + if (c1max > c1min) + for (c1 = c1max; c1 >= c1min; c1--) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c1max = c1max = c1; + goto have_c1max; + } + } + have_c1max: + if (c2max > c2min) + for (c2 = c2min; c2 <= c2max; c2++) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1min][c2]; + for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) + if (*histp != 0) { + boxp->c2min = c2min = c2; + goto have_c2min; + } + } + have_c2min: + if (c2max > c2min) + for (c2 = c2max; c2 >= c2min; c2--) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1min][c2]; + for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) + if (*histp != 0) { + boxp->c2max = c2max = c2; + goto have_c2max; + } + } + have_c2max: + + /* Update box volume. + * We use 2-norm rather than real volume here; this biases the method + * against making long narrow boxes, and it has the side benefit that + * a box is splittable iff norm > 0. + * Since the differences are expressed in histogram-cell units, + * we have to shift back to JSAMPLE units to get consistent distances; + * after which, we scale according to the selected distance scale factors. + */ + dist0 = ((c0max - c0min) << C0_SHIFT) * C0_SCALE; + dist1 = ((c1max - c1min) << C1_SHIFT) * C1_SCALE; + dist2 = ((c2max - c2min) << C2_SHIFT) * C2_SCALE; + boxp->volume = dist0*dist0 + dist1*dist1 + dist2*dist2; + + /* Now scan remaining volume of box and compute population */ + ccount = 0; + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++, histp++) + if (*histp != 0) { + ccount++; + } + } + boxp->colorcount = ccount; +} + + +LOCAL(int) +median_cut (j_decompress_ptr cinfo, boxptr boxlist, int numboxes, + int desired_colors) +/* Repeatedly select and split the largest box until we have enough boxes */ +{ + int n,lb; + int c0,c1,c2,cmax; + register boxptr b1,b2; + + while (numboxes < desired_colors) { + /* Select box to split. + * Current algorithm: by population for first half, then by volume. + */ + if (numboxes*2 <= desired_colors) { + b1 = find_biggest_color_pop(boxlist, numboxes); + } else { + b1 = find_biggest_volume(boxlist, numboxes); + } + if (b1 == NULL) /* no splittable boxes left! */ + break; + b2 = &boxlist[numboxes]; /* where new box will go */ + /* Copy the color bounds to the new box. */ + b2->c0max = b1->c0max; b2->c1max = b1->c1max; b2->c2max = b1->c2max; + b2->c0min = b1->c0min; b2->c1min = b1->c1min; b2->c2min = b1->c2min; + /* Choose which axis to split the box on. + * Current algorithm: longest scaled axis. + * See notes in update_box about scaling distances. + */ + c0 = ((b1->c0max - b1->c0min) << C0_SHIFT) * C0_SCALE; + c1 = ((b1->c1max - b1->c1min) << C1_SHIFT) * C1_SCALE; + c2 = ((b1->c2max - b1->c2min) << C2_SHIFT) * C2_SCALE; + /* We want to break any ties in favor of green, then red, blue last. + * This code does the right thing for R,G,B or B,G,R color orders only. + */ +#if RGB_RED == 0 + cmax = c1; n = 1; + if (c0 > cmax) { cmax = c0; n = 0; } + if (c2 > cmax) { n = 2; } +#else + cmax = c1; n = 1; + if (c2 > cmax) { cmax = c2; n = 2; } + if (c0 > cmax) { n = 0; } +#endif + /* Choose split point along selected axis, and update box bounds. + * Current algorithm: split at halfway point. + * (Since the box has been shrunk to minimum volume, + * any split will produce two nonempty subboxes.) + * Note that lb value is max for lower box, so must be < old max. + */ + switch (n) { + case 0: + lb = (b1->c0max + b1->c0min) / 2; + b1->c0max = lb; + b2->c0min = lb+1; + break; + case 1: + lb = (b1->c1max + b1->c1min) / 2; + b1->c1max = lb; + b2->c1min = lb+1; + break; + case 2: + lb = (b1->c2max + b1->c2min) / 2; + b1->c2max = lb; + b2->c2min = lb+1; + break; + } + /* Update stats for boxes */ + update_box(cinfo, b1); + update_box(cinfo, b2); + numboxes++; + } + return numboxes; +} + + +LOCAL(void) +compute_color (j_decompress_ptr cinfo, boxptr boxp, int icolor) +/* Compute representative color for a box, put it in colormap[icolor] */ +{ + /* Current algorithm: mean weighted by pixels (not colors) */ + /* Note it is important to get the rounding correct! */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0,c1,c2; + int c0min,c0max,c1min,c1max,c2min,c2max; + long count; + long total = 0; + long c0total = 0; + long c1total = 0; + long c2total = 0; + + c0min = boxp->c0min; c0max = boxp->c0max; + c1min = boxp->c1min; c1max = boxp->c1max; + c2min = boxp->c2min; c2max = boxp->c2max; + + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) { + if ((count = *histp++) != 0) { + total += count; + c0total += ((c0 << C0_SHIFT) + ((1<>1)) * count; + c1total += ((c1 << C1_SHIFT) + ((1<>1)) * count; + c2total += ((c2 << C2_SHIFT) + ((1<>1)) * count; + } + } + } + + cinfo->colormap[0][icolor] = (JSAMPLE) ((c0total + (total>>1)) / total); + cinfo->colormap[1][icolor] = (JSAMPLE) ((c1total + (total>>1)) / total); + cinfo->colormap[2][icolor] = (JSAMPLE) ((c2total + (total>>1)) / total); +} + + +LOCAL(void) +select_colors (j_decompress_ptr cinfo, int desired_colors) +/* Master routine for color selection */ +{ + boxptr boxlist; + int numboxes; + int i; + + /* Allocate workspace for box list */ + boxlist = (boxptr) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, desired_colors * SIZEOF(box)); + /* Initialize one box containing whole space */ + numboxes = 1; + boxlist[0].c0min = 0; + boxlist[0].c0max = MAXJSAMPLE >> C0_SHIFT; + boxlist[0].c1min = 0; + boxlist[0].c1max = MAXJSAMPLE >> C1_SHIFT; + boxlist[0].c2min = 0; + boxlist[0].c2max = MAXJSAMPLE >> C2_SHIFT; + /* Shrink it to actually-used volume and set its statistics */ + update_box(cinfo, & boxlist[0]); + /* Perform median-cut to produce final box list */ + numboxes = median_cut(cinfo, boxlist, numboxes, desired_colors); + /* Compute the representative color for each box, fill colormap */ + for (i = 0; i < numboxes; i++) + compute_color(cinfo, & boxlist[i], i); + cinfo->actual_number_of_colors = numboxes; + TRACEMS1(cinfo, 1, JTRC_QUANT_SELECTED, numboxes); +} + + +/* + * These routines are concerned with the time-critical task of mapping input + * colors to the nearest color in the selected colormap. + * + * We re-use the histogram space as an "inverse color map", essentially a + * cache for the results of nearest-color searches. All colors within a + * histogram cell will be mapped to the same colormap entry, namely the one + * closest to the cell's center. This may not be quite the closest entry to + * the actual input color, but it's almost as good. A zero in the cache + * indicates we haven't found the nearest color for that cell yet; the array + * is cleared to zeroes before starting the mapping pass. When we find the + * nearest color for a cell, its colormap index plus one is recorded in the + * cache for future use. The pass2 scanning routines call fill_inverse_cmap + * when they need to use an unfilled entry in the cache. + * + * Our method of efficiently finding nearest colors is based on the "locally + * sorted search" idea described by Heckbert and on the incremental distance + * calculation described by Spencer W. Thomas in chapter III.1 of Graphics + * Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that + * the distances from a given colormap entry to each cell of the histogram can + * be computed quickly using an incremental method: the differences between + * distances to adjacent cells themselves differ by a constant. This allows a + * fairly fast implementation of the "brute force" approach of computing the + * distance from every colormap entry to every histogram cell. Unfortunately, + * it needs a work array to hold the best-distance-so-far for each histogram + * cell (because the inner loop has to be over cells, not colormap entries). + * The work array elements have to be INT32s, so the work array would need + * 256Kb at our recommended precision. This is not feasible in DOS machines. + * + * To get around these problems, we apply Thomas' method to compute the + * nearest colors for only the cells within a small subbox of the histogram. + * The work array need be only as big as the subbox, so the memory usage + * problem is solved. Furthermore, we need not fill subboxes that are never + * referenced in pass2; many images use only part of the color gamut, so a + * fair amount of work is saved. An additional advantage of this + * approach is that we can apply Heckbert's locality criterion to quickly + * eliminate colormap entries that are far away from the subbox; typically + * three-fourths of the colormap entries are rejected by Heckbert's criterion, + * and we need not compute their distances to individual cells in the subbox. + * The speed of this approach is heavily influenced by the subbox size: too + * small means too much overhead, too big loses because Heckbert's criterion + * can't eliminate as many colormap entries. Empirically the best subbox + * size seems to be about 1/512th of the histogram (1/8th in each direction). + * + * Thomas' article also describes a refined method which is asymptotically + * faster than the brute-force method, but it is also far more complex and + * cannot efficiently be applied to small subboxes. It is therefore not + * useful for programs intended to be portable to DOS machines. On machines + * with plenty of memory, filling the whole histogram in one shot with Thomas' + * refined method might be faster than the present code --- but then again, + * it might not be any faster, and it's certainly more complicated. + */ + + +/* log2(histogram cells in update box) for each axis; this can be adjusted */ +#define BOX_C0_LOG (HIST_C0_BITS-3) +#define BOX_C1_LOG (HIST_C1_BITS-3) +#define BOX_C2_LOG (HIST_C2_BITS-3) + +#define BOX_C0_ELEMS (1<actual_number_of_colors; + int maxc0, maxc1, maxc2; + int centerc0, centerc1, centerc2; + int i, x, ncolors; + INT32 minmaxdist, min_dist, max_dist, tdist; + INT32 mindist[MAXNUMCOLORS]; /* min distance to colormap entry i */ + + /* Compute true coordinates of update box's upper corner and center. + * Actually we compute the coordinates of the center of the upper-corner + * histogram cell, which are the upper bounds of the volume we care about. + * Note that since ">>" rounds down, the "center" values may be closer to + * min than to max; hence comparisons to them must be "<=", not "<". + */ + maxc0 = minc0 + ((1 << BOX_C0_SHIFT) - (1 << C0_SHIFT)); + centerc0 = (minc0 + maxc0) >> 1; + maxc1 = minc1 + ((1 << BOX_C1_SHIFT) - (1 << C1_SHIFT)); + centerc1 = (minc1 + maxc1) >> 1; + maxc2 = minc2 + ((1 << BOX_C2_SHIFT) - (1 << C2_SHIFT)); + centerc2 = (minc2 + maxc2) >> 1; + + /* For each color in colormap, find: + * 1. its minimum squared-distance to any point in the update box + * (zero if color is within update box); + * 2. its maximum squared-distance to any point in the update box. + * Both of these can be found by considering only the corners of the box. + * We save the minimum distance for each color in mindist[]; + * only the smallest maximum distance is of interest. + */ + minmaxdist = 0x7FFFFFFFL; + + for (i = 0; i < numcolors; i++) { + /* We compute the squared-c0-distance term, then add in the other two. */ + x = GETJSAMPLE(cinfo->colormap[0][i]); + if (x < minc0) { + tdist = (x - minc0) * C0_SCALE; + min_dist = tdist*tdist; + tdist = (x - maxc0) * C0_SCALE; + max_dist = tdist*tdist; + } else if (x > maxc0) { + tdist = (x - maxc0) * C0_SCALE; + min_dist = tdist*tdist; + tdist = (x - minc0) * C0_SCALE; + max_dist = tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + min_dist = 0; + if (x <= centerc0) { + tdist = (x - maxc0) * C0_SCALE; + max_dist = tdist*tdist; + } else { + tdist = (x - minc0) * C0_SCALE; + max_dist = tdist*tdist; + } + } + + x = GETJSAMPLE(cinfo->colormap[1][i]); + if (x < minc1) { + tdist = (x - minc1) * C1_SCALE; + min_dist += tdist*tdist; + tdist = (x - maxc1) * C1_SCALE; + max_dist += tdist*tdist; + } else if (x > maxc1) { + tdist = (x - maxc1) * C1_SCALE; + min_dist += tdist*tdist; + tdist = (x - minc1) * C1_SCALE; + max_dist += tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + if (x <= centerc1) { + tdist = (x - maxc1) * C1_SCALE; + max_dist += tdist*tdist; + } else { + tdist = (x - minc1) * C1_SCALE; + max_dist += tdist*tdist; + } + } + + x = GETJSAMPLE(cinfo->colormap[2][i]); + if (x < minc2) { + tdist = (x - minc2) * C2_SCALE; + min_dist += tdist*tdist; + tdist = (x - maxc2) * C2_SCALE; + max_dist += tdist*tdist; + } else if (x > maxc2) { + tdist = (x - maxc2) * C2_SCALE; + min_dist += tdist*tdist; + tdist = (x - minc2) * C2_SCALE; + max_dist += tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + if (x <= centerc2) { + tdist = (x - maxc2) * C2_SCALE; + max_dist += tdist*tdist; + } else { + tdist = (x - minc2) * C2_SCALE; + max_dist += tdist*tdist; + } + } + + mindist[i] = min_dist; /* save away the results */ + if (max_dist < minmaxdist) + minmaxdist = max_dist; + } + + /* Now we know that no cell in the update box is more than minmaxdist + * away from some colormap entry. Therefore, only colors that are + * within minmaxdist of some part of the box need be considered. + */ + ncolors = 0; + for (i = 0; i < numcolors; i++) { + if (mindist[i] <= minmaxdist) + colorlist[ncolors++] = (JSAMPLE) i; + } + return ncolors; +} + + +LOCAL(void) +find_best_colors (j_decompress_ptr cinfo, int minc0, int minc1, int minc2, + int numcolors, JSAMPLE colorlist[], JSAMPLE bestcolor[]) +/* Find the closest colormap entry for each cell in the update box, + * given the list of candidate colors prepared by find_nearby_colors. + * Return the indexes of the closest entries in the bestcolor[] array. + * This routine uses Thomas' incremental distance calculation method to + * find the distance from a colormap entry to successive cells in the box. + */ +{ + int ic0, ic1, ic2; + int i, icolor; + register INT32 * bptr; /* pointer into bestdist[] array */ + JSAMPLE * cptr; /* pointer into bestcolor[] array */ + INT32 dist0, dist1; /* initial distance values */ + register INT32 dist2; /* current distance in inner loop */ + INT32 xx0, xx1; /* distance increments */ + register INT32 xx2; + INT32 inc0, inc1, inc2; /* initial values for increments */ + /* This array holds the distance to the nearest-so-far color for each cell */ + INT32 bestdist[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Initialize best-distance for each cell of the update box */ + bptr = bestdist; + for (i = BOX_C0_ELEMS*BOX_C1_ELEMS*BOX_C2_ELEMS-1; i >= 0; i--) + *bptr++ = 0x7FFFFFFFL; + + /* For each color selected by find_nearby_colors, + * compute its distance to the center of each cell in the box. + * If that's less than best-so-far, update best distance and color number. + */ + + /* Nominal steps between cell centers ("x" in Thomas article) */ +#define STEP_C0 ((1 << C0_SHIFT) * C0_SCALE) +#define STEP_C1 ((1 << C1_SHIFT) * C1_SCALE) +#define STEP_C2 ((1 << C2_SHIFT) * C2_SCALE) + + for (i = 0; i < numcolors; i++) { + icolor = GETJSAMPLE(colorlist[i]); + /* Compute (square of) distance from minc0/c1/c2 to this color */ + inc0 = (minc0 - GETJSAMPLE(cinfo->colormap[0][icolor])) * C0_SCALE; + dist0 = inc0*inc0; + inc1 = (minc1 - GETJSAMPLE(cinfo->colormap[1][icolor])) * C1_SCALE; + dist0 += inc1*inc1; + inc2 = (minc2 - GETJSAMPLE(cinfo->colormap[2][icolor])) * C2_SCALE; + dist0 += inc2*inc2; + /* Form the initial difference increments */ + inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0; + inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1; + inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2; + /* Now loop over all cells in box, updating distance per Thomas method */ + bptr = bestdist; + cptr = bestcolor; + xx0 = inc0; + for (ic0 = BOX_C0_ELEMS-1; ic0 >= 0; ic0--) { + dist1 = dist0; + xx1 = inc1; + for (ic1 = BOX_C1_ELEMS-1; ic1 >= 0; ic1--) { + dist2 = dist1; + xx2 = inc2; + for (ic2 = BOX_C2_ELEMS-1; ic2 >= 0; ic2--) { + if (dist2 < *bptr) { + *bptr = dist2; + *cptr = (JSAMPLE) icolor; + } + dist2 += xx2; + xx2 += 2 * STEP_C2 * STEP_C2; + bptr++; + cptr++; + } + dist1 += xx1; + xx1 += 2 * STEP_C1 * STEP_C1; + } + dist0 += xx0; + xx0 += 2 * STEP_C0 * STEP_C0; + } + } +} + + +LOCAL(void) +fill_inverse_cmap (j_decompress_ptr cinfo, int c0, int c1, int c2) +/* Fill the inverse-colormap entries in the update box that contains */ +/* histogram cell c0/c1/c2. (Only that one cell MUST be filled, but */ +/* we can fill as many others as we wish.) */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int minc0, minc1, minc2; /* lower left corner of update box */ + int ic0, ic1, ic2; + register JSAMPLE * cptr; /* pointer into bestcolor[] array */ + register histptr cachep; /* pointer into main cache array */ + /* This array lists the candidate colormap indexes. */ + JSAMPLE colorlist[MAXNUMCOLORS]; + int numcolors; /* number of candidate colors */ + /* This array holds the actually closest colormap index for each cell. */ + JSAMPLE bestcolor[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Convert cell coordinates to update box ID */ + c0 >>= BOX_C0_LOG; + c1 >>= BOX_C1_LOG; + c2 >>= BOX_C2_LOG; + + /* Compute true coordinates of update box's origin corner. + * Actually we compute the coordinates of the center of the corner + * histogram cell, which are the lower bounds of the volume we care about. + */ + minc0 = (c0 << BOX_C0_SHIFT) + ((1 << C0_SHIFT) >> 1); + minc1 = (c1 << BOX_C1_SHIFT) + ((1 << C1_SHIFT) >> 1); + minc2 = (c2 << BOX_C2_SHIFT) + ((1 << C2_SHIFT) >> 1); + + /* Determine which colormap entries are close enough to be candidates + * for the nearest entry to some cell in the update box. + */ + numcolors = find_nearby_colors(cinfo, minc0, minc1, minc2, colorlist); + + /* Determine the actually nearest colors. */ + find_best_colors(cinfo, minc0, minc1, minc2, numcolors, colorlist, + bestcolor); + + /* Save the best color numbers (plus 1) in the main cache array */ + c0 <<= BOX_C0_LOG; /* convert ID back to base cell indexes */ + c1 <<= BOX_C1_LOG; + c2 <<= BOX_C2_LOG; + cptr = bestcolor; + for (ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) { + for (ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) { + cachep = & histogram[c0+ic0][c1+ic1][c2]; + for (ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) { + *cachep++ = (histcell) (GETJSAMPLE(*cptr++) + 1); + } + } + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF(void) +pass2_no_dither (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) +/* This version performs no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register JSAMPROW inptr, outptr; + register histptr cachep; + register int c0, c1, c2; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + inptr = input_buf[row]; + outptr = output_buf[row]; + for (col = width; col > 0; col--) { + /* get pixel value and index into the cache */ + c0 = GETJSAMPLE(*inptr++) >> C0_SHIFT; + c1 = GETJSAMPLE(*inptr++) >> C1_SHIFT; + c2 = GETJSAMPLE(*inptr++) >> C2_SHIFT; + cachep = & histogram[c0][c1][c2]; + /* If we have not seen this color before, find nearest colormap entry */ + /* and update the cache */ + if (*cachep == 0) + fill_inverse_cmap(cinfo, c0,c1,c2); + /* Now emit the colormap index for this cell */ + *outptr++ = (JSAMPLE) (*cachep - 1); + } + } +} + + +METHODDEF(void) +pass2_fs_dither (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) +/* This version performs Floyd-Steinberg dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register LOCFSERROR cur0, cur1, cur2; /* current error or pixel value */ + LOCFSERROR belowerr0, belowerr1, belowerr2; /* error for pixel below cur */ + LOCFSERROR bpreverr0, bpreverr1, bpreverr2; /* error for below/prev col */ + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + JSAMPROW inptr; /* => current input pixel */ + JSAMPROW outptr; /* => current output pixel */ + histptr cachep; + int dir; /* +1 or -1 depending on direction */ + int dir3; /* 3*dir, for advancing inptr & errorptr */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE *range_limit = cinfo->sample_range_limit; + int *error_limit = cquantize->error_limiter; + JSAMPROW colormap0 = cinfo->colormap[0]; + JSAMPROW colormap1 = cinfo->colormap[1]; + JSAMPROW colormap2 = cinfo->colormap[2]; + SHIFT_TEMPS + + for (row = 0; row < num_rows; row++) { + inptr = input_buf[row]; + outptr = output_buf[row]; + if (cquantize->on_odd_row) { + /* work right to left in this row */ + inptr += (width-1) * 3; /* so point to rightmost pixel */ + outptr += width-1; + dir = -1; + dir3 = -3; + errorptr = cquantize->fserrors + (width+1)*3; /* => entry after last column */ + cquantize->on_odd_row = FALSE; /* flip for next time */ + } else { + /* work left to right in this row */ + dir = 1; + dir3 = 3; + errorptr = cquantize->fserrors; /* => entry before first real column */ + cquantize->on_odd_row = TRUE; /* flip for next time */ + } + /* Preset error values: no error propagated to first pixel from left */ + cur0 = cur1 = cur2 = 0; + /* and no error propagated to row below yet */ + belowerr0 = belowerr1 = belowerr2 = 0; + bpreverr0 = bpreverr1 = bpreverr2 = 0; + + for (col = width; col > 0; col--) { + /* curN holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur0 = RIGHT_SHIFT(cur0 + errorptr[dir3+0] + 8, 4); + cur1 = RIGHT_SHIFT(cur1 + errorptr[dir3+1] + 8, 4); + cur2 = RIGHT_SHIFT(cur2 + errorptr[dir3+2] + 8, 4); + /* Limit the error using transfer function set by init_error_limit. + * See comments with init_error_limit for rationale. + */ + cur0 = error_limit[cur0]; + cur1 = error_limit[cur1]; + cur2 = error_limit[cur2]; + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE (or less with error limiting); + * this sets the required size of the range_limit array. + */ + cur0 += GETJSAMPLE(inptr[0]); + cur1 += GETJSAMPLE(inptr[1]); + cur2 += GETJSAMPLE(inptr[2]); + cur0 = GETJSAMPLE(range_limit[cur0]); + cur1 = GETJSAMPLE(range_limit[cur1]); + cur2 = GETJSAMPLE(range_limit[cur2]); + /* Index into the cache with adjusted pixel value */ + cachep = & histogram[cur0>>C0_SHIFT][cur1>>C1_SHIFT][cur2>>C2_SHIFT]; + /* If we have not seen this color before, find nearest colormap */ + /* entry and update the cache */ + if (*cachep == 0) + fill_inverse_cmap(cinfo, cur0>>C0_SHIFT,cur1>>C1_SHIFT,cur2>>C2_SHIFT); + /* Now emit the colormap index for this cell */ + { register int pixcode = *cachep - 1; + *outptr = (JSAMPLE) pixcode; + /* Compute representation error for this pixel */ + cur0 -= GETJSAMPLE(colormap0[pixcode]); + cur1 -= GETJSAMPLE(colormap1[pixcode]); + cur2 -= GETJSAMPLE(colormap2[pixcode]); + } + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + { register LOCFSERROR bnexterr, delta; + + bnexterr = cur0; /* Process component 0 */ + delta = cur0 * 2; + cur0 += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr0 + cur0); + cur0 += delta; /* form error * 5 */ + bpreverr0 = belowerr0 + cur0; + belowerr0 = bnexterr; + cur0 += delta; /* form error * 7 */ + bnexterr = cur1; /* Process component 1 */ + delta = cur1 * 2; + cur1 += delta; /* form error * 3 */ + errorptr[1] = (FSERROR) (bpreverr1 + cur1); + cur1 += delta; /* form error * 5 */ + bpreverr1 = belowerr1 + cur1; + belowerr1 = bnexterr; + cur1 += delta; /* form error * 7 */ + bnexterr = cur2; /* Process component 2 */ + delta = cur2 * 2; + cur2 += delta; /* form error * 3 */ + errorptr[2] = (FSERROR) (bpreverr2 + cur2); + cur2 += delta; /* form error * 5 */ + bpreverr2 = belowerr2 + cur2; + belowerr2 = bnexterr; + cur2 += delta; /* form error * 7 */ + } + /* At this point curN contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + inptr += dir3; /* Advance pixel pointers to next column */ + outptr += dir; + errorptr += dir3; /* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error values into the + * final fserrors[] entry. Note we need not unload belowerrN because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr0; /* unload prev errs into array */ + errorptr[1] = (FSERROR) bpreverr1; + errorptr[2] = (FSERROR) bpreverr2; + } +} + + +/* + * Initialize the error-limiting transfer function (lookup table). + * The raw F-S error computation can potentially compute error values of up to + * +- MAXJSAMPLE. But we want the maximum correction applied to a pixel to be + * much less, otherwise obviously wrong pixels will be created. (Typical + * effects include weird fringes at color-area boundaries, isolated bright + * pixels in a dark area, etc.) The standard advice for avoiding this problem + * is to ensure that the "corners" of the color cube are allocated as output + * colors; then repeated errors in the same direction cannot cause cascading + * error buildup. However, that only prevents the error from getting + * completely out of hand; Aaron Giles reports that error limiting improves + * the results even with corner colors allocated. + * A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty + * well, but the smoother transfer function used below is even better. Thanks + * to Aaron Giles for this idea. + */ + +LOCAL(void) +init_error_limit (j_decompress_ptr cinfo) +/* Allocate and fill in the error_limiter table */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + int * table; + int in, out; + + table = (int *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, (MAXJSAMPLE*2+1) * SIZEOF(int)); + table += MAXJSAMPLE; /* so can index -MAXJSAMPLE .. +MAXJSAMPLE */ + cquantize->error_limiter = table; + +#define STEPSIZE ((MAXJSAMPLE+1)/16) + /* Map errors 1:1 up to +- MAXJSAMPLE/16 */ + out = 0; + for (in = 0; in < STEPSIZE; in++, out++) { + table[in] = out; table[-in] = -out; + } + /* Map errors 1:2 up to +- 3*MAXJSAMPLE/16 */ + for (; in < STEPSIZE*3; in++, out += (in&1) ? 0 : 1) { + table[in] = out; table[-in] = -out; + } + /* Clamp the rest to final out value (which is (MAXJSAMPLE+1)/8) */ + for (; in <= MAXJSAMPLE; in++) { + table[in] = out; table[-in] = -out; + } +#undef STEPSIZE +} + + +/* + * Finish up at the end of each pass. + */ + +METHODDEF(void) +finish_pass1 (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + + /* Select the representative colors and fill in cinfo->colormap */ + cinfo->colormap = cquantize->sv_colormap; + select_colors(cinfo, cquantize->desired); + /* Force next pass to zero the color index table */ + cquantize->needs_zeroed = TRUE; +} + + +METHODDEF(void) +finish_pass2 (j_decompress_ptr cinfo) +{ + /* no work */ +} + + +/* + * Initialize for each processing pass. + */ + +METHODDEF(void) +start_pass_2_quant (j_decompress_ptr cinfo, boolean is_pre_scan) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int i; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if (cinfo->dither_mode != JDITHER_NONE) + cinfo->dither_mode = JDITHER_FS; + + if (is_pre_scan) { + /* Set up method pointers */ + cquantize->pub.color_quantize = prescan_quantize; + cquantize->pub.finish_pass = finish_pass1; + cquantize->needs_zeroed = TRUE; /* Always zero histogram */ + } else { + /* Set up method pointers */ + if (cinfo->dither_mode == JDITHER_FS) + cquantize->pub.color_quantize = pass2_fs_dither; + else + cquantize->pub.color_quantize = pass2_no_dither; + cquantize->pub.finish_pass = finish_pass2; + + /* Make sure color count is acceptable */ + i = cinfo->actual_number_of_colors; + if (i < 1) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 1); + if (i > MAXNUMCOLORS) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS); + + if (cinfo->dither_mode == JDITHER_FS) { + size_t arraysize = (size_t) ((cinfo->output_width + 2) * + (3 * SIZEOF(FSERROR))); + /* Allocate Floyd-Steinberg workspace if we didn't already. */ + if (cquantize->fserrors == NULL) + cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize); + /* Initialize the propagated errors to zero. */ + jzero_far((void FAR *) cquantize->fserrors, arraysize); + /* Make the error-limit table if we didn't already. */ + if (cquantize->error_limiter == NULL) + init_error_limit(cinfo); + cquantize->on_odd_row = FALSE; + } + + } + /* Zero the histogram or inverse color map, if necessary */ + if (cquantize->needs_zeroed) { + for (i = 0; i < HIST_C0_ELEMS; i++) { + jzero_far((void FAR *) histogram[i], + HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell)); + } + cquantize->needs_zeroed = FALSE; + } +} + + +/* + * Switch to a new external colormap between output passes. + */ + +METHODDEF(void) +new_color_map_2_quant (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + + /* Reset the inverse color map */ + cquantize->needs_zeroed = TRUE; +} + + +/* + * Module initialization routine for 2-pass color quantization. + */ + +GLOBAL(void) +jinit_2pass_quantizer (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize; + int i; + + cquantize = (my_cquantize_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_cquantizer)); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_2_quant; + cquantize->pub.new_color_map = new_color_map_2_quant; + cquantize->fserrors = NULL; /* flag optional arrays not allocated */ + cquantize->error_limiter = NULL; + + /* Make sure jdmaster didn't give me a case I can't handle */ + if (cinfo->out_color_components != 3) + ERREXIT(cinfo, JERR_NOTIMPL); + + /* Allocate the histogram/inverse colormap storage */ + cquantize->histogram = (hist3d) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, HIST_C0_ELEMS * SIZEOF(hist2d)); + for (i = 0; i < HIST_C0_ELEMS; i++) { + cquantize->histogram[i] = (hist2d) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell)); + } + cquantize->needs_zeroed = TRUE; /* histogram is garbage now */ + + /* Allocate storage for the completed colormap, if required. + * We do this now since it is FAR storage and may affect + * the memory manager's space calculations. + */ + if (cinfo->enable_2pass_quant) { + /* Make sure color count is acceptable */ + int desired = cinfo->desired_number_of_colors; + /* Lower bound on # of colors ... somewhat arbitrary as long as > 0 */ + if (desired < 8) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 8); + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if (desired > MAXNUMCOLORS) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS); + cquantize->sv_colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo,JPOOL_IMAGE, (JDIMENSION) desired, (JDIMENSION) 3); + cquantize->desired = desired; + } else + cquantize->sv_colormap = NULL; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if (cinfo->dither_mode != JDITHER_NONE) + cinfo->dither_mode = JDITHER_FS; + + /* Allocate Floyd-Steinberg workspace if necessary. + * This isn't really needed until pass 2, but again it is FAR storage. + * Although we will cope with a later change in dither_mode, + * we do not promise to honor max_memory_to_use if dither_mode changes. + */ + if (cinfo->dither_mode == JDITHER_FS) { + cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) ((cinfo->output_width + 2) * (3 * SIZEOF(FSERROR)))); + /* Might as well create the error-limiting table too. */ + init_error_limit(cinfo); + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ diff --git a/src/jpeg-8c/jutils.c b/src/jpeg-8c/jutils.c new file mode 100644 index 0000000..0435179 --- /dev/null +++ b/src/jpeg-8c/jutils.c @@ -0,0 +1,231 @@ +/* + * jutils.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * Modified 2009 by Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains tables and miscellaneous utility routines needed + * for both compression and decompression. + * Note we prefix all global names with "j" to minimize conflicts with + * a surrounding application. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + * of a DCT block read in natural order (left to right, top to bottom). + */ + +#if 0 /* This table is not actually needed in v6a */ + +const int jpeg_zigzag_order[DCTSIZE2] = { + 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 +}; + +#endif + +/* + * jpeg_natural_order[i] is the natural-order position of the i'th element + * of zigzag order. + * + * When reading corrupted data, the Huffman decoders could attempt + * to reference an entry beyond the end of this array (if the decoded + * zero run length reaches past the end of the block). To prevent + * wild stores without adding an inner-loop test, we put some extra + * "63"s after the real entries. This will cause the extra coefficient + * to be stored in location 63 of the block, not somewhere random. + * The worst case would be a run-length of 15, which means we need 16 + * fake entries. + */ + +const int jpeg_natural_order[DCTSIZE2+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + +const int jpeg_natural_order7[7*7+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 14, 21, 28, 35, + 42, 49, 50, 43, 36, 29, 22, 30, + 37, 44, 51, 52, 45, 38, 46, 53, + 54, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + +const int jpeg_natural_order6[6*6+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 41, 34, 27, + 20, 13, 21, 28, 35, 42, 43, 36, + 29, 37, 44, 45, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + +const int jpeg_natural_order5[5*5+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 12, + 19, 26, 33, 34, 27, 20, 28, 35, + 36, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + +const int jpeg_natural_order4[4*4+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 25, 18, 11, 19, 26, 27, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + +const int jpeg_natural_order3[3*3+16] = { + 0, 1, 8, 16, 9, 2, 10, 17, + 18, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + +const int jpeg_natural_order2[2*2+16] = { + 0, 1, 8, 9, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + + +/* + * Arithmetic utilities + */ + +GLOBAL(long) +jdiv_round_up (long a, long b) +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ +{ + return (a + b - 1L) / b; +} + + +GLOBAL(long) +jround_up (long a, long b) +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ +/* Assumes a >= 0, b > 0 */ +{ + a += b - 1L; + return a - (a % b); +} + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines below do it the hard way. (The performance cost + * is not all that great, because these routines aren't very heavily used.) + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) +#define FMEMZERO(target,size) MEMZERO(target,size) +#else /* 80x86 case, define if we can */ +#ifdef USE_FMEM +#define FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) +#define FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) +#endif +#endif + + +GLOBAL(void) +jcopy_sample_rows (JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols) +/* Copy some rows of samples from one place to another. + * num_rows rows are copied from input_array[source_row++] + * to output_array[dest_row++]; these areas may overlap for duplication. + * The source and destination arrays must be at least as wide as num_cols. + */ +{ + register JSAMPROW inptr, outptr; +#ifdef FMEMCOPY + register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); +#else + register JDIMENSION count; +#endif + register int row; + + input_array += source_row; + output_array += dest_row; + + for (row = num_rows; row > 0; row--) { + inptr = *input_array++; + outptr = *output_array++; +#ifdef FMEMCOPY + FMEMCOPY(outptr, inptr, count); +#else + for (count = num_cols; count > 0; count--) + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ +#endif + } +} + + +GLOBAL(void) +jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks) +/* Copy a row of coefficient blocks from one place to another. */ +{ +#ifdef FMEMCOPY + FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); +#else + register JCOEFPTR inptr, outptr; + register long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) { + *outptr++ = *inptr++; + } +#endif +} + + +GLOBAL(void) +jzero_far (void FAR * target, size_t bytestozero) +/* Zero out a chunk of FAR memory. */ +/* This might be sample-array data, block-array data, or alloc_large data. */ +{ +#ifdef FMEMZERO + FMEMZERO(target, bytestozero); +#else + register char FAR * ptr = (char FAR *) target; + register size_t count; + + for (count = bytestozero; count > 0; count--) { + *ptr++ = 0; + } +#endif +} diff --git a/src/jpeg-8c/jversion.h b/src/jpeg-8c/jversion.h new file mode 100644 index 0000000..e868538 --- /dev/null +++ b/src/jpeg-8c/jversion.h @@ -0,0 +1,14 @@ +/* + * jversion.h + * + * Copyright (C) 1991-2011, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains software version identification. + */ + + +#define JVERSION "8c 16-Jan-2011" + +#define JCOPYRIGHT "Copyright (C) 2011, Thomas G. Lane, Guido Vollbeding" diff --git a/src/libcurl/curl/curl.h b/src/libcurl/curl/curl.h new file mode 100644 index 0000000..a0a0414 --- /dev/null +++ b/src/libcurl/curl/curl.h @@ -0,0 +1,1563 @@ +#ifndef __CURL_CURL_H +#define __CURL_CURL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2006, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: curl.h,v 1.304 2006-08-04 16:08:41 giva Exp $ + ***************************************************************************/ + +/* If you have problems, all libcurl docs and details are found here: + http://curl.haxx.se/libcurl/ +*/ + +#include "curlver.h" /* the libcurl version defines */ + +#include +#include + +/* The include stuff here below is mainly for time_t! */ +#ifdef vms +# include +# include +#else +# include +# include +#endif /* defined (vms) */ + +typedef void CURL; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Decorate exportable functions for Win32 DLL linking. + * This avoids using a .def file for building libcurl.dll. + */ +#if (defined(WIN32) || defined(_WIN32)) && !defined(CURL_STATICLIB) +#if defined(BUILDING_LIBCURL) +#define CURL_EXTERN __declspec(dllexport) +#else +#define CURL_EXTERN __declspec(dllimport) +#endif +#else + +#ifdef CURL_HIDDEN_SYMBOLS +/* + * This definition is used to make external definitions visibile in the + * shared library when symbols are hidden by default. It makes no + * difference when compiling applications whether this is set or not, + * only when compiling the library. + */ +#define CURL_EXTERN CURL_EXTERN_SYMBOL +#else +#define CURL_EXTERN +#endif +#endif + +/* + * We want the typedef curl_off_t setup for large file support on all + * platforms. We also provide a CURL_FORMAT_OFF_T define to use in *printf + * format strings when outputting a variable of type curl_off_t. + * + * Note: "pocc -Ze" is MSVC compatibily mode and this sets _MSC_VER! + */ + +#if (defined(_MSC_VER) && !defined(__POCC__)) || (defined(__LCC__) && defined(WIN32)) +/* MSVC */ +#ifdef _WIN32_WCE + typedef long curl_off_t; +#define CURL_FORMAT_OFF_T "%ld" +#else + typedef signed __int64 curl_off_t; +#define CURL_FORMAT_OFF_T "%I64d" +#endif +#else /* (_MSC_VER && !__POCC__) || (__LCC__ && WIN32) */ +#if (defined(__GNUC__) && defined(WIN32)) || defined(__WATCOMC__) +/* gcc on windows or Watcom */ + typedef long long curl_off_t; +#define CURL_FORMAT_OFF_T "%I64d" +#else /* GCC or Watcom on Windows */ + +/* "normal" POSIX approach, do note that this does not necessarily mean that + the type is >32 bits, see the SIZEOF_CURL_OFF_T define for that! */ + typedef off_t curl_off_t; + +/* Check a range of defines to detect large file support. On Linux it seems + none of these are set by default, so if you don't explicitly switches on + large file support, this define will be made for "small file" support. */ +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 0 /* to prevent warnings in the check below */ +#define UNDEF_FILE_OFFSET_BITS +#endif +#ifndef FILESIZEBITS +#define FILESIZEBITS 0 /* to prevent warnings in the check below */ +#define UNDEF_FILESIZEBITS +#endif + +#if defined(_LARGE_FILES) || (_FILE_OFFSET_BITS > 32) || (FILESIZEBITS > 32) \ + || defined(_LARGEFILE_SOURCE) || defined(_LARGEFILE64_SOURCE) + /* For now, we assume at least one of these to be set for large files to + work! */ +#define CURL_FORMAT_OFF_T "%lld" +#else /* LARGE_FILE support */ +#define CURL_FORMAT_OFF_T "%ld" +#endif +#endif /* GCC or Watcom on Windows */ +#endif /* (_MSC_VER && !__POCC__) || (__LCC__ && WIN32) */ + +#ifdef UNDEF_FILE_OFFSET_BITS +/* this was defined above for our checks, undefine it again */ +#undef _FILE_OFFSET_BITS +#endif + +#ifdef UNDEF_FILESIZEBITS +/* this was defined above for our checks, undefine it again */ +#undef FILESIZEBITS +#endif + +struct curl_httppost { + struct curl_httppost *next; /* next entry in the list */ + char *name; /* pointer to allocated name */ + long namelength; /* length of name length */ + char *contents; /* pointer to allocated data contents */ + long contentslength; /* length of contents field */ + char *buffer; /* pointer to allocated buffer contents */ + long bufferlength; /* length of buffer field */ + char *contenttype; /* Content-Type */ + struct curl_slist* contentheader; /* list of extra headers for this form */ + struct curl_httppost *more; /* if one field name has more than one + file, this link should link to following + files */ + long flags; /* as defined below */ +#define HTTPPOST_FILENAME (1<<0) /* specified content is a file name */ +#define HTTPPOST_READFILE (1<<1) /* specified content is a file name */ +#define HTTPPOST_PTRNAME (1<<2) /* name is only stored pointer + do not free in formfree */ +#define HTTPPOST_PTRCONTENTS (1<<3) /* contents is only stored pointer + do not free in formfree */ +#define HTTPPOST_BUFFER (1<<4) /* upload file from buffer */ +#define HTTPPOST_PTRBUFFER (1<<5) /* upload file from pointer contents */ + + char *showfilename; /* The file name to show. If not set, the + actual file name will be used (if this + is a file part) */ +}; + +typedef int (*curl_progress_callback)(void *clientp, + double dltotal, + double dlnow, + double ultotal, + double ulnow); + + /* Tests have proven that 20K is a very bad buffer size for uploads on + Windows, while 16K for some odd reason performed a lot better. */ +#define CURL_MAX_WRITE_SIZE 16384 + +typedef size_t (*curl_write_callback)(char *buffer, + size_t size, + size_t nitems, + void *outstream); + +/* This is a return code for the read callback that, when returned, will + signal libcurl to immediately abort the current transfer. */ +#define CURL_READFUNC_ABORT 0x10000000 +typedef size_t (*curl_read_callback)(char *buffer, + size_t size, + size_t nitems, + void *instream); + + +#ifndef CURL_NO_OLDIES + /* not used since 7.10.8, will be removed in a future release */ +typedef int (*curl_passwd_callback)(void *clientp, + const char *prompt, + char *buffer, + int buflen); +#endif + +typedef enum { + CURLIOE_OK, /* I/O operation successful */ + CURLIOE_UNKNOWNCMD, /* command was unknown to callback */ + CURLIOE_FAILRESTART, /* failed to restart the read */ + CURLIOE_LAST /* never use */ +} curlioerr; + +typedef enum { + CURLIOCMD_NOP, /* no operation */ + CURLIOCMD_RESTARTREAD, /* restart the read stream from start */ + CURLIOCMD_LAST /* never use */ +} curliocmd; + +typedef curlioerr (*curl_ioctl_callback)(CURL *handle, + int cmd, + void *clientp); + +/* + * The following typedef's are signatures of malloc, free, realloc, strdup and + * calloc respectively. Function pointers of these types can be passed to the + * curl_global_init_mem() function to set user defined memory management + * callback routines. + */ +typedef void *(*curl_malloc_callback)(size_t size); +typedef void (*curl_free_callback)(void *ptr); +typedef void *(*curl_realloc_callback)(void *ptr, size_t size); +typedef char *(*curl_strdup_callback)(const char *str); +typedef void *(*curl_calloc_callback)(size_t nmemb, size_t size); + +/* the kind of data that is passed to information_callback*/ +typedef enum { + CURLINFO_TEXT = 0, + CURLINFO_HEADER_IN, /* 1 */ + CURLINFO_HEADER_OUT, /* 2 */ + CURLINFO_DATA_IN, /* 3 */ + CURLINFO_DATA_OUT, /* 4 */ + CURLINFO_SSL_DATA_IN, /* 5 */ + CURLINFO_SSL_DATA_OUT, /* 6 */ + CURLINFO_END +} curl_infotype; + +typedef int (*curl_debug_callback) + (CURL *handle, /* the handle/transfer this concerns */ + curl_infotype type, /* what kind of data */ + char *data, /* points to the data */ + size_t size, /* size of the data pointed to */ + void *userptr); /* whatever the user please */ + +/* All possible error codes from all sorts of curl functions. Future versions + may return other values, stay prepared. + + Always add new return codes last. Never *EVER* remove any. The return + codes must remain the same! + */ + +typedef enum { + CURLE_OK = 0, + CURLE_UNSUPPORTED_PROTOCOL, /* 1 */ + CURLE_FAILED_INIT, /* 2 */ + CURLE_URL_MALFORMAT, /* 3 */ + CURLE_URL_MALFORMAT_USER, /* 4 - NOT USED */ + CURLE_COULDNT_RESOLVE_PROXY, /* 5 */ + CURLE_COULDNT_RESOLVE_HOST, /* 6 */ + CURLE_COULDNT_CONNECT, /* 7 */ + CURLE_FTP_WEIRD_SERVER_REPLY, /* 8 */ + CURLE_FTP_ACCESS_DENIED, /* 9 a service was denied by the FTP server + due to lack of access - when login fails + this is not returned. */ + CURLE_FTP_USER_PASSWORD_INCORRECT, /* 10 - NOT USED */ + CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */ + CURLE_FTP_WEIRD_USER_REPLY, /* 12 */ + CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */ + CURLE_FTP_WEIRD_227_FORMAT, /* 14 */ + CURLE_FTP_CANT_GET_HOST, /* 15 */ + CURLE_FTP_CANT_RECONNECT, /* 16 */ + CURLE_FTP_COULDNT_SET_BINARY, /* 17 */ + CURLE_PARTIAL_FILE, /* 18 */ + CURLE_FTP_COULDNT_RETR_FILE, /* 19 */ + CURLE_FTP_WRITE_ERROR, /* 20 */ + CURLE_FTP_QUOTE_ERROR, /* 21 */ + CURLE_HTTP_RETURNED_ERROR, /* 22 */ + CURLE_WRITE_ERROR, /* 23 */ + CURLE_MALFORMAT_USER, /* 24 - NOT USED */ + CURLE_FTP_COULDNT_STOR_FILE, /* 25 - failed FTP upload */ + CURLE_READ_ERROR, /* 26 - could open/read from file */ + CURLE_OUT_OF_MEMORY, /* 27 */ + /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error + instead of a memory allocation error if CURL_DOES_CONVERSIONS + is defined + */ + CURLE_OPERATION_TIMEOUTED, /* 28 - the timeout time was reached */ + CURLE_FTP_COULDNT_SET_ASCII, /* 29 - TYPE A failed */ + CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ + CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ + CURLE_FTP_COULDNT_GET_SIZE, /* 32 - the SIZE command failed */ + CURLE_HTTP_RANGE_ERROR, /* 33 - RANGE "command" didn't work */ + CURLE_HTTP_POST_ERROR, /* 34 */ + CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ + CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */ + CURLE_FILE_COULDNT_READ_FILE, /* 37 */ + CURLE_LDAP_CANNOT_BIND, /* 38 */ + CURLE_LDAP_SEARCH_FAILED, /* 39 */ + CURLE_LIBRARY_NOT_FOUND, /* 40 */ + CURLE_FUNCTION_NOT_FOUND, /* 41 */ + CURLE_ABORTED_BY_CALLBACK, /* 42 */ + CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */ + CURLE_BAD_CALLING_ORDER, /* 44 - NOT USED */ + CURLE_INTERFACE_FAILED, /* 45 - CURLOPT_INTERFACE failed */ + CURLE_BAD_PASSWORD_ENTERED, /* 46 - NOT USED */ + CURLE_TOO_MANY_REDIRECTS , /* 47 - catch endless re-direct loops */ + CURLE_UNKNOWN_TELNET_OPTION, /* 48 - User specified an unknown option */ + CURLE_TELNET_OPTION_SYNTAX , /* 49 - Malformed telnet option */ + CURLE_OBSOLETE, /* 50 - NOT USED */ + CURLE_SSL_PEER_CERTIFICATE, /* 51 - peer's certificate wasn't ok */ + CURLE_GOT_NOTHING, /* 52 - when this is a specific error */ + CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */ + CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as + default */ + CURLE_SEND_ERROR, /* 55 - failed sending network data */ + CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ + CURLE_SHARE_IN_USE, /* 57 - share is in use */ + CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ + CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */ + CURLE_SSL_CACERT, /* 60 - problem with the CA cert (path?) */ + CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized transfer encoding */ + CURLE_LDAP_INVALID_URL, /* 62 - Invalid LDAP URL */ + CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ + CURLE_FTP_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ + CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind + that failed */ + CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ + CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not + accepted and we failed to login */ + CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */ + CURLE_TFTP_PERM, /* 69 - permission problem on server */ + CURLE_TFTP_DISKFULL, /* 70 - out of disk space on server */ + CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */ + CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */ + CURLE_TFTP_EXISTS, /* 73 - File already exists */ + CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */ + CURLE_CONV_FAILED, /* 75 - conversion failed */ + CURLE_CONV_REQD, /* 76 - caller must register conversion + callbacks using curl_easy_setopt options + CURLOPT_CONV_FROM_NETWORK_FUNCTION, + CURLOPT_CONV_TO_NETWORK_FUNCTION, and + CURLOPT_CONV_FROM_UTF8_FUNCTION */ + CURL_LAST /* never use! */ +} CURLcode; + +/* This prototype applies to all conversion callbacks */ +typedef CURLcode (*curl_conv_callback)(char *buffer, size_t length); + +typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl, /* easy handle */ + void *ssl_ctx, /* actually an + OpenSSL SSL_CTX */ + void *userptr); + +/* Make a spelling correction for the operation timed-out define */ +#define CURLE_OPERATION_TIMEDOUT CURLE_OPERATION_TIMEOUTED + +#ifndef CURL_NO_OLDIES /* define this to test if your app builds with all + the obsolete stuff removed! */ +/* backwards compatibility with older names */ +#define CURLE_HTTP_NOT_FOUND CURLE_HTTP_RETURNED_ERROR +#define CURLE_HTTP_PORT_FAILED CURLE_INTERFACE_FAILED +#endif + +typedef enum { + CURLPROXY_HTTP = 0, + CURLPROXY_SOCKS4 = 4, + CURLPROXY_SOCKS5 = 5 +} curl_proxytype; + +#define CURLAUTH_NONE 0 /* nothing */ +#define CURLAUTH_BASIC (1<<0) /* Basic (default) */ +#define CURLAUTH_DIGEST (1<<1) /* Digest */ +#define CURLAUTH_GSSNEGOTIATE (1<<2) /* GSS-Negotiate */ +#define CURLAUTH_NTLM (1<<3) /* NTLM */ +#define CURLAUTH_ANY ~0 /* all types set */ +#define CURLAUTH_ANYSAFE (~CURLAUTH_BASIC) + +#ifndef CURL_NO_OLDIES /* define this to test if your app builds with all + the obsolete stuff removed! */ +/* this was the error code 50 in 7.7.3 and a few earlier versions, this + is no longer used by libcurl but is instead #defined here only to not + make programs break */ +#define CURLE_ALREADY_COMPLETE 99999 + +/* These are just to make older programs not break: */ +#define CURLE_FTP_PARTIAL_FILE CURLE_PARTIAL_FILE +#define CURLE_FTP_BAD_DOWNLOAD_RESUME CURLE_BAD_DOWNLOAD_RESUME +#endif + +#define CURL_ERROR_SIZE 256 + +/* parameter for the CURLOPT_FTP_SSL option */ +typedef enum { + CURLFTPSSL_NONE, /* do not attempt to use SSL */ + CURLFTPSSL_TRY, /* try using SSL, proceed anyway otherwise */ + CURLFTPSSL_CONTROL, /* SSL for the control connection or fail */ + CURLFTPSSL_ALL, /* SSL for all communication or fail */ + CURLFTPSSL_LAST /* not an option, never use */ +} curl_ftpssl; + +/* parameter for the CURLOPT_FTPSSLAUTH option */ +typedef enum { + CURLFTPAUTH_DEFAULT, /* let libcurl decide */ + CURLFTPAUTH_SSL, /* use "AUTH SSL" */ + CURLFTPAUTH_TLS, /* use "AUTH TLS" */ + CURLFTPAUTH_LAST /* not an option, never use */ +} curl_ftpauth; + +/* parameter for the CURLOPT_FTP_FILEMETHOD option */ +typedef enum { + CURLFTPMETHOD_DEFAULT, /* let libcurl pick */ + CURLFTPMETHOD_MULTICWD, /* single CWD operation for each path part */ + CURLFTPMETHOD_NOCWD, /* no CWD at all */ + CURLFTPMETHOD_SINGLECWD, /* one CWD to full dir, then work on file */ + CURLFTPMETHOD_LAST /* not an option, never use */ +} curl_ftpmethod; + +/* long may be 32 or 64 bits, but we should never depend on anything else + but 32 */ +#define CURLOPTTYPE_LONG 0 +#define CURLOPTTYPE_OBJECTPOINT 10000 +#define CURLOPTTYPE_FUNCTIONPOINT 20000 +#define CURLOPTTYPE_OFF_T 30000 + +/* name is uppercase CURLOPT_, + type is one of the defined CURLOPTTYPE_ + number is unique identifier */ +#ifdef CINIT +#undef CINIT +#endif +/* + * Figure out if we can use the ## operator, which is supported by ISO/ANSI C + * and C++. Some compilers support it without setting __STDC__ or __cplusplus + * so we need to carefully check for them too. We don't use configure-checks + * for these since we want these headers to remain generic and working for all + * platforms. + */ +#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) || \ + defined(__HP_aCC) || defined(__BORLANDC__) || defined(__LCC__) || \ + defined(__POCC__) || defined(__SALFORDC__) + /* This compiler is believed to have an ISO compatible preprocessor */ +#define CURL_ISOCPP +#else + /* This compiler is believed NOT to have an ISO compatible preprocessor */ +#undef CURL_ISOCPP +#endif + +#ifdef CURL_ISOCPP +#define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number +#else +/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +#define LONG CURLOPTTYPE_LONG +#define OBJECTPOINT CURLOPTTYPE_OBJECTPOINT +#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT +#define OFF_T CURLOPTTYPE_OFF_T +#define CINIT(name,type,number) CURLOPT_/**/name = type + number +#endif + +/* + * This macro-mania below setups the CURLOPT_[what] enum, to be used with + * curl_easy_setopt(). The first argument in the CINIT() macro is the [what] + * word. + */ + +typedef enum { + /* This is the FILE * or void * the regular output should be written to. */ + CINIT(FILE, OBJECTPOINT, 1), + + /* The full URL to get/put */ + CINIT(URL, OBJECTPOINT, 2), + + /* Port number to connect to, if other than default. */ + CINIT(PORT, LONG, 3), + + /* Name of proxy to use. */ + CINIT(PROXY, OBJECTPOINT, 4), + + /* "name:password" to use when fetching. */ + CINIT(USERPWD, OBJECTPOINT, 5), + + /* "name:password" to use with proxy. */ + CINIT(PROXYUSERPWD, OBJECTPOINT, 6), + + /* Range to get, specified as an ASCII string. */ + CINIT(RANGE, OBJECTPOINT, 7), + + /* not used */ + + /* Specified file stream to upload from (use as input): */ + CINIT(INFILE, OBJECTPOINT, 9), + + /* Buffer to receive error messages in, must be at least CURL_ERROR_SIZE + * bytes big. If this is not used, error messages go to stderr instead: */ + CINIT(ERRORBUFFER, OBJECTPOINT, 10), + + /* Function that will be called to store the output (instead of fwrite). The + * parameters will use fwrite() syntax, make sure to follow them. */ + CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11), + + /* Function that will be called to read the input (instead of fread). The + * parameters will use fread() syntax, make sure to follow them. */ + CINIT(READFUNCTION, FUNCTIONPOINT, 12), + + /* Time-out the read operation after this amount of seconds */ + CINIT(TIMEOUT, LONG, 13), + + /* If the CURLOPT_INFILE is used, this can be used to inform libcurl about + * how large the file being sent really is. That allows better error + * checking and better verifies that the upload was succcessful. -1 means + * unknown size. + * + * For large file support, there is also a _LARGE version of the key + * which takes an off_t type, allowing platforms with larger off_t + * sizes to handle larger files. See below for INFILESIZE_LARGE. + */ + CINIT(INFILESIZE, LONG, 14), + + /* POST input fields. */ + CINIT(POSTFIELDS, OBJECTPOINT, 15), + + /* Set the referer page (needed by some CGIs) */ + CINIT(REFERER, OBJECTPOINT, 16), + + /* Set the FTP PORT string (interface name, named or numerical IP address) + Use i.e '-' to use default address. */ + CINIT(FTPPORT, OBJECTPOINT, 17), + + /* Set the User-Agent string (examined by some CGIs) */ + CINIT(USERAGENT, OBJECTPOINT, 18), + + /* If the download receives less than "low speed limit" bytes/second + * during "low speed time" seconds, the operations is aborted. + * You could i.e if you have a pretty high speed connection, abort if + * it is less than 2000 bytes/sec during 20 seconds. + */ + + /* Set the "low speed limit" */ + CINIT(LOW_SPEED_LIMIT, LONG , 19), + + /* Set the "low speed time" */ + CINIT(LOW_SPEED_TIME, LONG, 20), + + /* Set the continuation offset. + * + * Note there is also a _LARGE version of this key which uses + * off_t types, allowing for large file offsets on platforms which + * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. + */ + CINIT(RESUME_FROM, LONG, 21), + + /* Set cookie in request: */ + CINIT(COOKIE, OBJECTPOINT, 22), + + /* This points to a linked list of headers, struct curl_slist kind */ + CINIT(HTTPHEADER, OBJECTPOINT, 23), + + /* This points to a linked list of post entries, struct HttpPost */ + CINIT(HTTPPOST, OBJECTPOINT, 24), + + /* name of the file keeping your private SSL-certificate */ + CINIT(SSLCERT, OBJECTPOINT, 25), + + /* password for the SSL-private key, keep this for compatibility */ + CINIT(SSLCERTPASSWD, OBJECTPOINT, 26), + /* password for the SSL private key */ + CINIT(SSLKEYPASSWD, OBJECTPOINT, 26), + + /* send TYPE parameter? */ + CINIT(CRLF, LONG, 27), + + /* send linked-list of QUOTE commands */ + CINIT(QUOTE, OBJECTPOINT, 28), + + /* send FILE * or void * to store headers to, if you use a callback it + is simply passed to the callback unmodified */ + CINIT(WRITEHEADER, OBJECTPOINT, 29), + + /* point to a file to read the initial cookies from, also enables + "cookie awareness" */ + CINIT(COOKIEFILE, OBJECTPOINT, 31), + + /* What version to specifly try to use. + See CURL_SSLVERSION defines below. */ + CINIT(SSLVERSION, LONG, 32), + + /* What kind of HTTP time condition to use, see defines */ + CINIT(TIMECONDITION, LONG, 33), + + /* Time to use with the above condition. Specified in number of seconds + since 1 Jan 1970 */ + CINIT(TIMEVALUE, LONG, 34), + + /* 35 = OBSOLETE */ + + /* Custom request, for customizing the get command like + HTTP: DELETE, TRACE and others + FTP: to use a different list command + */ + CINIT(CUSTOMREQUEST, OBJECTPOINT, 36), + + /* HTTP request, for odd commands like DELETE, TRACE and others */ + CINIT(STDERR, OBJECTPOINT, 37), + + /* 38 is not used */ + + /* send linked-list of post-transfer QUOTE commands */ + CINIT(POSTQUOTE, OBJECTPOINT, 39), + + /* Pass a pointer to string of the output using full variable-replacement + as described elsewhere. */ + CINIT(WRITEINFO, OBJECTPOINT, 40), + + CINIT(VERBOSE, LONG, 41), /* talk a lot */ + CINIT(HEADER, LONG, 42), /* throw the header out too */ + CINIT(NOPROGRESS, LONG, 43), /* shut off the progress meter */ + CINIT(NOBODY, LONG, 44), /* use HEAD to get http document */ + CINIT(FAILONERROR, LONG, 45), /* no output on http error codes >= 300 */ + CINIT(UPLOAD, LONG, 46), /* this is an upload */ + CINIT(POST, LONG, 47), /* HTTP POST method */ + CINIT(FTPLISTONLY, LONG, 48), /* Use NLST when listing ftp dir */ + + CINIT(FTPAPPEND, LONG, 50), /* Append instead of overwrite on upload! */ + + /* Specify whether to read the user+password from the .netrc or the URL. + * This must be one of the CURL_NETRC_* enums below. */ + CINIT(NETRC, LONG, 51), + + CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */ + + CINIT(TRANSFERTEXT, LONG, 53), /* transfer data in text/ASCII format */ + CINIT(PUT, LONG, 54), /* HTTP PUT */ + + /* 55 = OBSOLETE */ + + /* Function that will be called instead of the internal progress display + * function. This function should be defined as the curl_progress_callback + * prototype defines. */ + CINIT(PROGRESSFUNCTION, FUNCTIONPOINT, 56), + + /* Data passed to the progress callback */ + CINIT(PROGRESSDATA, OBJECTPOINT, 57), + + /* We want the referer field set automatically when following locations */ + CINIT(AUTOREFERER, LONG, 58), + + /* Port of the proxy, can be set in the proxy string as well with: + "[host]:[port]" */ + CINIT(PROXYPORT, LONG, 59), + + /* size of the POST input data, if strlen() is not good to use */ + CINIT(POSTFIELDSIZE, LONG, 60), + + /* tunnel non-http operations through a HTTP proxy */ + CINIT(HTTPPROXYTUNNEL, LONG, 61), + + /* Set the interface string to use as outgoing network interface */ + CINIT(INTERFACE, OBJECTPOINT, 62), + + /* Set the krb4 security level, this also enables krb4 awareness. This is a + * string, 'clear', 'safe', 'confidential' or 'private'. If the string is + * set but doesn't match one of these, 'private' will be used. */ + CINIT(KRB4LEVEL, OBJECTPOINT, 63), + + /* Set if we should verify the peer in ssl handshake, set 1 to verify. */ + CINIT(SSL_VERIFYPEER, LONG, 64), + + /* The CApath or CAfile used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + CINIT(CAINFO, OBJECTPOINT, 65), + + /* 66 = OBSOLETE */ + /* 67 = OBSOLETE */ + + /* Maximum number of http redirects to follow */ + CINIT(MAXREDIRS, LONG, 68), + + /* Pass a long set to 1 to get the date of the requested document (if + possible)! Pass a zero to shut it off. */ + CINIT(FILETIME, LONG, 69), + + /* This points to a linked list of telnet options */ + CINIT(TELNETOPTIONS, OBJECTPOINT, 70), + + /* Max amount of cached alive connections */ + CINIT(MAXCONNECTS, LONG, 71), + + /* What policy to use when closing connections when the cache is filled + up */ + CINIT(CLOSEPOLICY, LONG, 72), + + /* 73 = OBSOLETE */ + + /* Set to explicitly use a new connection for the upcoming transfer. + Do not use this unless you're absolutely sure of this, as it makes the + operation slower and is less friendly for the network. */ + CINIT(FRESH_CONNECT, LONG, 74), + + /* Set to explicitly forbid the upcoming transfer's connection to be re-used + when done. Do not use this unless you're absolutely sure of this, as it + makes the operation slower and is less friendly for the network. */ + CINIT(FORBID_REUSE, LONG, 75), + + /* Set to a file name that contains random data for libcurl to use to + seed the random engine when doing SSL connects. */ + CINIT(RANDOM_FILE, OBJECTPOINT, 76), + + /* Set to the Entropy Gathering Daemon socket pathname */ + CINIT(EGDSOCKET, OBJECTPOINT, 77), + + /* Time-out connect operations after this amount of seconds, if connects + are OK within this time, then fine... This only aborts the connect + phase. [Only works on unix-style/SIGALRM operating systems] */ + CINIT(CONNECTTIMEOUT, LONG, 78), + + /* Function that will be called to store headers (instead of fwrite). The + * parameters will use fwrite() syntax, make sure to follow them. */ + CINIT(HEADERFUNCTION, FUNCTIONPOINT, 79), + + /* Set this to force the HTTP request to get back to GET. Only really usable + if POST, PUT or a custom request have been used first. + */ + CINIT(HTTPGET, LONG, 80), + + /* Set if we should verify the Common name from the peer certificate in ssl + * handshake, set 1 to check existence, 2 to ensure that it matches the + * provided hostname. */ + CINIT(SSL_VERIFYHOST, LONG, 81), + + /* Specify which file name to write all known cookies in after completed + operation. Set file name to "-" (dash) to make it go to stdout. */ + CINIT(COOKIEJAR, OBJECTPOINT, 82), + + /* Specify which SSL ciphers to use */ + CINIT(SSL_CIPHER_LIST, OBJECTPOINT, 83), + + /* Specify which HTTP version to use! This must be set to one of the + CURL_HTTP_VERSION* enums set below. */ + CINIT(HTTP_VERSION, LONG, 84), + + /* Specificly switch on or off the FTP engine's use of the EPSV command. By + default, that one will always be attempted before the more traditional + PASV command. */ + CINIT(FTP_USE_EPSV, LONG, 85), + + /* type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") */ + CINIT(SSLCERTTYPE, OBJECTPOINT, 86), + + /* name of the file keeping your private SSL-key */ + CINIT(SSLKEY, OBJECTPOINT, 87), + + /* type of the file keeping your private SSL-key ("DER", "PEM", "ENG") */ + CINIT(SSLKEYTYPE, OBJECTPOINT, 88), + + /* crypto engine for the SSL-sub system */ + CINIT(SSLENGINE, OBJECTPOINT, 89), + + /* set the crypto engine for the SSL-sub system as default + the param has no meaning... + */ + CINIT(SSLENGINE_DEFAULT, LONG, 90), + + /* Non-zero value means to use the global dns cache */ + CINIT(DNS_USE_GLOBAL_CACHE, LONG, 91), /* To becomeO BSOLETE soon */ + + /* DNS cache timeout */ + CINIT(DNS_CACHE_TIMEOUT, LONG, 92), + + /* send linked-list of pre-transfer QUOTE commands (Wesley Laxton)*/ + CINIT(PREQUOTE, OBJECTPOINT, 93), + + /* set the debug function */ + CINIT(DEBUGFUNCTION, FUNCTIONPOINT, 94), + + /* set the data for the debug function */ + CINIT(DEBUGDATA, OBJECTPOINT, 95), + + /* mark this as start of a cookie session */ + CINIT(COOKIESESSION, LONG, 96), + + /* The CApath directory used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + CINIT(CAPATH, OBJECTPOINT, 97), + + /* Instruct libcurl to use a smaller receive buffer */ + CINIT(BUFFERSIZE, LONG, 98), + + /* Instruct libcurl to not use any signal/alarm handlers, even when using + timeouts. This option is useful for multi-threaded applications. + See libcurl-the-guide for more background information. */ + CINIT(NOSIGNAL, LONG, 99), + + /* Provide a CURLShare for mutexing non-ts data */ + CINIT(SHARE, OBJECTPOINT, 100), + + /* indicates type of proxy. accepted values are CURLPROXY_HTTP (default), + CURLPROXY_SOCKS4 and CURLPROXY_SOCKS5. */ + CINIT(PROXYTYPE, LONG, 101), + + /* Set the Accept-Encoding string. Use this to tell a server you would like + the response to be compressed. */ + CINIT(ENCODING, OBJECTPOINT, 102), + + /* Set pointer to private data */ + CINIT(PRIVATE, OBJECTPOINT, 103), + + /* Set aliases for HTTP 200 in the HTTP Response header */ + CINIT(HTTP200ALIASES, OBJECTPOINT, 104), + + /* Continue to send authentication (user+password) when following locations, + even when hostname changed. This can potentionally send off the name + and password to whatever host the server decides. */ + CINIT(UNRESTRICTED_AUTH, LONG, 105), + + /* Specificly switch on or off the FTP engine's use of the EPRT command ( it + also disables the LPRT attempt). By default, those ones will always be + attempted before the good old traditional PORT command. */ + CINIT(FTP_USE_EPRT, LONG, 106), + + /* Set this to a bitmask value to enable the particular authentications + methods you like. Use this in combination with CURLOPT_USERPWD. + Note that setting multiple bits may cause extra network round-trips. */ + CINIT(HTTPAUTH, LONG, 107), + + /* Set the ssl context callback function, currently only for OpenSSL ssl_ctx + in second argument. The function must be matching the + curl_ssl_ctx_callback proto. */ + CINIT(SSL_CTX_FUNCTION, FUNCTIONPOINT, 108), + + /* Set the userdata for the ssl context callback function's third + argument */ + CINIT(SSL_CTX_DATA, OBJECTPOINT, 109), + + /* FTP Option that causes missing dirs to be created on the remote server */ + CINIT(FTP_CREATE_MISSING_DIRS, LONG, 110), + + /* Set this to a bitmask value to enable the particular authentications + methods you like. Use this in combination with CURLOPT_PROXYUSERPWD. + Note that setting multiple bits may cause extra network round-trips. */ + CINIT(PROXYAUTH, LONG, 111), + + /* FTP option that changes the timeout, in seconds, associated with + getting a response. This is different from transfer timeout time and + essentially places a demand on the FTP server to acknowledge commands + in a timely manner. */ + CINIT(FTP_RESPONSE_TIMEOUT, LONG , 112), + + /* Set this option to one of the CURL_IPRESOLVE_* defines (see below) to + tell libcurl to resolve names to those IP versions only. This only has + affect on systems with support for more than one, i.e IPv4 _and_ IPv6. */ + CINIT(IPRESOLVE, LONG, 113), + + /* Set this option to limit the size of a file that will be downloaded from + an HTTP or FTP server. + + Note there is also _LARGE version which adds large file support for + platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ + CINIT(MAXFILESIZE, LONG, 114), + + /* See the comment for INFILESIZE above, but in short, specifies + * the size of the file being uploaded. -1 means unknown. + */ + CINIT(INFILESIZE_LARGE, OFF_T, 115), + + /* Sets the continuation offset. There is also a LONG version of this; + * look above for RESUME_FROM. + */ + CINIT(RESUME_FROM_LARGE, OFF_T, 116), + + /* Sets the maximum size of data that will be downloaded from + * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. + */ + CINIT(MAXFILESIZE_LARGE, OFF_T, 117), + + /* Set this option to the file name of your .netrc file you want libcurl + to parse (using the CURLOPT_NETRC option). If not set, libcurl will do + a poor attempt to find the user's home directory and check for a .netrc + file in there. */ + CINIT(NETRC_FILE, OBJECTPOINT, 118), + + /* Enable SSL/TLS for FTP, pick one of: + CURLFTPSSL_TRY - try using SSL, proceed anyway otherwise + CURLFTPSSL_CONTROL - SSL for the control connection or fail + CURLFTPSSL_ALL - SSL for all communication or fail + */ + CINIT(FTP_SSL, LONG, 119), + + /* The _LARGE version of the standard POSTFIELDSIZE option */ + CINIT(POSTFIELDSIZE_LARGE, OFF_T, 120), + + /* Enable/disable the TCP Nagle algorithm */ + CINIT(TCP_NODELAY, LONG, 121), + + /* 122 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + + /* When doing 3rd party transfer, set the source user and password with + this */ + CINIT(SOURCE_USERPWD, OBJECTPOINT, 123), + + /* 124 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 125 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 126 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + + /* When doing 3rd party transfer, set the source pre-quote linked list + of commands with this */ + CINIT(SOURCE_PREQUOTE, OBJECTPOINT, 127), + + /* When doing 3rd party transfer, set the source post-quote linked list + of commands with this */ + CINIT(SOURCE_POSTQUOTE, OBJECTPOINT, 128), + + /* When FTP over SSL/TLS is selected (with CURLOPT_FTP_SSL), this option + can be used to change libcurl's default action which is to first try + "AUTH SSL" and then "AUTH TLS" in this order, and proceed when a OK + response has been received. + + Available parameters are: + CURLFTPAUTH_DEFAULT - let libcurl decide + CURLFTPAUTH_SSL - try "AUTH SSL" first, then TLS + CURLFTPAUTH_TLS - try "AUTH TLS" first, then SSL + */ + CINIT(FTPSSLAUTH, LONG, 129), + + CINIT(IOCTLFUNCTION, FUNCTIONPOINT, 130), + CINIT(IOCTLDATA, OBJECTPOINT, 131), + + /* To make a 3rd party transfer, set the source URL with this */ + CINIT(SOURCE_URL, OBJECTPOINT, 132), + + /* When doing 3rd party transfer, set the source quote linked list of + commands with this */ + CINIT(SOURCE_QUOTE, OBJECTPOINT, 133), + + /* zero terminated string for pass on to the FTP server when asked for + "account" info */ + CINIT(FTP_ACCOUNT, OBJECTPOINT, 134), + + /* feed cookies into cookie engine */ + CINIT(COOKIELIST, OBJECTPOINT, 135), + + /* ignore Content-Length */ + CINIT(IGNORE_CONTENT_LENGTH, LONG, 136), + + /* Set to non-zero to skip the IP address received in a 227 PASV FTP server + response. Typically used for FTP-SSL purposes but is not restricted to + that. libcurl will then instead use the same IP address it used for the + control connection. */ + CINIT(FTP_SKIP_PASV_IP, LONG, 137), + + /* Select "file method" to use when doing FTP, see the curl_ftpmethod + above. */ + CINIT(FTP_FILEMETHOD, LONG, 138), + + /* Local port number to bind the socket to */ + CINIT(LOCALPORT, LONG, 139), + + /* Number of ports to try, including the first one set with LOCALPORT. + Thus, setting it to 1 will make no additional attempts but the first. + */ + CINIT(LOCALPORTRANGE, LONG, 140), + + /* no transfer, set up connection and let application use the socket by + extracting it with CURLINFO_LASTSOCKET */ + CINIT(CONNECT_ONLY, LONG, 141), + + /* Function that will be called to convert from the + network encoding (instead of using the iconv calls in libcurl) */ + CINIT(CONV_FROM_NETWORK_FUNCTION, FUNCTIONPOINT, 142), + + /* Function that will be called to convert to the + network encoding (instead of using the iconv calls in libcurl) */ + CINIT(CONV_TO_NETWORK_FUNCTION, FUNCTIONPOINT, 143), + + /* Function that will be called to convert from UTF8 + (instead of using the iconv calls in libcurl) + Note that this is used only for SSL certificate processing */ + CINIT(CONV_FROM_UTF8_FUNCTION, FUNCTIONPOINT, 144), + + /* if the connection proceeds too quickly then need to slow it down */ + /* limit-rate: maximum number of bytes per second to send or receive */ + CINIT(MAX_SEND_SPEED_LARGE, OFF_T, 145), + CINIT(MAX_RECV_SPEED_LARGE, OFF_T, 146), + + /* Pointer to command string to send if USER/PASS fails. */ + CINIT(FTP_ALTERNATIVE_TO_USER, OBJECTPOINT, 147), + + CURLOPT_LASTENTRY /* the last unused */ +} CURLoption; + + /* Below here follows defines for the CURLOPT_IPRESOLVE option. If a host + name resolves addresses using more than one IP protocol version, this + option might be handy to force libcurl to use a specific IP version. */ +#define CURL_IPRESOLVE_WHATEVER 0 /* default, resolves addresses to all IP + versions that your system allows */ +#define CURL_IPRESOLVE_V4 1 /* resolve to ipv4 addresses */ +#define CURL_IPRESOLVE_V6 2 /* resolve to ipv6 addresses */ + + /* three convenient "aliases" that follow the name scheme better */ +#define CURLOPT_WRITEDATA CURLOPT_FILE +#define CURLOPT_READDATA CURLOPT_INFILE +#define CURLOPT_HEADERDATA CURLOPT_WRITEHEADER + +#ifndef CURL_NO_OLDIES /* define this to test if your app builds with all + the obsolete stuff removed! */ +#define CURLOPT_HTTPREQUEST -1 +#define CURLOPT_FTPASCII CURLOPT_TRANSFERTEXT +#define CURLOPT_MUTE -2 +#define CURLOPT_PASSWDFUNCTION -3 +#define CURLOPT_PASSWDDATA -4 +#define CURLOPT_CLOSEFUNCTION -5 + +#define CURLOPT_SOURCE_HOST -6 +#define CURLOPT_SOURCE_PATH -7 +#define CURLOPT_SOURCE_PORT -8 +#define CURLOPT_PASV_HOST -9 + +#else +/* This is set if CURL_NO_OLDIES is defined at compile-time */ +#undef CURLOPT_DNS_USE_GLOBAL_CACHE /* soon obsolete */ +#endif + + + /* These enums are for use with the CURLOPT_HTTP_VERSION option. */ +enum { + CURL_HTTP_VERSION_NONE, /* setting this means we don't care, and that we'd + like the library to choose the best possible + for us! */ + CURL_HTTP_VERSION_1_0, /* please use HTTP 1.0 in the request */ + CURL_HTTP_VERSION_1_1, /* please use HTTP 1.1 in the request */ + + CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */ +}; + + /* These enums are for use with the CURLOPT_NETRC option. */ +enum CURL_NETRC_OPTION { + CURL_NETRC_IGNORED, /* The .netrc will never be read. + * This is the default. */ + CURL_NETRC_OPTIONAL, /* A user:password in the URL will be preferred + * to one in the .netrc. */ + CURL_NETRC_REQUIRED, /* A user:password in the URL will be ignored. + * Unless one is set programmatically, the .netrc + * will be queried. */ + CURL_NETRC_LAST +}; + +enum { + CURL_SSLVERSION_DEFAULT, + CURL_SSLVERSION_TLSv1, + CURL_SSLVERSION_SSLv2, + CURL_SSLVERSION_SSLv3, + + CURL_SSLVERSION_LAST /* never use, keep last */ +}; + + +typedef enum { + CURL_TIMECOND_NONE, + + CURL_TIMECOND_IFMODSINCE, + CURL_TIMECOND_IFUNMODSINCE, + CURL_TIMECOND_LASTMOD, + + CURL_TIMECOND_LAST +} curl_TimeCond; + +#ifdef __BEOS__ +#include +#endif + + +/* curl_strequal() and curl_strnequal() are subject for removal in a future + libcurl, see lib/README.curlx for details */ +CURL_EXTERN int (curl_strequal)(const char *s1, const char *s2); +CURL_EXTERN int (curl_strnequal)(const char *s1, const char *s2, size_t n); + +/* name is uppercase CURLFORM_ */ +#ifdef CFINIT +#undef CFINIT +#endif + +#ifdef CURL_ISOCPP +#define CFINIT(name) CURLFORM_ ## name +#else +/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +#define CFINIT(name) CURLFORM_/**/name +#endif + +typedef enum { + CFINIT(NOTHING), /********* the first one is unused ************/ + + /* */ + CFINIT(COPYNAME), + CFINIT(PTRNAME), + CFINIT(NAMELENGTH), + CFINIT(COPYCONTENTS), + CFINIT(PTRCONTENTS), + CFINIT(CONTENTSLENGTH), + CFINIT(FILECONTENT), + CFINIT(ARRAY), + CFINIT(OBSOLETE), + CFINIT(FILE), + + CFINIT(BUFFER), + CFINIT(BUFFERPTR), + CFINIT(BUFFERLENGTH), + + CFINIT(CONTENTTYPE), + CFINIT(CONTENTHEADER), + CFINIT(FILENAME), + CFINIT(END), + CFINIT(OBSOLETE2), + + CURLFORM_LASTENTRY /* the last unusued */ +} CURLformoption; + +#undef CFINIT /* done */ + +/* structure to be used as parameter for CURLFORM_ARRAY */ +struct curl_forms { + CURLformoption option; + const char *value; +}; + +/* use this for multipart formpost building */ +/* Returns code for curl_formadd() + * + * Returns: + * CURL_FORMADD_OK on success + * CURL_FORMADD_MEMORY if the FormInfo allocation fails + * CURL_FORMADD_OPTION_TWICE if one option is given twice for one Form + * CURL_FORMADD_NULL if a null pointer was given for a char + * CURL_FORMADD_MEMORY if the allocation of a FormInfo struct failed + * CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used + * CURL_FORMADD_INCOMPLETE if the some FormInfo is not complete (or error) + * CURL_FORMADD_MEMORY if a HttpPost struct cannot be allocated + * CURL_FORMADD_MEMORY if some allocation for string copying failed. + * CURL_FORMADD_ILLEGAL_ARRAY if an illegal option is used in an array + * + ***************************************************************************/ +typedef enum { + CURL_FORMADD_OK, /* first, no error */ + + CURL_FORMADD_MEMORY, + CURL_FORMADD_OPTION_TWICE, + CURL_FORMADD_NULL, + CURL_FORMADD_UNKNOWN_OPTION, + CURL_FORMADD_INCOMPLETE, + CURL_FORMADD_ILLEGAL_ARRAY, + CURL_FORMADD_DISABLED, /* libcurl was built with this disabled */ + + CURL_FORMADD_LAST /* last */ +} CURLFORMcode; + +/* + * NAME curl_formadd() + * + * DESCRIPTION + * + * Pretty advanved function for building multi-part formposts. Each invoke + * adds one part that together construct a full post. Then use + * CURLOPT_HTTPPOST to send it off to libcurl. + */ +CURL_EXTERN CURLFORMcode curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + ...); + +/* + * callback function for curl_formget() + * The void *arg pointer will be the one passed as second argument to curl_formget(). + * The character buffer passed to it must not be freed. + * Should return the buffer length passed to it as the argument "len" on success. + */ +typedef size_t (*curl_formget_callback)(void *arg, const char *buf, size_t len); + +/* + * NAME curl_formget() + * + * DESCRIPTION + * + * Serialize a curl_httppost struct built with curl_formadd(). + * Accepts a void pointer as second argument which will be passed to + * the curl_formget_callback function. + * Returns 0 on success. + */ +CURL_EXTERN int curl_formget(struct curl_httppost *form, void *arg, + curl_formget_callback append); +/* + * NAME curl_formfree() + * + * DESCRIPTION + * + * Free a multipart formpost previously built with curl_formadd(). + */ +CURL_EXTERN void curl_formfree(struct curl_httppost *form); + +/* + * NAME curl_getenv() + * + * DESCRIPTION + * + * Returns a malloc()'ed string that MUST be curl_free()ed after usage is + * complete. DEPRECATED - see lib/README.curlx + */ +CURL_EXTERN char *curl_getenv(const char *variable); + +/* + * NAME curl_version() + * + * DESCRIPTION + * + * Returns a static ascii string of the libcurl version. + */ +CURL_EXTERN char *curl_version(void); + +/* + * NAME curl_easy_escape() + * + * DESCRIPTION + * + * Escapes URL strings (converts all letters consider illegal in URLs to their + * %XX versions). This function returns a new allocated string or NULL if an + * error occurred. + */ +CURL_EXTERN char *curl_easy_escape(CURL *handle, + const char *string, + int length); + +/* the previous version: */ +CURL_EXTERN char *curl_escape(const char *string, + int length); + + +/* + * NAME curl_easy_unescape() + * + * DESCRIPTION + * + * Unescapes URL encoding in strings (converts all %XX codes to their 8bit + * versions). This function returns a new allocated string or NULL if an error + * occurred. + * Conversion Note: On non-ASCII platforms the ASCII %XX codes are + * converted into the host encoding. + */ +CURL_EXTERN char *curl_easy_unescape(CURL *handle, + const char *string, + int length, + int *outlength); + +/* the previous version */ +CURL_EXTERN char *curl_unescape(const char *string, + int length); + +/* + * NAME curl_free() + * + * DESCRIPTION + * + * Provided for de-allocation in the same translation unit that did the + * allocation. Added in libcurl 7.10 + */ +CURL_EXTERN void curl_free(void *p); + +/* + * NAME curl_global_init() + * + * DESCRIPTION + * + * curl_global_init() should be invoked exactly once for each application that + * uses libcurl + */ +CURL_EXTERN CURLcode curl_global_init(long flags); + +/* + * NAME curl_global_init_mem() + * + * DESCRIPTION + * + * curl_global_init() or curl_global_init_mem() should be invoked exactly once + * for each application that uses libcurl. This function can be used to + * initialize libcurl and set user defined memory management callback + * functions. Users can implement memory management routines to check for + * memory leaks, check for mis-use of the curl library etc. User registered + * callback routines with be invoked by this library instead of the system + * memory management routines like malloc, free etc. + */ +CURL_EXTERN CURLcode curl_global_init_mem(long flags, + curl_malloc_callback m, + curl_free_callback f, + curl_realloc_callback r, + curl_strdup_callback s, + curl_calloc_callback c); + +/* + * NAME curl_global_cleanup() + * + * DESCRIPTION + * + * curl_global_cleanup() should be invoked exactly once for each application + * that uses libcurl + */ +CURL_EXTERN void curl_global_cleanup(void); + +/* linked-list structure for the CURLOPT_QUOTE option (and other) */ +struct curl_slist { + char *data; + struct curl_slist *next; +}; + +/* + * NAME curl_slist_append() + * + * DESCRIPTION + * + * Appends a string to a linked list. If no list exists, it will be created + * first. Returns the new list, after appending. + */ +CURL_EXTERN struct curl_slist *curl_slist_append(struct curl_slist *, + const char *); + +/* + * NAME curl_slist_free_all() + * + * DESCRIPTION + * + * free a previously built curl_slist. + */ +CURL_EXTERN void curl_slist_free_all(struct curl_slist *); + +/* + * NAME curl_getdate() + * + * DESCRIPTION + * + * Returns the time, in seconds since 1 Jan 1970 of the time string given in + * the first argument. The time argument in the second parameter is unused + * and should be set to NULL. + */ +CURL_EXTERN time_t curl_getdate(const char *p, const time_t *unused); + +#define CURLINFO_STRING 0x100000 +#define CURLINFO_LONG 0x200000 +#define CURLINFO_DOUBLE 0x300000 +#define CURLINFO_SLIST 0x400000 +#define CURLINFO_MASK 0x0fffff +#define CURLINFO_TYPEMASK 0xf00000 + +typedef enum { + CURLINFO_NONE, /* first, never use this */ + CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1, + CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, + CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3, + CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4, + CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5, + CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6, + CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7, + CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8, + CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9, + CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10, + CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11, + CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12, + CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13, + CURLINFO_FILETIME = CURLINFO_LONG + 14, + CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, + CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16, + CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17, + CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, + CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19, + CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20, + CURLINFO_PRIVATE = CURLINFO_STRING + 21, + CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22, + CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23, + CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24, + CURLINFO_OS_ERRNO = CURLINFO_LONG + 25, + CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26, + CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27, + CURLINFO_COOKIELIST = CURLINFO_SLIST + 28, + CURLINFO_LASTSOCKET = CURLINFO_LONG + 29, + CURLINFO_FTP_ENTRY_PATH = CURLINFO_STRING + 30, + /* Fill in new entries below here! */ + + CURLINFO_LASTONE = 30 +} CURLINFO; + +/* CURLINFO_RESPONSE_CODE is the new name for the option previously known as + CURLINFO_HTTP_CODE */ +#define CURLINFO_HTTP_CODE CURLINFO_RESPONSE_CODE + +typedef enum { + CURLCLOSEPOLICY_NONE, /* first, never use this */ + + CURLCLOSEPOLICY_OLDEST, + CURLCLOSEPOLICY_LEAST_RECENTLY_USED, + CURLCLOSEPOLICY_LEAST_TRAFFIC, + CURLCLOSEPOLICY_SLOWEST, + CURLCLOSEPOLICY_CALLBACK, + + CURLCLOSEPOLICY_LAST /* last, never use this */ +} curl_closepolicy; + +#define CURL_GLOBAL_SSL (1<<0) +#define CURL_GLOBAL_WIN32 (1<<1) +#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32) +#define CURL_GLOBAL_NOTHING 0 +#define CURL_GLOBAL_DEFAULT CURL_GLOBAL_ALL + + +/***************************************************************************** + * Setup defines, protos etc for the sharing stuff. + */ + +/* Different data locks for a single share */ +typedef enum { + CURL_LOCK_DATA_NONE = 0, + /* CURL_LOCK_DATA_SHARE is used internaly to say that + * the locking is just made to change the internal state of the share + * itself. + */ + CURL_LOCK_DATA_SHARE, + CURL_LOCK_DATA_COOKIE, + CURL_LOCK_DATA_DNS, + CURL_LOCK_DATA_SSL_SESSION, + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_LAST +} curl_lock_data; + +/* Different lock access types */ +typedef enum { + CURL_LOCK_ACCESS_NONE = 0, /* unspecified action */ + CURL_LOCK_ACCESS_SHARED = 1, /* for read perhaps */ + CURL_LOCK_ACCESS_SINGLE = 2, /* for write perhaps */ + CURL_LOCK_ACCESS_LAST /* never use */ +} curl_lock_access; + +typedef void (*curl_lock_function)(CURL *handle, + curl_lock_data data, + curl_lock_access locktype, + void *userptr); +typedef void (*curl_unlock_function)(CURL *handle, + curl_lock_data data, + void *userptr); + +typedef void CURLSH; + +typedef enum { + CURLSHE_OK, /* all is fine */ + CURLSHE_BAD_OPTION, /* 1 */ + CURLSHE_IN_USE, /* 2 */ + CURLSHE_INVALID, /* 3 */ + CURLSHE_NOMEM, /* out of memory */ + CURLSHE_LAST /* never use */ +} CURLSHcode; + +typedef enum { + CURLSHOPT_NONE, /* don't use */ + CURLSHOPT_SHARE, /* specify a data type to share */ + CURLSHOPT_UNSHARE, /* specify shich data type to stop sharing */ + CURLSHOPT_LOCKFUNC, /* pass in a 'curl_lock_function' pointer */ + CURLSHOPT_UNLOCKFUNC, /* pass in a 'curl_unlock_function' pointer */ + CURLSHOPT_USERDATA, /* pass in a user data pointer used in the lock/unlock + callback functions */ + CURLSHOPT_LAST /* never use */ +} CURLSHoption; + +CURL_EXTERN CURLSH *curl_share_init(void); +CURL_EXTERN CURLSHcode curl_share_setopt(CURLSH *, CURLSHoption option, ...); +CURL_EXTERN CURLSHcode curl_share_cleanup(CURLSH *); + +/**************************************************************************** + * Structures for querying information about the curl library at runtime. + */ + +typedef enum { + CURLVERSION_FIRST, + CURLVERSION_SECOND, + CURLVERSION_THIRD, + CURLVERSION_LAST /* never actually use this */ +} CURLversion; + +/* The 'CURLVERSION_NOW' is the symbolic name meant to be used by + basicly all programs ever, that want to get version information. It is + meant to be a built-in version number for what kind of struct the caller + expects. If the struct ever changes, we redefine the NOW to another enum + from above. */ +#define CURLVERSION_NOW CURLVERSION_THIRD + +typedef struct { + CURLversion age; /* age of the returned struct */ + const char *version; /* LIBCURL_VERSION */ + unsigned int version_num; /* LIBCURL_VERSION_NUM */ + const char *host; /* OS/host/cpu/machine when configured */ + int features; /* bitmask, see defines below */ + const char *ssl_version; /* human readable string */ + long ssl_version_num; /* not used anymore, always 0 */ + const char *libz_version; /* human readable string */ + /* protocols is terminated by an entry with a NULL protoname */ + const char * const *protocols; + + /* The fields below this were added in CURLVERSION_SECOND */ + const char *ares; + int ares_num; + + /* This field was added in CURLVERSION_THIRD */ + const char *libidn; + + /* Same as '_libiconv_version' if built with HAVE_ICONV */ + int iconv_ver_num; +} curl_version_info_data; + +#define CURL_VERSION_IPV6 (1<<0) /* IPv6-enabled */ +#define CURL_VERSION_KERBEROS4 (1<<1) /* kerberos auth is supported */ +#define CURL_VERSION_SSL (1<<2) /* SSL options are present */ +#define CURL_VERSION_LIBZ (1<<3) /* libz features are present */ +#define CURL_VERSION_NTLM (1<<4) /* NTLM auth is supported */ +#define CURL_VERSION_GSSNEGOTIATE (1<<5) /* Negotiate auth support */ +#define CURL_VERSION_DEBUG (1<<6) /* built with debug capabilities */ +#define CURL_VERSION_ASYNCHDNS (1<<7) /* asynchronous dns resolves */ +#define CURL_VERSION_SPNEGO (1<<8) /* SPNEGO auth */ +#define CURL_VERSION_LARGEFILE (1<<9) /* supports files bigger than 2GB */ +#define CURL_VERSION_IDN (1<<10) /* International Domain Names support */ +#define CURL_VERSION_SSPI (1<<11) /* SSPI is supported */ +#define CURL_VERSION_CONV (1<<12) /* character conversions are + supported */ + +/* + * NAME curl_version_info() + * + * DESCRIPTION + * + * This function returns a pointer to a static copy of the version info + * struct. See above. + */ +CURL_EXTERN curl_version_info_data *curl_version_info(CURLversion); + +/* + * NAME curl_easy_strerror() + * + * DESCRIPTION + * + * The curl_easy_strerror function may be used to turn a CURLcode value + * into the equivalent human readable error string. This is useful + * for printing meaningful error messages. + */ +CURL_EXTERN const char *curl_easy_strerror(CURLcode); + +/* + * NAME curl_share_strerror() + * + * DESCRIPTION + * + * The curl_share_strerror function may be used to turn a CURLSHcode value + * into the equivalent human readable error string. This is useful + * for printing meaningful error messages. + */ +CURL_EXTERN const char *curl_share_strerror(CURLSHcode); + +#ifdef __cplusplus +} +#endif + +/* unfortunately, the easy.h and multi.h include files need options and info + stuff before they can be included! */ +#include "easy.h" /* nothing in curl is fun without the easy stuff */ +#include "multi.h" + +#endif /* __CURL_CURL_H */ diff --git a/src/libcurl/curl/curlver.h b/src/libcurl/curl/curlver.h new file mode 100644 index 0000000..1634b17 --- /dev/null +++ b/src/libcurl/curl/curlver.h @@ -0,0 +1,56 @@ +#ifndef __CURL_CURLVER_H +#define __CURL_CURLVER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2006, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: curlver.h,v 1.21 2006-06-12 07:24:14 bagder Exp $ + ***************************************************************************/ + +/* This header file contains nothing but libcurl version info, generated by + a script at release-time. This was made its own header file in 7.11.2 */ + +/* This is the version number of the libcurl package from which this header + file origins: */ +#define LIBCURL_VERSION "7.15.5" + +/* The numeric version number is also available "in parts" by using these + defines: */ +#define LIBCURL_VERSION_MAJOR 7 +#define LIBCURL_VERSION_MINOR 15 +#define LIBCURL_VERSION_PATCH 5 + +/* This is the numeric version of the libcurl version number, meant for easier + parsing and comparions by programs. The LIBCURL_VERSION_NUM define will + always follow this syntax: + + 0xXXYYZZ + + Where XX, YY and ZZ are the main version, release and patch numbers in + hexadecimal (using 8 bits each). All three numbers are always represented + using two digits. 1.2 would appear as "0x010200" while version 9.11.7 + appears as "0x090b07". + + This 6-digit (24 bits) hexadecimal number does not show pre-release number, + and it is always a greater number in a more recent release. It makes + comparisons with greater than and less than work. +*/ +#define LIBCURL_VERSION_NUM 0x070f05 + +#endif /* __CURL_CURLVER_H */ diff --git a/src/libcurl/curl/easy.h b/src/libcurl/curl/easy.h new file mode 100644 index 0000000..b586720 --- /dev/null +++ b/src/libcurl/curl/easy.h @@ -0,0 +1,81 @@ +#ifndef __CURL_EASY_H +#define __CURL_EASY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: easy.h,v 1.13 2004/11/09 14:02:58 giva Exp $ + ***************************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif + +CURL_EXTERN CURL *curl_easy_init(void); +CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); +CURL_EXTERN CURLcode curl_easy_perform(CURL *curl); +CURL_EXTERN void curl_easy_cleanup(CURL *curl); + +/* + * NAME curl_easy_getinfo() + * + * DESCRIPTION + * + * Request internal information from the curl session with this function. The + * third argument MUST be a pointer to a long, a pointer to a char * or a + * pointer to a double (as the documentation describes elsewhere). The data + * pointed to will be filled in accordingly and can be relied upon only if the + * function returns CURLE_OK. This function is intended to get used *AFTER* a + * performed transfer, all results from this function are undefined until the + * transfer is completed. + */ +CURL_EXTERN CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...); + + +/* + * NAME curl_easy_duphandle() + * + * DESCRIPTION + * + * Creates a new curl session handle with the same options set for the handle + * passed in. Duplicating a handle could only be a matter of cloning data and + * options, internal state info and things like persistant connections cannot + * be transfered. It is useful in multithreaded applications when you can run + * curl_easy_duphandle() for each new thread to avoid a series of identical + * curl_easy_setopt() invokes in every thread. + */ +CURL_EXTERN CURL* curl_easy_duphandle(CURL *curl); + +/* + * NAME curl_easy_reset() + * + * DESCRIPTION + * + * Re-initializes a CURL handle to the default values. This puts back the + * handle to the same state as it was in when it was just created. + * + * It does keep: live connections, the Session ID cache, the DNS cache and the + * cookies. + */ +CURL_EXTERN void curl_easy_reset(CURL *curl); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libcurl/curl/mprintf.h b/src/libcurl/curl/mprintf.h new file mode 100644 index 0000000..8d835f1 --- /dev/null +++ b/src/libcurl/curl/mprintf.h @@ -0,0 +1,62 @@ +#ifndef __CURL_MPRINTF_H +#define __CURL_MPRINTF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2006, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: mprintf.h,v 1.13 2006-03-28 10:08:54 bagder Exp $ + ***************************************************************************/ + +#include +#include /* needed for FILE */ + +#include "curl.h" + +CURL_EXTERN int curl_mprintf(const char *format, ...); +CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...); +CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...); +CURL_EXTERN int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...); +CURL_EXTERN int curl_mvprintf(const char *format, va_list args); +CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args); +CURL_EXTERN int curl_mvsprintf(char *buffer, const char *format, va_list args); +CURL_EXTERN int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, va_list args); +CURL_EXTERN char *curl_maprintf(const char *format, ...); +CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args); + +#ifdef _MPRINTF_REPLACE +# define printf curl_mprintf +# define fprintf curl_mfprintf +#ifdef CURLDEBUG +/* When built with CURLDEBUG we define away the sprintf() functions since we + don't want internal code to be using them */ +# define sprintf sprintf_was_used +# define vsprintf vsprintf_was_used +#else +# define sprintf curl_msprintf +# define vsprintf curl_mvsprintf +#endif +# define snprintf curl_msnprintf +# define vprintf curl_mvprintf +# define vfprintf curl_mvfprintf +# define vsnprintf curl_mvsnprintf +# define aprintf curl_maprintf +# define vaprintf curl_mvaprintf +#endif + +#endif /* __CURL_MPRINTF_H */ diff --git a/src/libcurl/curl/multi.h b/src/libcurl/curl/multi.h new file mode 100644 index 0000000..05aeafc --- /dev/null +++ b/src/libcurl/curl/multi.h @@ -0,0 +1,344 @@ +#ifndef __CURL_MULTI_H +#define __CURL_MULTI_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2006, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: multi.h,v 1.38 2006-08-04 18:53:48 danf Exp $ + ***************************************************************************/ +/* + This is an "external" header file. Don't give away any internals here! + + GOALS + + o Enable a "pull" interface. The application that uses libcurl decides where + and when to ask libcurl to get/send data. + + o Enable multiple simultaneous transfers in the same thread without making it + complicated for the application. + + o Enable the application to select() on its own file descriptors and curl's + file descriptors simultaneous easily. + +*/ +#if defined(_WIN32) && !defined(WIN32) +/* Chris Lewis mentioned that he doesn't get WIN32 defined, only _WIN32 so we + make this adjustment to catch this. */ +#define WIN32 1 +#endif + +#if defined(WIN32) && !defined(_WIN32_WCE) && !defined(__GNUC__) && \ + !defined(__CYGWIN__) || defined(__MINGW32__) +#if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H)) +/* The check above prevents the winsock2 inclusion if winsock.h already was + included, since they can't co-exist without problems */ +#include +#endif +#else + +/* HP-UX systems version 9, 10 and 11 lack sys/select.h and so does oldish + libc5-based Linux systems. Only include it on system that are known to + require it! */ +#if defined(_AIX) || defined(NETWARE) || defined(__NetBSD__) || defined(_MINIX) +#include +#endif + +#ifndef _WIN32_WCE +#include +#endif +#include +#include +#endif + +/* + * This header file should not really need to include "curl.h" since curl.h + * itself includes this file and we expect user applications to do #include + * without the need for especially including multi.h. + * + * For some reason we added this include here at one point, and rather than to + * break existing (wrongly written) libcurl applications, we leave it as-is + * but with this warning attached. + */ +#include "curl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void CURLM; + +#ifndef curl_socket_typedef +/* Public socket typedef */ +#ifdef WIN32 +typedef SOCKET curl_socket_t; +#define CURL_SOCKET_BAD INVALID_SOCKET +#else +typedef int curl_socket_t; +#define CURL_SOCKET_BAD -1 +#endif +#define curl_socket_typedef +#endif /* curl_socket_typedef */ + +typedef enum { + CURLM_CALL_MULTI_PERFORM = -1, /* please call curl_multi_perform() or + curl_multi_socket*() soon */ + CURLM_OK, + CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ + CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ + CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ + CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ + CURLM_BAD_SOCKET, /* the passed in socket argument did not match */ + CURLM_UNKNOWN_OPTION, /* curl_multi_setopt() with unsupported option */ + CURLM_LAST +} CURLMcode; + +/* just to make code nicer when using curl_multi_socket() you can now check + for CURLM_CALL_MULTI_SOCKET too in the same style it works for + curl_multi_perform() and CURLM_CALL_MULTI_PERFORM */ +#define CURLM_CALL_MULTI_SOCKET CURLM_CALL_MULTI_PERFORM + +typedef enum { + CURLMSG_NONE, /* first, not used */ + CURLMSG_DONE, /* This easy handle has completed. 'result' contains + the CURLcode of the transfer */ + CURLMSG_LAST /* last, not used */ +} CURLMSG; + +struct CURLMsg { + CURLMSG msg; /* what this message means */ + CURL *easy_handle; /* the handle it concerns */ + union { + void *whatever; /* message-specific data */ + CURLcode result; /* return code for transfer */ + } data; +}; +typedef struct CURLMsg CURLMsg; + +/* + * Name: curl_multi_init() + * + * Desc: inititalize multi-style curl usage + * + * Returns: a new CURLM handle to use in all 'curl_multi' functions. + */ +CURL_EXTERN CURLM *curl_multi_init(void); + +/* + * Name: curl_multi_add_handle() + * + * Desc: add a standard curl handle to the multi stack + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_add_handle(CURLM *multi_handle, + CURL *curl_handle); + + /* + * Name: curl_multi_remove_handle() + * + * Desc: removes a curl handle from the multi stack again + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_remove_handle(CURLM *multi_handle, + CURL *curl_handle); + + /* + * Name: curl_multi_fdset() + * + * Desc: Ask curl for its fd_set sets. The app can use these to select() or + * poll() on. We want curl_multi_perform() called as soon as one of + * them are ready. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *exc_fd_set, + int *max_fd); + + /* + * Name: curl_multi_perform() + * + * Desc: When the app thinks there's data available for curl it calls this + * function to read/write whatever there is right now. This returns + * as soon as the reads and writes are done. This function does not + * require that there actually is data available for reading or that + * data can be written, it can be called just in case. It returns + * the number of handles that still transfer data in the second + * argument's integer-pointer. + * + * Returns: CURLMcode type, general multi error code. *NOTE* that this only + * returns errors etc regarding the whole multi stack. There might + * still have occurred problems on invidual transfers even when this + * returns OK. + */ +CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle, + int *running_handles); + + /* + * Name: curl_multi_cleanup() + * + * Desc: Cleans up and removes a whole multi stack. It does not free or + * touch any individual easy handles in any way. We need to define + * in what state those handles will be if this function is called + * in the middle of a transfer. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); + +/* + * Name: curl_multi_info_read() + * + * Desc: Ask the multi handle if there's any messages/informationals from + * the individual transfers. Messages include informationals such as + * error code from the transfer or just the fact that a transfer is + * completed. More details on these should be written down as well. + * + * Repeated calls to this function will return a new struct each + * time, until a special "end of msgs" struct is returned as a signal + * that there is no more to get at this point. + * + * The data the returned pointer points to will not survive calling + * curl_multi_cleanup(). + * + * The 'CURLMsg' struct is meant to be very simple and only contain + * very basic informations. If more involved information is wanted, + * we will provide the particular "transfer handle" in that struct + * and that should/could/would be used in subsequent + * curl_easy_getinfo() calls (or similar). The point being that we + * must never expose complex structs to applications, as then we'll + * undoubtably get backwards compatibility problems in the future. + * + * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out + * of structs. It also writes the number of messages left in the + * queue (after this read) in the integer the second argument points + * to. + */ +CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle, + int *msgs_in_queue); + +/* + * Name: curl_multi_strerror() + * + * Desc: The curl_multi_strerror function may be used to turn a CURLMcode + * value into the equivalent human readable error string. This is + * useful for printing meaningful error messages. + * + * Returns: A pointer to a zero-terminated error message. + */ +CURL_EXTERN const char *curl_multi_strerror(CURLMcode); + +/* + * Name: curl_multi_socket() and + * curl_multi_socket_all() + * + * Desc: An alternative version of curl_multi_perform() that allows the + * application to pass in one of the file descriptors that have been + * detected to have "action" on them and let libcurl perform. + * See man page for details. + */ +#define CURL_POLL_NONE 0 +#define CURL_POLL_IN 1 +#define CURL_POLL_OUT 2 +#define CURL_POLL_INOUT 3 +#define CURL_POLL_REMOVE 4 + +#define CURL_SOCKET_TIMEOUT CURL_SOCKET_BAD + +typedef int (*curl_socket_callback)(CURL *easy, /* easy handle */ + curl_socket_t s, /* socket */ + int what, /* see above */ + void *userp, /* private callback + pointer */ + void *socketp); /* private socket + pointer */ + +CURL_EXTERN CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, + int *running_handles); + +CURL_EXTERN CURLMcode curl_multi_socket_all(CURLM *multi_handle, + int *running_handles); + +/* + * Name: curl_multi_timeout() + * + * Desc: Returns the maximum number of milliseconds the app is allowed to + * wait before curl_multi_socket() or curl_multi_perform() must be + * called (to allow libcurl's timed events to take place). + * + * Returns: CURLM error code. + */ +CURL_EXTERN CURLMcode curl_multi_timeout(CURLM *multi_handle, + long *milliseconds); + +#undef CINIT /* re-using the same name as in curl.h */ + +#ifdef CURL_ISOCPP +#define CINIT(name,type,number) CURLMOPT_ ## name = CURLOPTTYPE_ ## type + number +#else +/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +#define LONG CURLOPTTYPE_LONG +#define OBJECTPOINT CURLOPTTYPE_OBJECTPOINT +#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT +#define OFF_T CURLOPTTYPE_OFF_T +#define CINIT(name,type,number) CURLMOPT_/**/name = type + number +#endif + +typedef enum { + /* This is the socket callback function pointer */ + CINIT(SOCKETFUNCTION, FUNCTIONPOINT, 1), + + /* This is the argument passed to the socket callback */ + CINIT(SOCKETDATA, OBJECTPOINT, 2), + + CURLMOPT_LASTENTRY /* the last unused */ +} CURLMoption; + + +/* + * Name: curl_multi_setopt() + * + * Desc: Sets options for the multi handle. + * + * Returns: CURLM error code. + */ +CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *multi_handle, + CURLMoption option, ...); + + +/* + * Name: curl_multi_assign() + * + * Desc: This function sets an association in the multi handle between the + * given socket and a private pointer of the application. This is + * (only) useful for curl_multi_socket uses. + * + * Returns: CURLM error code. + */ +CURL_EXTERN CURLMcode curl_multi_assign(CURLM *multi_handle, + curl_socket_t sockfd, void *sockp); + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif diff --git a/src/libcurl/curl/stdcheaders.h b/src/libcurl/curl/stdcheaders.h new file mode 100644 index 0000000..024413a --- /dev/null +++ b/src/libcurl/curl/stdcheaders.h @@ -0,0 +1,34 @@ +#ifndef __STDC_HEADERS_H +#define __STDC_HEADERS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: stdcheaders.h,v 1.8 2004/01/07 09:19:34 bagder Exp $ + ***************************************************************************/ + +#include + +size_t fread (void *, size_t, size_t, FILE *); +size_t fwrite (const void *, size_t, size_t, FILE *); + +int strcasecmp(const char *, const char *); +int strncasecmp(const char *, const char *, size_t); + +#endif diff --git a/src/libcurl/curl/types.h b/src/libcurl/curl/types.h new file mode 100644 index 0000000..d37d6ae --- /dev/null +++ b/src/libcurl/curl/types.h @@ -0,0 +1 @@ +/* not used */ diff --git a/src/libs/macosx/libSDL-1.2.0.dylib b/src/libs/macosx/libSDL-1.2.0.dylib new file mode 100755 index 0000000..701e596 Binary files /dev/null and b/src/libs/macosx/libSDL-1.2.0.dylib differ diff --git a/src/libs/macosx/libSDLmain.a b/src/libs/macosx/libSDLmain.a new file mode 100644 index 0000000..873e1b2 Binary files /dev/null and b/src/libs/macosx/libSDLmain.a differ diff --git a/src/libs/win32/libSDL.dll.a b/src/libs/win32/libSDL.dll.a new file mode 100644 index 0000000..64772b0 Binary files /dev/null and b/src/libs/win32/libSDL.dll.a differ diff --git a/src/libs/win32/libSDL.la b/src/libs/win32/libSDL.la new file mode 100644 index 0000000..4f0519f --- /dev/null +++ b/src/libs/win32/libSDL.la @@ -0,0 +1,41 @@ +# libSDL.la - a libtool library file +# Generated by ltmain.sh (GNU libtool) 2.2.6 +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='../bin/SDL.dll' + +# Names of this library. +library_names='libSDL.dll.a' + +# The name of the static archive. +old_library='libSDL.a' + +# Linker flags that can not go in dependency_libs. +inherited_linker_flags='' + +# Libraries that this one depends upon. +dependency_libs=' -luser32 -lgdi32 -lwinmm -ldxguid' + +# Names of additional weak libraries provided by this library +weak_library_names='' + +# Version information for libSDL. +current=11 +age=11 +revision=3 + +# Is this an already installed library? +installed=yes + +# Should we warn about portability when linking against -modules? +shouldnotlink=no + +# Files to dlopen/dlpreopen +dlopen='' +dlpreopen='' + +# Directory that this library needs to be installed in: +libdir='/usr/local/lib' diff --git a/src/libs/win32/libSDLmain.a b/src/libs/win32/libSDLmain.a new file mode 100644 index 0000000..068941a Binary files /dev/null and b/src/libs/win32/libSDLmain.a differ diff --git a/src/libs/win32/libcurl.a b/src/libs/win32/libcurl.a new file mode 100644 index 0000000..7f6ed2d Binary files /dev/null and b/src/libs/win32/libcurl.a differ diff --git a/src/libspeex/_kiss_fft_guts.h b/src/libspeex/_kiss_fft_guts.h new file mode 100644 index 0000000..6571e79 --- /dev/null +++ b/src/libspeex/_kiss_fft_guts.h @@ -0,0 +1,160 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define MIN(a,b) ((a)<(b) ? (a):(b)) +#define MAX(a,b) ((a)>(b) ? (a):(b)) + +/* kiss_fft.h + defines kiss_fft_scalar as either short or a float type + and defines + typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ +#include "kiss_fft.h" +#include "math_approx.h" + +#define MAXFACTORS 32 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +struct kiss_fft_state{ + int nfft; + int inverse; + int factors[2*MAXFACTORS]; + kiss_fft_cpx twiddles[1]; +}; + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ +#ifdef FIXED_POINT +#include "arch.h" +# define FRACBITS 15 +# define SAMPPROD spx_int32_t +#define SAMP_MAX 32767 + +#define SAMP_MIN -SAMP_MAX + +#if defined(CHECK_OVERFLOW) +# define CHECK_OVERFLOW_OP(a,op,b) \ + if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \ + fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); } +#endif + + +# define smul(a,b) ( (SAMPPROD)(a)*(b) ) +# define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) + +# define S_MUL(a,b) sround( smul(a,b) ) + +# define C_MUL(m,a,b) \ + do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \ + (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) + +# define C_MUL4(m,a,b) \ + do{ (m).r = PSHR32( smul((a).r,(b).r) - smul((a).i,(b).i),17 ); \ + (m).i = PSHR32( smul((a).r,(b).i) + smul((a).i,(b).r),17 ); }while(0) + +# define DIVSCALAR(x,k) \ + (x) = sround( smul( x, SAMP_MAX/k ) ) + +# define C_FIXDIV(c,div) \ + do { DIVSCALAR( (c).r , div); \ + DIVSCALAR( (c).i , div); }while (0) + +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r = sround( smul( (c).r , s ) ) ;\ + (c).i = sround( smul( (c).i , s ) ) ; }while(0) + +#else /* not FIXED_POINT*/ + +# define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) + +#define C_MUL4(m,a,b) C_MUL(m,a,b) + +# define C_FIXDIV(c,div) /* NOOP */ +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) +#endif + +#ifndef CHECK_OVERFLOW_OP +# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ +#endif + +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) + + +#ifdef FIXED_POINT +# define KISS_FFT_COS(phase) floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase)))) +# define KISS_FFT_SIN(phase) floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase)))) +# define HALF_OF(x) ((x)>>1) +#elif defined(USE_SIMD) +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5)) +#else +# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) +# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) +# define HALF_OF(x) ((x)*.5) +#endif + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = KISS_FFT_COS(phase);\ + (x)->i = KISS_FFT_SIN(phase);\ + }while(0) +#define kf_cexp2(x,phase) \ + do{ \ + (x)->r = spx_cos_norm((phase));\ + (x)->i = spx_cos_norm((phase)-32768);\ +}while(0) + + +/* a debugging function */ +#define pcpx(c)\ + fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) diff --git a/src/libspeex/arch.h b/src/libspeex/arch.h new file mode 100644 index 0000000..ccd24ae --- /dev/null +++ b/src/libspeex/arch.h @@ -0,0 +1,239 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file arch.h + @brief Various architecture definitions Speex +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef ARCH_H +#define ARCH_H + +#ifndef SPEEX_VERSION +#define SPEEX_MAJOR_VERSION 1 /**< Major Speex version. */ +#define SPEEX_MINOR_VERSION 1 /**< Minor Speex version. */ +#define SPEEX_MICRO_VERSION 15 /**< Micro Speex version. */ +#define SPEEX_EXTRA_VERSION "" /**< Extra Speex version. */ +#define SPEEX_VERSION "speex-1.2beta3" /**< Speex version string. */ +#endif + +/* A couple test to catch stupid option combinations */ +#ifdef FIXED_POINT + +#ifdef FLOATING_POINT +#error You cannot compile as floating point and fixed point at the same time +#endif +#ifdef _USE_SSE +#error SSE is only for floating-point +#endif +#if ((defined (ARM4_ASM)||defined (ARM4_ASM)) && defined(BFIN_ASM)) || (defined (ARM4_ASM)&&defined(ARM5E_ASM)) +#error Make up your mind. What CPU do you have? +#endif +#ifdef VORBIS_PSYCHO +#error Vorbis-psy model currently not implemented in fixed-point +#endif + +#else + +#ifndef FLOATING_POINT +#error You now need to define either FIXED_POINT or FLOATING_POINT +#endif +#if defined (ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM) +#error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions? +#endif +#ifdef FIXED_POINT_DEBUG +#error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?" +#endif + + +#endif + +#ifndef OUTSIDE_SPEEX +#include "speex/speex_types.h" +#endif + +#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */ +#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */ +#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */ +#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */ +#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ + +#ifdef FIXED_POINT + +typedef spx_int16_t spx_word16_t; +typedef spx_int32_t spx_word32_t; +typedef spx_word32_t spx_mem_t; +typedef spx_word16_t spx_coef_t; +typedef spx_word16_t spx_lsp_t; +typedef spx_word32_t spx_sig_t; + +#define Q15ONE 32767 + +#define LPC_SCALING 8192 +#define SIG_SCALING 16384 +#define LSP_SCALING 8192. +#define GAMMA_SCALING 32768. +#define GAIN_SCALING 64 +#define GAIN_SCALING_1 0.015625 + +#define LPC_SHIFT 13 +#define LSP_SHIFT 13 +#define SIG_SHIFT 14 +#define GAIN_SHIFT 6 + +#define VERY_SMALL 0 +#define VERY_LARGE32 ((spx_word32_t)2147483647) +#define VERY_LARGE16 ((spx_word16_t)32767) +#define Q15_ONE ((spx_word16_t)32767) + + +#ifdef FIXED_DEBUG +#include "fixed_debug.h" +#else + +#include "fixed_generic.h" + +#ifdef ARM5E_ASM +#include "fixed_arm5e.h" +#elif defined (ARM4_ASM) +#include "fixed_arm4.h" +#elif defined (BFIN_ASM) +#include "fixed_bfin.h" +#endif + +#endif + + +#else + +typedef float spx_mem_t; +typedef float spx_coef_t; +typedef float spx_lsp_t; +typedef float spx_sig_t; +typedef float spx_word16_t; +typedef float spx_word32_t; + +#define Q15ONE 1.0f +#define LPC_SCALING 1.f +#define SIG_SCALING 1.f +#define LSP_SCALING 1.f +#define GAMMA_SCALING 1.f +#define GAIN_SCALING 1.f +#define GAIN_SCALING_1 1.f + + +#define VERY_SMALL 1e-15f +#define VERY_LARGE32 1e15f +#define VERY_LARGE16 1e15f +#define Q15_ONE ((spx_word16_t)1.f) + +#define QCONST16(x,bits) (x) +#define QCONST32(x,bits) (x) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define EXTRACT16(x) (x) +#define EXTEND32(x) (x) +#define SHR16(a,shift) (a) +#define SHL16(a,shift) (a) +#define SHR32(a,shift) (a) +#define SHL32(a,shift) (a) +#define PSHR16(a,shift) (a) +#define PSHR32(a,shift) (a) +#define VSHR32(a,shift) (a) +#define SATURATE16(x,a) (x) +#define SATURATE32(x,a) (x) + +#define PSHR(a,shift) (a) +#define SHR(a,shift) (a) +#define SHL(a,shift) (a) +#define SATURATE(x,a) (x) + +#define ADD16(a,b) ((a)+(b)) +#define SUB16(a,b) ((a)-(b)) +#define ADD32(a,b) ((a)+(b)) +#define SUB32(a,b) ((a)-(b)) +#define MULT16_16_16(a,b) ((a)*(b)) +#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b)) +#define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b)) + +#define MULT16_32_Q11(a,b) ((a)*(b)) +#define MULT16_32_Q13(a,b) ((a)*(b)) +#define MULT16_32_Q14(a,b) ((a)*(b)) +#define MULT16_32_Q15(a,b) ((a)*(b)) +#define MULT16_32_P15(a,b) ((a)*(b)) + +#define MAC16_32_Q11(c,a,b) ((c)+(a)*(b)) +#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) + +#define MAC16_16_Q11(c,a,b) ((c)+(a)*(b)) +#define MAC16_16_Q13(c,a,b) ((c)+(a)*(b)) +#define MAC16_16_P13(c,a,b) ((c)+(a)*(b)) +#define MULT16_16_Q11_32(a,b) ((a)*(b)) +#define MULT16_16_Q13(a,b) ((a)*(b)) +#define MULT16_16_Q14(a,b) ((a)*(b)) +#define MULT16_16_Q15(a,b) ((a)*(b)) +#define MULT16_16_P15(a,b) ((a)*(b)) +#define MULT16_16_P13(a,b) ((a)*(b)) +#define MULT16_16_P14(a,b) ((a)*(b)) + +#define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) +#define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) +#define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) +#define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) + + +#endif + + +#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + +/* 2 on TI C5x DSP */ +#define BYTES_PER_CHAR 2 +#define BITS_PER_CHAR 16 +#define LOG2_BITS_PER_CHAR 4 + +#else + +#define BYTES_PER_CHAR 1 +#define BITS_PER_CHAR 8 +#define LOG2_BITS_PER_CHAR 3 + +#endif + + + +#ifdef FIXED_DEBUG +long long spx_mips=0; +#endif + + +#endif diff --git a/src/libspeex/bits.c b/src/libspeex/bits.c new file mode 100644 index 0000000..f61c932 --- /dev/null +++ b/src/libspeex/bits.c @@ -0,0 +1,372 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: speex_bits.c + + Handles bit packing/unpacking + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "arch.h" +#include "os_support.h" + +/* Maximum size of the bit-stream (for fixed-size allocation) */ +#ifndef MAX_CHARS_PER_FRAME +#define MAX_CHARS_PER_FRAME (2000/BYTES_PER_CHAR) +#endif + +void speex_bits_init(SpeexBits *bits) +{ + bits->chars = (char*)speex_alloc(MAX_CHARS_PER_FRAME); + if (!bits->chars) + return; + + bits->buf_size = MAX_CHARS_PER_FRAME; + + bits->owner=1; + + speex_bits_reset(bits); +} + +void speex_bits_init_buffer(SpeexBits *bits, void *buff, int buf_size) +{ + bits->chars = (char*)buff; + bits->buf_size = buf_size; + + bits->owner=0; + + speex_bits_reset(bits); +} + +void speex_bits_set_bit_buffer(SpeexBits *bits, void *buff, int buf_size) +{ + bits->chars = (char*)buff; + bits->buf_size = buf_size; + + bits->owner=0; + + bits->nbBits=buf_size<charPtr=0; + bits->bitPtr=0; + bits->overflow=0; + +} + +void speex_bits_destroy(SpeexBits *bits) +{ + if (bits->owner) + speex_free(bits->chars); + /* Will do something once the allocation is dynamic */ +} + +void speex_bits_reset(SpeexBits *bits) +{ + /* We only need to clear the first byte now */ + bits->chars[0]=0; + bits->nbBits=0; + bits->charPtr=0; + bits->bitPtr=0; + bits->overflow=0; +} + +void speex_bits_rewind(SpeexBits *bits) +{ + bits->charPtr=0; + bits->bitPtr=0; + bits->overflow=0; +} + +void speex_bits_read_from(SpeexBits *bits, char *chars, int len) +{ + int i; + int nchars = len / BYTES_PER_CHAR; + if (nchars > bits->buf_size) + { + speex_notify("Packet is larger than allocated buffer"); + if (bits->owner) + { + char *tmp = (char*)speex_realloc(bits->chars, nchars); + if (tmp) + { + bits->buf_size=nchars; + bits->chars=tmp; + } else { + nchars=bits->buf_size; + speex_warning("Could not resize input buffer: truncating input"); + } + } else { + speex_warning("Do not own input buffer: truncating oversize input"); + nchars=bits->buf_size; + } + } +#if (BYTES_PER_CHAR==2) +/* Swap bytes to proper endian order (could be done externally) */ +#define HTOLS(A) ((((A) >> 8)&0xff)|(((A) & 0xff)<<8)) +#else +#define HTOLS(A) (A) +#endif + for (i=0;ichars[i]=HTOLS(chars[i]); + + bits->nbBits=nchars<charPtr=0; + bits->bitPtr=0; + bits->overflow=0; +} + +static void speex_bits_flush(SpeexBits *bits) +{ + int nchars = ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR); + if (bits->charPtr>0) + SPEEX_MOVE(bits->chars, &bits->chars[bits->charPtr], nchars-bits->charPtr); + bits->nbBits -= bits->charPtr<charPtr=0; +} + +void speex_bits_read_whole_bytes(SpeexBits *bits, char *chars, int nbytes) +{ + int i,pos; + int nchars = nbytes/BYTES_PER_CHAR; + + if (((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR)+nchars > bits->buf_size) + { + /* Packet is larger than allocated buffer */ + if (bits->owner) + { + char *tmp = (char*)speex_realloc(bits->chars, (bits->nbBits>>LOG2_BITS_PER_CHAR)+nchars+1); + if (tmp) + { + bits->buf_size=(bits->nbBits>>LOG2_BITS_PER_CHAR)+nchars+1; + bits->chars=tmp; + } else { + nchars=bits->buf_size-(bits->nbBits>>LOG2_BITS_PER_CHAR)-1; + speex_warning("Could not resize input buffer: truncating oversize input"); + } + } else { + speex_warning("Do not own input buffer: truncating oversize input"); + nchars=bits->buf_size; + } + } + + speex_bits_flush(bits); + pos=bits->nbBits>>LOG2_BITS_PER_CHAR; + for (i=0;ichars[pos+i]=HTOLS(chars[i]); + bits->nbBits+=nchars<bitPtr; + charPtr=bits->charPtr; + nbBits=bits->nbBits; + speex_bits_insert_terminator(bits); + bits->bitPtr=bitPtr; + bits->charPtr=charPtr; + bits->nbBits=nbBits; + + if (max_nchars > ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR)) + max_nchars = ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR); + + for (i=0;ichars[i]); + return max_nchars*BYTES_PER_CHAR; +} + +int speex_bits_write_whole_bytes(SpeexBits *bits, char *chars, int max_nbytes) +{ + int max_nchars = max_nbytes/BYTES_PER_CHAR; + int i; + if (max_nchars > ((bits->nbBits)>>LOG2_BITS_PER_CHAR)) + max_nchars = ((bits->nbBits)>>LOG2_BITS_PER_CHAR); + for (i=0;ichars[i]); + + if (bits->bitPtr>0) + bits->chars[0]=bits->chars[max_nchars]; + else + bits->chars[0]=0; + bits->charPtr=0; + bits->nbBits &= (BITS_PER_CHAR-1); + return max_nchars*BYTES_PER_CHAR; +} + +void speex_bits_pack(SpeexBits *bits, int data, int nbBits) +{ + unsigned int d=data; + + if (bits->charPtr+((nbBits+bits->bitPtr)>>LOG2_BITS_PER_CHAR) >= bits->buf_size) + { + speex_notify("Buffer too small to pack bits"); + if (bits->owner) + { + int new_nchars = ((bits->buf_size+5)*3)>>1; + char *tmp = (char*)speex_realloc(bits->chars, new_nchars); + if (tmp) + { + bits->buf_size=new_nchars; + bits->chars=tmp; + } else { + speex_warning("Could not resize input buffer: not packing"); + return; + } + } else { + speex_warning("Do not own input buffer: not packing"); + return; + } + } + + while(nbBits) + { + int bit; + bit = (d>>(nbBits-1))&1; + bits->chars[bits->charPtr] |= bit<<(BITS_PER_CHAR-1-bits->bitPtr); + bits->bitPtr++; + + if (bits->bitPtr==BITS_PER_CHAR) + { + bits->bitPtr=0; + bits->charPtr++; + bits->chars[bits->charPtr] = 0; + } + bits->nbBits++; + nbBits--; + } +} + +int speex_bits_unpack_signed(SpeexBits *bits, int nbBits) +{ + unsigned int d=speex_bits_unpack_unsigned(bits,nbBits); + /* If number is negative */ + if (d>>(nbBits-1)) + { + d |= (-1)<charPtr<bitPtr+nbBits>bits->nbBits) + bits->overflow=1; + if (bits->overflow) + return 0; + while(nbBits) + { + d<<=1; + d |= (bits->chars[bits->charPtr]>>(BITS_PER_CHAR-1 - bits->bitPtr))&1; + bits->bitPtr++; + if (bits->bitPtr==BITS_PER_CHAR) + { + bits->bitPtr=0; + bits->charPtr++; + } + nbBits--; + } + return d; +} + +unsigned int speex_bits_peek_unsigned(SpeexBits *bits, int nbBits) +{ + unsigned int d=0; + int bitPtr, charPtr; + char *chars; + + if ((bits->charPtr<bitPtr+nbBits>bits->nbBits) + bits->overflow=1; + if (bits->overflow) + return 0; + + bitPtr=bits->bitPtr; + charPtr=bits->charPtr; + chars = bits->chars; + while(nbBits) + { + d<<=1; + d |= (chars[charPtr]>>(BITS_PER_CHAR-1 - bitPtr))&1; + bitPtr++; + if (bitPtr==BITS_PER_CHAR) + { + bitPtr=0; + charPtr++; + } + nbBits--; + } + return d; +} + +int speex_bits_peek(SpeexBits *bits) +{ + if ((bits->charPtr<bitPtr+1>bits->nbBits) + bits->overflow=1; + if (bits->overflow) + return 0; + return (bits->chars[bits->charPtr]>>(BITS_PER_CHAR-1 - bits->bitPtr))&1; +} + +void speex_bits_advance(SpeexBits *bits, int n) +{ + if (((bits->charPtr<bitPtr+n>bits->nbBits) || bits->overflow){ + bits->overflow=1; + return; + } + bits->charPtr += (bits->bitPtr+n) >> LOG2_BITS_PER_CHAR; /* divide by BITS_PER_CHAR */ + bits->bitPtr = (bits->bitPtr+n) & (BITS_PER_CHAR-1); /* modulo by BITS_PER_CHAR */ +} + +int speex_bits_remaining(SpeexBits *bits) +{ + if (bits->overflow) + return -1; + else + return bits->nbBits-((bits->charPtr<bitPtr); +} + +int speex_bits_nbytes(SpeexBits *bits) +{ + return ((bits->nbBits+BITS_PER_CHAR-1)>>LOG2_BITS_PER_CHAR); +} + +void speex_bits_insert_terminator(SpeexBits *bits) +{ + if (bits->bitPtr) + speex_bits_pack(bits, 0, 1); + while (bits->bitPtr) + speex_bits_pack(bits, 1, 1); +} diff --git a/src/libspeex/buffer.c b/src/libspeex/buffer.c new file mode 100644 index 0000000..e0761fd --- /dev/null +++ b/src/libspeex/buffer.c @@ -0,0 +1,176 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: buffer.c + This is a very simple ring buffer implementation. It is not thread-safe + so you need to do your own locking. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "os_support.h" +#include "arch.h" +#include + +struct SpeexBuffer_ { + char *data; + int size; + int read_ptr; + int write_ptr; + int available; +}; + +SpeexBuffer *speex_buffer_init(int size) +{ + SpeexBuffer *st = speex_alloc(sizeof(SpeexBuffer)); + st->data = speex_alloc(size); + st->size = size; + st->read_ptr = 0; + st->write_ptr = 0; + st->available = 0; + return st; +} + +void speex_buffer_destroy(SpeexBuffer *st) +{ + speex_free(st->data); + speex_free(st); +} + +int speex_buffer_write(SpeexBuffer *st, void *_data, int len) +{ + int end; + int end1; + char *data = _data; + if (len > st->size) + { + data += len-st->size; + len = st->size; + } + end = st->write_ptr + len; + end1 = end; + if (end1 > st->size) + end1 = st->size; + SPEEX_COPY(st->data + st->write_ptr, data, end1 - st->write_ptr); + if (end > st->size) + { + end -= st->size; + SPEEX_COPY(st->data, data+end1 - st->write_ptr, end); + } + st->available += len; + if (st->available > st->size) + { + st->available = st->size; + st->read_ptr = st->write_ptr; + } + st->write_ptr += len; + if (st->write_ptr > st->size) + st->write_ptr -= st->size; + return len; +} + +int speex_buffer_writezeros(SpeexBuffer *st, int len) +{ + /* This is almost the same as for speex_buffer_write() but using + SPEEX_MEMSET() instead of SPEEX_COPY(). Update accordingly. */ + int end; + int end1; + if (len > st->size) + { + len = st->size; + } + end = st->write_ptr + len; + end1 = end; + if (end1 > st->size) + end1 = st->size; + SPEEX_MEMSET(st->data + st->write_ptr, 0, end1 - st->write_ptr); + if (end > st->size) + { + end -= st->size; + SPEEX_MEMSET(st->data, 0, end); + } + st->available += len; + if (st->available > st->size) + { + st->available = st->size; + st->read_ptr = st->write_ptr; + } + st->write_ptr += len; + if (st->write_ptr > st->size) + st->write_ptr -= st->size; + return len; +} + +int speex_buffer_read(SpeexBuffer *st, void *_data, int len) +{ + int end, end1; + char *data = _data; + if (len > st->available) + { + SPEEX_MEMSET(data+st->available, 0, st->size-st->available); + len = st->available; + } + end = st->read_ptr + len; + end1 = end; + if (end1 > st->size) + end1 = st->size; + SPEEX_COPY(data, st->data + st->read_ptr, end1 - st->read_ptr); + + if (end > st->size) + { + end -= st->size; + SPEEX_COPY(data+end1 - st->read_ptr, st->data, end); + } + st->available -= len; + st->read_ptr += len; + if (st->read_ptr > st->size) + st->read_ptr -= st->size; + return len; +} + +int speex_buffer_get_available(SpeexBuffer *st) +{ + return st->available; +} + +int speex_buffer_resize(SpeexBuffer *st, int len) +{ + int old_len = st->size; + if (len > old_len) + { + st->data = speex_realloc(st->data, len); + /* FIXME: move data/pointers properly for growing the buffer */ + } else { + /* FIXME: move data/pointers properly for shrinking the buffer */ + st->data = speex_realloc(st->data, len); + } + return len; +} diff --git a/src/libspeex/cb_search.c b/src/libspeex/cb_search.c new file mode 100644 index 0000000..63f4c6a --- /dev/null +++ b/src/libspeex/cb_search.c @@ -0,0 +1,612 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: cb_search.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cb_search.h" +#include "filters.h" +#include "stack_alloc.h" +#include "vq.h" +#include "arch.h" +#include "math_approx.h" +#include "os_support.h" + +#ifdef _USE_SSE +#include "cb_search_sse.h" +#elif defined(ARM4_ASM) || defined(ARM5E_ASM) +#include "cb_search_arm4.h" +#elif defined(BFIN_ASM) +#include "cb_search_bfin.h" +#endif + +#ifndef OVERRIDE_COMPUTE_WEIGHTED_CODEBOOK +static void compute_weighted_codebook(const signed char *shape_cb, const spx_word16_t *r, spx_word16_t *resp, spx_word16_t *resp2, spx_word32_t *E, int shape_cb_size, int subvect_size, char *stack) +{ + int i, j, k; + VARDECL(spx_word16_t *shape); + ALLOC(shape, subvect_size, spx_word16_t); + for (i=0;isubvect_size; + nb_subvect = params->nb_subvect; + shape_cb_size = 1<shape_bits; + shape_cb = params->shape_cb; + have_sign = params->have_sign; + ALLOC(resp, shape_cb_size*subvect_size, spx_word16_t); +#ifdef _USE_SSE + ALLOC(resp2, (shape_cb_size*subvect_size)>>2, __m128); + ALLOC(E, shape_cb_size>>2, __m128); +#else + resp2 = resp; + ALLOC(E, shape_cb_size, spx_word32_t); +#endif + ALLOC(t, nsf, spx_word16_t); + ALLOC(e, nsf, spx_sig_t); + + /* FIXME: Do we still need to copy the target? */ + SPEEX_COPY(t, target, nsf); + + compute_weighted_codebook(shape_cb, r, resp, resp2, E, shape_cb_size, subvect_size, stack); + + for (i=0;ishape_bits+have_sign); + + { + int rind; + spx_word16_t *res; + spx_word16_t sign=1; + rind = best_index; + if (rind>=shape_cb_size) + { + sign=-1; + rind-=shape_cb_size; + } + res = resp+rind*subvect_size; + if (sign>0) + for (m=0;m=shape_cb_size) + { + sign=-1; + rind-=shape_cb_size; + } + + q=subvect_size-m; +#ifdef FIXED_POINT + g=sign*shape_cb[rind*subvect_size+m]; +#else + g=sign*0.03125*shape_cb[rind*subvect_size+m]; +#endif + target_update(t+subvect_size*(i+1), g, r+q, nsf-subvect_size*(i+1)); + } + } + + /* Update excitation */ + /* FIXME: We could update the excitation directly above */ + for (j=0;j10) + N=10; + /* Complexity isn't as important for the codebooks as it is for the pitch */ + N=(2*N)/3; + if (N<1) + N=1; + if (N==1) + { + split_cb_search_shape_sign_N1(target,ak,awk1,awk2,par,p,nsf,exc,r,bits,stack,update_target); + return; + } + ALLOC(ot2, N, spx_word16_t*); + ALLOC(nt2, N, spx_word16_t*); + ALLOC(oind, N, int*); + ALLOC(nind, N, int*); + + params = (const split_cb_params *) par; + subvect_size = params->subvect_size; + nb_subvect = params->nb_subvect; + shape_cb_size = 1<shape_bits; + shape_cb = params->shape_cb; + have_sign = params->have_sign; + ALLOC(resp, shape_cb_size*subvect_size, spx_word16_t); +#ifdef _USE_SSE + ALLOC(resp2, (shape_cb_size*subvect_size)>>2, __m128); + ALLOC(E, shape_cb_size>>2, __m128); +#else + resp2 = resp; + ALLOC(E, shape_cb_size, spx_word32_t); +#endif + ALLOC(t, nsf, spx_word16_t); + ALLOC(e, nsf, spx_sig_t); + ALLOC(ind, nb_subvect, int); + + ALLOC(tmp, 2*N*nsf, spx_word16_t); + for (i=0;im;n--) + { + ndist[n] = ndist[n-1]; + best_nind[n] = best_nind[n-1]; + best_ntarget[n] = best_ntarget[n-1]; + } + /* n is equal to m here, so they're interchangeable */ + ndist[m] = err; + best_nind[n] = best_index[k]; + best_ntarget[n] = j; + break; + } + } + } + } + if (i==0) + break; + } + for (j=0;j=shape_cb_size) + { + sign=-1; + rind-=shape_cb_size; + } + + q=subvect_size-m; +#ifdef FIXED_POINT + g=sign*shape_cb[rind*subvect_size+m]; +#else + g=sign*0.03125*shape_cb[rind*subvect_size+m]; +#endif + target_update(nt[j]+subvect_size*(i+1), g, r+q, nsf-subvect_size*(i+1)); + } + + for (q=0;qshape_bits+have_sign); + } + + /* Put everything back together */ + for (i=0;i=shape_cb_size) + { + sign=-1; + rind-=shape_cb_size; + } +#ifdef FIXED_POINT + if (sign==1) + { + for (j=0;jsubvect_size; + nb_subvect = params->nb_subvect; + shape_cb_size = 1<shape_bits; + shape_cb = params->shape_cb; + have_sign = params->have_sign; + + ALLOC(ind, nb_subvect, int); + ALLOC(signs, nb_subvect, int); + + /* Decode codewords and gains */ + for (i=0;ishape_bits); + } + /* Compute decoded excitation */ + for (i=0;i +#include "arch.h" + +/** Split codebook parameters. */ +typedef struct split_cb_params { + int subvect_size; + int nb_subvect; + const signed char *shape_cb; + int shape_bits; + int have_sign; +} split_cb_params; + + +void split_cb_search_shape_sign( +spx_word16_t target[], /* target vector */ +spx_coef_t ak[], /* LPCs for this subframe */ +spx_coef_t awk1[], /* Weighted LPCs for this subframe */ +spx_coef_t awk2[], /* Weighted LPCs for this subframe */ +const void *par, /* Codebook/search parameters */ +int p, /* number of LPC coeffs */ +int nsf, /* number of samples in subframe */ +spx_sig_t *exc, +spx_word16_t *r, +SpeexBits *bits, +char *stack, +int complexity, +int update_target +); + +void split_cb_shape_sign_unquant( +spx_sig_t *exc, +const void *par, /* non-overlapping codebook */ +int nsf, /* number of samples in subframe */ +SpeexBits *bits, +char *stack, +spx_int32_t *seed +); + + +void noise_codebook_quant( +spx_word16_t target[], /* target vector */ +spx_coef_t ak[], /* LPCs for this subframe */ +spx_coef_t awk1[], /* Weighted LPCs for this subframe */ +spx_coef_t awk2[], /* Weighted LPCs for this subframe */ +const void *par, /* Codebook/search parameters */ +int p, /* number of LPC coeffs */ +int nsf, /* number of samples in subframe */ +spx_sig_t *exc, +spx_word16_t *r, +SpeexBits *bits, +char *stack, +int complexity, +int update_target +); + + +void noise_codebook_unquant( +spx_sig_t *exc, +const void *par, /* non-overlapping codebook */ +int nsf, /* number of samples in subframe */ +SpeexBits *bits, +char *stack, +spx_int32_t *seed +); + +#endif diff --git a/src/libspeex/cb_search_arm4.h b/src/libspeex/cb_search_arm4.h new file mode 100644 index 0000000..19b752a --- /dev/null +++ b/src/libspeex/cb_search_arm4.h @@ -0,0 +1,137 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file cb_search_arm4.h + @brief Fixed codebook functions (ARM4 version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* This optimization is temporaly disabled until it is fixed to account for the fact + that "r" is now a 16-bit array */ +#if 0 +#define OVERRIDE_COMPUTE_WEIGHTED_CODEBOOK +static void compute_weighted_codebook(const signed char *shape_cb, const spx_word16_t *r, spx_word16_t *resp, spx_word16_t *resp2, spx_word32_t *E, int shape_cb_size, int subvect_size, char *stack) +{ + int i, j, k; + //const signed char *shape; + for (i=0;i>>= 13;\n\t" + "A1 += R0.L*R0.L (IS);\n\t" + "W[P3++] = R0;\n\t" + "P0 += 1;\n\t" + "P2 += 2;\n\t" + "LOOP_END outter%=;\n\t" + "P4 = %4;\n\t" + "R1 = A1;\n\t" + "[P4] = R1;\n\t" + : + : "m" (subvect_size), "m" (shape_cb), "m" (r), "m" (resp), "m" (E) + : "A0", "P0", "P1", "P2", "P3", "P4", "R0", "R1", "R2", "I0", "I1", "L0", + "L1", "A0", "A1", "memory" +#if !(__GNUC__ == 3) + , "LC0", "LC1" /* gcc 3.4 doesn't know about LC registers */ +#endif + ); + shape_cb += subvect_size; + resp += subvect_size; + E++; + } +} + +#define OVERRIDE_TARGET_UPDATE +static inline void target_update(spx_word16_t *t, spx_word16_t g, spx_word16_t *r, int len) +{ + if (!len) + return; + __asm__ __volatile__ + ( + "I0 = %0;\n\t" + "I1 = %1;\n\t" + "L0 = 0;\n\t" + "L1 = 0;\n\t" + "R2 = 4096;\n\t" + "LOOP tupdate%= LC0 = %3;\n\t" + "LOOP_BEGIN tupdate%=;\n\t" + "R0.L = W[I0] || R1.L = W[I1++];\n\t" + "R1 = (A1 = R1.L*%2.L) (IS);\n\t" + "R1 = R1 + R2;\n\t" + "R1 >>>= 13;\n\t" + "R0.L = R0.L - R1.L;\n\t" + "W[I0++] = R0.L;\n\t" + "LOOP_END tupdate%=;\n\t" + : + : "a" (t), "a" (r), "d" (g), "a" (len) + : "R0", "R1", "R2", "A1", "I0", "I1", "L0", "L1" + ); +} diff --git a/src/libspeex/cb_search_sse.h b/src/libspeex/cb_search_sse.h new file mode 100644 index 0000000..8b03968 --- /dev/null +++ b/src/libspeex/cb_search_sse.h @@ -0,0 +1,84 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file cb_search_sse.h + @brief Fixed codebook functions (SSE version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +static inline void _spx_mm_getr_ps (__m128 U, float *__Z, float *__Y, float *__X, float *__W) +{ + union { + float __a[4]; + __m128 __v; + } __u; + + __u.__v = U; + + *__Z = __u.__a[0]; + *__Y = __u.__a[1]; + *__X = __u.__a[2]; + *__W = __u.__a[3]; + +} + +#define OVERRIDE_COMPUTE_WEIGHTED_CODEBOOK +static void compute_weighted_codebook(const signed char *shape_cb, const spx_sig_t *_r, float *resp, __m128 *resp2, __m128 *E, int shape_cb_size, int subvect_size, char *stack) +{ + int i, j, k; + __m128 resj, EE; + VARDECL(__m128 *r); + VARDECL(__m128 *shape); + ALLOC(r, subvect_size, __m128); + ALLOC(shape, subvect_size, __m128); + for(j=0;j>2] = EE; + } +} diff --git a/src/libspeex/config.h b/src/libspeex/config.h new file mode 100644 index 0000000..e6657fc --- /dev/null +++ b/src/libspeex/config.h @@ -0,0 +1,23 @@ +// Microsoft version of 'inline' +#define inline __inline + +// Visual Studio support alloca(), but it always align variables to 16-bit +// boundary, while SSE need 128-bit alignment. So we disable alloca() when +// SSE is enabled. +#ifndef _USE_SSE +# define USE_ALLOCA +# if defined(__sun) /* Solaris needs this for alloca(). */ +# define HAVE_ALLOCA_H +# endif +#endif + +/* Default to floating point */ +#ifndef FIXED_POINT +# define FLOATING_POINT +# define USE_SMALLFT +#else +# define USE_KISS_FFT +#endif + +/* We don't support visibility on Win32 */ +#define EXPORT diff --git a/src/libspeex/echo_diagnostic.m b/src/libspeex/echo_diagnostic.m new file mode 100644 index 0000000..aebf390 --- /dev/null +++ b/src/libspeex/echo_diagnostic.m @@ -0,0 +1,72 @@ +% Attempts to diagnose AEC problems from recorded samples +% +% out = echo_diagnostic(rec_file, play_file, out_file, tail_length) +% +% Computes the full matrix inversion to cancel echo from the +% recording 'rec_file' using the far end signal 'play_file' using +% a filter length of 'tail_length'. The output is saved to 'out_file'. +function out = echo_diagnostic(rec_file, play_file, out_file, tail_length) + +F=fopen(rec_file,'rb'); +rec=fread(F,Inf,'short'); +fclose (F); +F=fopen(play_file,'rb'); +play=fread(F,Inf,'short'); +fclose (F); + +rec = [rec; zeros(1024,1)]; +play = [play; zeros(1024,1)]; + +N = length(rec); +corr = real(ifft(fft(rec).*conj(fft(play)))); +acorr = real(ifft(fft(play).*conj(fft(play)))); + +[a,b] = max(corr); + +if b > N/2 + b = b-N; +end +printf ("Far end to near end delay is %d samples\n", b); +if (b > .3*tail_length) + printf ('This is too much delay, try delaying the far-end signal a bit\n'); +else if (b < 0) + printf ('You have a negative delay, the echo canceller has no chance to cancel anything!\n'); + else + printf ('Delay looks OK.\n'); + end + end +end +N2 = round(N/2); +corr1 = real(ifft(fft(rec(1:N2)).*conj(fft(play(1:N2))))); +corr2 = real(ifft(fft(rec(N2+1:end)).*conj(fft(play(N2+1:end))))); + +[a,b1] = max(corr1); +if b1 > N2/2 + b1 = b1-N2; +end +[a,b2] = max(corr2); +if b2 > N2/2 + b2 = b2-N2; +end +drift = (b1-b2)/N2; +printf ('Drift estimate is %f%% (%d samples)\n', 100*drift, b1-b2); +if abs(b1-b2) < 10 + printf ('A drift of a few (+-10) samples is normal.\n'); +else + if abs(b1-b2) < 30 + printf ('There may be (not sure) excessive clock drift. Is the capture and playback done on the same soundcard?\n'); + else + printf ('Your clock is drifting! No way the AEC will be able to do anything with that. Most likely, you''re doing capture and playback from two different cards.\n'); + end + end +end +acorr(1) = .001+1.00001*acorr(1); +AtA = toeplitz(acorr(1:tail_length)); +bb = corr(1:tail_length); +h = AtA\bb; + +out = (rec - filter(h, 1, play)); + +F=fopen(out_file,'w'); +fwrite(F,out,'short'); +fclose (F); diff --git a/src/libspeex/exc_10_16_table.c b/src/libspeex/exc_10_16_table.c new file mode 100644 index 0000000..98ae357 --- /dev/null +++ b/src/libspeex/exc_10_16_table.c @@ -0,0 +1,50 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_10_16_table.c + Codebook for excitation in narrowband CELP mode (3200 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_10_16_table[160] = { +22,39,14,44,11,35,-2,23,-4,6, +46,-28,13,-27,-23,12,4,20,-5,9, +37,-18,-23,23,0,9,-6,-20,4,-1, +-17,-5,-4,17,0,1,9,-2,1,2, +2,-12,8,-25,39,15,9,16,-55,-11, +9,11,5,10,-2,-60,8,13,-6,11, +-16,27,-47,-12,11,1,16,-7,9,-3, +-29,9,-14,25,-19,34,36,12,40,-10, +-3,-24,-14,-37,-21,-35,-2,-36,3,-6, +67,28,6,-17,-3,-12,-16,-15,-17,-7, +-59,-36,-13,1,7,1,2,10,2,11, +13,10,8,-2,7,3,5,4,2,2, +-3,-8,4,-5,6,7,-42,15,35,-2, +-46,38,28,-20,-9,1,7,-3,0,-2, +0,0,0,0,0,0,0,0,0,0, +-15,-28,52,32,5,-5,-17,-20,-10,-1}; diff --git a/src/libspeex/exc_10_32_table.c b/src/libspeex/exc_10_32_table.c new file mode 100644 index 0000000..1ee56a2 --- /dev/null +++ b/src/libspeex/exc_10_32_table.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_10_32_table.c + Codebook for excitation in narrowband CELP mode (4000 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_10_32_table[320] = { +7,17,17,27,25,22,12,4,-3,0, +28,-36,39,-24,-15,3,-9,15,-5,10, +31,-28,11,31,-21,9,-11,-11,-2,-7, +-25,14,-22,31,4,-14,19,-12,14,-5, +4,-7,4,-5,9,0,-2,42,-47,-16, +1,8,0,9,23,-57,0,28,-11,6, +-31,55,-45,3,-5,4,2,-2,4,-7, +-3,6,-2,7,-3,12,5,8,54,-10, +8,-7,-8,-24,-25,-27,-14,-5,8,5, +44,23,5,-9,-11,-11,-13,-9,-12,-8, +-29,-8,-22,6,-15,3,-12,-1,-5,-3, +34,-1,29,-16,17,-4,12,2,1,4, +-2,-4,2,-1,11,-3,-52,28,30,-9, +-32,25,44,-20,-24,4,6,-1,0,0, +0,0,0,0,0,0,0,0,0,0, +-25,-10,22,29,13,-13,-22,-13,-4,0, +-4,-16,10,15,-36,-24,28,25,-1,-3, +66,-33,-11,-15,6,0,3,4,-2,5, +24,-20,-47,29,19,-2,-4,-1,0,-1, +-2,3,1,8,-11,5,5,-57,28,28, +0,-16,4,-4,12,-6,-1,2,-20,61, +-9,24,-22,-42,29,6,17,8,4,2, +-65,15,8,10,5,6,5,3,2,-2, +-3,5,-9,4,-5,23,13,23,-3,-63, +3,-5,-4,-6,0,-3,23,-36,-46,9, +5,5,8,4,9,-5,1,-3,10,1, +-6,10,-11,24,-47,31,22,-12,14,-10, +6,11,-7,-7,7,-31,51,-12,-6,7, +6,-17,9,-11,-20,52,-19,3,-6,-6, +-8,-5,23,-41,37,1,-21,10,-14,8, +7,5,-15,-15,23,39,-26,-33,7,2, +-32,-30,-21,-8,4,12,17,15,14,11}; diff --git a/src/libspeex/exc_20_32_table.c b/src/libspeex/exc_20_32_table.c new file mode 100644 index 0000000..e4098b8 --- /dev/null +++ b/src/libspeex/exc_20_32_table.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_20_32_table.c + Codebook for excitation in narrowband CELP mode (2000 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_20_32_table[640] = { +12,32,25,46,36,33,9,14,-3,6,1,-8,0,-10,-5,-7,-7,-7,-5,-5, +31,-27,24,-32,-4,10,-11,21,-3,19,23,-9,22,24,-10,-1,-10,-13,-7,-11, +42,-33,31,19,-8,0,-10,-16,1,-21,-17,10,-8,14,8,4,11,-2,5,-2, +-33,11,-16,33,11,-4,9,-4,11,2,6,-5,8,-5,11,-4,-6,26,-36,-16, +0,4,-2,-8,12,6,-1,34,-46,-22,9,9,21,9,5,-66,-5,26,2,10, +13,2,19,9,12,-81,3,13,13,0,-14,22,-35,6,-7,-4,6,-6,10,-6, +-31,38,-33,0,-10,-11,5,-12,12,-17,5,0,-6,13,-9,10,8,25,33,2, +-12,8,-6,10,-2,21,7,17,43,5,11,-7,-9,-20,-36,-20,-23,-4,-4,-3, +27,-9,-9,-49,-39,-38,-11,-9,6,5,23,25,5,3,3,4,1,2,-3,-1, +87,39,17,-21,-9,-19,-9,-15,-13,-14,-17,-11,-10,-11,-8,-6,-1,-3,-3,-1, +-54,-34,-27,-8,-11,-4,-5,0,0,4,8,6,9,7,9,7,6,5,5,5, +48,10,19,-10,12,-1,9,-3,2,5,-3,2,-2,-2,0,-2,-26,6,9,-7, +-16,-9,2,7,7,-5,-43,11,22,-11,-9,34,37,-15,-13,-6,1,-1,1,1, +-64,56,52,-11,-27,5,4,3,1,2,1,3,-1,-4,-4,-10,-7,-4,-4,2, +-1,-7,-7,-12,-10,-15,-9,-5,-5,-11,-16,-13,6,16,4,-13,-16,-10,-4,2, +-47,-13,25,47,19,-14,-20,-8,-17,0,-3,-13,1,6,-17,-14,15,1,10,6, +-24,0,-10,19,-69,-8,14,49,17,-5,33,-29,3,-4,0,2,-8,5,-6,2, +120,-56,-12,-47,23,-9,6,-5,1,2,-5,1,-10,4,-1,-1,4,-1,0,-3, +30,-52,-67,30,22,11,-1,-4,3,0,7,2,0,1,-10,-4,-8,-13,5,1, +1,-1,5,13,-9,-3,-10,-62,22,48,-4,-6,2,3,5,1,1,4,1,13, +3,-20,10,-9,13,-2,-4,9,-20,44,-1,20,-32,-67,19,0,28,11,8,2, +-11,15,-19,-53,31,2,34,10,6,-4,-58,8,10,13,14,1,12,2,0,0, +-128,37,-8,44,-9,26,-3,18,2,6,11,-1,9,1,5,3,0,1,1,2, +12,3,-2,-3,7,25,9,18,-6,-37,3,-8,-16,3,-10,-7,17,-34,-44,11, +17,-15,-3,-16,-1,-13,11,-46,-65,-2,8,13,2,4,4,5,15,5,9,6, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +-9,19,-12,12,-28,38,29,-1,12,2,5,23,-10,3,4,-15,21,-4,3,3, +6,17,-9,-4,-8,-20,26,5,-10,6,1,-19,18,-15,-12,47,-6,-2,-7,-9, +-1,-17,-2,-2,-14,30,-14,2,-7,-4,-1,-12,11,-25,16,-3,-12,11,-7,7, +-17,1,19,-28,31,-7,-10,7,-10,3,12,5,-16,6,24,41,-29,-54,0,1, +7,-1,5,-6,13,10,-4,-8,8,-9,-27,-53,-38,-1,10,19,17,16,12,12, +0,3,-7,-4,13,12,-31,-14,6,-5,3,5,17,43,50,25,10,1,-6,-2}; diff --git a/src/libspeex/exc_5_256_table.c b/src/libspeex/exc_5_256_table.c new file mode 100644 index 0000000..4137996 --- /dev/null +++ b/src/libspeex/exc_5_256_table.c @@ -0,0 +1,290 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_5_256_table.c + Codebook for excitation in narrowband CELP mode (12800 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_5_256_table[1280] = { +-8,-37,5,-43,5, +73,61,39,12,-3, +-61,-32,2,42,30, +-3,17,-27,9,34, +20,-1,-5,2,23, +-7,-46,26,53,-47, +20,-2,-33,-89,-51, +-64,27,11,15,-34, +-5,-56,25,-9,-1, +-29,1,40,67,-23, +-16,16,33,19,7, +14,85,22,-10,-10, +-12,-7,-1,52,89, +29,11,-20,-37,-46, +-15,17,-24,-28,24, +2,1,0,23,-101, +23,14,-1,-23,-18, +9,5,-13,38,1, +-28,-28,4,27,51, +-26,34,-40,35,47, +54,38,-54,-26,-6, +42,-25,13,-30,-36, +18,41,-4,-33,23, +-32,-7,-4,51,-3, +17,-52,56,-47,36, +-2,-21,36,10,8, +-33,31,19,9,-5, +-40,10,-9,-21,19, +18,-78,-18,-5,0, +-26,-36,-47,-51,-44, +18,40,27,-2,29, +49,-26,2,32,-54, +30,-73,54,3,-5, +36,22,53,10,-1, +-84,-53,-29,-5,3, +-44,53,-51,4,22, +71,-35,-1,33,-5, +-27,-7,36,17,-23, +-39,16,-9,-55,-15, +-20,39,-35,6,-39, +-14,18,48,-64,-17, +-15,9,39,81,37, +-68,37,47,-21,-6, +-104,13,6,9,-2, +35,8,-23,18,42, +45,21,33,-5,-49, +9,-6,-43,-56,39, +2,-16,-25,87,1, +-3,-9,17,-25,-11, +-9,-1,10,2,-14, +-14,4,-1,-10,28, +-23,40,-32,26,-9, +26,4,-27,-23,3, +42,-60,1,49,-3, +27,10,-52,-40,-2, +18,45,-23,17,-44, +3,-3,17,-46,52, +-40,-47,25,75,31, +-49,53,30,-30,-32, +-36,38,-6,-15,-16, +54,-27,-48,3,38, +-29,-32,-22,-14,-4, +-23,-13,32,-39,9, +8,-45,-13,34,-16, +49,40,32,31,28, +23,23,32,47,59, +-68,8,62,44,25, +-14,-24,-65,-16,36, +67,-25,-38,-21,4, +-33,-2,42,5,-63, +40,11,26,-42,-23, +-61,79,-31,23,-20, +10,-32,53,-25,-36, +10,-26,-5,3,0, +-71,5,-10,-37,1, +-24,21,-54,-17,1, +-29,-25,-15,-27,32, +68,45,-16,-37,-18, +-5,1,0,-77,71, +-6,3,-20,71,-67, +29,-35,10,-30,19, +4,16,17,5,0, +-14,19,2,28,26, +59,3,2,24,39, +55,-50,-45,-18,-17, +33,-35,14,-1,1, +8,87,-35,-29,0, +-27,13,-7,23,-13, +37,-40,50,-35,14, +19,-7,-14,49,54, +-5,22,-2,-29,-8, +-27,38,13,27,48, +12,-41,-21,-15,28, +7,-16,-24,-19,-20, +11,-20,9,2,13, +23,-20,11,27,-27, +71,-69,8,2,-6, +22,12,16,16,9, +-16,-8,-17,1,25, +1,40,-37,-33,66, +94,53,4,-22,-25, +-41,-42,25,35,-16, +-15,57,31,-29,-32, +21,16,-60,45,15, +-1,7,57,-26,-47, +-29,11,8,15,19, +-105,-8,54,27,10, +-17,6,-12,-1,-10, +4,0,23,-10,31, +13,11,10,12,-64, +23,-3,-8,-19,16, +52,24,-40,16,10, +40,5,9,0,-13, +-7,-21,-8,-6,-7, +-21,59,16,-53,18, +-60,11,-47,14,-18, +25,-13,-24,4,-39, +16,-28,54,26,-67, +30,27,-20,-52,20, +-12,55,12,18,-16, +39,-14,-6,-26,56, +-88,-55,12,25,26, +-37,6,75,0,-34, +-81,54,-30,1,-7, +49,-23,-14,21,10, +-62,-58,-57,-47,-34, +15,-4,34,-78,31, +25,-11,7,50,-10, +42,-63,14,-36,-4, +57,55,57,53,42, +-42,-1,15,40,37, +15,25,-11,6,1, +31,-2,-6,-1,-7, +-64,34,28,30,-1, +3,21,0,-88,-12, +-56,25,-28,40,8, +-28,-14,9,12,2, +-6,-17,22,49,-6, +-26,14,28,-20,4, +-12,50,35,40,13, +-38,-58,-29,17,30, +22,60,26,-54,-39, +-12,58,-28,-63,10, +-21,-8,-12,26,-62, +6,-10,-11,-22,-6, +-7,4,1,18,2, +-70,11,14,4,13, +19,-24,-34,24,67, +17,51,-21,13,23, +54,-30,48,1,-13, +80,26,-16,-2,13, +-4,6,-30,29,-24, +73,-58,30,-27,20, +-2,-21,41,45,30, +-27,-3,-5,-18,-20, +-49,-3,-35,10,42, +-19,-67,-53,-11,9, +13,-15,-33,-51,-30, +15,7,25,-30,4, +28,-22,-34,54,-29, +39,-46,20,16,34, +-4,47,75,1,-44, +-55,-24,7,-1,9, +-42,50,-8,-36,41, +68,0,-4,-10,-23, +-15,-50,64,36,-9, +-27,12,25,-38,-47, +-37,32,-49,51,-36, +2,-4,69,-26,19, +7,45,67,46,13, +-63,46,15,-47,4, +-41,13,-6,5,-21, +37,26,-55,-7,33, +-1,-28,10,-17,-64, +-14,0,-36,-17,93, +-3,-9,-66,44,-21, +3,-12,38,-6,-13, +-12,19,13,43,-43, +-10,-12,6,-5,9, +-49,32,-5,2,4, +5,15,-16,10,-21, +8,-62,-8,64,8, +79,-1,-66,-49,-18, +5,40,-5,-30,-45, +1,-6,21,-32,93, +-18,-30,-21,32,21, +-18,22,8,5,-41, +-54,80,22,-10,-7, +-8,-23,-64,66,56, +-14,-30,-41,-46,-14, +-29,-37,27,-14,42, +-2,-9,-29,34,14, +33,-14,22,4,10, +26,26,28,32,23, +-72,-32,3,0,-14, +35,-42,-78,-32,6, +29,-18,-45,-5,7, +-33,-45,-3,-22,-34, +8,-8,4,-51,-25, +-9,59,-78,21,-5, +-25,-48,66,-15,-17, +-24,-49,-13,25,-23, +-64,-6,40,-24,-19, +-11,57,-33,-8,1, +10,-52,-54,28,39, +49,34,-11,-61,-41, +-43,10,15,-15,51, +30,15,-51,32,-34, +-2,-34,14,18,16, +1,1,-3,-3,1, +1,-18,6,16,48, +12,-5,-42,7,36, +48,7,-20,-10,7, +12,2,54,39,-38, +37,54,4,-11,-8, +-46,-10,5,-10,-34, +46,-12,29,-37,39, +36,-11,24,56,17, +14,20,25,0,-25, +-28,55,-7,-5,27, +3,9,-26,-8,6, +-24,-10,-30,-31,-34, +18,4,22,21,40, +-1,-29,-37,-8,-21, +92,-29,11,-3,11, +73,23,22,7,4, +-44,-9,-11,21,-13, +11,9,-78,-1,47, +114,-12,-37,-19,-5, +-11,-22,19,12,-30, +7,38,45,-21,-8, +-9,55,-45,56,-21, +7,17,46,-57,-87, +-6,27,31,31,7, +-56,-12,46,21,-5, +-12,36,3,3,-21, +43,19,12,-7,9, +-14,0,-9,-33,-91, +7,26,3,-11,64, +83,-31,-46,25,2, +9,5,2,2,-1, +20,-17,10,-5,-27, +-8,20,8,-19,16, +-21,-13,-31,5,5, +42,24,9,34,-20, +28,-61,22,11,-39, +64,-20,-1,-30,-9, +-20,24,-25,-24,-29, +22,-60,6,-5,41, +-9,-87,14,34,15, +-57,52,69,15,-3, +-102,58,16,3,6, +60,-75,-32,26,7, +-57,-27,-32,-24,-21, +-29,-16,62,-46,31, +30,-27,-15,7,15}; diff --git a/src/libspeex/exc_5_64_table.c b/src/libspeex/exc_5_64_table.c new file mode 100644 index 0000000..2c66d51 --- /dev/null +++ b/src/libspeex/exc_5_64_table.c @@ -0,0 +1,98 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_5_64_table.c + Codebook for excitation in narrowband CELP mode (9600 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_5_64_table[320]={ +1,5,-15,49,-66, +-48,-4,50,-44,7, +37,16,-18,25,-26, +-26,-15,19,19,-27, +-47,28,57,5,-17, +-32,-41,68,21,-2, +64,56,8,-16,-13, +-26,-9,-16,11,6, +-39,25,-19,22,-31, +20,-45,55,-43,10, +-16,47,-40,40,-20, +-51,3,-17,-14,-15, +-24,53,-20,-46,46, +27,-68,32,3,-18, +-5,9,-31,16,-9, +-10,-1,-23,48,95, +47,25,-41,-32,-3, +15,-25,-55,36,41, +-27,20,5,13,14, +-22,5,2,-23,18, +46,-15,17,-18,-34, +-5,-8,27,-55,73, +16,2,-1,-17,40, +-78,33,0,2,19, +4,53,-16,-15,-16, +-28,-3,-13,49,8, +-7,-29,27,-13,32, +20,32,-61,16,14, +41,44,40,24,20, +7,4,48,-60,-77, +17,-6,-48,65,-15, +32,-30,-71,-10,-3, +-6,10,-2,-7,-29, +-56,67,-30,7,-5, +86,-6,-10,0,5, +-31,60,34,-38,-3, +24,10,-2,30,23, +24,-41,12,70,-43, +15,-17,6,13,16, +-13,8,30,-15,-8, +5,23,-34,-98,-4, +-13,13,-48,-31,70, +12,31,25,24,-24, +26,-7,33,-16,8, +5,-11,-14,-8,-65, +13,10,-2,-9,0, +-3,-68,5,35,7, +0,-31,-1,-17,-9, +-9,16,-37,-18,-1, +69,-48,-28,22,-21, +-11,5,49,55,23, +-86,-36,16,2,13, +63,-51,30,-11,13, +24,-18,-6,14,-19, +1,41,9,-5,27, +-36,-44,-34,-37,-21, +-26,31,-39,15,43, +5,-8,29,20,-8, +-20,-52,-28,-1,13, +26,-34,-10,-9,27, +-8,8,27,-66,4, +12,-22,49,10,-77, +32,-18,3,-38,12, +-3,-1,2,2,0}; diff --git a/src/libspeex/exc_8_128_table.c b/src/libspeex/exc_8_128_table.c new file mode 100644 index 0000000..17ee64b --- /dev/null +++ b/src/libspeex/exc_8_128_table.c @@ -0,0 +1,162 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: exc_8_128_table.c + Codebook for excitation in narrowband CELP mode (7000 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +const signed char exc_8_128_table[1024] = { +-14,9,13,-32,2,-10,31,-10, +-8,-8,6,-4,-1,10,-64,23, +6,20,13,6,8,-22,16,34, +7,42,-49,-28,5,26,4,-15, +41,34,41,32,33,24,23,14, +8,40,34,4,-24,-41,-19,-15, +13,-13,33,-54,24,27,-44,33, +27,-15,-15,24,-19,14,-36,14, +-9,24,-12,-4,37,-5,16,-34, +5,10,33,-15,-54,-16,12,25, +12,1,2,0,3,-1,-4,-4, +11,2,-56,54,27,-20,13,-6, +-46,-41,-33,-11,-5,7,12,14, +-14,-5,8,20,6,3,4,-8, +-5,-42,11,8,-14,25,-2,2, +13,11,-22,39,-9,9,5,-45, +-9,7,-9,12,-7,34,-17,-102, +7,2,-42,18,35,-9,-34,11, +-5,-2,3,22,46,-52,-25,-9, +-94,8,11,-5,-5,-5,4,-7, +-35,-7,54,5,-32,3,24,-9, +-22,8,65,37,-1,-12,-23,-6, +-9,-28,55,-33,14,-3,2,18, +-60,41,-17,8,-16,17,-11,0, +-11,29,-28,37,9,-53,33,-14, +-9,7,-25,-7,-11,26,-32,-8, +24,-21,22,-19,19,-10,29,-14, +0,0,0,0,0,0,0,0, +-5,-52,10,41,6,-30,-4,16, +32,22,-27,-22,32,-3,-28,-3, +3,-35,6,17,23,21,8,2, +4,-45,-17,14,23,-4,-31,-11, +-3,14,1,19,-11,2,61,-8, +9,-12,7,-10,12,-3,-24,99, +-48,23,50,-37,-5,-23,0,8, +-14,35,-64,-5,46,-25,13,-1, +-49,-19,-15,9,34,50,25,11, +-6,-9,-16,-20,-32,-33,-32,-27, +10,-8,12,-15,56,-14,-32,33, +3,-9,1,65,-9,-9,-10,-2, +-6,-23,9,17,3,-28,13,-32, +4,-2,-10,4,-16,76,12,-52, +6,13,33,-6,4,-14,-9,-3, +1,-15,-16,28,1,-15,11,16, +9,4,-21,-37,-40,-6,22,12, +-15,-23,-14,-17,-16,-9,-10,-9, +13,-39,41,5,-9,16,-38,25, +46,-47,4,49,-14,17,-2,6, +18,5,-6,-33,-22,44,50,-2, +1,3,-6,7,7,-3,-21,38, +-18,34,-14,-41,60,-13,6,16, +-24,35,19,-13,-36,24,3,-17, +-14,-10,36,44,-44,-29,-3,3, +-54,-8,12,55,26,4,-2,-5, +2,-11,22,-23,2,22,1,-25, +-39,66,-49,21,-8,-2,10,-14, +-60,25,6,10,27,-25,16,5, +-2,-9,26,-13,-20,58,-2,7, +52,-9,2,5,-4,-15,23,-1, +-38,23,8,27,-6,0,-27,-7, +39,-10,-14,26,11,-45,-12,9, +-5,34,4,-35,10,43,-22,-11, +56,-7,20,1,10,1,-26,9, +94,11,-27,-14,-13,1,-11,0, +14,-5,-6,-10,-4,-15,-8,-41, +21,-5,1,-28,-8,22,-9,33, +-23,-4,-4,-12,39,4,-7,3, +-60,80,8,-17,2,-6,12,-5, +1,9,15,27,31,30,27,23, +61,47,26,10,-5,-8,-12,-13, +5,-18,25,-15,-4,-15,-11,12, +-2,-2,-16,-2,-6,24,12,11, +-4,9,1,-9,14,-45,57,12, +20,-35,26,11,-64,32,-10,-10, +42,-4,-9,-16,32,24,7,10, +52,-11,-57,29,0,8,0,-6, +17,-17,-56,-40,7,20,18,12, +-6,16,5,7,-1,9,1,10, +29,12,16,13,-2,23,7,9, +-3,-4,-5,18,-64,13,55,-25, +9,-9,24,14,-25,15,-11,-40, +-30,37,1,-19,22,-5,-31,13, +-2,0,7,-4,16,-67,12,66, +-36,24,-8,18,-15,-23,19,0, +-45,-7,4,3,-13,13,35,5, +13,33,10,27,23,0,-7,-11, +43,-74,36,-12,2,5,-8,6, +-33,11,-16,-14,-5,-7,-3,17, +-34,27,-16,11,-9,15,33,-31, +8,-16,7,-6,-7,63,-55,-17, +11,-1,20,-46,34,-30,6,9, +19,28,-9,5,-24,-8,-23,-2, +31,-19,-16,-5,-15,-18,0,26, +18,37,-5,-15,-2,17,5,-27, +21,-33,44,12,-27,-9,17,11, +25,-21,-31,-7,13,33,-8,-25, +-7,7,-10,4,-6,-9,48,-82, +-23,-8,6,11,-23,3,-3,49, +-29,25,31,4,14,16,9,-4, +-18,10,-26,3,5,-44,-9,9, +-47,-55,15,9,28,1,4,-3, +46,6,-6,-38,-29,-31,-15,-6, +3,0,14,-6,8,-54,-50,33, +-5,1,-14,33,-48,26,-4,-5, +-3,-5,-3,-5,-28,-22,77,55, +-1,2,10,10,-9,-14,-66,-49, +11,-36,-6,-20,10,-10,16,12, +4,-1,-16,45,-44,-50,31,-2, +25,42,23,-32,-22,0,11,20, +-40,-35,-40,-36,-32,-26,-21,-13, +52,-22,6,-24,-20,17,-5,-8, +36,-25,-11,21,-26,6,34,-8, +7,20,-3,5,-25,-8,18,-5, +-9,-4,1,-9,20,20,39,48, +-24,9,5,-65,22,29,4,3, +-43,-11,32,-6,9,19,-27,-10, +-47,-14,24,10,-7,-36,-7,-1, +-4,-5,-5,16,53,25,-26,-29, +-4,-12,45,-58,-34,33,-5,2, +-1,27,-48,31,-15,22,-5,4, +7,7,-25,-3,11,-22,16,-12, +8,-3,7,-11,45,14,-73,-19, +56,-46,24,-20,28,-12,-2,-1, +-36,-3,-33,19,-6,7,2,-15, +5,-31,-45,8,35,13,20,0, +-9,48,-13,-43,-3,-13,2,-5, +72,-68,-27,2,1,-2,-7,5, +36,33,-40,-12,-4,-5,23,19}; diff --git a/src/libspeex/fftwrap.c b/src/libspeex/fftwrap.c new file mode 100644 index 0000000..0b462c2 --- /dev/null +++ b/src/libspeex/fftwrap.c @@ -0,0 +1,288 @@ +/* Copyright (C) 2005-2006 Jean-Marc Valin + File: fftwrap.c + + Wrapper for various FFTs + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/*#define USE_SMALLFT*/ +#define USE_KISS_FFT + + +#include "arch.h" +#include "os_support.h" + +#define MAX_FFT_SIZE 2048 + +#ifdef FIXED_POINT +static int maximize_range(spx_word16_t *in, spx_word16_t *out, spx_word16_t bound, int len) +{ + int i, shift; + spx_word16_t max_val = 0; + for (i=0;imax_val) + max_val = in[i]; + if (-in[i]>max_val) + max_val = -in[i]; + } + shift=0; + while (max_val <= (bound>>1) && max_val != 0) + { + max_val <<= 1; + shift++; + } + for (i=0;i + +void *spx_fft_init(int size) +{ + struct drft_lookup *table; + table = speex_alloc(sizeof(struct drft_lookup)); + spx_drft_init((struct drft_lookup *)table, size); + return (void*)table; +} + +void spx_fft_destroy(void *table) +{ + spx_drft_clear(table); + speex_free(table); +} + +void spx_fft(void *table, float *in, float *out) +{ + if (in==out) + { + int i; + float scale = 1./((struct drft_lookup *)table)->n; + speex_warning("FFT should not be done in-place"); + for (i=0;i<((struct drft_lookup *)table)->n;i++) + out[i] = scale*in[i]; + } else { + int i; + float scale = 1./((struct drft_lookup *)table)->n; + for (i=0;i<((struct drft_lookup *)table)->n;i++) + out[i] = scale*in[i]; + } + spx_drft_forward((struct drft_lookup *)table, out); +} + +void spx_ifft(void *table, float *in, float *out) +{ + if (in==out) + { + speex_warning("FFT should not be done in-place"); + } else { + int i; + for (i=0;i<((struct drft_lookup *)table)->n;i++) + out[i] = in[i]; + } + spx_drft_backward((struct drft_lookup *)table, out); +} + +#elif defined(USE_KISS_FFT) + +#include "kiss_fftr.h" +#include "kiss_fft.h" + +struct kiss_config { + kiss_fftr_cfg forward; + kiss_fftr_cfg backward; + int N; +}; + +void *spx_fft_init(int size) +{ + struct kiss_config *table; + table = (struct kiss_config*)speex_alloc(sizeof(struct kiss_config)); + table->forward = kiss_fftr_alloc(size,0,NULL,NULL); + table->backward = kiss_fftr_alloc(size,1,NULL,NULL); + table->N = size; + return table; +} + +void spx_fft_destroy(void *table) +{ + struct kiss_config *t = (struct kiss_config *)table; + kiss_fftr_free(t->forward); + kiss_fftr_free(t->backward); + speex_free(table); +} + +#ifdef FIXED_POINT + +void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + int shift; + struct kiss_config *t = (struct kiss_config *)table; + shift = maximize_range(in, in, 32000, t->N); + kiss_fftr2(t->forward, in, out); + renorm_range(in, in, shift, t->N); + renorm_range(out, out, shift, t->N); +} + +#else + +void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + int i; + float scale; + struct kiss_config *t = (struct kiss_config *)table; + scale = 1./t->N; + kiss_fftr2(t->forward, in, out); + for (i=0;iN;i++) + out[i] *= scale; +} +#endif + +void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out) +{ + struct kiss_config *t = (struct kiss_config *)table; + kiss_fftri2(t->backward, in, out); +} + + +#else + +#error No other FFT implemented + +#endif + + +#ifdef FIXED_POINT +/*#include "smallft.h"*/ + + +void spx_fft_float(void *table, float *in, float *out) +{ + int i; +#ifdef USE_SMALLFT + int N = ((struct drft_lookup *)table)->n; +#elif defined(USE_KISS_FFT) + int N = ((struct kiss_config *)table)->N; +#else +#endif +#ifdef VAR_ARRAYS + spx_word16_t _in[N]; + spx_word16_t _out[N]; +#else + spx_word16_t _in[MAX_FFT_SIZE]; + spx_word16_t _out[MAX_FFT_SIZE]; +#endif + for (i=0;iN); + scale = 1./((struct kiss_config *)table)->N; + for (i=0;i<((struct kiss_config *)table)->N;i++) + out[i] = scale*in[i]; + spx_drft_forward(&t, out); + spx_drft_clear(&t); + } +#endif +} + +void spx_ifft_float(void *table, float *in, float *out) +{ + int i; +#ifdef USE_SMALLFT + int N = ((struct drft_lookup *)table)->n; +#elif defined(USE_KISS_FFT) + int N = ((struct kiss_config *)table)->N; +#else +#endif +#ifdef VAR_ARRAYS + spx_word16_t _in[N]; + spx_word16_t _out[N]; +#else + spx_word16_t _in[MAX_FFT_SIZE]; + spx_word16_t _out[MAX_FFT_SIZE]; +#endif + for (i=0;iN); + for (i=0;i<((struct kiss_config *)table)->N;i++) + out[i] = in[i]; + spx_drft_backward(&t, out); + spx_drft_clear(&t); + } +#endif +} + +#else + +void spx_fft_float(void *table, float *in, float *out) +{ + spx_fft(table, in, out); +} +void spx_ifft_float(void *table, float *in, float *out) +{ + spx_ifft(table, in, out); +} + +#endif diff --git a/src/libspeex/fftwrap.h b/src/libspeex/fftwrap.h new file mode 100644 index 0000000..dfaf489 --- /dev/null +++ b/src/libspeex/fftwrap.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2005 Jean-Marc Valin + File: fftwrap.h + + Wrapper for various FFTs + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef FFTWRAP_H +#define FFTWRAP_H + +#include "arch.h" + +/** Compute tables for an FFT */ +void *spx_fft_init(int size); + +/** Destroy tables for an FFT */ +void spx_fft_destroy(void *table); + +/** Forward (real to half-complex) transform */ +void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out); + +/** Backward (half-complex to real) transform */ +void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out); + +/** Forward (real to half-complex) transform of float data */ +void spx_fft_float(void *table, float *in, float *out); + +/** Backward (half-complex to real) transform of float data */ +void spx_ifft_float(void *table, float *in, float *out); + +#endif diff --git a/src/libspeex/filterbank.c b/src/libspeex/filterbank.c new file mode 100644 index 0000000..e2fb71d --- /dev/null +++ b/src/libspeex/filterbank.c @@ -0,0 +1,227 @@ +/* Copyright (C) 2006 Jean-Marc Valin */ +/** + @file filterbank.c + @brief Converting between psd and filterbank + */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "filterbank.h" +#include "arch.h" +#include +#include "math_approx.h" +#include "os_support.h" + +#ifdef FIXED_POINT + +#define toBARK(n) (MULT16_16(26829,spx_atan(SHR32(MULT16_16(97,n),2))) + MULT16_16(4588,spx_atan(MULT16_32_Q15(20,MULT16_16(n,n)))) + MULT16_16(3355,n)) + +#else +#define toBARK(n) (13.1f*atan(.00074f*(n))+2.24f*atan((n)*(n)*1.85e-8f)+1e-4f*(n)) +#endif + +#define toMEL(n) (2595.f*log10(1.f+(n)/700.f)) + +FilterBank *filterbank_new(int banks, spx_word32_t sampling, int len, int type) +{ + FilterBank *bank; + spx_word32_t df; + spx_word32_t max_mel, mel_interval; + int i; + int id1; + int id2; + df = DIV32(SHL32(sampling,15),MULT16_16(2,len)); + max_mel = toBARK(EXTRACT16(sampling/2)); + mel_interval = PDIV32(max_mel,banks-1); + + bank = (FilterBank*)speex_alloc(sizeof(FilterBank)); + bank->nb_banks = banks; + bank->len = len; + bank->bank_left = (int*)speex_alloc(len*sizeof(int)); + bank->bank_right = (int*)speex_alloc(len*sizeof(int)); + bank->filter_left = (spx_word16_t*)speex_alloc(len*sizeof(spx_word16_t)); + bank->filter_right = (spx_word16_t*)speex_alloc(len*sizeof(spx_word16_t)); + /* Think I can safely disable normalisation that for fixed-point (and probably float as well) */ +#ifndef FIXED_POINT + bank->scaling = (float*)speex_alloc(banks*sizeof(float)); +#endif + for (i=0;i max_mel) + break; +#ifdef FIXED_POINT + id1 = DIV32(mel,mel_interval); +#else + id1 = (int)(floor(mel/mel_interval)); +#endif + if (id1>banks-2) + { + id1 = banks-2; + val = Q15_ONE; + } else { + val = DIV32_16(mel - id1*mel_interval,EXTRACT16(PSHR32(mel_interval,15))); + } + id2 = id1+1; + bank->bank_left[i] = id1; + bank->filter_left[i] = SUB16(Q15_ONE,val); + bank->bank_right[i] = id2; + bank->filter_right[i] = val; + } + + /* Think I can safely disable normalisation for fixed-point (and probably float as well) */ +#ifndef FIXED_POINT + for (i=0;inb_banks;i++) + bank->scaling[i] = 0; + for (i=0;ilen;i++) + { + int id = bank->bank_left[i]; + bank->scaling[id] += bank->filter_left[i]; + id = bank->bank_right[i]; + bank->scaling[id] += bank->filter_right[i]; + } + for (i=0;inb_banks;i++) + bank->scaling[i] = Q15_ONE/(bank->scaling[i]); +#endif + return bank; +} + +void filterbank_destroy(FilterBank *bank) +{ + speex_free(bank->bank_left); + speex_free(bank->bank_right); + speex_free(bank->filter_left); + speex_free(bank->filter_right); +#ifndef FIXED_POINT + speex_free(bank->scaling); +#endif + speex_free(bank); +} + +void filterbank_compute_bank32(FilterBank *bank, spx_word32_t *ps, spx_word32_t *mel) +{ + int i; + for (i=0;inb_banks;i++) + mel[i] = 0; + + for (i=0;ilen;i++) + { + int id; + id = bank->bank_left[i]; + mel[id] += MULT16_32_P15(bank->filter_left[i],ps[i]); + id = bank->bank_right[i]; + mel[id] += MULT16_32_P15(bank->filter_right[i],ps[i]); + } + /* Think I can safely disable normalisation that for fixed-point (and probably float as well) */ +#ifndef FIXED_POINT + /*for (i=0;inb_banks;i++) + mel[i] = MULT16_32_P15(Q15(bank->scaling[i]),mel[i]); + */ +#endif +} + +void filterbank_compute_psd16(FilterBank *bank, spx_word16_t *mel, spx_word16_t *ps) +{ + int i; + for (i=0;ilen;i++) + { + spx_word32_t tmp; + int id1, id2; + id1 = bank->bank_left[i]; + id2 = bank->bank_right[i]; + tmp = MULT16_16(mel[id1],bank->filter_left[i]); + tmp += MULT16_16(mel[id2],bank->filter_right[i]); + ps[i] = EXTRACT16(PSHR32(tmp,15)); + } +} + + +#ifndef FIXED_POINT +void filterbank_compute_bank(FilterBank *bank, float *ps, float *mel) +{ + int i; + for (i=0;inb_banks;i++) + mel[i] = 0; + + for (i=0;ilen;i++) + { + int id = bank->bank_left[i]; + mel[id] += bank->filter_left[i]*ps[i]; + id = bank->bank_right[i]; + mel[id] += bank->filter_right[i]*ps[i]; + } + for (i=0;inb_banks;i++) + mel[i] *= bank->scaling[i]; +} + +void filterbank_compute_psd(FilterBank *bank, float *mel, float *ps) +{ + int i; + for (i=0;ilen;i++) + { + int id = bank->bank_left[i]; + ps[i] = mel[id]*bank->filter_left[i]; + id = bank->bank_right[i]; + ps[i] += mel[id]*bank->filter_right[i]; + } +} + +void filterbank_psy_smooth(FilterBank *bank, float *ps, float *mask) +{ + /* Low freq slope: 14 dB/Bark*/ + /* High freq slope: 9 dB/Bark*/ + /* Noise vs tone: 5 dB difference */ + /* FIXME: Temporary kludge */ + float bark[100]; + int i; + /* Assumes 1/3 Bark resolution */ + float decay_low = 0.34145f; + float decay_high = 0.50119f; + filterbank_compute_bank(bank, ps, bark); + for (i=1;inb_banks;i++) + { + /*float decay_high = 13-1.6*log10(bark[i-1]); + decay_high = pow(10,(-decay_high/30.f));*/ + bark[i] = bark[i] + decay_high*bark[i-1]; + } + for (i=bank->nb_banks-2;i>=0;i--) + { + bark[i] = bark[i] + decay_low*bark[i+1]; + } + filterbank_compute_psd(bank, bark, mask); +} + +#endif diff --git a/src/libspeex/filterbank.h b/src/libspeex/filterbank.h new file mode 100644 index 0000000..3e889a2 --- /dev/null +++ b/src/libspeex/filterbank.h @@ -0,0 +1,66 @@ +/* Copyright (C) 2006 Jean-Marc Valin */ +/** + @file filterbank.h + @brief Converting between psd and filterbank + */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FILTERBANK_H +#define FILTERBANK_H + +#include "arch.h" + +typedef struct { + int *bank_left; + int *bank_right; + spx_word16_t *filter_left; + spx_word16_t *filter_right; +#ifndef FIXED_POINT + float *scaling; +#endif + int nb_banks; + int len; +} FilterBank; + + +FilterBank *filterbank_new(int banks, spx_word32_t sampling, int len, int type); + +void filterbank_destroy(FilterBank *bank); + +void filterbank_compute_bank32(FilterBank *bank, spx_word32_t *ps, spx_word32_t *mel); + +void filterbank_compute_psd16(FilterBank *bank, spx_word16_t *mel, spx_word16_t *psd); + +#ifndef FIXED_POINT +void filterbank_compute_bank(FilterBank *bank, float *psd, float *mel); +void filterbank_compute_psd(FilterBank *bank, float *mel, float *psd); +#endif + + +#endif diff --git a/src/libspeex/filters.c b/src/libspeex/filters.c new file mode 100644 index 0000000..36ef4f6 --- /dev/null +++ b/src/libspeex/filters.c @@ -0,0 +1,821 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: filters.c + Various analysis/synthesis filters + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "filters.h" +#include "stack_alloc.h" +#include "arch.h" +#include "math_approx.h" +#include "ltp.h" +#include + +#ifdef _USE_SSE +#include "filters_sse.h" +#elif defined (ARM4_ASM) || defined(ARM5E_ASM) +#include "filters_arm4.h" +#elif defined (BFIN_ASM) +#include "filters_bfin.h" +#endif + + + +void bw_lpc(spx_word16_t gamma, const spx_coef_t *lpc_in, spx_coef_t *lpc_out, int order) +{ + int i; + spx_word16_t tmp=gamma; + for (i=0;i=min_val && vec[i] <= max_val)) + { + if (vec[i] < min_val) + vec[i] = min_val; + else if (vec[i] > max_val) + vec[i] = max_val; + else /* Has to be NaN */ + vec[i] = 0; + } + } +} + +void highpass(const spx_word16_t *x, spx_word16_t *y, int len, int filtID, spx_mem_t *mem) +{ + int i; +#ifdef FIXED_POINT + const spx_word16_t Pcoef[5][3] = {{16384, -31313, 14991}, {16384, -31569, 15249}, {16384, -31677, 15328}, {16384, -32313, 15947}, {16384, -22446, 6537}}; + const spx_word16_t Zcoef[5][3] = {{15672, -31344, 15672}, {15802, -31601, 15802}, {15847, -31694, 15847}, {16162, -32322, 16162}, {14418, -28836, 14418}}; +#else + const spx_word16_t Pcoef[5][3] = {{1.00000f, -1.91120f, 0.91498f}, {1.00000f, -1.92683f, 0.93071f}, {1.00000f, -1.93338f, 0.93553f}, {1.00000f, -1.97226f, 0.97332f}, {1.00000f, -1.37000f, 0.39900f}}; + const spx_word16_t Zcoef[5][3] = {{0.95654f, -1.91309f, 0.95654f}, {0.96446f, -1.92879f, 0.96446f}, {0.96723f, -1.93445f, 0.96723f}, {0.98645f, -1.97277f, 0.98645f}, {0.88000f, -1.76000f, 0.88000f}}; +#endif + const spx_word16_t *den, *num; + if (filtID>4) + filtID=4; + + den = Pcoef[filtID]; num = Zcoef[filtID]; + /*return;*/ + for (i=0;i SHL32(EXTEND32(SIG_SCALING), 8)) + { + spx_word16_t scale_1; + scale = PSHR32(scale, SIG_SHIFT); + scale_1 = EXTRACT16(PDIV32_16(SHL32(EXTEND32(SIG_SCALING),7),scale)); + for (i=0;i SHR32(EXTEND32(SIG_SCALING), 2)) { + spx_word16_t scale_1; + scale = PSHR32(scale, SIG_SHIFT-5); + scale_1 = DIV32_16(SHL32(EXTEND32(SIG_SCALING),3),scale); + for (i=0;i max_val) + max_val = tmp; + } + + sig_shift=0; + while (max_val>16383) + { + sig_shift++; + max_val >>= 1; + } + + for (i=0;i max_val) + max_val = tmp; + } + if (max_val>16383) + { + spx_word32_t sum=0; + for (i=0;i= max_val) + max_val = tmp; + } + + sig_shift=0; + while (max_val>max_scale) + { + sig_shift++; + max_val >>= 1; + } + + for (i=0;i>1; + for (i=0;i>1; + N2 = N>>1; + ALLOC(xx1, M2+N2, spx_word16_t); + ALLOC(xx2, M2+N2, spx_word16_t); + + for (i = 0; i < N2; i++) + xx1[i] = x1[N2-1-i]; + for (i = 0; i < M2; i++) + xx1[N2+i] = mem1[2*i+1]; + for (i = 0; i < N2; i++) + xx2[i] = x2[N2-1-i]; + for (i = 0; i < M2; i++) + xx2[N2+i] = mem2[2*i+1]; + + for (i = 0; i < N2; i += 2) { + spx_sig_t y0, y1, y2, y3; + spx_word16_t x10, x20; + + y0 = y1 = y2 = y3 = 0; + x10 = xx1[N2-2-i]; + x20 = xx2[N2-2-i]; + + for (j = 0; j < M2; j += 2) { + spx_word16_t x11, x21; + spx_word16_t a0, a1; + + a0 = a[2*j]; + a1 = a[2*j+1]; + x11 = xx1[N2-1+j-i]; + x21 = xx2[N2-1+j-i]; + +#ifdef FIXED_POINT + /* We multiply twice by the same coef to avoid overflows */ + y0 = MAC16_16(MAC16_16(y0, a0, x11), NEG16(a0), x21); + y1 = MAC16_16(MAC16_16(y1, a1, x11), a1, x21); + y2 = MAC16_16(MAC16_16(y2, a0, x10), NEG16(a0), x20); + y3 = MAC16_16(MAC16_16(y3, a1, x10), a1, x20); +#else + y0 = ADD32(y0,MULT16_16(a0, x11-x21)); + y1 = ADD32(y1,MULT16_16(a1, x11+x21)); + y2 = ADD32(y2,MULT16_16(a0, x10-x20)); + y3 = ADD32(y3,MULT16_16(a1, x10+x20)); +#endif + a0 = a[2*j+2]; + a1 = a[2*j+3]; + x10 = xx1[N2+j-i]; + x20 = xx2[N2+j-i]; + +#ifdef FIXED_POINT + /* We multiply twice by the same coef to avoid overflows */ + y0 = MAC16_16(MAC16_16(y0, a0, x10), NEG16(a0), x20); + y1 = MAC16_16(MAC16_16(y1, a1, x10), a1, x20); + y2 = MAC16_16(MAC16_16(y2, a0, x11), NEG16(a0), x21); + y3 = MAC16_16(MAC16_16(y3, a1, x11), a1, x21); +#else + y0 = ADD32(y0,MULT16_16(a0, x10-x20)); + y1 = ADD32(y1,MULT16_16(a1, x10+x20)); + y2 = ADD32(y2,MULT16_16(a0, x11-x21)); + y3 = ADD32(y3,MULT16_16(a1, x11+x21)); +#endif + } +#ifdef FIXED_POINT + y[2*i] = EXTRACT16(SATURATE32(PSHR32(y0,15),32767)); + y[2*i+1] = EXTRACT16(SATURATE32(PSHR32(y1,15),32767)); + y[2*i+2] = EXTRACT16(SATURATE32(PSHR32(y2,15),32767)); + y[2*i+3] = EXTRACT16(SATURATE32(PSHR32(y3,15),32767)); +#else + /* Normalize up explicitly if we're in float */ + y[2*i] = 2.f*y0; + y[2*i+1] = 2.f*y1; + y[2*i+2] = 2.f*y2; + y[2*i+3] = 2.f*y3; +#endif + } + + for (i = 0; i < M2; i++) + mem1[2*i+1] = xx1[i]; + for (i = 0; i < M2; i++) + mem2[2*i+1] = xx2[i]; +} + +#ifdef FIXED_POINT +#if 0 +const spx_word16_t shift_filt[3][7] = {{-33, 1043, -4551, 19959, 19959, -4551, 1043}, + {-98, 1133, -4425, 29179, 8895, -2328, 444}, + {444, -2328, 8895, 29179, -4425, 1133, -98}}; +#else +const spx_word16_t shift_filt[3][7] = {{-390, 1540, -4993, 20123, 20123, -4993, 1540}, + {-1064, 2817, -6694, 31589, 6837, -990, -209}, + {-209, -990, 6837, 31589, -6694, 2817, -1064}}; +#endif +#else +#if 0 +const float shift_filt[3][7] = {{-9.9369e-04, 3.1831e-02, -1.3889e-01, 6.0910e-01, 6.0910e-01, -1.3889e-01, 3.1831e-02}, + {-0.0029937, 0.0345613, -0.1350474, 0.8904793, 0.2714479, -0.0710304, 0.0135403}, + {0.0135403, -0.0710304, 0.2714479, 0.8904793, -0.1350474, 0.0345613, -0.0029937}}; +#else +const float shift_filt[3][7] = {{-0.011915f, 0.046995f, -0.152373f, 0.614108f, 0.614108f, -0.152373f, 0.046995f}, + {-0.0324855f, 0.0859768f, -0.2042986f, 0.9640297f, 0.2086420f, -0.0302054f, -0.0063646f}, + {-0.0063646f, -0.0302054f, 0.2086420f, 0.9640297f, -0.2042986f, 0.0859768f, -0.0324855f}}; +#endif +#endif + +int interp_pitch( +spx_word16_t *exc, /*decoded excitation*/ +spx_word16_t *interp, /*decoded excitation*/ +int pitch, /*pitch period*/ +int len +) +{ + int i,j,k; + spx_word32_t corr[4][7]; + spx_word32_t maxcorr; + int maxi, maxj; + for (i=0;i<7;i++) + { + corr[0][i] = inner_prod(exc, exc-pitch-3+i, len); + } + for (i=0;i<3;i++) + { + for (j=0;j<7;j++) + { + int i1, i2; + spx_word32_t tmp=0; + i1 = 3-j; + if (i1<0) + i1 = 0; + i2 = 10-j; + if (i2>7) + i2 = 7; + for (k=i1;k maxcorr) + { + maxcorr = corr[i][j]; + maxi=i; + maxj=j; + } + } + } + for (i=0;i0) + { + for (k=0;k<7;k++) + { + tmp += MULT16_16(exc[i-(pitch-maxj+3)+k-3],shift_filt[maxi-1][k]); + } + } else { + tmp = SHL32(exc[i-(pitch-maxj+3)],15); + } + interp[i] = PSHR32(tmp,15); + } + return pitch-maxj+3; +} + +void multicomb( +spx_word16_t *exc, /*decoded excitation*/ +spx_word16_t *new_exc, /*enhanced excitation*/ +spx_coef_t *ak, /*LPC filter coefs*/ +int p, /*LPC order*/ +int nsf, /*sub-frame size*/ +int pitch, /*pitch period*/ +int max_pitch, +spx_word16_t comb_gain, /*gain of comb filter*/ +char *stack +) +{ + int i; + VARDECL(spx_word16_t *iexc); + spx_word16_t old_ener, new_ener; + int corr_pitch; + + spx_word16_t iexc0_mag, iexc1_mag, exc_mag; + spx_word32_t corr0, corr1; + spx_word16_t gain0, gain1; + spx_word16_t pgain1, pgain2; + spx_word16_t c1, c2; + spx_word16_t g1, g2; + spx_word16_t ngain; + spx_word16_t gg1, gg2; +#ifdef FIXED_POINT + int scaledown=0; +#endif +#if 0 /* Set to 1 to enable full pitch search */ + int nol_pitch[6]; + spx_word16_t nol_pitch_coef[6]; + spx_word16_t ol_pitch_coef; + open_loop_nbest_pitch(exc, 20, 120, nsf, + nol_pitch, nol_pitch_coef, 6, stack); + corr_pitch=nol_pitch[0]; + ol_pitch_coef = nol_pitch_coef[0]; + /*Try to remove pitch multiples*/ + for (i=1;i<6;i++) + { +#ifdef FIXED_POINT + if ((nol_pitch_coef[i]>MULT16_16_Q15(nol_pitch_coef[0],19661)) && +#else + if ((nol_pitch_coef[i]>.6*nol_pitch_coef[0]) && +#endif + (ABS(2*nol_pitch[i]-corr_pitch)<=2 || ABS(3*nol_pitch[i]-corr_pitch)<=3 || + ABS(4*nol_pitch[i]-corr_pitch)<=4 || ABS(5*nol_pitch[i]-corr_pitch)<=5)) + { + corr_pitch = nol_pitch[i]; + } + } +#else + corr_pitch = pitch; +#endif + + ALLOC(iexc, 2*nsf, spx_word16_t); + + interp_pitch(exc, iexc, corr_pitch, 80); + if (corr_pitch>max_pitch) + interp_pitch(exc, iexc+nsf, 2*corr_pitch, 80); + else + interp_pitch(exc, iexc+nsf, -corr_pitch, 80); + +#ifdef FIXED_POINT + for (i=0;i16383) + { + scaledown = 1; + break; + } + } + if (scaledown) + { + for (i=0;i MULT16_16(iexc0_mag,exc_mag)) + pgain1 = QCONST16(1., 14); + else + pgain1 = PDIV32_16(SHL32(PDIV32(corr0, exc_mag),14),iexc0_mag); + if (corr1 > MULT16_16(iexc1_mag,exc_mag)) + pgain2 = QCONST16(1., 14); + else + pgain2 = PDIV32_16(SHL32(PDIV32(corr1, exc_mag),14),iexc1_mag); + gg1 = PDIV32_16(SHL32(EXTEND32(exc_mag),8), iexc0_mag); + gg2 = PDIV32_16(SHL32(EXTEND32(exc_mag),8), iexc1_mag); + if (comb_gain>0) + { +#ifdef FIXED_POINT + c1 = (MULT16_16_Q15(QCONST16(.4,15),comb_gain)+QCONST16(.07,15)); + c2 = QCONST16(.5,15)+MULT16_16_Q14(QCONST16(1.72,14),(c1-QCONST16(.07,15))); +#else + c1 = .4*comb_gain+.07; + c2 = .5+1.72*(c1-.07); +#endif + } else + { + c1=c2=0; + } +#ifdef FIXED_POINT + g1 = 32767 - MULT16_16_Q13(MULT16_16_Q15(c2, pgain1),pgain1); + g2 = 32767 - MULT16_16_Q13(MULT16_16_Q15(c2, pgain2),pgain2); +#else + g1 = 1-c2*pgain1*pgain1; + g2 = 1-c2*pgain2*pgain2; +#endif + if (g1max_pitch) + { + gain0 = MULT16_16_Q15(QCONST16(.7,15),MULT16_16_Q14(g1,gg1)); + gain1 = MULT16_16_Q15(QCONST16(.3,15),MULT16_16_Q14(g2,gg2)); + } else { + gain0 = MULT16_16_Q15(QCONST16(.6,15),MULT16_16_Q14(g1,gg1)); + gain1 = MULT16_16_Q15(QCONST16(.6,15),MULT16_16_Q14(g2,gg2)); + } + for (i=0;i new_ener) + old_ener = new_ener; + ngain = PDIV32_16(SHL32(EXTEND32(old_ener),14),new_ener); + + for (i=0;imax_scale) + { + sig_shift++; + max_val >>= 1; + } + + __asm__ __volatile__ ( + ".normalize16loop%=: \n" + + "\tldr %4, [%0], #4 \n" + "\tldr %5, [%0], #4 \n" + "\tmov %4, %4, asr %3 \n" + "\tstrh %4, [%1], #2 \n" + "\tldr %4, [%0], #4 \n" + "\tmov %5, %5, asr %3 \n" + "\tstrh %5, [%1], #2 \n" + "\tldr %5, [%0], #4 \n" + "\tmov %4, %4, asr %3 \n" + "\tstrh %4, [%1], #2 \n" + "\tsubs %2, %2, #1 \n" + "\tmov %5, %5, asr %3 \n" + "\tstrh %5, [%1], #2 \n" + + "\tbge .normalize16loop%=\n" + : "=r" (dead1), "=r" (dead2), "=r" (dead3), "=r" (dead4), + "=r" (dead5), "=r" (dead6) + : "0" (x), "1" (y), "2" (len>>2), "3" (sig_shift) + : "cc", "memory"); + return sig_shift; +} + diff --git a/src/libspeex/filters_bfin.h b/src/libspeex/filters_bfin.h new file mode 100644 index 0000000..1e433ee --- /dev/null +++ b/src/libspeex/filters_bfin.h @@ -0,0 +1,515 @@ +/* Copyright (C) 2005 Analog Devices */ +/** + @file filters_bfin.h + @brief Various analysis/synthesis filters (Blackfin version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_NORMALIZE16 +int normalize16(const spx_sig_t *x, spx_word16_t *y, spx_sig_t max_scale, int len) +{ + spx_sig_t max_val=1; + int sig_shift; + __asm__ + ( + "%0 = 0;\n\t" + "I0 = %1;\n\t" + "L0 = 0;\n\t" + "R1 = [I0++];\n\t" + "LOOP norm_max%= LC0 = %2;\n\t" + "LOOP_BEGIN norm_max%=;\n\t" + "R2 = ABS R1 || R1 = [I0++];\n\t" + "%0 = MAX(%0, R2);\n\t" + "LOOP_END norm_max%=;\n\t" + : "=&d" (max_val) + : "a" (x), "a" (len) + : "R1", "R2" + ); + + sig_shift=0; + while (max_val>max_scale) + { + sig_shift++; + max_val >>= 1; + } + + __asm__ __volatile__ + ( + "I0 = %0;\n\t" + "L0 = 0;\n\t" + "P1 = %1;\n\t" + "R0 = [I0++];\n\t" + "LOOP norm_shift%= LC0 = %3;\n\t" + "LOOP_BEGIN norm_shift%=;\n\t" + "R1 = ASHIFT R0 by %2.L || R0 = [I0++];\n\t" + "W[P1++] = R1;\n\t" + "LOOP_END norm_shift%=;\n\t" + "R1 = ASHIFT R0 by %2.L;\n\t" + "W[P1++] = R1;\n\t" + : : "a" (x), "a" (y), "d" (-sig_shift), "a" (len-1) + : "I0", "L0", "P1", "R0", "R1", "memory" + ); + return sig_shift; +} + + + +#define OVERRIDE_FILTER_MEM16 +void filter_mem16(const spx_word16_t *_x, const spx_coef_t *num, const spx_coef_t *den, spx_word16_t *_y, int N, int ord, spx_mem_t *mem, char *stack) +{ + VARDECL(spx_word32_t *xy2); + VARDECL(spx_word32_t *numden_a); + spx_word32_t *xy; + spx_word16_t *numden; + int i; + + ALLOC(xy2, (N+1), spx_word32_t); + ALLOC(numden_a, (2*ord+2), spx_word32_t); + xy = xy2+1; + numden = (spx_word16_t*) numden_a; + + for (i=0;i>> 13;\n\t" + "W[%0] = R3.L;\n\t" + "R0 <<= 1;\n\t" + "R1 = R1 + R0;\n\t" + "R1 >>>= 13;\n\t" + "W[%1] = R1.L;\n\t" + "LOOP_END samples%=;\n\t" + : "=a" (ytmp2), "=a" (y) + : "a" (awk2), "a" (ak), "d" (ord), "m" (N), "0" (ytmp2), "1" (y) + : "A0", "A1", "R0", "R1", "R2", "R3", "I0", "I1", "I2", "I3", "L0", "L1", "L2", "L3", "A0", "A1" + ); +} + + + +#if 0 /* Equivalent C function for filter_mem2 and compute_impulse_response */ +#define min(a,b) ((a)<(b) ? (a):(b)) + +void compute_impulse_response(const spx_coef_t *ak, const spx_coef_t *awk1, const spx_coef_t *awk2, spx_word16_t *y, int N, int ord, char *stack) +{ + int i,j; + VARDECL(spx_word16_t *ytmp); + ALLOC(ytmp, N, spx_word16_t); + + y[0] = LPC_SCALING; + for (i=0;i + +void filter_mem16_10(const float *x, const float *_num, const float *_den, float *y, int N, int ord, float *_mem) +{ + __m128 num[3], den[3], mem[3]; + + int i; + + /* Copy numerator, denominator and memory to aligned xmm */ + for (i=0;i<2;i++) + { + mem[i] = _mm_loadu_ps(_mem+4*i); + num[i] = _mm_loadu_ps(_num+4*i); + den[i] = _mm_loadu_ps(_den+4*i); + } + mem[2] = _mm_setr_ps(_mem[8], _mem[9], 0, 0); + num[2] = _mm_setr_ps(_num[8], _num[9], 0, 0); + den[2] = _mm_setr_ps(_den[8], _den[9], 0, 0); + + for (i=0;i>1; + __asm__ ( + "P0 = 15;\n\t" + "R0 = %1;\n\t" + "R1 = %2;\n\t" + //"R0 = R0 + R1;\n\t" + "R0 <<= 1;\n\t" + "DIVS (R0, R1);\n\t" + "LOOP divide%= LC0 = P0;\n\t" + "LOOP_BEGIN divide%=;\n\t" + "DIVQ (R0, R1);\n\t" + "LOOP_END divide%=;\n\t" + "R0 = R0.L;\n\t" + "%0 = R0;\n\t" + : "=m" (res) + : "m" (a), "m" (bb) + : "P0", "R0", "R1", "cc"); + return res; +} + +#undef DIV32_16 +static inline spx_word16_t DIV32_16(spx_word32_t a, spx_word16_t b) +{ + spx_word32_t res, bb; + bb = b; + /* Make the roundinf consistent with the C version + (do we need to do that?)*/ + if (a<0) + a += (b-1); + __asm__ ( + "P0 = 15;\n\t" + "R0 = %1;\n\t" + "R1 = %2;\n\t" + "R0 <<= 1;\n\t" + "DIVS (R0, R1);\n\t" + "LOOP divide%= LC0 = P0;\n\t" + "LOOP_BEGIN divide%=;\n\t" + "DIVQ (R0, R1);\n\t" + "LOOP_END divide%=;\n\t" + "R0 = R0.L;\n\t" + "%0 = R0;\n\t" + : "=m" (res) + : "m" (a), "m" (bb) + : "P0", "R0", "R1", "cc"); + return res; +} + +#undef MAX16 +static inline spx_word16_t MAX16(spx_word16_t a, spx_word16_t b) +{ + spx_word32_t res; + __asm__ ( + "%1 = %1.L (X);\n\t" + "%2 = %2.L (X);\n\t" + "%0 = MAX(%1,%2);" + : "=d" (res) + : "%d" (a), "d" (b) + ); + return res; +} + +#undef MULT16_32_Q15 +static inline spx_word32_t MULT16_32_Q15(spx_word16_t a, spx_word32_t b) +{ + spx_word32_t res; + __asm__ + ( + "A1 = %2.L*%1.L (M);\n\t" + "A1 = A1 >>> 15;\n\t" + "%0 = (A1 += %2.L*%1.H) ;\n\t" + : "=&W" (res), "=&d" (b) + : "d" (a), "1" (b) + : "A1" + ); + return res; +} + +#undef MAC16_32_Q15 +static inline spx_word32_t MAC16_32_Q15(spx_word32_t c, spx_word16_t a, spx_word32_t b) +{ + spx_word32_t res; + __asm__ + ( + "A1 = %2.L*%1.L (M);\n\t" + "A1 = A1 >>> 15;\n\t" + "%0 = (A1 += %2.L*%1.H);\n\t" + "%0 = %0 + %4;\n\t" + : "=&W" (res), "=&d" (b) + : "d" (a), "1" (b), "d" (c) + : "A1" + ); + return res; +} + +#undef MULT16_32_Q14 +static inline spx_word32_t MULT16_32_Q14(spx_word16_t a, spx_word32_t b) +{ + spx_word32_t res; + __asm__ + ( + "%2 <<= 1;\n\t" + "A1 = %1.L*%2.L (M);\n\t" + "A1 = A1 >>> 15;\n\t" + "%0 = (A1 += %1.L*%2.H);\n\t" + : "=W" (res), "=d" (a), "=d" (b) + : "1" (a), "2" (b) + : "A1" + ); + return res; +} + +#undef MAC16_32_Q14 +static inline spx_word32_t MAC16_32_Q14(spx_word32_t c, spx_word16_t a, spx_word32_t b) +{ + spx_word32_t res; + __asm__ + ( + "%1 <<= 1;\n\t" + "A1 = %2.L*%1.L (M);\n\t" + "A1 = A1 >>> 15;\n\t" + "%0 = (A1 += %2.L*%1.H);\n\t" + "%0 = %0 + %4;\n\t" + : "=&W" (res), "=&d" (b) + : "d" (a), "1" (b), "d" (c) + : "A1" + ); + return res; +} + +#endif diff --git a/src/libspeex/fixed_debug.h b/src/libspeex/fixed_debug.h new file mode 100644 index 0000000..d5c449f --- /dev/null +++ b/src/libspeex/fixed_debug.h @@ -0,0 +1,487 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file fixed_debug.h + @brief Fixed-point operations with debugging +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_DEBUG_H +#define FIXED_DEBUG_H + +#include + +extern long long spx_mips; +#define MIPS_INC spx_mips++, + +#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) +#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) + + +#define VERIFY_SHORT(x) ((x)<=32767&&(x)>=-32768) +#define VERIFY_INT(x) ((x)<=2147483647LL&&(x)>=-2147483648LL) + +static inline short NEG16(int x) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "NEG16: input is not short: %d\n", (int)x); + } + res = -x; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "NEG16: output is not short: %d\n", (int)res); + spx_mips++; + return res; +} +static inline int NEG32(long long x) +{ + long long res; + if (!VERIFY_INT(x)) + { + fprintf (stderr, "NEG16: input is not int: %d\n", (int)x); + } + res = -x; + if (!VERIFY_INT(res)) + fprintf (stderr, "NEG16: output is not int: %d\n", (int)res); + spx_mips++; + return res; +} + +#define EXTRACT16(x) _EXTRACT16(x, __FILE__, __LINE__) +static inline short _EXTRACT16(int x, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "EXTRACT16: input is not short: %d in %s: line %d\n", x, file, line); + } + res = x; + spx_mips++; + return res; +} + +#define EXTEND32(x) _EXTEND32(x, __FILE__, __LINE__) +static inline int _EXTEND32(int x, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "EXTEND32: input is not short: %d in %s: line %d\n", x, file, line); + } + res = x; + spx_mips++; + return res; +} + +#define SHR16(a, shift) _SHR16(a, shift, __FILE__, __LINE__) +static inline short _SHR16(int a, int shift, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHR16: inputs are not short: %d >> %d in %s: line %d\n", a, shift, file, line); + } + res = a>>shift; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "SHR16: output is not short: %d in %s: line %d\n", res, file, line); + spx_mips++; + return res; +} +#define SHL16(a, shift) _SHL16(a, shift, __FILE__, __LINE__) +static inline short _SHL16(int a, int shift, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHL16: inputs are not short: %d %d in %s: line %d\n", a, shift, file, line); + } + res = a<>shift; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "SHR32: output is not int: %d\n", (int)res); + } + spx_mips++; + return res; +} +static inline int SHL32(long long a, int shift) +{ + long long res; + if (!VERIFY_INT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHL32: inputs are not int: %d %d\n", (int)a, shift); + } + res = a<>1))),shift)) +#define PSHR32(a,shift) (SHR32(ADD32((a),((1<<((shift))>>1))),shift)) +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) + +#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) +#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + +//#define SHR(a,shift) ((a) >> (shift)) +//#define SHL(a,shift) ((a) << (shift)) + +#define ADD16(a, b) _ADD16(a, b, __FILE__, __LINE__) +static inline short _ADD16(int a, int b, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "ADD16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); + } + res = a+b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "ADD16: output is not short: %d+%d=%d in %s: line %d\n", a,b,res, file, line); + } + spx_mips++; + return res; +} + +#define SUB16(a, b) _SUB16(a, b, __FILE__, __LINE__) +static inline short _SUB16(int a, int b, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "SUB16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); + } + res = a-b; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "SUB16: output is not short: %d in %s: line %d\n", res, file, line); + spx_mips++; + return res; +} + +#define ADD32(a, b) _ADD32(a, b, __FILE__, __LINE__) +static inline int _ADD32(long long a, long long b, char *file, int line) +{ + long long res; + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "ADD32: inputs are not int: %d %d in %s: line %d\n", (int)a, (int)b, file, line); + } + res = a+b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "ADD32: output is not int: %d in %s: line %d\n", (int)res, file, line); + } + spx_mips++; + return res; +} + +static inline int SUB32(long long a, long long b) +{ + long long res; + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "SUB32: inputs are not int: %d %d\n", (int)a, (int)b); + } + res = a-b; + if (!VERIFY_INT(res)) + fprintf (stderr, "SUB32: output is not int: %d\n", (int)res); + spx_mips++; + return res; +} + +#define ADD64(a,b) (MIPS_INC(a)+(b)) + +/* result fits in 16 bits */ +static inline short MULT16_16_16(int a, int b) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_16: inputs are not short: %d %d\n", a, b); + } + res = a*b; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_16: output is not short: %d\n", res); + spx_mips++; + return res; +} + +#define MULT16_16(a, b) _MULT16_16(a, b, __FILE__, __LINE__) +static inline int _MULT16_16(int a, int b, char *file, int line) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); + } + res = ((long long)a)*b; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16: output is not int: %d in %s: line %d\n", (int)res, file, line); + spx_mips++; + return res; +} + +#define MAC16_16(c,a,b) (spx_mips--,ADD32((c),MULT16_16((a),(b)))) +#define MAC16_16_Q11(c,a,b) (EXTRACT16(ADD16((c),EXTRACT16(SHR32(MULT16_16((a),(b)),11))))) +#define MAC16_16_Q13(c,a,b) (EXTRACT16(ADD16((c),EXTRACT16(SHR32(MULT16_16((a),(b)),13))))) +#define MAC16_16_P13(c,a,b) (EXTRACT16(ADD32((c),SHR32(ADD32(4096,MULT16_16((a),(b))),13)))) + + +#define MULT16_32_QX(a, b, Q) _MULT16_32_QX(a, b, Q, __FILE__, __LINE__) +static inline int _MULT16_32_QX(int a, long long b, int Q, char *file, int line) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "MULT16_32_Q%d: inputs are not short+int: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); + } + if (ABS32(b)>=(1<<(15+Q))) + fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); + res = (((long long)a)*(long long)b) >> Q; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_32_Q%d: output is not int: %d*%d=%d in %s: line %d\n", Q, (int)a, (int)b,(int)res, file, line); + spx_mips+=5; + return res; +} + +static inline int MULT16_32_PX(int a, long long b, int Q) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "MULT16_32_P%d: inputs are not short+int: %d %d\n", Q, (int)a, (int)b); + } + if (ABS32(b)>=(1<<(15+Q))) + fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d\n", Q, (int)a, (int)b); + res = ((((long long)a)*(long long)b) + ((1<>1))>> Q; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_32_P%d: output is not int: %d*%d=%d\n", Q, (int)a, (int)b,(int)res); + spx_mips+=5; + return res; +} + + +#define MULT16_32_Q11(a,b) MULT16_32_QX(a,b,11) +#define MAC16_32_Q11(c,a,b) ADD32((c),MULT16_32_Q11((a),(b))) +#define MULT16_32_Q12(a,b) MULT16_32_QX(a,b,12) +#define MULT16_32_Q13(a,b) MULT16_32_QX(a,b,13) +#define MULT16_32_Q14(a,b) MULT16_32_QX(a,b,14) +#define MULT16_32_Q15(a,b) MULT16_32_QX(a,b,15) +#define MULT16_32_P15(a,b) MULT16_32_PX(a,b,15) +#define MAC16_32_Q15(c,a,b) ADD32((c),MULT16_32_Q15((a),(b))) + +static inline int SATURATE(int a, int b) +{ + if (a>b) + a=b; + if (a<-b) + a = -b; + return a; +} + +static inline int MULT16_16_Q11_32(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q11: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res >>= 11; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16_Q11: output is not short: %d*%d=%d\n", (int)a, (int)b, (int)res); + spx_mips+=3; + return res; +} +static inline short MULT16_16_Q13(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q13: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res >>= 13; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_Q13: output is not short: %d*%d=%d\n", a, b, (int)res); + spx_mips+=3; + return res; +} +static inline short MULT16_16_Q14(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q14: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res >>= 14; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_Q14: output is not short: %d\n", (int)res); + spx_mips+=3; + return res; +} +static inline short MULT16_16_Q15(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q15: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res >>= 15; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_Q15: output is not short: %d\n", (int)res); + } + spx_mips+=3; + return res; +} + +static inline short MULT16_16_P13(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P13: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res += 4096; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16_P13: overflow: %d*%d=%d\n", a, b, (int)res); + res >>= 13; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_P13: output is not short: %d*%d=%d\n", a, b, (int)res); + spx_mips+=4; + return res; +} +static inline short MULT16_16_P14(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P14: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res += 8192; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16_P14: overflow: %d*%d=%d\n", a, b, (int)res); + res >>= 14; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_P14: output is not short: %d*%d=%d\n", a, b, (int)res); + spx_mips+=4; + return res; +} +static inline short MULT16_16_P15(int a, int b) +{ + long long res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P15: inputs are not short: %d %d\n", a, b); + } + res = ((long long)a)*b; + res += 16384; + if (!VERIFY_INT(res)) + fprintf (stderr, "MULT16_16_P15: overflow: %d*%d=%d\n", a, b, (int)res); + res >>= 15; + if (!VERIFY_SHORT(res)) + fprintf (stderr, "MULT16_16_P15: output is not short: %d*%d=%d\n", a, b, (int)res); + spx_mips+=4; + return res; +} + +#define DIV32_16(a, b) _DIV32_16(a, b, __FILE__, __LINE__) + +static inline int _DIV32_16(long long a, long long b, char *file, int line) +{ + long long res; + if (b==0) + { + fprintf(stderr, "DIV32_16: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); + return 0; + } + if (!VERIFY_INT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "DIV32_16: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); + } + res = a/b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "DIV32_16: output is not short: %d / %d = %d in %s: line %d\n", (int)a,(int)b,(int)res, file, line); + if (res>32767) + res = 32767; + if (res<-32768) + res = -32768; + } + spx_mips+=20; + return res; +} + +#define DIV32(a, b) _DIV32(a, b, __FILE__, __LINE__) +static inline int _DIV32(long long a, long long b, char *file, int line) +{ + long long res; + if (b==0) + { + fprintf(stderr, "DIV32: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); + return 0; + } + + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "DIV32: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); + } + res = a/b; + if (!VERIFY_INT(res)) + fprintf (stderr, "DIV32: output is not int: %d in %s: line %d\n", (int)res, file, line); + spx_mips+=36; + return res; +} +#define PDIV32(a,b) DIV32(ADD32((a),(b)>>1),b) +#define PDIV32_16(a,b) DIV32_16(ADD32((a),(b)>>1),b) + +#endif diff --git a/src/libspeex/fixed_generic.h b/src/libspeex/fixed_generic.h new file mode 100644 index 0000000..2948177 --- /dev/null +++ b/src/libspeex/fixed_generic.h @@ -0,0 +1,106 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file fixed_generic.h + @brief Generic fixed-point operations +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_GENERIC_H +#define FIXED_GENERIC_H + +#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) +#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define EXTRACT16(x) ((spx_word16_t)(x)) +#define EXTEND32(x) ((spx_word32_t)(x)) +#define SHR16(a,shift) ((a) >> (shift)) +#define SHL16(a,shift) ((a) << (shift)) +#define SHR32(a,shift) ((a) >> (shift)) +#define SHL32(a,shift) ((a) << (shift)) +#define PSHR16(a,shift) (SHR16((a)+((1<<((shift))>>1)),shift)) +#define PSHR32(a,shift) (SHR32((a)+((1<<((shift))>>1)),shift)) +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) +#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) +#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + +#define SHR(a,shift) ((a) >> (shift)) +#define SHL(a,shift) ((spx_word32_t)(a) << (shift)) +#define PSHR(a,shift) (SHR((a)+((1<<((shift))>>1)),shift)) +#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + + +#define ADD16(a,b) ((spx_word16_t)((spx_word16_t)(a)+(spx_word16_t)(b))) +#define SUB16(a,b) ((spx_word16_t)(a)-(spx_word16_t)(b)) +#define ADD32(a,b) ((spx_word32_t)(a)+(spx_word32_t)(b)) +#define SUB32(a,b) ((spx_word32_t)(a)-(spx_word32_t)(b)) + + +/* result fits in 16 bits */ +#define MULT16_16_16(a,b) ((((spx_word16_t)(a))*((spx_word16_t)(b)))) + +/* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */ +#define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b))) + +#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) +#define MULT16_32_Q12(a,b) ADD32(MULT16_16((a),SHR((b),12)), SHR(MULT16_16((a),((b)&0x00000fff)),12)) +#define MULT16_32_Q13(a,b) ADD32(MULT16_16((a),SHR((b),13)), SHR(MULT16_16((a),((b)&0x00001fff)),13)) +#define MULT16_32_Q14(a,b) ADD32(MULT16_16((a),SHR((b),14)), SHR(MULT16_16((a),((b)&0x00003fff)),14)) + +#define MULT16_32_Q11(a,b) ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11)) +#define MAC16_32_Q11(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11))) + +#define MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) + + +#define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11))) +#define MAC16_16_Q13(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),13))) +#define MAC16_16_P13(c,a,b) (ADD32((c),SHR(ADD32(4096,MULT16_16((a),(b))),13))) + +#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) +#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13)) +#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14)) +#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15)) + +#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13)) +#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14)) +#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15)) + +#define MUL_16_32_R15(a,bh,bl) ADD32(MULT16_16((a),(bh)), SHR(MULT16_16((a),(bl)),15)) + +#define DIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a))/((spx_word16_t)(b)))) +#define PDIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word16_t)(b)))) +#define DIV32(a,b) (((spx_word32_t)(a))/((spx_word32_t)(b))) +#define PDIV32(a,b) (((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word32_t)(b))) + +#endif diff --git a/src/libspeex/gain_table.c b/src/libspeex/gain_table.c new file mode 100644 index 0000000..00b8244 --- /dev/null +++ b/src/libspeex/gain_table.c @@ -0,0 +1,160 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: gain_table.c + Codebook for 3-tap pitch prediction gain (128 entries) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char gain_cdbk_nb[512] = { +-32, -32, -32, 0, +-28, -67, -5, 33, +-42, -6, -32, 18, +-57, -10, -54, 35, +-16, 27, -41, 42, +19, -19, -40, 36, +-45, 24, -21, 40, +-8, -14, -18, 28, +1, 14, -58, 53, +-18, -88, -39, 39, +-38, 21, -18, 37, +-19, 20, -43, 38, +10, 17, -48, 54, +-52, -58, -13, 33, +-44, -1, -11, 32, +-12, -11, -34, 22, +14, 0, -46, 46, +-37, -35, -34, 5, +-25, 44, -30, 43, +6, -4, -63, 49, +-31, 43, -41, 43, +-23, 30, -43, 41, +-43, 26, -14, 44, +-33, 1, -13, 27, +-13, 18, -37, 37, +-46, -73, -45, 34, +-36, 24, -25, 34, +-36, -11, -20, 19, +-25, 12, -18, 33, +-36, -69, -59, 34, +-45, 6, 8, 46, +-22, -14, -24, 18, +-1, 13, -44, 44, +-39, -48, -26, 15, +-32, 31, -37, 34, +-33, 15, -46, 31, +-24, 30, -36, 37, +-41, 31, -23, 41, +-50, 22, -4, 50, +-22, 2, -21, 28, +-17, 30, -34, 40, +-7, -60, -28, 29, +-38, 42, -28, 42, +-44, -11, 21, 43, +-16, 8, -44, 34, +-39, -55, -43, 21, +-11, -35, 26, 41, +-9, 0, -34, 29, +-8, 121, -81, 113, +7, -16, -22, 33, +-37, 33, -31, 36, +-27, -7, -36, 17, +-34, 70, -57, 65, +-37, -11, -48, 21, +-40, 17, -1, 44, +-33, 6, -6, 33, +-9, 0, -20, 34, +-21, 69, -33, 57, +-29, 33, -31, 35, +-55, 12, -1, 49, +-33, 27, -22, 35, +-50, -33, -47, 17, +-50, 54, 51, 94, +-1, -5, -44, 35, +-4, 22, -40, 45, +-39, -66, -25, 24, +-33, 1, -26, 20, +-24, -23, -25, 12, +-11, 21, -45, 44, +-25, -45, -19, 17, +-43, 105, -16, 82, +5, -21, 1, 41, +-16, 11, -33, 30, +-13, -99, -4, 57, +-37, 33, -15, 44, +-25, 37, -63, 54, +-36, 24, -31, 31, +-53, -56, -38, 26, +-41, -4, 4, 37, +-33, 13, -30, 24, +49, 52, -94, 114, +-5, -30, -15, 23, +1, 38, -40, 56, +-23, 12, -36, 29, +-17, 40, -47, 51, +-37, -41, -39, 11, +-49, 34, 0, 58, +-18, -7, -4, 34, +-16, 17, -27, 35, +30, 5, -62, 65, +4, 48, -68, 76, +-43, 11, -11, 38, +-18, 19, -15, 41, +-23, -62, -39, 23, +-42, 10, -2, 41, +-21, -13, -13, 25, +-9, 13, -47, 42, +-23, -62, -24, 24, +-44, 60, -21, 58, +-18, -3, -52, 32, +-22, 22, -36, 34, +-75, 57, 16, 90, +-19, 3, 10, 45, +-29, 23, -38, 32, +-5, -62, -51, 38, +-51, 40, -18, 53, +-42, 13, -24, 32, +-34, 14, -20, 30, +-56, -75, -26, 37, +-26, 32, 15, 59, +-26, 17, -29, 29, +-7, 28, -52, 53, +-12, -30, 5, 30, +-5, -48, -5, 35, +2, 2, -43, 40, +21, 16, 16, 75, +-25, -45, -32, 10, +-43, 18, -10, 42, +9, 0, -1, 52, +-1, 7, -30, 36, +19, -48, -4, 48, +-28, 25, -29, 32, +-22, 0, -31, 22, +-32, 17, -10, 36, +-64, -41, -62, 36, +-52, 15, 16, 58, +-30, -22, -32, 6, +-7, 9, -38, 36}; diff --git a/src/libspeex/gain_table_lbr.c b/src/libspeex/gain_table_lbr.c new file mode 100644 index 0000000..3c1c3db --- /dev/null +++ b/src/libspeex/gain_table_lbr.c @@ -0,0 +1,64 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: gain_table_lbr.c + Codebook for 3-tap pitch prediction gain (32 entries) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char gain_cdbk_lbr[128] = { +-32, -32, -32, 0, +-31, -58, -16, 22, +-41, -24, -43, 14, +-56, -22, -55, 29, +-13, 33, -41, 47, +-4, -39, -9, 29, +-41, 15, -12, 38, +-8, -15, -12, 31, +1, 2, -44, 40, +-22, -66, -42, 27, +-38, 28, -23, 38, +-21, 14, -37, 31, +0, 21, -50, 52, +-53, -71, -27, 33, +-37, -1, -19, 25, +-19, -5, -28, 22, +6, 65, -44, 74, +-33, -48, -33, 9, +-40, 57, -14, 58, +-17, 4, -45, 32, +-31, 38, -33, 36, +-23, 28, -40, 39, +-43, 29, -12, 46, +-34, 13, -23, 28, +-16, 15, -27, 34, +-14, -82, -15, 43, +-31, 25, -32, 29, +-21, 5, -5, 38, +-47, -63, -51, 33, +-46, 12, 3, 47, +-28, -17, -29, 11, +-10, 14, -40, 38}; diff --git a/src/libspeex/hexc_10_32_table.c b/src/libspeex/hexc_10_32_table.c new file mode 100644 index 0000000..8dd408f --- /dev/null +++ b/src/libspeex/hexc_10_32_table.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: hexc_10_32_table.c + Codebook for high-band excitation in SB-CELP mode (4000 bps) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char hexc_10_32_table[320] = { +-3, -2, -1, 0, -4, 5, 35, -40, -9, 13, +-44, 5, -27, -1, -7, 6, -11, 7, -8, 7, +19, -14, 15, -4, 9, -10, 10, -8, 10, -9, +-1, 1, 0, 0, 2, 5, -18, 22, -53, 50, +1, -23, 50, -36, 15, 3, -13, 14, -10, 6, +1, 5, -3, 4, -2, 5, -32, 25, 5, -2, +-1, -4, 1, 11, -29, 26, -6, -15, 30, -18, +0, 15, -17, 40, -41, 3, 9, -2, -2, 3, +-3, -1, -5, 2, 21, -6, -16, -21, 23, 2, +60, 15, 16, -16, -9, 14, 9, -1, 7, -9, +0, 1, 1, 0, -1, -6, 17, -28, 54, -45, +-1, 1, -1, -6, -6, 2, 11, 26, -29, -2, +46, -21, 34, 12, -23, 32, -23, 16, -10, 3, +66, 19, -20, 24, 7, 11, -3, 0, -3, -1, +-50, -46, 2, -18, -3, 4, -1, -2, 3, -3, +-19, 41, -36, 9, 11, -24, 21, -16, 9, -3, +-25, -3, 10, 18, -9, -2, -5, -1, -5, 6, +-4, -3, 2, -26, 21, -19, 35, -15, 7, -13, +17, -19, 39, -43, 48, -31, 16, -9, 7, -2, +-5, 3, -4, 9, -19, 27, -55, 63, -35, 10, +26, -44, -2, 9, 4, 1, -6, 8, -9, 5, +-8, -1, -3, -16, 45, -42, 5, 15, -16, 10, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +-16, 24, -55, 47, -38, 27, -19, 7, -3, 1, +16, 27, 20, -19, 18, 5, -7, 1, -5, 2, +-6, 8, -22, 0, -3, -3, 8, -1, 7, -8, +1, -3, 5, 0, 17, -48, 58, -52, 29, -7, +-2, 3, -10, 6, -26, 58, -31, 1, -6, 3, +93, -29, 39, 3, 17, 5, 6, -1, -1, -1, +27, 13, 10, 19, -7, -34, 12, 10, -4, 9, +-76, 9, 8, -28, -2, -11, 2, -1, 3, 1, +-83, 38, -39, 4, -16, -6, -2, -5, 5, -2, +}; diff --git a/src/libspeex/hexc_table.c b/src/libspeex/hexc_table.c new file mode 100644 index 0000000..268408a --- /dev/null +++ b/src/libspeex/hexc_table.c @@ -0,0 +1,162 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: hexc_table.c + Codebook for high-band excitation in SB-CELP mode (8000 bps with sign) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char hexc_table[1024] = { +-24, 21, -20, 5, -5, -7, 14, -10, +2, -27, 16, -20, 0, -32, 26, 19, +8, -11, -41, 31, 28, -27, -32, 34, +42, 34, -17, 22, -10, 13, -29, 18, +-12, -26, -24, 11, 22, 5, -5, -5, +54, -68, -43, 57, -25, 24, 4, 4, +26, -8, -12, -17, 54, 30, -45, 1, +10, -15, 18, -41, 11, 68, -67, 37, +-16, -24, -16, 38, -22, 6, -29, 30, +66, -27, 5, 7, -16, 13, 2, -12, +-7, -3, -20, 36, 4, -28, 9, 3, +32, 48, 26, 39, 3, 0, 7, -21, +-13, 5, -82, -7, 73, -20, 34, -9, +-5, 1, -1, 10, -5, -10, -1, 9, +1, -9, 10, 0, -14, 11, -1, -2, +-1, 11, 20, 96, -81, -22, -12, -9, +-58, 9, 24, -30, 26, -35, 27, -12, +13, -18, 56, -59, 15, -7, 23, -15, +-1, 6, -25, 14, -22, -20, 47, -11, +16, 2, 38, -23, -19, -30, -9, 40, +-11, 5, 4, -6, 8, 26, -21, -11, +127, 4, 1, 6, -9, 2, -7, -2, +-3, 7, -5, 10, -19, 7, -106, 91, +-3, 9, -4, 21, -8, 26, -80, 8, +1, -2, -10, -17, -17, -27, 32, 71, +6, -29, 11, -23, 54, -38, 29, -22, +39, 87, -31, -12, -20, 3, -2, -2, +2, 20, 0, -1, -35, 27, 9, -6, +-12, 3, -12, -6, 13, 1, 14, -22, +-59, -15, -17, -25, 13, -7, 7, 3, +0, 1, -7, 6, -3, 61, -37, -23, +-23, -29, 38, -31, 27, 1, -8, 2, +-27, 23, -26, 36, -34, 5, 24, -24, +-6, 7, 3, -59, 78, -62, 44, -16, +1, 6, 0, 17, 8, 45, 0, -110, +6, 14, -2, 32, -77, -56, 62, -3, +3, -13, 4, -16, 102, -15, -36, -1, +9, -113, 6, 23, 0, 9, 9, 5, +-8, -1, -14, 5, -12, 121, -53, -27, +-8, -9, 22, -13, 3, 2, -3, 1, +-2, -71, 95, 38, -19, 15, -16, -5, +71, 10, 2, -32, -13, -5, 15, -1, +-2, -14, -85, 30, 29, 6, 3, 2, +0, 0, 0, 0, 0, 0, 0, 0, +2, -65, -56, -9, 18, 18, 23, -14, +-2, 0, 12, -29, 26, -12, 1, 2, +-12, -64, 90, -6, 4, 1, 5, -5, +-110, -3, -31, 22, -29, 9, 0, 8, +-40, -5, 21, -5, -5, 13, 10, -18, +40, 1, 35, -20, 30, -28, 11, -6, +19, 7, 14, 18, -64, 9, -6, 16, +51, 68, 8, 16, 12, -8, 0, -9, +20, -22, 25, 7, -4, -13, 41, -35, +93, -18, -54, 11, -1, 1, -9, 4, +-66, 66, -31, 20, -22, 25, -23, 11, +10, 9, 19, 15, 11, -5, -31, -10, +-23, -28, -6, -6, -3, -4, 5, 3, +-28, 22, -11, -42, 25, -25, -16, 41, +34, 47, -6, 2, 42, -19, -22, 5, +-39, 32, 6, -35, 22, 17, -30, 8, +-26, -11, -11, 3, -12, 33, 33, -37, +21, -1, 6, -4, 3, 0, -5, 5, +12, -12, 57, 27, -61, -3, 20, -17, +2, 0, 4, 0, -2, -33, -58, 81, +-23, 39, -10, -5, 2, 6, -7, 5, +4, -3, -2, -13, -23, -72, 107, 15, +-5, 0, -7, -3, -6, 5, -4, 15, +47, 12, -31, 25, -16, 8, 22, -25, +-62, -56, -18, 14, 28, 12, 2, -11, +74, -66, 41, -20, -7, 16, -20, 16, +-8, 0, -16, 4, -19, 92, 12, -59, +-14, -39, 49, -25, -16, 23, -27, 19, +-3, -33, 19, 85, -29, 6, -7, -10, +16, -7, -12, 1, -6, 2, 4, -2, +64, 10, -25, 41, -2, -31, 15, 0, +110, 50, 69, 35, 28, 19, -10, 2, +-43, -49, -56, -15, -16, 10, 3, 12, +-1, -8, 1, 26, -12, -1, 7, -11, +-27, 41, 25, 1, -11, -18, 22, -7, +-1, -47, -8, 23, -3, -17, -7, 18, +-125, 59, -5, 3, 18, 1, 2, 3, +27, -35, 65, -53, 50, -46, 37, -21, +-28, 7, 14, -37, -5, -5, 12, 5, +-8, 78, -19, 21, -6, -16, 8, -7, +5, 2, 7, 2, 10, -6, 12, -60, +44, 11, -36, -32, 31, 0, 2, -2, +2, 1, -3, 7, -10, 17, -21, 10, +6, -2, 19, -2, 59, -38, -86, 38, +8, -41, -30, -45, -33, 7, 15, 28, +29, -7, 24, -40, 7, 7, 5, -2, +9, 24, -23, -18, 6, -29, 30, 2, +28, 49, -11, -46, 10, 43, -13, -9, +-1, -3, -7, -7, -17, -6, 97, -33, +-21, 3, 5, 1, 12, -43, -8, 28, +7, -43, -7, 17, -20, 19, -1, 2, +-13, 9, 54, 34, 9, -28, -11, -9, +-17, 110, -59, 44, -26, 0, 3, -12, +-47, 73, -34, -43, 38, -33, 16, -5, +-46, -4, -6, -2, -25, 19, -29, 28, +-13, 5, 14, 27, -40, -43, 4, 32, +-13, -2, -35, -4, 112, -42, 9, -12, +37, -28, 17, 14, -19, 35, -39, 23, +3, -14, -1, -57, -5, 94, -9, 3, +-39, 5, 30, -10, -32, 42, -13, -14, +-97, -63, 30, -9, 1, -7, 12, 5, +20, 17, -9, -36, -30, 25, 47, -9, +-15, 12, -22, 98, -8, -50, 15, -27, +21, -16, -11, 2, 12, -10, 10, -3, +33, 36, -96, 0, -17, 31, -9, 9, +3, -20, 13, -11, 8, -4, 10, -10, +9, 1, 112, -70, -27, 5, -21, 2, +-57, -3, -29, 10, 19, -21, 21, -10, +-66, -3, 91, -35, 30, -12, 0, -7, +59, -28, 26, 2, 14, -18, 1, 1, +11, 17, 20, -54, -59, 27, 4, 29, +32, 5, 19, 12, -4, 1, 7, -10, +5, -2, 10, 0, 23, -5, 28, -104, +46, 11, 16, 3, 29, 1, -8, -14, +1, 7, -50, 88, -62, 26, 8, -17, +-14, 50, 0, 32, -12, -3, -27, 18, +-8, -5, 8, 3, -20, -11, 37, -12, +9, 33, 46, -101, -1, -4, 1, 6, +-1, 28, -42, -15, 16, 5, -1, -2, +-55, 85, 38, -9, -4, 11, -2, -9, +-6, 3, -20, -10, -77, 89, 24, -3, +-104, -57, -26, -31, -20, -6, -9, 14, +20, -23, 46, -15, -31, 28, 1, -15, +-2, 6, -2, 31, 45, -76, 23, -25, +}; diff --git a/src/libspeex/high_lsp_tables.c b/src/libspeex/high_lsp_tables.c new file mode 100644 index 0000000..e82e875 --- /dev/null +++ b/src/libspeex/high_lsp_tables.c @@ -0,0 +1,163 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: high_lsp_tables.c + Codebooks for high-band LSPs in SB-CELP mode + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char high_lsp_cdbk[512]={ +39,12,-14,-20,-29,-61,-67,-76, +-32,-71,-67,68,77,46,34,5, +-13,-48,-46,-72,-81,-84,-60,-58, +-40,-28,82,93,68,45,29,3, +-19,-47,-28,-43,-35,-30,-8,-13, +-39,-91,-91,-123,-96,10,10,-6, +-18,-55,-60,-91,-56,-36,-27,-16, +-48,-75,40,28,-10,-28,35,9, +37,19,1,-20,-31,-41,-18,-25, +-35,-68,-80,45,27,-1,47,13, +0,-29,-35,-57,-50,-79,-73,-38, +-19,5,35,14,-10,-23,16,-8, +5,-24,-40,-62,-23,-27,-22,-16, +-18,-46,-72,-77,43,21,33,1, +-80,-70,-70,-64,-56,-52,-39,-33, +-31,-38,-19,-19,-15,32,33,-2, +7,-15,-15,-24,-23,-33,-41,-56, +-24,-57,5,89,64,41,27,5, +-9,-47,-60,-97,-97,-124,-20,-9, +-44,-73,31,29,-4,64,48,7, +-35,-57,0,-3,-26,-47,-3,-6, +-40,-76,-79,-48,12,81,55,10, +9,-24,-43,-73,-57,-69,16,5, +-28,-53,18,29,20,0,-4,-11, +6,-13,23,7,-17,-35,-37,-37, +-30,-68,-63,6,24,-9,-14,3, +21,-13,-27,-57,-49,-80,-24,-41, +-5,-16,-5,1,45,25,12,-7, +3,-15,-6,-16,-15,-8,6,-13, +-42,-81,-80,-87,14,1,-10,-3, +-43,-69,-46,-24,-28,-29,36,6, +-43,-56,-12,12,54,79,43,9, +54,22,2,8,-12,-43,-46,-52, +-38,-69,-89,-5,75,38,33,5, +-13,-53,-62,-87,-89,-113,-99,-55, +-34,-37,62,55,33,16,21,-2, +-17,-46,-29,-38,-38,-48,-39,-42, +-36,-75,-72,-88,-48,-30,21,2, +-15,-57,-64,-98,-84,-76,25,1, +-46,-80,-12,18,-7,3,34,6, +38,31,23,4,-1,20,14,-15, +-43,-78,-91,-24,14,-3,54,16, +0,-27,-28,-44,-56,-83,-92,-89, +-3,34,56,41,36,22,20,-8, +-7,-35,-42,-62,-49,3,12,-10, +-50,-87,-96,-66,92,70,38,9, +-70,-71,-62,-42,-39,-43,-11,-7, +-50,-79,-58,-50,-31,32,31,-6, +-4,-25,7,-17,-38,-70,-58,-27, +-43,-83,-28,59,36,20,31,2, +-27,-71,-80,-109,-98,-75,-33,-32, +-31,-2,33,15,-6,43,33,-5, +0,-22,-10,-27,-34,-49,-11,-20, +-41,-91,-100,-121,-39,57,41,10, +-19,-50,-38,-59,-60,-70,-18,-20, +-8,-31,-8,-15,1,-14,-26,-25, +33,21,32,17,1,-19,-19,-26, +-58,-81,-35,-22,45,30,11,-11, +3,-26,-48,-87,-67,-83,-58,3, +-1,-26,-20,44,10,25,39,5, +-9,-35,-27,-38,7,10,4,-9, +-42,-85,-102,-127,52,44,28,10, +-47,-61,-40,-39,-17,-1,-10,-33, +-42,-74,-48,21,-4,70,52,10}; + + +const signed char high_lsp_cdbk2[512]={ +-36,-62,6,-9,-10,-14,-56,23, +1,-26,23,-48,-17,12,8,-7, +23,29,-36,-28,-6,-29,-17,-5, +40,23,10,10,-46,-13,36,6, +4,-30,-29,62,32,-32,-1,22, +-14,1,-4,-22,-45,2,54,4, +-30,-57,-59,-12,27,-3,-31,8, +-9,5,10,-14,32,66,19,9, +2,-25,-37,23,-15,18,-38,-31, +5,-9,-21,15,0,22,62,30, +15,-12,-14,-46,77,21,33,3, +34,29,-19,50,2,11,9,-38, +-12,-37,62,1,-15,54,32,6, +2,-24,20,35,-21,2,19,24, +-13,55,4,9,39,-19,30,-1, +-21,73,54,33,8,18,3,15, +6,-19,-47,6,-3,-48,-50,1, +26,20,8,-23,-50,65,-14,-55, +-17,-31,-37,-28,53,-1,-17,-53, +1,57,11,-8,-25,-30,-37,64, +5,-52,-45,15,23,31,15,14, +-25,24,33,-2,-44,-56,-18,6, +-21,-43,4,-12,17,-37,20,-10, +34,15,2,15,55,21,-11,-31, +-6,46,25,16,-9,-25,-8,-62, +28,17,20,-32,-29,26,30,25, +-19,2,-16,-17,26,-51,2,50, +42,19,-66,23,29,-2,3,19, +-19,-37,32,15,6,30,-34,13, +11,-5,40,31,10,-42,4,-9, +26,-9,-70,17,-2,-23,20,-22, +-55,51,-24,-31,22,-22,15,-13, +3,-10,-28,-16,56,4,-63,11, +-18,-15,-18,-38,-35,16,-7,34, +-1,-21,-49,-47,9,-37,7,8, +69,55,20,6,-33,-45,-10,-9, +6,-9,12,71,15,-3,-42,-7, +-24,32,-35,-2,-42,-17,-5,0, +-2,-33,-54,13,-12,-34,47,23, +19,55,7,-8,74,31,14,16, +-23,-26,19,12,-18,-49,-28,-31, +-20,2,-14,-20,-47,78,40,13, +-23,-11,21,-6,18,1,47,5, +38,35,32,46,22,8,13,16, +-14,18,51,19,40,39,11,-26, +-1,-17,47,2,-53,-15,31,-22, +38,21,-15,-16,5,-33,53,15, +-38,86,11,-3,-24,49,13,-4, +-11,-18,28,20,-12,-27,-26,35, +-25,-35,-3,-20,-61,30,10,-55, +-12,-22,-52,-54,-14,19,-32,-12, +45,15,-8,-48,-9,11,-32,8, +-16,-34,-13,51,18,38,-2,-32, +-17,22,-2,-18,-28,-70,59,27, +-28,-19,-10,-20,-9,-9,-8,-21, +21,-8,35,-2,45,-3,-9,12, +0,30,7,-39,43,27,-38,-91, +30,26,19,-55,-4,63,14,-17, +13,9,13,2,7,4,6,61, +72,-1,-17,29,-1,-22,-17,8, +-28,-37,63,44,41,3,2,14, +9,-6,75,-8,-7,-12,-15,-12, +13,9,-4,30,-22,-65,15,0, +-45,4,-4,1,5,22,11,23}; diff --git a/src/libspeex/include/speex/speex.h b/src/libspeex/include/speex/speex.h new file mode 100644 index 0000000..82ba016 --- /dev/null +++ b/src/libspeex/include/speex/speex.h @@ -0,0 +1,424 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin*/ +/** + @file speex.h + @brief Describes the different modes of the codec +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SPEEX_H +#define SPEEX_H +/** @defgroup Codec Speex encoder and decoder + * This is the Speex codec itself. + * @{ + */ + +#include "speex/speex_bits.h" +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Values allowed for *ctl() requests */ + +/** Set enhancement on/off (decoder only) */ +#define SPEEX_SET_ENH 0 +/** Get enhancement state (decoder only) */ +#define SPEEX_GET_ENH 1 + +/*Would be SPEEX_SET_FRAME_SIZE, but it's (currently) invalid*/ +/** Obtain frame size used by encoder/decoder */ +#define SPEEX_GET_FRAME_SIZE 3 + +/** Set quality value */ +#define SPEEX_SET_QUALITY 4 +/** Get current quality setting */ +/* #define SPEEX_GET_QUALITY 5 -- Doesn't make much sense, does it? */ + +/** Set sub-mode to use */ +#define SPEEX_SET_MODE 6 +/** Get current sub-mode in use */ +#define SPEEX_GET_MODE 7 + +/** Set low-band sub-mode to use (wideband only)*/ +#define SPEEX_SET_LOW_MODE 8 +/** Get current low-band mode in use (wideband only)*/ +#define SPEEX_GET_LOW_MODE 9 + +/** Set high-band sub-mode to use (wideband only)*/ +#define SPEEX_SET_HIGH_MODE 10 +/** Get current high-band mode in use (wideband only)*/ +#define SPEEX_GET_HIGH_MODE 11 + +/** Set VBR on (1) or off (0) */ +#define SPEEX_SET_VBR 12 +/** Get VBR status (1 for on, 0 for off) */ +#define SPEEX_GET_VBR 13 + +/** Set quality value for VBR encoding (0-10) */ +#define SPEEX_SET_VBR_QUALITY 14 +/** Get current quality value for VBR encoding (0-10) */ +#define SPEEX_GET_VBR_QUALITY 15 + +/** Set complexity of the encoder (0-10) */ +#define SPEEX_SET_COMPLEXITY 16 +/** Get current complexity of the encoder (0-10) */ +#define SPEEX_GET_COMPLEXITY 17 + +/** Set bit-rate used by the encoder (or lower) */ +#define SPEEX_SET_BITRATE 18 +/** Get current bit-rate used by the encoder or decoder */ +#define SPEEX_GET_BITRATE 19 + +/** Define a handler function for in-band Speex request*/ +#define SPEEX_SET_HANDLER 20 + +/** Define a handler function for in-band user-defined request*/ +#define SPEEX_SET_USER_HANDLER 22 + +/** Set sampling rate used in bit-rate computation */ +#define SPEEX_SET_SAMPLING_RATE 24 +/** Get sampling rate used in bit-rate computation */ +#define SPEEX_GET_SAMPLING_RATE 25 + +/** Reset the encoder/decoder memories to zero*/ +#define SPEEX_RESET_STATE 26 + +/** Get VBR info (mostly used internally) */ +#define SPEEX_GET_RELATIVE_QUALITY 29 + +/** Set VAD status (1 for on, 0 for off) */ +#define SPEEX_SET_VAD 30 + +/** Get VAD status (1 for on, 0 for off) */ +#define SPEEX_GET_VAD 31 + +/** Set Average Bit-Rate (ABR) to n bits per seconds */ +#define SPEEX_SET_ABR 32 +/** Get Average Bit-Rate (ABR) setting (in bps) */ +#define SPEEX_GET_ABR 33 + +/** Set DTX status (1 for on, 0 for off) */ +#define SPEEX_SET_DTX 34 +/** Get DTX status (1 for on, 0 for off) */ +#define SPEEX_GET_DTX 35 + +/** Set submode encoding in each frame (1 for yes, 0 for no, setting to no breaks the standard) */ +#define SPEEX_SET_SUBMODE_ENCODING 36 +/** Get submode encoding in each frame */ +#define SPEEX_GET_SUBMODE_ENCODING 37 + +/*#define SPEEX_SET_LOOKAHEAD 38*/ +/** Returns the lookahead used by Speex */ +#define SPEEX_GET_LOOKAHEAD 39 + +/** Sets tuning for packet-loss concealment (expected loss rate) */ +#define SPEEX_SET_PLC_TUNING 40 +/** Gets tuning for PLC */ +#define SPEEX_GET_PLC_TUNING 41 + +/** Sets the max bit-rate allowed in VBR mode */ +#define SPEEX_SET_VBR_MAX_BITRATE 42 +/** Gets the max bit-rate allowed in VBR mode */ +#define SPEEX_GET_VBR_MAX_BITRATE 43 + +/** Turn on/off input/output high-pass filtering */ +#define SPEEX_SET_HIGHPASS 44 +/** Get status of input/output high-pass filtering */ +#define SPEEX_GET_HIGHPASS 45 + +/** Get "activity level" of the last decoded frame, i.e. + how much damage we cause if we remove the frame */ +#define SPEEX_GET_ACTIVITY 47 + + +/* Preserving compatibility:*/ +/** Equivalent to SPEEX_SET_ENH */ +#define SPEEX_SET_PF 0 +/** Equivalent to SPEEX_GET_ENH */ +#define SPEEX_GET_PF 1 + + + + +/* Values allowed for mode queries */ +/** Query the frame size of a mode */ +#define SPEEX_MODE_FRAME_SIZE 0 + +/** Query the size of an encoded frame for a particular sub-mode */ +#define SPEEX_SUBMODE_BITS_PER_FRAME 1 + + + +/** Get major Speex version */ +#define SPEEX_LIB_GET_MAJOR_VERSION 1 +/** Get minor Speex version */ +#define SPEEX_LIB_GET_MINOR_VERSION 3 +/** Get micro Speex version */ +#define SPEEX_LIB_GET_MICRO_VERSION 5 +/** Get extra Speex version */ +#define SPEEX_LIB_GET_EXTRA_VERSION 7 +/** Get Speex version string */ +#define SPEEX_LIB_GET_VERSION_STRING 9 + +/*#define SPEEX_LIB_SET_ALLOC_FUNC 10 +#define SPEEX_LIB_GET_ALLOC_FUNC 11 +#define SPEEX_LIB_SET_FREE_FUNC 12 +#define SPEEX_LIB_GET_FREE_FUNC 13 + +#define SPEEX_LIB_SET_WARNING_FUNC 14 +#define SPEEX_LIB_GET_WARNING_FUNC 15 +#define SPEEX_LIB_SET_ERROR_FUNC 16 +#define SPEEX_LIB_GET_ERROR_FUNC 17 +*/ + +/** Number of defined modes in Speex */ +#define SPEEX_NB_MODES 3 + +/** modeID for the defined narrowband mode */ +#define SPEEX_MODEID_NB 0 + +/** modeID for the defined wideband mode */ +#define SPEEX_MODEID_WB 1 + +/** modeID for the defined ultra-wideband mode */ +#define SPEEX_MODEID_UWB 2 + +struct SpeexMode; + + +/* Prototypes for mode function pointers */ + +/** Encoder state initialization function */ +typedef void *(*encoder_init_func)(const struct SpeexMode *mode); + +/** Encoder state destruction function */ +typedef void (*encoder_destroy_func)(void *st); + +/** Main encoding function */ +typedef int (*encode_func)(void *state, void *in, SpeexBits *bits); + +/** Function for controlling the encoder options */ +typedef int (*encoder_ctl_func)(void *state, int request, void *ptr); + +/** Decoder state initialization function */ +typedef void *(*decoder_init_func)(const struct SpeexMode *mode); + +/** Decoder state destruction function */ +typedef void (*decoder_destroy_func)(void *st); + +/** Main decoding function */ +typedef int (*decode_func)(void *state, SpeexBits *bits, void *out); + +/** Function for controlling the decoder options */ +typedef int (*decoder_ctl_func)(void *state, int request, void *ptr); + + +/** Query function for a mode */ +typedef int (*mode_query_func)(const void *mode, int request, void *ptr); + +/** Struct defining a Speex mode */ +typedef struct SpeexMode { + /** Pointer to the low-level mode data */ + const void *mode; + + /** Pointer to the mode query function */ + mode_query_func query; + + /** The name of the mode (you should not rely on this to identify the mode)*/ + const char *modeName; + + /**ID of the mode*/ + int modeID; + + /**Version number of the bitstream (incremented every time we break + bitstream compatibility*/ + int bitstream_version; + + /** Pointer to encoder initialization function */ + encoder_init_func enc_init; + + /** Pointer to encoder destruction function */ + encoder_destroy_func enc_destroy; + + /** Pointer to frame encoding function */ + encode_func enc; + + /** Pointer to decoder initialization function */ + decoder_init_func dec_init; + + /** Pointer to decoder destruction function */ + decoder_destroy_func dec_destroy; + + /** Pointer to frame decoding function */ + decode_func dec; + + /** ioctl-like requests for encoder */ + encoder_ctl_func enc_ctl; + + /** ioctl-like requests for decoder */ + decoder_ctl_func dec_ctl; + +} SpeexMode; + +/** + * Returns a handle to a newly created Speex encoder state structure. For now, + * the "mode" argument can be &nb_mode or &wb_mode . In the future, more modes + * may be added. Note that for now if you have more than one channels to + * encode, you need one state per channel. + * + * @param mode The mode to use (either speex_nb_mode or speex_wb.mode) + * @return A newly created encoder state or NULL if state allocation fails + */ +void *speex_encoder_init(const SpeexMode *mode); + +/** Frees all resources associated to an existing Speex encoder state. + * @param state Encoder state to be destroyed */ +void speex_encoder_destroy(void *state); + +/** Uses an existing encoder state to encode one frame of speech pointed to by + "in". The encoded bit-stream is saved in "bits". + @param state Encoder state + @param in Frame that will be encoded with a +-2^15 range. This data MAY be + overwritten by the encoder and should be considered uninitialised + after the call. + @param bits Bit-stream where the data will be written + @return 0 if frame needs not be transmitted (DTX only), 1 otherwise + */ +int speex_encode(void *state, float *in, SpeexBits *bits); + +/** Uses an existing encoder state to encode one frame of speech pointed to by + "in". The encoded bit-stream is saved in "bits". + @param state Encoder state + @param in Frame that will be encoded with a +-2^15 range + @param bits Bit-stream where the data will be written + @return 0 if frame needs not be transmitted (DTX only), 1 otherwise + */ +int speex_encode_int(void *state, spx_int16_t *in, SpeexBits *bits); + +/** Used like the ioctl function to control the encoder parameters + * + * @param state Encoder state + * @param request ioctl-type request (one of the SPEEX_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown, -2 for invalid parameter + */ +int speex_encoder_ctl(void *state, int request, void *ptr); + + +/** Returns a handle to a newly created decoder state structure. For now, + * the mode argument can be &nb_mode or &wb_mode . In the future, more modes + * may be added. Note that for now if you have more than one channels to + * decode, you need one state per channel. + * + * @param mode Speex mode (one of speex_nb_mode or speex_wb_mode) + * @return A newly created decoder state or NULL if state allocation fails + */ +void *speex_decoder_init(const SpeexMode *mode); + +/** Frees all resources associated to an existing decoder state. + * + * @param state State to be destroyed + */ +void speex_decoder_destroy(void *state); + +/** Uses an existing decoder state to decode one frame of speech from + * bit-stream bits. The output speech is saved written to out. + * + * @param state Decoder state + * @param bits Bit-stream from which to decode the frame (NULL if the packet was lost) + * @param out Where to write the decoded frame + * @return return status (0 for no error, -1 for end of stream, -2 corrupt stream) + */ +int speex_decode(void *state, SpeexBits *bits, float *out); + +/** Uses an existing decoder state to decode one frame of speech from + * bit-stream bits. The output speech is saved written to out. + * + * @param state Decoder state + * @param bits Bit-stream from which to decode the frame (NULL if the packet was lost) + * @param out Where to write the decoded frame + * @return return status (0 for no error, -1 for end of stream, -2 corrupt stream) + */ +int speex_decode_int(void *state, SpeexBits *bits, spx_int16_t *out); + +/** Used like the ioctl function to control the encoder parameters + * + * @param state Decoder state + * @param request ioctl-type request (one of the SPEEX_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown, -2 for invalid parameter + */ +int speex_decoder_ctl(void *state, int request, void *ptr); + + +/** Query function for mode information + * + * @param mode Speex mode + * @param request ioctl-type request (one of the SPEEX_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown, -2 for invalid parameter + */ +int speex_mode_query(const SpeexMode *mode, int request, void *ptr); + +/** Functions for controlling the behavior of libspeex + * @param request ioctl-type request (one of the SPEEX_LIB_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown, -2 for invalid parameter + */ +int speex_lib_ctl(int request, void *ptr); + +/** Default narrowband mode */ +extern const SpeexMode speex_nb_mode; + +/** Default wideband mode */ +extern const SpeexMode speex_wb_mode; + +/** Default "ultra-wideband" mode */ +extern const SpeexMode speex_uwb_mode; + +/** List of all modes available */ +extern const SpeexMode * const speex_mode_list[SPEEX_NB_MODES]; + +/** Obtain one of the modes available */ +const SpeexMode * speex_lib_get_mode (int mode); + +#ifndef WIN32 +/* We actually override the function in the narrowband case so that we can avoid linking in the wideband stuff */ +#define speex_lib_get_mode(mode) ((mode)==SPEEX_MODEID_NB ? &speex_nb_mode : speex_lib_get_mode (mode)) +#endif + +#ifdef __cplusplus +} +#endif + +/** @}*/ +#endif diff --git a/src/libspeex/include/speex/speex_bits.h b/src/libspeex/include/speex/speex_bits.h new file mode 100644 index 0000000..a26fb4c --- /dev/null +++ b/src/libspeex/include/speex/speex_bits.h @@ -0,0 +1,174 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file speex_bits.h + @brief Handles bit packing/unpacking +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef BITS_H +#define BITS_H +/** @defgroup SpeexBits SpeexBits: Bit-stream manipulations + * This is the structure that holds the bit-stream when encoding or decoding + * with Speex. It allows some manipulations as well. + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Bit-packing data structure representing (part of) a bit-stream. */ +typedef struct SpeexBits { + char *chars; /**< "raw" data */ + int nbBits; /**< Total number of bits stored in the stream*/ + int charPtr; /**< Position of the byte "cursor" */ + int bitPtr; /**< Position of the bit "cursor" within the current char */ + int owner; /**< Does the struct "own" the "raw" buffer (member "chars") */ + int overflow;/**< Set to one if we try to read past the valid data */ + int buf_size;/**< Allocated size for buffer */ + int reserved1; /**< Reserved for future use */ + void *reserved2; /**< Reserved for future use */ +} SpeexBits; + +/** Initializes and allocates resources for a SpeexBits struct */ +void speex_bits_init(SpeexBits *bits); + +/** Initializes SpeexBits struct using a pre-allocated buffer*/ +void speex_bits_init_buffer(SpeexBits *bits, void *buff, int buf_size); + +/** Sets the bits in a SpeexBits struct to use data from an existing buffer (for decoding without copying data) */ +void speex_bits_set_bit_buffer(SpeexBits *bits, void *buff, int buf_size); + +/** Frees all resources associated to a SpeexBits struct. Right now this does nothing since no resources are allocated, but this could change in the future.*/ +void speex_bits_destroy(SpeexBits *bits); + +/** Resets bits to initial value (just after initialization, erasing content)*/ +void speex_bits_reset(SpeexBits *bits); + +/** Rewind the bit-stream to the beginning (ready for read) without erasing the content */ +void speex_bits_rewind(SpeexBits *bits); + +/** Initializes the bit-stream from the data in an area of memory */ +void speex_bits_read_from(SpeexBits *bits, char *bytes, int len); + +/** Append bytes to the bit-stream + * + * @param bits Bit-stream to operate on + * @param bytes pointer to the bytes what will be appended + * @param len Number of bytes of append + */ +void speex_bits_read_whole_bytes(SpeexBits *bits, char *bytes, int len); + +/** Write the content of a bit-stream to an area of memory + * + * @param bits Bit-stream to operate on + * @param bytes Memory location where to write the bits + * @param max_len Maximum number of bytes to write (i.e. size of the "bytes" buffer) + * @return Number of bytes written to the "bytes" buffer +*/ +int speex_bits_write(SpeexBits *bits, char *bytes, int max_len); + +/** Like speex_bits_write, but writes only the complete bytes in the stream. Also removes the written bytes from the stream */ +int speex_bits_write_whole_bytes(SpeexBits *bits, char *bytes, int max_len); + +/** Append bits to the bit-stream + * @param bits Bit-stream to operate on + * @param data Value to append as integer + * @param nbBits number of bits to consider in "data" + */ +void speex_bits_pack(SpeexBits *bits, int data, int nbBits); + +/** Interpret the next bits in the bit-stream as a signed integer + * + * @param bits Bit-stream to operate on + * @param nbBits Number of bits to interpret + * @return A signed integer represented by the bits read + */ +int speex_bits_unpack_signed(SpeexBits *bits, int nbBits); + +/** Interpret the next bits in the bit-stream as an unsigned integer + * + * @param bits Bit-stream to operate on + * @param nbBits Number of bits to interpret + * @return An unsigned integer represented by the bits read + */ +unsigned int speex_bits_unpack_unsigned(SpeexBits *bits, int nbBits); + +/** Returns the number of bytes in the bit-stream, including the last one even if it is not "full" + * + * @param bits Bit-stream to operate on + * @return Number of bytes in the stream + */ +int speex_bits_nbytes(SpeexBits *bits); + +/** Same as speex_bits_unpack_unsigned, but without modifying the cursor position + * + * @param bits Bit-stream to operate on + * @param nbBits Number of bits to look for + * @return Value of the bits peeked, interpreted as unsigned + */ +unsigned int speex_bits_peek_unsigned(SpeexBits *bits, int nbBits); + +/** Get the value of the next bit in the stream, without modifying the + * "cursor" position + * + * @param bits Bit-stream to operate on + * @return Value of the bit peeked (one bit only) + */ +int speex_bits_peek(SpeexBits *bits); + +/** Advances the position of the "bit cursor" in the stream + * + * @param bits Bit-stream to operate on + * @param n Number of bits to advance + */ +void speex_bits_advance(SpeexBits *bits, int n); + +/** Returns the number of bits remaining to be read in a stream + * + * @param bits Bit-stream to operate on + * @return Number of bits that can still be read from the stream + */ +int speex_bits_remaining(SpeexBits *bits); + +/** Insert a terminator so that the data can be sent as a packet while auto-detecting + * the number of frames in each packet + * + * @param bits Bit-stream to operate on + */ +void speex_bits_insert_terminator(SpeexBits *bits); + +#ifdef __cplusplus +} +#endif + +/* @} */ +#endif diff --git a/src/libspeex/include/speex/speex_buffer.h b/src/libspeex/include/speex/speex_buffer.h new file mode 100644 index 0000000..df56f5f --- /dev/null +++ b/src/libspeex/include/speex/speex_buffer.h @@ -0,0 +1,68 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: speex_buffer.h + This is a very simple ring buffer implementation. It is not thread-safe + so you need to do your own locking. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SPEEX_BUFFER_H +#define SPEEX_BUFFER_H + +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct SpeexBuffer_; +typedef struct SpeexBuffer_ SpeexBuffer; + +SpeexBuffer *speex_buffer_init(int size); + +void speex_buffer_destroy(SpeexBuffer *st); + +int speex_buffer_write(SpeexBuffer *st, void *data, int len); + +int speex_buffer_writezeros(SpeexBuffer *st, int len); + +int speex_buffer_read(SpeexBuffer *st, void *data, int len); + +int speex_buffer_get_available(SpeexBuffer *st); + +int speex_buffer_resize(SpeexBuffer *st, int len); + +#ifdef __cplusplus +} +#endif + +#endif + + + + diff --git a/src/libspeex/include/speex/speex_callbacks.h b/src/libspeex/include/speex/speex_callbacks.h new file mode 100644 index 0000000..6f450b3 --- /dev/null +++ b/src/libspeex/include/speex/speex_callbacks.h @@ -0,0 +1,134 @@ +/* Copyright (C) 2002 Jean-Marc Valin*/ +/** + @file speex_callbacks.h + @brief Describes callback handling and in-band signalling +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SPEEX_CALLBACKS_H +#define SPEEX_CALLBACKS_H +/** @defgroup SpeexCallbacks Various definitions for Speex callbacks supported by the decoder. + * @{ + */ + +#include "speex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Total number of callbacks */ +#define SPEEX_MAX_CALLBACKS 16 + +/* Describes all the in-band requests */ + +/*These are 1-bit requests*/ +/** Request for perceptual enhancement (1 for on, 0 for off) */ +#define SPEEX_INBAND_ENH_REQUEST 0 +/** Reserved */ +#define SPEEX_INBAND_RESERVED1 1 + +/*These are 4-bit requests*/ +/** Request for a mode change */ +#define SPEEX_INBAND_MODE_REQUEST 2 +/** Request for a low mode change */ +#define SPEEX_INBAND_LOW_MODE_REQUEST 3 +/** Request for a high mode change */ +#define SPEEX_INBAND_HIGH_MODE_REQUEST 4 +/** Request for VBR (1 on, 0 off) */ +#define SPEEX_INBAND_VBR_QUALITY_REQUEST 5 +/** Request to be sent acknowledge */ +#define SPEEX_INBAND_ACKNOWLEDGE_REQUEST 6 +/** Request for VBR (1 for on, 0 for off) */ +#define SPEEX_INBAND_VBR_REQUEST 7 + +/*These are 8-bit requests*/ +/** Send a character in-band */ +#define SPEEX_INBAND_CHAR 8 +/** Intensity stereo information */ +#define SPEEX_INBAND_STEREO 9 + +/*These are 16-bit requests*/ +/** Transmit max bit-rate allowed */ +#define SPEEX_INBAND_MAX_BITRATE 10 + +/*These are 32-bit requests*/ +/** Acknowledge packet reception */ +#define SPEEX_INBAND_ACKNOWLEDGE 12 + +/** Callback function type */ +typedef int (*speex_callback_func)(SpeexBits *bits, void *state, void *data); + +/** Callback information */ +typedef struct SpeexCallback { + int callback_id; /**< ID associated to the callback */ + speex_callback_func func; /**< Callback handler function */ + void *data; /**< Data that will be sent to the handler */ + void *reserved1; /**< Reserved for future use */ + int reserved2; /**< Reserved for future use */ +} SpeexCallback; + +/** Handle in-band request */ +int speex_inband_handler(SpeexBits *bits, SpeexCallback *callback_list, void *state); + +/** Standard handler for mode request (change mode, no questions asked) */ +int speex_std_mode_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for high mode request (change high mode, no questions asked) */ +int speex_std_high_mode_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for in-band characters (write to stderr) */ +int speex_std_char_handler(SpeexBits *bits, void *state, void *data); + +/** Default handler for user-defined requests: in this case, just ignore */ +int speex_default_user_handler(SpeexBits *bits, void *state, void *data); + + + +/** Standard handler for low mode request (change low mode, no questions asked) */ +int speex_std_low_mode_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for VBR request (Set VBR, no questions asked) */ +int speex_std_vbr_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for enhancer request (Turn enhancer on/off, no questions asked) */ +int speex_std_enh_request_handler(SpeexBits *bits, void *state, void *data); + +/** Standard handler for VBR quality request (Set VBR quality, no questions asked) */ +int speex_std_vbr_quality_request_handler(SpeexBits *bits, void *state, void *data); + + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif diff --git a/src/libspeex/include/speex/speex_config_types.h b/src/libspeex/include/speex/speex_config_types.h new file mode 100644 index 0000000..bfad776 --- /dev/null +++ b/src/libspeex/include/speex/speex_config_types.h @@ -0,0 +1,18 @@ +#ifndef __SPEEX_TYPES_H__ +#define __SPEEX_TYPES_H__ + +#ifdef _MSC_VER +typedef __int16 spx_int16_t; +typedef __int32 spx_int32_t; +typedef unsigned __int16 spx_uint16_t; +typedef unsigned __int32 spx_uint32_t; +#else +#include +typedef int16_t spx_int16_t; +typedef int32_t spx_int32_t; +typedef uint16_t spx_uint16_t; +typedef uint32_t spx_uint32_t; +#endif + +#endif + diff --git a/src/libspeex/include/speex/speex_echo.h b/src/libspeex/include/speex/speex_echo.h new file mode 100644 index 0000000..77a7846 --- /dev/null +++ b/src/libspeex/include/speex/speex_echo.h @@ -0,0 +1,123 @@ +/* Copyright (C) Jean-Marc Valin */ +/** + @file speex_echo.h + @brief Echo cancellation +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SPEEX_ECHO_H +#define SPEEX_ECHO_H +/** @defgroup SpeexEchoState SpeexEchoState: Acoustic echo canceller + * This is the acoustic echo canceller module. + * @{ + */ +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Obtain frame size used by the AEC */ +#define SPEEX_ECHO_GET_FRAME_SIZE 3 + +/** Set sampling rate */ +#define SPEEX_ECHO_SET_SAMPLING_RATE 24 +/** Get sampling rate */ +#define SPEEX_ECHO_GET_SAMPLING_RATE 25 + +/** Internal echo canceller state. Should never be accessed directly. */ +struct SpeexEchoState_; + +/** @class SpeexEchoState + * This holds the state of the echo canceller. You need one per channel. +*/ + +/** Internal echo canceller state. Should never be accessed directly. */ +typedef struct SpeexEchoState_ SpeexEchoState; + +/** Creates a new echo canceller state + * @param frame_size Number of samples to process at one time (should correspond to 10-20 ms) + * @param filter_length Number of samples of echo to cancel (should generally correspond to 100-500 ms) + * @return Newly-created echo canceller state + */ +SpeexEchoState *speex_echo_state_init(int frame_size, int filter_length); + +/** Destroys an echo canceller state + * @param st Echo canceller state +*/ +void speex_echo_state_destroy(SpeexEchoState *st); + +/** Performs echo cancellation a frame, based on the audio sent to the speaker (no delay is added + * to playback in this form) + * + * @param st Echo canceller state + * @param rec Signal from the microphone (near end + far end echo) + * @param play Signal played to the speaker (received from far end) + * @param out Returns near-end signal with echo removed + */ +void speex_echo_cancellation(SpeexEchoState *st, const spx_int16_t *rec, const spx_int16_t *play, spx_int16_t *out); + +/** Performs echo cancellation a frame (deprecated) */ +void speex_echo_cancel(SpeexEchoState *st, const spx_int16_t *rec, const spx_int16_t *play, spx_int16_t *out, spx_int32_t *Yout); + +/** Perform echo cancellation using internal playback buffer, which is delayed by two frames + * to account for the delay introduced by most soundcards (but it could be off!) + * @param st Echo canceller state + * @param rec Signal from the microphone (near end + far end echo) + * @param out Returns near-end signal with echo removed +*/ +void speex_echo_capture(SpeexEchoState *st, const spx_int16_t *rec, spx_int16_t *out); + +/** Let the echo canceller know that a frame was just queued to the soundcard + * @param st Echo canceller state + * @param play Signal played to the speaker (received from far end) +*/ +void speex_echo_playback(SpeexEchoState *st, const spx_int16_t *play); + +/** Reset the echo canceller to its original state + * @param st Echo canceller state + */ +void speex_echo_state_reset(SpeexEchoState *st); + +/** Used like the ioctl function to control the echo canceller parameters + * + * @param st Echo canceller state + * @param request ioctl-type request (one of the SPEEX_ECHO_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown + */ +int speex_echo_ctl(SpeexEchoState *st, int request, void *ptr); + +#ifdef __cplusplus +} +#endif + + +/** @}*/ +#endif diff --git a/src/libspeex/include/speex/speex_header.h b/src/libspeex/include/speex/speex_header.h new file mode 100644 index 0000000..f85b249 --- /dev/null +++ b/src/libspeex/include/speex/speex_header.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file speex_header.h + @brief Describes the Speex header +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef SPEEX_HEADER_H +#define SPEEX_HEADER_H +/** @defgroup SpeexHeader SpeexHeader: Makes it easy to write/parse an Ogg/Speex header + * This is the Speex header for the Ogg encapsulation. You don't need that if you just use RTP. + * @{ + */ + +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct SpeexMode; + +/** Length of the Speex header identifier */ +#define SPEEX_HEADER_STRING_LENGTH 8 + +/** Maximum number of characters for encoding the Speex version number in the header */ +#define SPEEX_HEADER_VERSION_LENGTH 20 + +/** Speex header info for file-based formats */ +typedef struct SpeexHeader { + char speex_string[SPEEX_HEADER_STRING_LENGTH]; /**< Identifies a Speex bit-stream, always set to "Speex " */ + char speex_version[SPEEX_HEADER_VERSION_LENGTH]; /**< Speex version */ + spx_int32_t speex_version_id; /**< Version for Speex (for checking compatibility) */ + spx_int32_t header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ + spx_int32_t rate; /**< Sampling rate used */ + spx_int32_t mode; /**< Mode used (0 for narrowband, 1 for wideband) */ + spx_int32_t mode_bitstream_version; /**< Version ID of the bit-stream */ + spx_int32_t nb_channels; /**< Number of channels encoded */ + spx_int32_t bitrate; /**< Bit-rate used */ + spx_int32_t frame_size; /**< Size of frames */ + spx_int32_t vbr; /**< 1 for a VBR encoding, 0 otherwise */ + spx_int32_t frames_per_packet; /**< Number of frames stored per Ogg packet */ + spx_int32_t extra_headers; /**< Number of additional headers after the comments */ + spx_int32_t reserved1; /**< Reserved for future use, must be zero */ + spx_int32_t reserved2; /**< Reserved for future use, must be zero */ +} SpeexHeader; + +/** Initializes a SpeexHeader using basic information */ +void speex_init_header(SpeexHeader *header, int rate, int nb_channels, const struct SpeexMode *m); + +/** Creates the header packet from the header itself (mostly involves endianness conversion) */ +char *speex_header_to_packet(SpeexHeader *header, int *size); + +/** Creates a SpeexHeader from a packet */ +SpeexHeader *speex_packet_to_header(char *packet, int size); + +/** Frees the memory allocated by either speex_header_to_packet() or speex_packet_to_header() */ +void speex_header_free(void *ptr); + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif diff --git a/src/libspeex/include/speex/speex_jitter.h b/src/libspeex/include/speex/speex_jitter.h new file mode 100644 index 0000000..d68674b --- /dev/null +++ b/src/libspeex/include/speex/speex_jitter.h @@ -0,0 +1,197 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file speex_jitter.h + @brief Adaptive jitter buffer for Speex +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SPEEX_JITTER_H +#define SPEEX_JITTER_H +/** @defgroup JitterBuffer JitterBuffer: Adaptive jitter buffer + * This is the jitter buffer that reorders UDP/RTP packets and adjusts the buffer size + * to maintain good quality and low latency. + * @{ + */ + +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Generic adaptive jitter buffer state */ +struct JitterBuffer_; + +/** Generic adaptive jitter buffer state */ +typedef struct JitterBuffer_ JitterBuffer; + +/** Definition of an incoming packet */ +typedef struct _JitterBufferPacket JitterBufferPacket; + +/** Definition of an incoming packet */ +struct _JitterBufferPacket { + char *data; /**< Data bytes contained in the packet */ + spx_uint32_t len; /**< Length of the packet in bytes */ + spx_uint32_t timestamp; /**< Timestamp for the packet */ + spx_uint32_t span; /**< Time covered by the packet (same units as timestamp) */ + spx_uint16_t sequence; /**< RTP Sequence number if available (0 otherwise) */ + spx_uint32_t user_data; /**< Put whatever data you like here (it's ignored by the jitter buffer) */ +}; + +/** Packet has been retrieved */ +#define JITTER_BUFFER_OK 0 +/** Packet is lost or is late */ +#define JITTER_BUFFER_MISSING 1 +/** A "fake" packet is meant to be inserted here to increase buffering */ +#define JITTER_BUFFER_INSERTION 2 +/** There was an error in the jitter buffer */ +#define JITTER_BUFFER_INTERNAL_ERROR -1 +/** Invalid argument */ +#define JITTER_BUFFER_BAD_ARGUMENT -2 + + +/** Set minimum amount of extra buffering required (margin) */ +#define JITTER_BUFFER_SET_MARGIN 0 +/** Get minimum amount of extra buffering required (margin) */ +#define JITTER_BUFFER_GET_MARGIN 1 +/* JITTER_BUFFER_SET_AVAILABLE_COUNT wouldn't make sense */ + +/** Get the amount of available packets currently buffered */ +#define JITTER_BUFFER_GET_AVAILABLE_COUNT 3 +/** Included because of an early misspelling (will remove in next release) */ +#define JITTER_BUFFER_GET_AVALIABLE_COUNT 3 + +/** Assign a function to destroy unused packet. When setting that, the jitter + buffer no longer copies packet data. */ +#define JITTER_BUFFER_SET_DESTROY_CALLBACK 4 +/** */ +#define JITTER_BUFFER_GET_DESTROY_CALLBACK 5 + +/** Tell the jitter buffer to only adjust the delay in multiples of the step parameter provided */ +#define JITTER_BUFFER_SET_DELAY_STEP 6 +/** */ +#define JITTER_BUFFER_GET_DELAY_STEP 7 + +/** Tell the jitter buffer to only do concealment in multiples of the size parameter provided */ +#define JITTER_BUFFER_SET_CONCEALMENT_SIZE 8 +#define JITTER_BUFFER_GET_CONCEALMENT_SIZE 9 + +/** Absolute max amount of loss that can be tolerated regardless of the delay. Typical loss + should be half of that or less. */ +#define JITTER_BUFFER_SET_MAX_LATE_RATE 10 +#define JITTER_BUFFER_GET_MAX_LATE_RATE 11 + +/** Equivalent cost of one percent late packet in timestamp units */ +#define JITTER_BUFFER_SET_LATE_COST 12 +#define JITTER_BUFFER_GET_LATE_COST 13 + + +/** Initialises jitter buffer + * + * @param step_size Starting value for the size of concleanment packets and delay + adjustment steps. Can be changed at any time using JITTER_BUFFER_SET_DELAY_STEP + and JITTER_BUFFER_GET_CONCEALMENT_SIZE. + * @return Newly created jitter buffer state + */ +JitterBuffer *jitter_buffer_init(int step_size); + +/** Restores jitter buffer to its original state + * + * @param jitter Jitter buffer state + */ +void jitter_buffer_reset(JitterBuffer *jitter); + +/** Destroys jitter buffer + * + * @param jitter Jitter buffer state + */ +void jitter_buffer_destroy(JitterBuffer *jitter); + +/** Put one packet into the jitter buffer + * + * @param jitter Jitter buffer state + * @param packet Incoming packet +*/ +void jitter_buffer_put(JitterBuffer *jitter, const JitterBufferPacket *packet); + +/** Get one packet from the jitter buffer + * + * @param jitter Jitter buffer state + * @param packet Returned packet + * @param desired_span Number of samples (or units) we wish to get from the buffer (no guarantee) + * @param current_timestamp Timestamp for the returned packet +*/ +int jitter_buffer_get(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t desired_span, spx_int32_t *start_offset); + +/** Used right after jitter_buffer_get() to obtain another packet that would have the same timestamp. + * This is mainly useful for media where a single "frame" can be split into several packets. + * + * @param jitter Jitter buffer state + * @param packet Returned packet + */ +int jitter_buffer_get_another(JitterBuffer *jitter, JitterBufferPacket *packet); + +/** Get pointer timestamp of jitter buffer + * + * @param jitter Jitter buffer state +*/ +int jitter_buffer_get_pointer_timestamp(JitterBuffer *jitter); + +/** Advance by one tick + * + * @param jitter Jitter buffer state +*/ +void jitter_buffer_tick(JitterBuffer *jitter); + +/** Telling the jitter buffer about the remaining data in the application buffer + * @param jitter Jitter buffer state + * @param rem Amount of data buffered by the application (timestamp units) + */ +void jitter_buffer_remaining_span(JitterBuffer *jitter, spx_uint32_t rem); + +/** Used like the ioctl function to control the jitter buffer parameters + * + * @param jitter Jitter buffer state + * @param request ioctl-type request (one of the JITTER_BUFFER_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown +*/ +int jitter_buffer_ctl(JitterBuffer *jitter, int request, void *ptr); + +int jitter_buffer_update_delay(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t *start_offset); + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libspeex/include/speex/speex_preprocess.h b/src/libspeex/include/speex/speex_preprocess.h new file mode 100644 index 0000000..a0d3aa3 --- /dev/null +++ b/src/libspeex/include/speex/speex_preprocess.h @@ -0,0 +1,190 @@ +/* Copyright (C) 2003 Epic Games + Written by Jean-Marc Valin */ +/** + * @file speex_preprocess.h + * @brief Speex preprocessor. The preprocess can do noise suppression, + * residual echo suppression (after using the echo canceller), automatic + * gain control (AGC) and voice activity detection (VAD). +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SPEEX_PREPROCESS_H +#define SPEEX_PREPROCESS_H +/** @defgroup SpeexPreprocessState SpeexPreprocessState: The Speex preprocessor + * This is the Speex preprocessor. The preprocess can do noise suppression, + * residual echo suppression (after using the echo canceller), automatic + * gain control (AGC) and voice activity detection (VAD). + * @{ + */ + +#include "speex/speex_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** State of the preprocessor (one per channel). Should never be accessed directly. */ +struct SpeexPreprocessState_; + +/** State of the preprocessor (one per channel). Should never be accessed directly. */ +typedef struct SpeexPreprocessState_ SpeexPreprocessState; + + +/** Creates a new preprocessing state. You MUST create one state per channel processed. + * @param frame_size Number of samples to process at one time (should correspond to 10-20 ms). Must be + * the same value as that used for the echo canceller for residual echo cancellation to work. + * @param sampling_rate Sampling rate used for the input. + * @return Newly created preprocessor state +*/ +SpeexPreprocessState *speex_preprocess_state_init(int frame_size, int sampling_rate); + +/** Destroys a preprocessor state + * @param st Preprocessor state to destroy +*/ +void speex_preprocess_state_destroy(SpeexPreprocessState *st); + +/** Preprocess a frame + * @param st Preprocessor state + * @param x Audio sample vector (in and out). Must be same size as specified in speex_preprocess_state_init(). + * @return Bool value for voice activity (1 for speech, 0 for noise/silence), ONLY if VAD turned on. +*/ +int speex_preprocess_run(SpeexPreprocessState *st, spx_int16_t *x); + +/** Preprocess a frame (deprecated, use speex_preprocess_run() instead)*/ +int speex_preprocess(SpeexPreprocessState *st, spx_int16_t *x, spx_int32_t *echo); + +/** Update preprocessor state, but do not compute the output + * @param st Preprocessor state + * @param x Audio sample vector (in only). Must be same size as specified in speex_preprocess_state_init(). +*/ +void speex_preprocess_estimate_update(SpeexPreprocessState *st, spx_int16_t *x); + +/** Used like the ioctl function to control the preprocessor parameters + * @param st Preprocessor state + * @param request ioctl-type request (one of the SPEEX_PREPROCESS_* macros) + * @param ptr Data exchanged to-from function + * @return 0 if no error, -1 if request in unknown +*/ +int speex_preprocess_ctl(SpeexPreprocessState *st, int request, void *ptr); + + + +/** Set preprocessor denoiser state */ +#define SPEEX_PREPROCESS_SET_DENOISE 0 +/** Get preprocessor denoiser state */ +#define SPEEX_PREPROCESS_GET_DENOISE 1 + +/** Set preprocessor Automatic Gain Control state */ +#define SPEEX_PREPROCESS_SET_AGC 2 +/** Get preprocessor Automatic Gain Control state */ +#define SPEEX_PREPROCESS_GET_AGC 3 + +/** Set preprocessor Voice Activity Detection state */ +#define SPEEX_PREPROCESS_SET_VAD 4 +/** Get preprocessor Voice Activity Detection state */ +#define SPEEX_PREPROCESS_GET_VAD 5 + +/** Set preprocessor Automatic Gain Control level */ +#define SPEEX_PREPROCESS_SET_AGC_LEVEL 6 +/** Get preprocessor Automatic Gain Control level */ +#define SPEEX_PREPROCESS_GET_AGC_LEVEL 7 + +/** Set preprocessor dereverb state */ +#define SPEEX_PREPROCESS_SET_DEREVERB 8 +/** Get preprocessor dereverb state */ +#define SPEEX_PREPROCESS_GET_DEREVERB 9 + +/** Set preprocessor dereverb level */ +#define SPEEX_PREPROCESS_SET_DEREVERB_LEVEL 10 +/** Get preprocessor dereverb level */ +#define SPEEX_PREPROCESS_GET_DEREVERB_LEVEL 11 + +/** Set preprocessor dereverb decay */ +#define SPEEX_PREPROCESS_SET_DEREVERB_DECAY 12 +/** Get preprocessor dereverb decay */ +#define SPEEX_PREPROCESS_GET_DEREVERB_DECAY 13 + +/** Set probability required for the VAD to go from silence to voice */ +#define SPEEX_PREPROCESS_SET_PROB_START 14 +/** Get probability required for the VAD to go from silence to voice */ +#define SPEEX_PREPROCESS_GET_PROB_START 15 + +/** Set probability required for the VAD to stay in the voice state (integer percent) */ +#define SPEEX_PREPROCESS_SET_PROB_CONTINUE 16 +/** Get probability required for the VAD to stay in the voice state (integer percent) */ +#define SPEEX_PREPROCESS_GET_PROB_CONTINUE 17 + +/** Set maximum attenuation of the noise in dB (negative number) */ +#define SPEEX_PREPROCESS_SET_NOISE_SUPPRESS 18 +/** Get maximum attenuation of the noise in dB (negative number) */ +#define SPEEX_PREPROCESS_GET_NOISE_SUPPRESS 19 + +/** Set maximum attenuation of the residual echo in dB (negative number) */ +#define SPEEX_PREPROCESS_SET_ECHO_SUPPRESS 20 +/** Get maximum attenuation of the residual echo in dB (negative number) */ +#define SPEEX_PREPROCESS_GET_ECHO_SUPPRESS 21 + +/** Set maximum attenuation of the residual echo in dB when near end is active (negative number) */ +#define SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE 22 +/** Get maximum attenuation of the residual echo in dB when near end is active (negative number) */ +#define SPEEX_PREPROCESS_GET_ECHO_SUPPRESS_ACTIVE 23 + +/** Set the corresponding echo canceller state so that residual echo suppression can be performed (NULL for no residual echo suppression) */ +#define SPEEX_PREPROCESS_SET_ECHO_STATE 24 +/** Get the corresponding echo canceller state */ +#define SPEEX_PREPROCESS_GET_ECHO_STATE 25 + +/** Set maximal gain increase in dB/second (int32) */ +#define SPEEX_PREPROCESS_SET_AGC_INCREMENT 26 + +/** Get maximal gain increase in dB/second (int32) */ +#define SPEEX_PREPROCESS_GET_AGC_INCREMENT 27 + +/** Set maximal gain decrease in dB/second (int32) */ +#define SPEEX_PREPROCESS_SET_AGC_DECREMENT 28 + +/** Get maximal gain decrease in dB/second (int32) */ +#define SPEEX_PREPROCESS_GET_AGC_DECREMENT 29 + +/** Set maximal gain in dB (int32) */ +#define SPEEX_PREPROCESS_SET_AGC_MAX_GAIN 30 + +/** Get maximal gain in dB (int32) */ +#define SPEEX_PREPROCESS_GET_AGC_MAX_GAIN 31 + +/* Can't set loudness */ +/** Get loudness */ +#define SPEEX_PREPROCESS_GET_AGC_LOUDNESS 33 + +#ifdef __cplusplus +} +#endif + +/** @}*/ +#endif diff --git a/src/libspeex/include/speex/speex_resampler.h b/src/libspeex/include/speex/speex_resampler.h new file mode 100644 index 0000000..54eef8d --- /dev/null +++ b/src/libspeex/include/speex/speex_resampler.h @@ -0,0 +1,340 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: speex_resampler.h + Resampling code + + The design goals of this code are: + - Very fast algorithm + - Low memory requirement + - Good *perceptual* quality (and not best SNR) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef SPEEX_RESAMPLER_H +#define SPEEX_RESAMPLER_H + +#ifdef OUTSIDE_SPEEX + +/********* WARNING: MENTAL SANITY ENDS HERE *************/ + +/* If the resampler is defined outside of Speex, we change the symbol names so that + there won't be any clash if linking with Speex later on. */ + +/* #define RANDOM_PREFIX your software name here */ +#ifndef RANDOM_PREFIX +#error "Please define RANDOM_PREFIX (above) to something specific to your project to prevent symbol name clashes" +#endif + +#define CAT_PREFIX2(a,b) a ## b +#define CAT_PREFIX(a,b) CAT_PREFIX2(a, b) + +#define speex_resampler_init CAT_PREFIX(RANDOM_PREFIX,_resampler_init) +#define speex_resampler_init_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_init_frac) +#define speex_resampler_destroy CAT_PREFIX(RANDOM_PREFIX,_resampler_destroy) +#define speex_resampler_process_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_float) +#define speex_resampler_process_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_int) +#define speex_resampler_process_interleaved_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_float) +#define speex_resampler_process_interleaved_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_int) +#define speex_resampler_set_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate) +#define speex_resampler_get_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_get_rate) +#define speex_resampler_set_rate_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate_frac) +#define speex_resampler_get_ratio CAT_PREFIX(RANDOM_PREFIX,_resampler_get_ratio) +#define speex_resampler_set_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_set_quality) +#define speex_resampler_get_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_get_quality) +#define speex_resampler_set_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_input_stride) +#define speex_resampler_get_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_stride) +#define speex_resampler_set_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_output_stride) +#define speex_resampler_get_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_stride) +#define speex_resampler_get_input_latency CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_latency) +#define speex_resampler_get_output_latency CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_latency) +#define speex_resampler_skip_zeros CAT_PREFIX(RANDOM_PREFIX,_resampler_skip_zeros) +#define speex_resampler_reset_mem CAT_PREFIX(RANDOM_PREFIX,_resampler_reset_mem) +#define speex_resampler_strerror CAT_PREFIX(RANDOM_PREFIX,_resampler_strerror) + +#define spx_int16_t short +#define spx_int32_t int +#define spx_uint16_t unsigned short +#define spx_uint32_t unsigned int + +#else /* OUTSIDE_SPEEX */ + +#include "speex/speex_types.h" + +#endif /* OUTSIDE_SPEEX */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPEEX_RESAMPLER_QUALITY_MAX 10 +#define SPEEX_RESAMPLER_QUALITY_MIN 0 +#define SPEEX_RESAMPLER_QUALITY_DEFAULT 4 +#define SPEEX_RESAMPLER_QUALITY_VOIP 3 +#define SPEEX_RESAMPLER_QUALITY_DESKTOP 5 + +enum { + RESAMPLER_ERR_SUCCESS = 0, + RESAMPLER_ERR_ALLOC_FAILED = 1, + RESAMPLER_ERR_BAD_STATE = 2, + RESAMPLER_ERR_INVALID_ARG = 3, + RESAMPLER_ERR_PTR_OVERLAP = 4, + + RESAMPLER_ERR_MAX_ERROR +}; + +struct SpeexResamplerState_; +typedef struct SpeexResamplerState_ SpeexResamplerState; + +/** Create a new resampler with integer input and output rates. + * @param nb_channels Number of channels to be processed + * @param in_rate Input sampling rate (integer number of Hz). + * @param out_rate Output sampling rate (integer number of Hz). + * @param quality Resampling quality between 0 and 10, where 0 has poor quality + * and 10 has very high quality. + * @return Newly created resampler state + * @retval NULL Error: not enough memory + */ +SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, + spx_uint32_t in_rate, + spx_uint32_t out_rate, + int quality, + int *err); + +/** Create a new resampler with fractional input/output rates. The sampling + * rate ratio is an arbitrary rational number with both the numerator and + * denominator being 32-bit integers. + * @param nb_channels Number of channels to be processed + * @param ratio_num Numerator of the sampling rate ratio + * @param ratio_den Denominator of the sampling rate ratio + * @param in_rate Input sampling rate rounded to the nearest integer (in Hz). + * @param out_rate Output sampling rate rounded to the nearest integer (in Hz). + * @param quality Resampling quality between 0 and 10, where 0 has poor quality + * and 10 has very high quality. + * @return Newly created resampler state + * @retval NULL Error: not enough memory + */ +SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, + spx_uint32_t ratio_num, + spx_uint32_t ratio_den, + spx_uint32_t in_rate, + spx_uint32_t out_rate, + int quality, + int *err); + +/** Destroy a resampler state. + * @param st Resampler state + */ +void speex_resampler_destroy(SpeexResamplerState *st); + +/** Resample a float array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param channel_index Index of the channel to process for the multi-channel + * base (0 otherwise) + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the + * number of samples processed + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written + */ +int speex_resampler_process_float(SpeexResamplerState *st, + spx_uint32_t channel_index, + const float *in, + spx_uint32_t *in_len, + float *out, + spx_uint32_t *out_len); + +/** Resample an int array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param channel_index Index of the channel to process for the multi-channel + * base (0 otherwise) + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written + */ +int speex_resampler_process_int(SpeexResamplerState *st, + spx_uint32_t channel_index, + const spx_int16_t *in, + spx_uint32_t *in_len, + spx_int16_t *out, + spx_uint32_t *out_len); + +/** Resample an interleaved float array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed. This is all per-channel. + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written. + * This is all per-channel. + */ +int speex_resampler_process_interleaved_float(SpeexResamplerState *st, + const float *in, + spx_uint32_t *in_len, + float *out, + spx_uint32_t *out_len); + +/** Resample an interleaved int array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed. This is all per-channel. + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written. + * This is all per-channel. + */ +int speex_resampler_process_interleaved_int(SpeexResamplerState *st, + const spx_int16_t *in, + spx_uint32_t *in_len, + spx_int16_t *out, + spx_uint32_t *out_len); + +/** Set (change) the input/output sampling rates (integer value). + * @param st Resampler state + * @param in_rate Input sampling rate (integer number of Hz). + * @param out_rate Output sampling rate (integer number of Hz). + */ +int speex_resampler_set_rate(SpeexResamplerState *st, + spx_uint32_t in_rate, + spx_uint32_t out_rate); + +/** Get the current input/output sampling rates (integer value). + * @param st Resampler state + * @param in_rate Input sampling rate (integer number of Hz) copied. + * @param out_rate Output sampling rate (integer number of Hz) copied. + */ +void speex_resampler_get_rate(SpeexResamplerState *st, + spx_uint32_t *in_rate, + spx_uint32_t *out_rate); + +/** Set (change) the input/output sampling rates and resampling ratio + * (fractional values in Hz supported). + * @param st Resampler state + * @param ratio_num Numerator of the sampling rate ratio + * @param ratio_den Denominator of the sampling rate ratio + * @param in_rate Input sampling rate rounded to the nearest integer (in Hz). + * @param out_rate Output sampling rate rounded to the nearest integer (in Hz). + */ +int speex_resampler_set_rate_frac(SpeexResamplerState *st, + spx_uint32_t ratio_num, + spx_uint32_t ratio_den, + spx_uint32_t in_rate, + spx_uint32_t out_rate); + +/** Get the current resampling ratio. This will be reduced to the least + * common denominator. + * @param st Resampler state + * @param ratio_num Numerator of the sampling rate ratio copied + * @param ratio_den Denominator of the sampling rate ratio copied + */ +void speex_resampler_get_ratio(SpeexResamplerState *st, + spx_uint32_t *ratio_num, + spx_uint32_t *ratio_den); + +/** Set (change) the conversion quality. + * @param st Resampler state + * @param quality Resampling quality between 0 and 10, where 0 has poor + * quality and 10 has very high quality. + */ +int speex_resampler_set_quality(SpeexResamplerState *st, + int quality); + +/** Get the conversion quality. + * @param st Resampler state + * @param quality Resampling quality between 0 and 10, where 0 has poor + * quality and 10 has very high quality. + */ +void speex_resampler_get_quality(SpeexResamplerState *st, + int *quality); + +/** Set (change) the input stride. + * @param st Resampler state + * @param stride Input stride + */ +void speex_resampler_set_input_stride(SpeexResamplerState *st, + spx_uint32_t stride); + +/** Get the input stride. + * @param st Resampler state + * @param stride Input stride copied + */ +void speex_resampler_get_input_stride(SpeexResamplerState *st, + spx_uint32_t *stride); + +/** Set (change) the output stride. + * @param st Resampler state + * @param stride Output stride + */ +void speex_resampler_set_output_stride(SpeexResamplerState *st, + spx_uint32_t stride); + +/** Get the output stride. + * @param st Resampler state copied + * @param stride Output stride + */ +void speex_resampler_get_output_stride(SpeexResamplerState *st, + spx_uint32_t *stride); + +/** Get the latency in input samples introduced by the resampler. + * @param st Resampler state + */ +int speex_resampler_get_input_latency(SpeexResamplerState *st); + +/** Get the latency in output samples introduced by the resampler. + * @param st Resampler state + */ +int speex_resampler_get_output_latency(SpeexResamplerState *st); + +/** Make sure that the first samples to go out of the resamplers don't have + * leading zeros. This is only useful before starting to use a newly created + * resampler. It is recommended to use that when resampling an audio file, as + * it will generate a file with the same length. For real-time processing, + * it is probably easier not to use this call (so that the output duration + * is the same for the first frame). + * @param st Resampler state + */ +int speex_resampler_skip_zeros(SpeexResamplerState *st); + +/** Reset a resampler so a new (unrelated) stream can be processed. + * @param st Resampler state + */ +int speex_resampler_reset_mem(SpeexResamplerState *st); + +/** Returns the English meaning for an error code + * @param err Error code + * @return English string + */ +const char *speex_resampler_strerror(int err); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libspeex/include/speex/speex_stereo.h b/src/libspeex/include/speex/speex_stereo.h new file mode 100644 index 0000000..415915d --- /dev/null +++ b/src/libspeex/include/speex/speex_stereo.h @@ -0,0 +1,91 @@ +/* Copyright (C) 2002 Jean-Marc Valin*/ +/** + @file speex_stereo.h + @brief Describes the handling for intensity stereo +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef STEREO_H +#define STEREO_H +/** @defgroup SpeexStereoState SpeexStereoState: Handling Speex stereo files + * This describes the Speex intensity stereo encoding/decoding + * @{ + */ + +#include "speex/speex_types.h" +#include "speex/speex_bits.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** If you access any of these fields directly, I'll personally come and bite you */ +typedef struct SpeexStereoState { + float balance; /**< Left/right balance info */ + float e_ratio; /**< Ratio of energies: E(left+right)/[E(left)+E(right)] */ + float smooth_left; /**< Smoothed left channel gain */ + float smooth_right; /**< Smoothed right channel gain */ + float reserved1; /**< Reserved for future use */ + float reserved2; /**< Reserved for future use */ +} SpeexStereoState; + +/** Deprecated. Use speex_stereo_state_init() instead. */ +#define SPEEX_STEREO_STATE_INIT {1,.5,1,1,0,0} + +/** Initialise/create a stereo stereo state */ +SpeexStereoState *speex_stereo_state_init(void); + +/** Reset/re-initialise an already allocated stereo state */ +void speex_stereo_state_reset(SpeexStereoState *stereo); + +/** Destroy a stereo stereo state */ +void speex_stereo_state_destroy(SpeexStereoState *stereo); + +/** Transforms a stereo frame into a mono frame and stores intensity stereo info in 'bits' */ +void speex_encode_stereo(float *data, int frame_size, SpeexBits *bits); + +/** Transforms a stereo frame into a mono frame and stores intensity stereo info in 'bits' */ +void speex_encode_stereo_int(spx_int16_t *data, int frame_size, SpeexBits *bits); + +/** Transforms a mono frame into a stereo frame using intensity stereo info */ +void speex_decode_stereo(float *data, int frame_size, SpeexStereoState *stereo); + +/** Transforms a mono frame into a stereo frame using intensity stereo info */ +void speex_decode_stereo_int(spx_int16_t *data, int frame_size, SpeexStereoState *stereo); + +/** Callback handler for intensity stereo info */ +int speex_std_stereo_request_handler(SpeexBits *bits, void *state, void *data); + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif diff --git a/src/libspeex/include/speex/speex_types.h b/src/libspeex/include/speex/speex_types.h new file mode 100644 index 0000000..852fed8 --- /dev/null +++ b/src/libspeex/include/speex/speex_types.h @@ -0,0 +1,126 @@ +/* speex_types.h taken from libogg */ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: #ifdef jail to whip a few platforms into the UNIX ideal. + last mod: $Id: os_types.h 7524 2004-08-11 04:20:36Z conrad $ + + ********************************************************************/ +/** + @file speex_types.h + @brief Speex types +*/ +#ifndef _SPEEX_TYPES_H +#define _SPEEX_TYPES_H + +#if defined(_WIN32) + +# if defined(__CYGWIN__) +# include <_G_config.h> + typedef _G_int32_t spx_int32_t; + typedef _G_uint32_t spx_uint32_t; + typedef _G_int16_t spx_int16_t; + typedef _G_uint16_t spx_uint16_t; +# elif defined(__MINGW32__) + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; +# elif defined(__MWERKS__) + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; +# else + /* MSVC/Borland */ + typedef __int32 spx_int32_t; + typedef unsigned __int32 spx_uint32_t; + typedef __int16 spx_int16_t; + typedef unsigned __int16 spx_uint16_t; +# endif + +#elif defined(__MACOS__) + +# include + typedef SInt16 spx_int16_t; + typedef UInt16 spx_uint16_t; + typedef SInt32 spx_int32_t; + typedef UInt32 spx_uint32_t; + +#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ + +# include + typedef int16_t spx_int16_t; + typedef u_int16_t spx_uint16_t; + typedef int32_t spx_int32_t; + typedef u_int32_t spx_uint32_t; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16_t spx_int16_t; + typedef u_int16_t spx_uint16_t; + typedef int32_t spx_int32_t; + typedef u_int32_t spx_uint32_t; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short spx_int16_t; + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; + +#elif defined(R5900) + + /* PS2 EE */ + typedef int spx_int32_t; + typedef unsigned spx_uint32_t; + typedef short spx_int16_t; + +#elif defined(__SYMBIAN32__) + + /* Symbian GCC */ + typedef signed short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef signed int spx_int32_t; + typedef unsigned int spx_uint32_t; + +#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef long spx_int32_t; + typedef unsigned long spx_uint32_t; + +#elif defined(CONFIG_TI_C6X) + + typedef short spx_int16_t; + typedef unsigned short spx_uint16_t; + typedef int spx_int32_t; + typedef unsigned int spx_uint32_t; + +#else + +# include + +#endif + +#endif /* _SPEEX_TYPES_H */ diff --git a/src/libspeex/jitter.c b/src/libspeex/jitter.c new file mode 100644 index 0000000..6a32e75 --- /dev/null +++ b/src/libspeex/jitter.c @@ -0,0 +1,840 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: speex_jitter.h + + Adaptive jitter buffer for Speex + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +/* +TODO: +- Add short-term estimate +- Defensive programming + + warn when last returned < last desired (begative buffering) + + warn if update_delay not called between get() and tick() or is called twice in a row +- Linked list structure for holding the packets instead of the current fixed-size array + + return memory to a pool + + allow pre-allocation of the pool + + optional max number of elements +- Statistics + + drift + + loss + + late + + jitter + + buffering delay +*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "arch.h" +#include +#include +#include +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + +#define SPEEX_JITTER_MAX_BUFFER_SIZE 200 /**< Maximum number of packets in jitter buffer */ + +#define TSUB(a,b) ((spx_int32_t)((a)-(b))) + +#define GT32(a,b) (((spx_int32_t)((a)-(b)))>0) +#define GE32(a,b) (((spx_int32_t)((a)-(b)))>=0) +#define LT32(a,b) (((spx_int32_t)((a)-(b)))<0) +#define LE32(a,b) (((spx_int32_t)((a)-(b)))<=0) + +#define ROUND_DOWN(x, step) ((x)<0 ? ((x)-(step)+1)/(step)*(step) : (x)/(step)*(step)) + +#define MAX_TIMINGS 20 +#define MAX_BUFFERS 3 +#define TOP_DELAY 20 + +/** Buffer that keeps the time of arrival of the latest packets */ +struct TimingBuffer { + int filled; /**< Number of entries occupied in "timing" and "counts"*/ + int curr_count; /**< Number of packet timings we got (including those we discarded) */ + spx_int16_t timing[MAX_TIMINGS]; /**< Sorted list of all timings ("latest" packets first) */ + spx_int16_t counts[MAX_TIMINGS]; /**< Order the packets were put in (will be used for short-term estimate) */ +}; + +static void tb_init(struct TimingBuffer *tb) +{ + tb->filled = 0; + tb->curr_count = 0; +} + +/* Add the timing of a new packet to the TimingBuffer */ +static void tb_add(struct TimingBuffer *tb, spx_int16_t timing) +{ + int pos; + /* Discard packet that won't make it into the list because they're too early */ + if (tb->filled >= MAX_TIMINGS && timing >= tb->timing[tb->filled-1]) + { + tb->curr_count++; + return; + } + + /* Find where the timing info goes in the sorted list */ + pos = 0; + /* FIXME: Do bisection instead of linear search */ + while (posfilled && timing >= tb->timing[pos]) + { + pos++; + } + + speex_assert(pos <= tb->filled && pos < MAX_TIMINGS); + + /* Shift everything so we can perform the insertion */ + if (pos < tb->filled) + { + int move_size = tb->filled-pos; + if (tb->filled == MAX_TIMINGS) + move_size -= 1; + SPEEX_MOVE(&tb->timing[pos+1], &tb->timing[pos], move_size); + SPEEX_MOVE(&tb->counts[pos+1], &tb->counts[pos], move_size); + } + /* Insert */ + tb->timing[pos] = timing; + tb->counts[pos] = tb->curr_count; + + tb->curr_count++; + if (tb->filledfilled++; +} + + + +/** Jitter buffer structure */ +struct JitterBuffer_ { + spx_uint32_t pointer_timestamp; /**< Timestamp of what we will *get* next */ + spx_uint32_t last_returned_timestamp; /**< Useful for getting the next packet with the same timestamp (for fragmented media) */ + spx_uint32_t next_stop; /**< Estimated time the next get() will be called */ + + spx_int32_t buffered; /**< Amount of data we think is still buffered by the application (timestamp units)*/ + + JitterBufferPacket packets[SPEEX_JITTER_MAX_BUFFER_SIZE]; /**< Packets stored in the buffer */ + spx_uint32_t arrival[SPEEX_JITTER_MAX_BUFFER_SIZE]; /**< Packet arrival time (0 means it was late, even though it's a valid timestamp) */ + + void (*destroy) (void *); /**< Callback for destroying a packet */ + + spx_int32_t delay_step; /**< Size of the steps when adjusting buffering (timestamp units) */ + spx_int32_t concealment_size; /**< Size of the packet loss concealment "units" */ + int reset_state; /**< True if state was just reset */ + int buffer_margin; /**< How many frames we want to keep in the buffer (lower bound) */ + int late_cutoff; /**< How late must a packet be for it not to be considered at all */ + int interp_requested; /**< An interpolation is requested by speex_jitter_update_delay() */ + int auto_adjust; /**< Whether to automatically adjust the delay at any time */ + + struct TimingBuffer _tb[MAX_BUFFERS]; /**< Don't use those directly */ + struct TimingBuffer *timeBuffers[MAX_BUFFERS]; /**< Storing arrival time of latest frames so we can compute some stats */ + int window_size; /**< Total window over which the late frames are counted */ + int subwindow_size; /**< Sub-window size for faster computation */ + int max_late_rate; /**< Absolute maximum amount of late packets tolerable (in percent) */ + int latency_tradeoff; /**< Latency equivalent of losing one percent of packets */ + int auto_tradeoff; /**< Latency equivalent of losing one percent of packets (automatic default) */ + + int lost_count; /**< Number of consecutive lost packets */ +}; + +/** Based on available data, this computes the optimal delay for the jitter buffer. + The optimised function is in timestamp units and is: + cost = delay + late_factor*[number of frames that would be late if we used that delay] + @param tb Array of buffers + @param late_factor Equivalent cost of a late frame (in timestamp units) + */ +static spx_int16_t compute_opt_delay(JitterBuffer *jitter) +{ + int i; + spx_int16_t opt=0; + spx_int32_t best_cost=0x7fffffff; + int late = 0; + int pos[MAX_BUFFERS]; + int tot_count; + float late_factor; + int penalty_taken = 0; + int best = 0; + int worst = 0; + spx_int32_t deltaT; + struct TimingBuffer *tb; + + tb = jitter->_tb; + + /* Number of packet timings we have received (including those we didn't keep) */ + tot_count = 0; + for (i=0;ilatency_tradeoff != 0) + late_factor = jitter->latency_tradeoff * 100.0f / tot_count; + else + late_factor = jitter->auto_tradeoff * jitter->window_size/tot_count; + + /*fprintf(stderr, "late_factor = %f\n", late_factor);*/ + for (i=0;idelay_step); + pos[next]++; + + /* Actual cost function that tells us how bad using this delay would be */ + cost = -latest + late_factor*late; + /*fprintf(stderr, "cost %d = %d + %f * %d\n", cost, -latest, late_factor, late);*/ + if (cost < best_cost) + { + best_cost = cost; + opt = latest; + } + } else { + break; + } + + /* For the next timing we will consider, there will be one more late packet to count */ + late++; + /* Two-frame penalty if we're going to increase the amount of late frames (hysteresis) */ + if (latest >= 0 && !penalty_taken) + { + penalty_taken = 1; + late+=2; + } + } + + deltaT = best-worst; + /* This is a default "automatic latency tradeoff" when none is provided */ + jitter->auto_tradeoff = 1 + deltaT/TOP_DELAY; + /*fprintf(stderr, "auto_tradeoff = %d (%d %d %d)\n", jitter->auto_tradeoff, best, worst, i);*/ + + /* FIXME: Compute a short-term estimate too and combine with the long-term one */ + + /* Prevents reducing the buffer size when we haven't really had much data */ + if (tot_count < TOP_DELAY && opt > 0) + return 0; + return opt; +} + + +/** Initialise jitter buffer */ +JitterBuffer *jitter_buffer_init(int step_size) +{ + JitterBuffer *jitter = (JitterBuffer*)speex_alloc(sizeof(JitterBuffer)); + if (jitter) + { + int i; + spx_int32_t tmp; + for (i=0;ipackets[i].data=NULL; + jitter->delay_step = step_size; + jitter->concealment_size = step_size; + /*FIXME: Should this be 0 or 1?*/ + jitter->buffer_margin = 0; + jitter->late_cutoff = 50; + jitter->destroy = NULL; + jitter->latency_tradeoff = 0; + jitter->auto_adjust = 1; + tmp = 4; + jitter_buffer_ctl(jitter, JITTER_BUFFER_SET_MAX_LATE_RATE, &tmp); + jitter_buffer_reset(jitter); + } + return jitter; +} + +/** Reset jitter buffer */ +void jitter_buffer_reset(JitterBuffer *jitter) +{ + int i; + for (i=0;ipackets[i].data) + { + if (jitter->destroy) + jitter->destroy(jitter->packets[i].data); + else + speex_free(jitter->packets[i].data); + jitter->packets[i].data = NULL; + } + } + /* Timestamp is actually undefined at this point */ + jitter->pointer_timestamp = 0; + jitter->next_stop = 0; + jitter->reset_state = 1; + jitter->lost_count = 0; + jitter->buffered = 0; + jitter->auto_tradeoff = 32000; + + for (i=0;i_tb[i]); + jitter->timeBuffers[i] = &jitter->_tb[i]; + } + /*fprintf (stderr, "reset\n");*/ +} + +/** Destroy jitter buffer */ +void jitter_buffer_destroy(JitterBuffer *jitter) +{ + jitter_buffer_reset(jitter); + speex_free(jitter); +} + +/** Take the following timing into consideration for future calculations */ +static void update_timings(JitterBuffer *jitter, spx_int32_t timing) +{ + if (timing < -32767) + timing = -32767; + if (timing > 32767) + timing = 32767; + /* If the current sub-window is full, perform a rotation and discard oldest sub-widow */ + if (jitter->timeBuffers[0]->curr_count >= jitter->subwindow_size) + { + int i; + /*fprintf(stderr, "Rotate buffer\n");*/ + struct TimingBuffer *tmp = jitter->timeBuffers[MAX_BUFFERS-1]; + for (i=MAX_BUFFERS-1;i>=1;i--) + jitter->timeBuffers[i] = jitter->timeBuffers[i-1]; + jitter->timeBuffers[0] = tmp; + tb_init(jitter->timeBuffers[0]); + } + tb_add(jitter->timeBuffers[0], timing); +} + +/** Compensate all timings when we do an adjustment of the buffering */ +static void shift_timings(JitterBuffer *jitter, spx_int16_t amount) +{ + int i, j; + for (i=0;itimeBuffers[i]->filled;j++) + jitter->timeBuffers[i]->timing[j] += amount; + } +} + + +/** Put one packet into the jitter buffer */ +void jitter_buffer_put(JitterBuffer *jitter, const JitterBufferPacket *packet) +{ + int i,j; + int late; + /*fprintf (stderr, "put packet %d %d\n", timestamp, span);*/ + + /* Cleanup buffer (remove old packets that weren't played) */ + if (!jitter->reset_state) + { + for (i=0;ipackets[i].data && LE32(jitter->packets[i].timestamp + jitter->packets[i].span, jitter->pointer_timestamp)) + { + /*fprintf (stderr, "cleaned (not played)\n");*/ + if (jitter->destroy) + jitter->destroy(jitter->packets[i].data); + else + speex_free(jitter->packets[i].data); + jitter->packets[i].data = NULL; + } + } + } + + /*fprintf(stderr, "arrival: %d %d %d\n", packet->timestamp, jitter->next_stop, jitter->pointer_timestamp);*/ + /* Check if packet is late (could still be useful though) */ + if (!jitter->reset_state && LT32(packet->timestamp, jitter->next_stop)) + { + update_timings(jitter, ((spx_int32_t)packet->timestamp) - ((spx_int32_t)jitter->next_stop) - jitter->buffer_margin); + late = 1; + } else { + late = 0; + } + + /* Only insert the packet if it's not hopelessly late (i.e. totally useless) */ + if (jitter->reset_state || GE32(packet->timestamp+packet->span+jitter->delay_step, jitter->pointer_timestamp)) + { + + /*Find an empty slot in the buffer*/ + for (i=0;ipackets[i].data==NULL) + break; + } + + /*No place left in the buffer, need to make room for it by discarding the oldest packet */ + if (i==SPEEX_JITTER_MAX_BUFFER_SIZE) + { + int earliest=jitter->packets[0].timestamp; + i=0; + for (j=1;jpackets[i].data || LT32(jitter->packets[j].timestamp,earliest)) + { + earliest = jitter->packets[j].timestamp; + i=j; + } + } + if (jitter->destroy) + jitter->destroy(jitter->packets[i].data); + else + speex_free(jitter->packets[i].data); + jitter->packets[i].data=NULL; + if (jitter->lost_count>20) + { + jitter_buffer_reset(jitter); + } + /*fprintf (stderr, "Buffer is full, discarding earliest frame %d (currently at %d)\n", timestamp, jitter->pointer_timestamp);*/ + } + + /* Copy packet in buffer */ + if (jitter->destroy) + { + jitter->packets[i].data = packet->data; + } else { + jitter->packets[i].data=(char*)speex_alloc(packet->len); + for (j=0;jlen;j++) + jitter->packets[i].data[j]=packet->data[j]; + } + jitter->packets[i].timestamp=packet->timestamp; + jitter->packets[i].span=packet->span; + jitter->packets[i].len=packet->len; + jitter->packets[i].sequence=packet->sequence; + jitter->packets[i].user_data=packet->user_data; + if (jitter->reset_state || late) + jitter->arrival[i] = 0; + else + jitter->arrival[i] = jitter->next_stop; + } + + +} + +/** Get one packet from the jitter buffer */ +int jitter_buffer_get(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t desired_span, spx_int32_t *start_offset) +{ + int i; + unsigned int j; + int incomplete = 0; + spx_int16_t opt; + + if (start_offset != NULL) + *start_offset = 0; + + /* Syncing on the first call */ + if (jitter->reset_state) + { + int found = 0; + /* Find the oldest packet */ + spx_uint32_t oldest=0; + for (i=0;ipackets[i].data && (!found || LT32(jitter->packets[i].timestamp,oldest))) + { + oldest = jitter->packets[i].timestamp; + found = 1; + } + } + if (found) + { + jitter->reset_state=0; + jitter->pointer_timestamp = oldest; + jitter->next_stop = oldest; + } else { + packet->timestamp = 0; + packet->span = jitter->interp_requested; + return JITTER_BUFFER_MISSING; + } + } + + + jitter->last_returned_timestamp = jitter->pointer_timestamp; + + if (jitter->interp_requested != 0) + { + packet->timestamp = jitter->pointer_timestamp; + packet->span = jitter->interp_requested; + + /* Increment the pointer because it got decremented in the delay update */ + jitter->pointer_timestamp += jitter->interp_requested; + packet->len = 0; + /*fprintf (stderr, "Deferred interpolate\n");*/ + + jitter->interp_requested = 0; + + jitter->buffered = packet->span - desired_span; + + return JITTER_BUFFER_INSERTION; + } + + /* Searching for the packet that fits best */ + + /* Search the buffer for a packet with the right timestamp and spanning the whole current chunk */ + for (i=0;ipackets[i].data && jitter->packets[i].timestamp==jitter->pointer_timestamp && GE32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp+desired_span)) + break; + } + + /* If no match, try for an "older" packet that still spans (fully) the current chunk */ + if (i==SPEEX_JITTER_MAX_BUFFER_SIZE) + { + for (i=0;ipackets[i].data && LE32(jitter->packets[i].timestamp, jitter->pointer_timestamp) && GE32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp+desired_span)) + break; + } + } + + /* If still no match, try for an "older" packet that spans part of the current chunk */ + if (i==SPEEX_JITTER_MAX_BUFFER_SIZE) + { + for (i=0;ipackets[i].data && LE32(jitter->packets[i].timestamp, jitter->pointer_timestamp) && GT32(jitter->packets[i].timestamp+jitter->packets[i].span,jitter->pointer_timestamp)) + break; + } + } + + /* If still no match, try for earliest packet possible */ + if (i==SPEEX_JITTER_MAX_BUFFER_SIZE) + { + int found = 0; + spx_uint32_t best_time=0; + int best_span=0; + int besti=0; + for (i=0;ipackets[i].data && LT32(jitter->packets[i].timestamp,jitter->pointer_timestamp+desired_span) && GE32(jitter->packets[i].timestamp,jitter->pointer_timestamp)) + { + if (!found || LT32(jitter->packets[i].timestamp,best_time) || (jitter->packets[i].timestamp==best_time && GT32(jitter->packets[i].span,best_span))) + { + best_time = jitter->packets[i].timestamp; + best_span = jitter->packets[i].span; + besti = i; + found = 1; + } + } + } + if (found) + { + i=besti; + incomplete = 1; + /*fprintf (stderr, "incomplete: %d %d %d %d\n", jitter->packets[i].timestamp, jitter->pointer_timestamp, chunk_size, jitter->packets[i].span);*/ + } + } + + /* If we find something */ + if (i!=SPEEX_JITTER_MAX_BUFFER_SIZE) + { + spx_int32_t offset; + + /* We (obviously) haven't lost this packet */ + jitter->lost_count = 0; + + /* In this case, 0 isn't as a valid timestamp */ + if (jitter->arrival[i] != 0) + { + update_timings(jitter, ((spx_int32_t)jitter->packets[i].timestamp) - ((spx_int32_t)jitter->arrival[i]) - jitter->buffer_margin); + } + + + /* Copy packet */ + if (jitter->destroy) + { + packet->data = jitter->packets[i].data; + packet->len = jitter->packets[i].len; + } else { + if (jitter->packets[i].len > packet->len) + { + speex_warning_int("jitter_buffer_get(): packet too large to fit. Size is", jitter->packets[i].len); + } else { + packet->len = jitter->packets[i].len; + } + for (j=0;jlen;j++) + packet->data[j] = jitter->packets[i].data[j]; + /* Remove packet */ + speex_free(jitter->packets[i].data); + } + jitter->packets[i].data = NULL; + /* Set timestamp and span (if requested) */ + offset = (spx_int32_t)jitter->packets[i].timestamp-(spx_int32_t)jitter->pointer_timestamp; + if (start_offset != NULL) + *start_offset = offset; + else if (offset != 0) + speex_warning_int("jitter_buffer_get() discarding non-zero start_offset", offset); + + packet->timestamp = jitter->packets[i].timestamp; + jitter->last_returned_timestamp = packet->timestamp; + + packet->span = jitter->packets[i].span; + packet->sequence = jitter->packets[i].sequence; + packet->user_data = jitter->packets[i].user_data; + /* Point to the end of the current packet */ + jitter->pointer_timestamp = jitter->packets[i].timestamp+jitter->packets[i].span; + + jitter->buffered = packet->span - desired_span; + + if (start_offset != NULL) + jitter->buffered += *start_offset; + + return JITTER_BUFFER_OK; + } + + + /* If we haven't found anything worth returning */ + + /*fprintf (stderr, "not found\n");*/ + jitter->lost_count++; + /*fprintf (stderr, "m");*/ + /*fprintf (stderr, "lost_count = %d\n", jitter->lost_count);*/ + + opt = compute_opt_delay(jitter); + + /* Should we force an increase in the buffer or just do normal interpolation? */ + if (opt < 0) + { + /* Need to increase buffering */ + + /* Shift histogram to compensate */ + shift_timings(jitter, -opt); + + packet->timestamp = jitter->pointer_timestamp; + packet->span = -opt; + /* Don't move the pointer_timestamp forward */ + packet->len = 0; + + jitter->buffered = packet->span - desired_span; + return JITTER_BUFFER_INSERTION; + /*jitter->pointer_timestamp -= jitter->delay_step;*/ + /*fprintf (stderr, "Forced to interpolate\n");*/ + } else { + /* Normal packet loss */ + packet->timestamp = jitter->pointer_timestamp; + + desired_span = ROUND_DOWN(desired_span, jitter->concealment_size); + packet->span = desired_span; + jitter->pointer_timestamp += desired_span; + packet->len = 0; + + jitter->buffered = packet->span - desired_span; + return JITTER_BUFFER_MISSING; + /*fprintf (stderr, "Normal loss\n");*/ + } + + +} + +int jitter_buffer_get_another(JitterBuffer *jitter, JitterBufferPacket *packet) +{ + int i, j; + for (i=0;ipackets[i].data && jitter->packets[i].timestamp==jitter->last_returned_timestamp) + break; + } + if (i!=SPEEX_JITTER_MAX_BUFFER_SIZE) + { + /* Copy packet */ + packet->len = jitter->packets[i].len; + if (jitter->destroy) + { + packet->data = jitter->packets[i].data; + } else { + for (j=0;jlen;j++) + packet->data[j] = jitter->packets[i].data[j]; + /* Remove packet */ + speex_free(jitter->packets[i].data); + } + jitter->packets[i].data = NULL; + packet->timestamp = jitter->packets[i].timestamp; + packet->span = jitter->packets[i].span; + packet->sequence = jitter->packets[i].sequence; + packet->user_data = jitter->packets[i].user_data; + return JITTER_BUFFER_OK; + } else { + packet->data = NULL; + packet->len = 0; + packet->span = 0; + return JITTER_BUFFER_MISSING; + } +} + +/* Let the jitter buffer know it's the right time to adjust the buffering delay to the network conditions */ +static int _jitter_buffer_update_delay(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t *start_offset) +{ + spx_int16_t opt = compute_opt_delay(jitter); + /*fprintf(stderr, "opt adjustment is %d ", opt);*/ + + if (opt < 0) + { + shift_timings(jitter, -opt); + + jitter->pointer_timestamp += opt; + jitter->interp_requested = -opt; + /*fprintf (stderr, "Decision to interpolate %d samples\n", -opt);*/ + } else if (opt > 0) + { + shift_timings(jitter, -opt); + jitter->pointer_timestamp += opt; + /*fprintf (stderr, "Decision to drop %d samples\n", opt);*/ + } + + return opt; +} + +/* Let the jitter buffer know it's the right time to adjust the buffering delay to the network conditions */ +int jitter_buffer_update_delay(JitterBuffer *jitter, JitterBufferPacket *packet, spx_int32_t *start_offset) +{ + /* If the programmer calls jitter_buffer_update_delay() directly, + automatically disable auto-adjustment */ + jitter->auto_adjust = 0; + + return _jitter_buffer_update_delay(jitter, packet, start_offset); +} + +/** Get pointer timestamp of jitter buffer */ +int jitter_buffer_get_pointer_timestamp(JitterBuffer *jitter) +{ + return jitter->pointer_timestamp; +} + +void jitter_buffer_tick(JitterBuffer *jitter) +{ + /* Automatically-adjust the buffering delay if requested */ + if (jitter->auto_adjust) + _jitter_buffer_update_delay(jitter, NULL, NULL); + + if (jitter->buffered >= 0) + { + jitter->next_stop = jitter->pointer_timestamp - jitter->buffered; + } else { + jitter->next_stop = jitter->pointer_timestamp; + speex_warning_int("jitter buffer sees negative buffering, your code might be broken. Value is ", jitter->buffered); + } + jitter->buffered = 0; +} + +void jitter_buffer_remaining_span(JitterBuffer *jitter, spx_uint32_t rem) +{ + /* Automatically-adjust the buffering delay if requested */ + if (jitter->auto_adjust) + _jitter_buffer_update_delay(jitter, NULL, NULL); + + if (jitter->buffered < 0) + speex_warning_int("jitter buffer sees negative buffering, your code might be broken. Value is ", jitter->buffered); + jitter->next_stop = jitter->pointer_timestamp - rem; +} + + +/* Used like the ioctl function to control the jitter buffer parameters */ +int jitter_buffer_ctl(JitterBuffer *jitter, int request, void *ptr) +{ + int count, i; + switch(request) + { + case JITTER_BUFFER_SET_MARGIN: + jitter->buffer_margin = *(spx_int32_t*)ptr; + break; + case JITTER_BUFFER_GET_MARGIN: + *(spx_int32_t*)ptr = jitter->buffer_margin; + break; + case JITTER_BUFFER_GET_AVALIABLE_COUNT: + count = 0; + for (i=0;ipackets[i].data && LE32(jitter->pointer_timestamp, jitter->packets[i].timestamp)) + { + count++; + } + } + *(spx_int32_t*)ptr = count; + break; + case JITTER_BUFFER_SET_DESTROY_CALLBACK: + jitter->destroy = (void (*) (void *))ptr; + break; + case JITTER_BUFFER_GET_DESTROY_CALLBACK: + *(void (**) (void *))ptr = jitter->destroy; + break; + case JITTER_BUFFER_SET_DELAY_STEP: + jitter->delay_step = *(spx_int32_t*)ptr; + break; + case JITTER_BUFFER_GET_DELAY_STEP: + *(spx_int32_t*)ptr = jitter->delay_step; + break; + case JITTER_BUFFER_SET_CONCEALMENT_SIZE: + jitter->concealment_size = *(spx_int32_t*)ptr; + break; + case JITTER_BUFFER_GET_CONCEALMENT_SIZE: + *(spx_int32_t*)ptr = jitter->concealment_size; + break; + case JITTER_BUFFER_SET_MAX_LATE_RATE: + jitter->max_late_rate = *(spx_int32_t*)ptr; + jitter->window_size = 100*TOP_DELAY/jitter->max_late_rate; + jitter->subwindow_size = jitter->window_size/MAX_BUFFERS; + break; + case JITTER_BUFFER_GET_MAX_LATE_RATE: + *(spx_int32_t*)ptr = jitter->max_late_rate; + break; + case JITTER_BUFFER_SET_LATE_COST: + jitter->latency_tradeoff = *(spx_int32_t*)ptr; + break; + case JITTER_BUFFER_GET_LATE_COST: + *(spx_int32_t*)ptr = jitter->latency_tradeoff; + break; + default: + speex_warning_int("Unknown jitter_buffer_ctl request: ", request); + return -1; + } + return 0; +} + diff --git a/src/libspeex/kiss_fft.c b/src/libspeex/kiss_fft.c new file mode 100644 index 0000000..6778281 --- /dev/null +++ b/src/libspeex/kiss_fft.c @@ -0,0 +1,523 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding +Copyright (c) 2005-2007, Jean-Marc Valin + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "_kiss_fft_guts.h" +#include "arch.h" +#include "os_support.h" + +/* The guts header contains all the multiplication and addition macros that are defined for + fixed or floating point complex numbers. It also delares the kf_ internal functions. + */ + +static void kf_bfly2( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m, + int N, + int mm + ) +{ + kiss_fft_cpx * Fout2; + kiss_fft_cpx * tw1; + kiss_fft_cpx t; + if (!st->inverse) { + int i,j; + kiss_fft_cpx * Fout_beg = Fout; + for (i=0;itwiddles; + for(j=0;jr , tw1->r),MULT16_16(Fout2->i , tw1->i)), 1); + ti = SHR32(ADD32(MULT16_16(Fout2->i , tw1->r),MULT16_16(Fout2->r , tw1->i)), 1); + tw1 += fstride; + Fout2->r = PSHR32(SUB32(SHL32(EXTEND32(Fout->r), 14), tr), 15); + Fout2->i = PSHR32(SUB32(SHL32(EXTEND32(Fout->i), 14), ti), 15); + Fout->r = PSHR32(ADD32(SHL32(EXTEND32(Fout->r), 14), tr), 15); + Fout->i = PSHR32(ADD32(SHL32(EXTEND32(Fout->i), 14), ti), 15); + ++Fout2; + ++Fout; + } + } + } else { + int i,j; + kiss_fft_cpx * Fout_beg = Fout; + for (i=0;itwiddles; + for(j=0;jinverse) + { + kiss_fft_cpx * Fout_beg = Fout; + for (i=0;itwiddles; + for (j=0;jtwiddles; + for (j=0;jr = PSHR16(Fout->r, 2); + Fout->i = PSHR16(Fout->i, 2); + C_SUB( scratch[5] , *Fout, scratch[1] ); + C_ADDTO(*Fout, scratch[1]); + C_ADD( scratch[3] , scratch[0] , scratch[2] ); + C_SUB( scratch[4] , scratch[0] , scratch[2] ); + Fout[m2].r = PSHR16(Fout[m2].r, 2); + Fout[m2].i = PSHR16(Fout[m2].i, 2); + C_SUB( Fout[m2], *Fout, scratch[3] ); + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + C_ADDTO( *Fout , scratch[3] ); + + Fout[m].r = scratch[5].r + scratch[4].i; + Fout[m].i = scratch[5].i - scratch[4].r; + Fout[m3].r = scratch[5].r - scratch[4].i; + Fout[m3].i = scratch[5].i + scratch[4].r; + ++Fout; + } + } + } +} + +static void kf_bfly3( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + size_t m + ) +{ + size_t k=m; + const size_t m2 = 2*m; + kiss_fft_cpx *tw1,*tw2; + kiss_fft_cpx scratch[5]; + kiss_fft_cpx epi3; + epi3 = st->twiddles[fstride*m]; + + tw1=tw2=st->twiddles; + + do{ + if (!st->inverse) { + C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); + } + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = Fout->r - HALF_OF(scratch[3].r); + Fout[m].i = Fout->i - HALF_OF(scratch[3].i); + + C_MULBYSCALAR( scratch[0] , epi3.i ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = Fout[m].r + scratch[0].i; + Fout[m2].i = Fout[m].i - scratch[0].r; + + Fout[m].r -= scratch[0].i; + Fout[m].i += scratch[0].r; + + ++Fout; + }while(--k); +} + +static void kf_bfly5( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + int u; + kiss_fft_cpx scratch[13]; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx *tw; + kiss_fft_cpx ya,yb; + ya = twiddles[fstride*m]; + yb = twiddles[fstride*2*m]; + + Fout0=Fout; + Fout1=Fout0+m; + Fout2=Fout0+2*m; + Fout3=Fout0+3*m; + Fout4=Fout0+4*m; + + tw=st->twiddles; + for ( u=0; uinverse) { + C_FIXDIV( *Fout0,5); C_FIXDIV( *Fout1,5); C_FIXDIV( *Fout2,5); C_FIXDIV( *Fout3,5); C_FIXDIV( *Fout4,5); + } + scratch[0] = *Fout0; + + C_MUL(scratch[1] ,*Fout1, tw[u*fstride]); + C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]); + C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]); + C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]); + + C_ADD( scratch[7],scratch[1],scratch[4]); + C_SUB( scratch[10],scratch[1],scratch[4]); + C_ADD( scratch[8],scratch[2],scratch[3]); + C_SUB( scratch[9],scratch[2],scratch[3]); + + Fout0->r += scratch[7].r + scratch[8].r; + Fout0->i += scratch[7].i + scratch[8].i; + + scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); + scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); + + scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); + scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); + scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); + scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); + scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } +} + +/* perform the butterfly for one stage of a mixed radix FFT */ +static void kf_bfly_generic( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m, + int p + ) +{ + int u,k,q1,q; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx t; + kiss_fft_cpx scratchbuf[17]; + int Norig = st->nfft; + + /*CHECKBUF(scratchbuf,nscratchbuf,p);*/ + if (p>17) + speex_fatal("KissFFT: max radix supported is 17"); + + for ( u=0; uinverse) { + C_FIXDIV(scratchbuf[q1],p); + } + k += m; + } + + k=u; + for ( q1=0 ; q1

=Norig) twidx-=Norig; + C_MUL(t,scratchbuf[q] , twiddles[twidx] ); + C_ADDTO( Fout[ k ] ,t); + } + k += m; + } + } +} + +static +void kf_shuffle( + kiss_fft_cpx * Fout, + const kiss_fft_cpx * f, + const size_t fstride, + int in_stride, + int * factors, + const kiss_fft_cfg st + ) +{ + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + + /*printf ("fft %d %d %d %d %d %d\n", p*m, m, p, s2, fstride*in_stride, N);*/ + if (m==1) + { + int j; + for (j=0;j32000 || (spx_int32_t)p*(spx_int32_t)p > n) + p = n; /* no more factors, skip to end */ + } + n /= p; + *facbuf++ = p; + *facbuf++ = n; + } while (n > 1); +} +/* + * + * User-callable function to allocate all necessary storage space for the fft. + * + * The return value is a contiguous block of memory, allocated with malloc. As such, + * It can be freed with free(), rather than a kiss_fft-specific function. + * */ +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem ) +{ + kiss_fft_cfg st=NULL; + size_t memneeded = sizeof(struct kiss_fft_state) + + sizeof(kiss_fft_cpx)*(nfft-1); /* twiddle factors*/ + + if ( lenmem==NULL ) { + st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded ); + }else{ + if (mem != NULL && *lenmem >= memneeded) + st = (kiss_fft_cfg)mem; + *lenmem = memneeded; + } + if (st) { + int i; + st->nfft=nfft; + st->inverse = inverse_fft; +#ifdef FIXED_POINT + for (i=0;iinverse) + phase = -phase; + kf_cexp2(st->twiddles+i, DIV32(SHL32(phase,17),nfft)); + } +#else + for (i=0;iinverse) + phase *= -1; + kf_cexp(st->twiddles+i, phase ); + } +#endif + kf_factor(nfft,st->factors); + } + return st; +} + + + + +void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int in_stride) +{ + if (fin == fout) + { + speex_fatal("In-place FFT not supported"); + /*CHECKBUF(tmpbuf,ntmpbuf,st->nfft); + kf_work(tmpbuf,fin,1,in_stride, st->factors,st); + SPEEX_MOVE(fout,tmpbuf,st->nfft);*/ + } else { + kf_shuffle( fout, fin, 1,in_stride, st->factors,st); + kf_work( fout, fin, 1,in_stride, st->factors,st, 1, in_stride, 1); + } +} + +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + kiss_fft_stride(cfg,fin,fout,1); +} + diff --git a/src/libspeex/kiss_fft.h b/src/libspeex/kiss_fft.h new file mode 100644 index 0000000..fa3f2c6 --- /dev/null +++ b/src/libspeex/kiss_fft.h @@ -0,0 +1,108 @@ +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include +#include +#include "arch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ATTENTION! + If you would like a : + -- a utility that will handle the caching of fft objects + -- real-only (no imaginary time component ) FFT + -- a multi-dimensional FFT + -- a command-line utility to perform ffts + -- a command-line utility to perform fast-convolution filtering + + Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c + in the tools/ directory. +*/ + +#ifdef USE_SIMD +# include +# define kiss_fft_scalar __m128 +#define KISS_FFT_MALLOC(nbytes) memalign(16,nbytes) +#else +#define KISS_FFT_MALLOC speex_alloc +#endif + + +#ifdef FIXED_POINT +#include "arch.h" +# define kiss_fft_scalar spx_int16_t +#else +# ifndef kiss_fft_scalar +/* default is float */ +# define kiss_fft_scalar float +# endif +#endif + +typedef struct { + kiss_fft_scalar r; + kiss_fft_scalar i; +}kiss_fft_cpx; + +typedef struct kiss_fft_state* kiss_fft_cfg; + +/* + * kiss_fft_alloc + * + * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. + * + * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); + * + * The return value from fft_alloc is a cfg buffer used internally + * by the fft routine or NULL. + * + * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. + * The returned value should be free()d when done to avoid memory leaks. + * + * The state can be placed in a user supplied buffer 'mem': + * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, + * then the function places the cfg in mem and the size used in *lenmem + * and returns mem. + * + * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), + * then the function returns NULL and places the minimum cfg + * buffer size in *lenmem. + * */ + +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); + +/* + * kiss_fft(cfg,in_out_buf) + * + * Perform an FFT on a complex input buffer. + * for a forward FFT, + * fin should be f[0] , f[1] , ... ,f[nfft-1] + * fout will be F[0] , F[1] , ... ,F[nfft-1] + * Note that each element is complex and can be accessed like + f[k].r and f[k].i + * */ +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +/* + A more generic version of the above function. It reads its input from every Nth sample. + * */ +void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); + +/* If kiss_fft_alloc allocated a buffer, it is one contiguous + buffer and can be simply free()d when no longer needed*/ +#define kiss_fft_free speex_free + +/* + Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up + your compiler output to call this before you exit. +*/ +void kiss_fft_cleanup(void); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libspeex/kiss_fftr.c b/src/libspeex/kiss_fftr.c new file mode 100644 index 0000000..f6275b8 --- /dev/null +++ b/src/libspeex/kiss_fftr.c @@ -0,0 +1,297 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "os_support.h" +#include "kiss_fftr.h" +#include "_kiss_fft_guts.h" + +struct kiss_fftr_state{ + kiss_fft_cfg substate; + kiss_fft_cpx * tmpbuf; + kiss_fft_cpx * super_twiddles; +#ifdef USE_SIMD + long pad; +#endif +}; + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) +{ + int i; + kiss_fftr_cfg st = NULL; + size_t subsize, memneeded; + + if (nfft & 1) { + speex_warning("Real FFT optimization must be even.\n"); + return NULL; + } + nfft >>= 1; + + kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); + memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 2); + + if (lenmem == NULL) { + st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded); + } else { + if (*lenmem >= memneeded) + st = (kiss_fftr_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */ + st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize); + st->super_twiddles = st->tmpbuf + nfft; + kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); + +#ifdef FIXED_POINT + for (i=0;i>1); + if (!inverse_fft) + phase = -phase; + kf_cexp2(st->super_twiddles+i, DIV32(SHL32(phase,16),nfft)); + } +#else + for (i=0;isuper_twiddles+i, phase ); + } +#endif + return st; +} + +void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; + + if ( st->substate->inverse) { + speex_fatal("kiss fft usage error: improper alloc\n"); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0].r = tdc.r + tdc.i; + freqdata[ncfft].r = tdc.r - tdc.i; +#ifdef USE_SIMD + freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); +#else + freqdata[ncfft].i = freqdata[0].i = 0; +#endif + + for ( k=1;k <= ncfft/2 ; ++k ) { + fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + C_MUL( tw , f2k , st->super_twiddles[k]); + + freqdata[k].r = HALF_OF(f1k.r + tw.r); + freqdata[k].i = HALF_OF(f1k.i + tw.i); + freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r); + freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i); + } +} + +void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata, kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + speex_fatal("kiss fft usage error: improper alloc\n"); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; + st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; + /*C_FIXDIV(st->tmpbuf[0],2);*/ + + for (k = 1; k <= ncfft / 2; ++k) { + kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk = freqdata[k]; + fnkc.r = freqdata[ncfft - k].r; + fnkc.i = -freqdata[ncfft - k].i; + /*C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 );*/ + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); +#ifdef USE_SIMD + st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); +#else + st->tmpbuf[ncfft - k].i *= -1; +#endif + } + kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); +} + +void kiss_fftr2(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_scalar *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + kiss_fft_cpx f2k,tdc; + spx_word32_t f1kr, f1ki, twr, twi; + + if ( st->substate->inverse) { + speex_fatal("kiss fft usage error: improper alloc\n"); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0] = tdc.r + tdc.i; + freqdata[2*ncfft-1] = tdc.r - tdc.i; + + for ( k=1;k <= ncfft/2 ; ++k ) + { + /*fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + + C_MUL( tw , f2k , st->super_twiddles[k]); + + freqdata[2*k-1] = HALF_OF(f1k.r + tw.r); + freqdata[2*k] = HALF_OF(f1k.i + tw.i); + freqdata[2*(ncfft-k)-1] = HALF_OF(f1k.r - tw.r); + freqdata[2*(ncfft-k)] = HALF_OF(tw.i - f1k.i); + */ + + /*f1k.r = PSHR32(ADD32(EXTEND32(st->tmpbuf[k].r), EXTEND32(st->tmpbuf[ncfft-k].r)),1); + f1k.i = PSHR32(SUB32(EXTEND32(st->tmpbuf[k].i), EXTEND32(st->tmpbuf[ncfft-k].i)),1); + f2k.r = PSHR32(SUB32(EXTEND32(st->tmpbuf[k].r), EXTEND32(st->tmpbuf[ncfft-k].r)),1); + f2k.i = SHR32(ADD32(EXTEND32(st->tmpbuf[k].i), EXTEND32(st->tmpbuf[ncfft-k].i)),1); + + C_MUL( tw , f2k , st->super_twiddles[k]); + + freqdata[2*k-1] = HALF_OF(f1k.r + tw.r); + freqdata[2*k] = HALF_OF(f1k.i + tw.i); + freqdata[2*(ncfft-k)-1] = HALF_OF(f1k.r - tw.r); + freqdata[2*(ncfft-k)] = HALF_OF(tw.i - f1k.i); + */ + f2k.r = SHR32(SUB32(EXTEND32(st->tmpbuf[k].r), EXTEND32(st->tmpbuf[ncfft-k].r)),1); + f2k.i = PSHR32(ADD32(EXTEND32(st->tmpbuf[k].i), EXTEND32(st->tmpbuf[ncfft-k].i)),1); + + f1kr = SHL32(ADD32(EXTEND32(st->tmpbuf[k].r), EXTEND32(st->tmpbuf[ncfft-k].r)),13); + f1ki = SHL32(SUB32(EXTEND32(st->tmpbuf[k].i), EXTEND32(st->tmpbuf[ncfft-k].i)),13); + + twr = SHR32(SUB32(MULT16_16(f2k.r,st->super_twiddles[k].r),MULT16_16(f2k.i,st->super_twiddles[k].i)), 1); + twi = SHR32(ADD32(MULT16_16(f2k.i,st->super_twiddles[k].r),MULT16_16(f2k.r,st->super_twiddles[k].i)), 1); + +#ifdef FIXED_POINT + freqdata[2*k-1] = PSHR32(f1kr + twr, 15); + freqdata[2*k] = PSHR32(f1ki + twi, 15); + freqdata[2*(ncfft-k)-1] = PSHR32(f1kr - twr, 15); + freqdata[2*(ncfft-k)] = PSHR32(twi - f1ki, 15); +#else + freqdata[2*k-1] = .5f*(f1kr + twr); + freqdata[2*k] = .5f*(f1ki + twi); + freqdata[2*(ncfft-k)-1] = .5f*(f1kr - twr); + freqdata[2*(ncfft-k)] = .5f*(twi - f1ki); + +#endif + } +} + +void kiss_fftri2(kiss_fftr_cfg st,const kiss_fft_scalar *freqdata,kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + speex_fatal ("kiss fft usage error: improper alloc\n"); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0] + freqdata[2*ncfft-1]; + st->tmpbuf[0].i = freqdata[0] - freqdata[2*ncfft-1]; + /*C_FIXDIV(st->tmpbuf[0],2);*/ + + for (k = 1; k <= ncfft / 2; ++k) { + kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk.r = freqdata[2*k-1]; + fk.i = freqdata[2*k]; + fnkc.r = freqdata[2*(ncfft - k)-1]; + fnkc.i = -freqdata[2*(ncfft - k)]; + /*C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 );*/ + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); +#ifdef USE_SIMD + st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); +#else + st->tmpbuf[ncfft - k].i *= -1; +#endif + } + kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); +} diff --git a/src/libspeex/kiss_fftr.h b/src/libspeex/kiss_fftr.h new file mode 100644 index 0000000..7bfb423 --- /dev/null +++ b/src/libspeex/kiss_fftr.h @@ -0,0 +1,51 @@ +#ifndef KISS_FTR_H +#define KISS_FTR_H + +#include "kiss_fft.h" +#ifdef __cplusplus +extern "C" { +#endif + + +/* + + Real optimized version can save about 45% cpu time vs. complex fft of a real seq. + + + + */ + +typedef struct kiss_fftr_state *kiss_fftr_cfg; + + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); +/* + nfft must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); +/* + input timedata has nfft scalar points + output freqdata has nfft/2+1 complex points +*/ + +void kiss_fftr2(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_scalar *freqdata); + +void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); + +void kiss_fftri2(kiss_fftr_cfg st,const kiss_fft_scalar *freqdata, kiss_fft_scalar *timedata); + +/* + input freqdata has nfft/2+1 complex points + output timedata has nfft scalar points +*/ + +#define kiss_fftr_free speex_free + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/libspeex/lpc.c b/src/libspeex/lpc.c new file mode 100644 index 0000000..fd5d382 --- /dev/null +++ b/src/libspeex/lpc.c @@ -0,0 +1,201 @@ +/* + Copyright 1992, 1993, 1994 by Jutta Degener and Carsten Bormann, + Technische Universitaet Berlin + + Any use of this software is permitted provided that this notice is not + removed and that neither the authors nor the Technische Universitaet Berlin + are deemed to have made any representations as to the suitability of this + software for any purpose nor are held responsible for any defects of + this software. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. + + As a matter of courtesy, the authors request to be informed about uses + this software has found, about bugs in this software, and about any + improvements that may be of general interest. + + Berlin, 28.11.1994 + Jutta Degener + Carsten Bormann + + + Code modified by Jean-Marc Valin + + Speex License: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "lpc.h" + +#ifdef BFIN_ASM +#include "lpc_bfin.h" +#endif + +/* LPC analysis + * + * The next two functions calculate linear prediction coefficients + * and/or the related reflection coefficients from the first P_MAX+1 + * values of the autocorrelation function. + */ + +/* Invented by N. Levinson in 1947, modified by J. Durbin in 1959. + */ + +/* returns minimum mean square error */ +spx_word32_t _spx_lpc( +spx_coef_t *lpc, /* out: [0...p-1] LPC coefficients */ +const spx_word16_t *ac, /* in: [0...p] autocorrelation values */ +int p +) +{ + int i, j; + spx_word16_t r; + spx_word16_t error = ac[0]; + + if (ac[0] == 0) + { + for (i = 0; i < p; i++) + lpc[i] = 0; + return 0; + } + + for (i = 0; i < p; i++) { + + /* Sum up this iteration's reflection coefficient */ + spx_word32_t rr = NEG32(SHL32(EXTEND32(ac[i + 1]),13)); + for (j = 0; j < i; j++) + rr = SUB32(rr,MULT16_16(lpc[j],ac[i - j])); +#ifdef FIXED_POINT + r = DIV32_16(rr+PSHR32(error,1),ADD16(error,8)); +#else + r = rr/(error+.003*ac[0]); +#endif + /* Update LPC coefficients and total error */ + lpc[i] = r; + for (j = 0; j < i>>1; j++) + { + spx_word16_t tmp = lpc[j]; + lpc[j] = MAC16_16_P13(lpc[j],r,lpc[i-1-j]); + lpc[i-1-j] = MAC16_16_P13(lpc[i-1-j],r,tmp); + } + if (i & 1) + lpc[j] = MAC16_16_P13(lpc[j],lpc[j],r); + + error = SUB16(error,MULT16_16_Q13(r,MULT16_16_Q13(error,r))); + } + return error; +} + + +#ifdef FIXED_POINT + +/* Compute the autocorrelation + * ,--, + * ac(i) = > x(n) * x(n-i) for all n + * `--' + * for lags between 0 and lag-1, and x == 0 outside 0...n-1 + */ + +#ifndef OVERRIDE_SPEEX_AUTOCORR +void _spx_autocorr( +const spx_word16_t *x, /* in: [0...n-1] samples x */ +spx_word16_t *ac, /* out: [0...lag-1] ac values */ +int lag, +int n +) +{ + spx_word32_t d; + int i, j; + spx_word32_t ac0=1; + int shift, ac_shift; + + for (j=0;j x(n) * x(n-i) for all n + * `--' + * for lags between 0 and lag-1, and x == 0 outside 0...n-1 + */ +void _spx_autocorr( +const spx_word16_t *x, /* in: [0...n-1] samples x */ +float *ac, /* out: [0...lag-1] ac values */ +int lag, +int n +) +{ + float d; + int i; + while (lag--) + { + for (i = lag, d = 0; i < n; i++) + d += x[i] * x[i-lag]; + ac[lag] = d; + } + ac[0] += 10; +} + +#endif + + diff --git a/src/libspeex/lpc.h b/src/libspeex/lpc.h new file mode 100644 index 0000000..952ecdd --- /dev/null +++ b/src/libspeex/lpc.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file lpc.h + @brief Functions for LPC (Linear Prediction Coefficients) analysis +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef LPC_H +#define LPC_H + +#include "arch.h" + +void _spx_autocorr( + const spx_word16_t * x, /* in: [0...n-1] samples x */ + spx_word16_t *ac, /* out: [0...lag-1] ac values */ + int lag, int n); + +spx_word32_t /* returns minimum mean square error */ +_spx_lpc( + spx_coef_t * lpc, /* [0...p-1] LPC coefficients */ + const spx_word16_t * ac, /* in: [0...p] autocorrelation values */ + int p + ); + + +#endif diff --git a/src/libspeex/lpc_bfin.h b/src/libspeex/lpc_bfin.h new file mode 100644 index 0000000..7310ffb --- /dev/null +++ b/src/libspeex/lpc_bfin.h @@ -0,0 +1,131 @@ +/* Copyright (C) 2005 Analog Devices */ +/** + @file lpc_bfin.h + @author Jean-Marc Valin + @brief Functions for LPC (Linear Prediction Coefficients) analysis (Blackfin version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_SPEEX_AUTOCORR +void _spx_autocorr( +const spx_word16_t *x, /* in: [0...n-1] samples x */ +spx_word16_t *ac, /* out: [0...lag-1] ac values */ +int lag, +int n + ) +{ + spx_word32_t d; + const spx_word16_t *xs; + int i, j; + spx_word32_t ac0=1; + spx_word32_t ac32[11], *ac32top; + int shift, ac_shift; + ac32top = ac32+lag-1; + int lag_1, N_lag; + int nshift; + lag_1 = lag-1; + N_lag = n-lag_1; + for (j=0;j> 1;\n\t" + "LOOP_BEGIN pitch%=;\n\t" + "I1 = P0;\n\t" + "A1 = A0 = 0;\n\t" + "R1 = [I1++];\n\t" + "LOOP inner_prod%= LC1 = P3 >> 1;\n\t" + "LOOP_BEGIN inner_prod%=;\n\t" + "A1 += R0.L*R1.H, A0 += R0.L*R1.L (IS) || R1.L = W[I1++];\n\t" + "A1 += R0.H*R1.L, A0 += R0.H*R1.H (IS) || R1.H = W[I1++] || R0 = [I0++];\n\t" + "LOOP_END inner_prod%=;\n\t" + "A0 = ASHIFT A0 by R4.L;\n\t" + "A1 = ASHIFT A1 by R4.L;\n\t" + + "R2 = A0, R3 = A1;\n\t" + "[P1--] = R2;\n\t" + "[P1--] = R3;\n\t" + "P0 += 4;\n\t" + "LOOP_END pitch%=;\n\t" + : : "m" (xs), "m" (x), "m" (ac32top), "m" (N_lag), "m" (lag_1), "m" (nshift) + : "A0", "A1", "P0", "P1", "P2", "P3", "P4", "R0", "R1", "R2", "R3", "R4", "I0", "I1", "L0", "L1", "B0", "B1", "memory" + ); + d=0; + for (j=0;j +#include "lsp.h" +#include "stack_alloc.h" +#include "math_approx.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#ifdef FIXED_POINT + +#define FREQ_SCALE 16384 + +/*#define ANGLE2X(a) (32768*cos(((a)/8192.)))*/ +#define ANGLE2X(a) (SHL16(spx_cos(a),2)) + +/*#define X2ANGLE(x) (acos(.00006103515625*(x))*LSP_SCALING)*/ +#define X2ANGLE(x) (spx_acos(x)) + +#ifdef BFIN_ASM +#include "lsp_bfin.h" +#endif + +#else + +/*#define C1 0.99940307 +#define C2 -0.49558072 +#define C3 0.03679168*/ + +#define FREQ_SCALE 1. +#define ANGLE2X(a) (spx_cos(a)) +#define X2ANGLE(x) (acos(x)) + +#endif + + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: cheb_poly_eva() + + AUTHOR......: David Rowe + DATE CREATED: 24/2/93 + + This function evaluates a series of Chebyshev polynomials + +\*---------------------------------------------------------------------------*/ + +#ifdef FIXED_POINT + +#ifndef OVERRIDE_CHEB_POLY_EVA +static inline spx_word32_t cheb_poly_eva( + spx_word16_t *coef, /* P or Q coefs in Q13 format */ + spx_word16_t x, /* cos of freq (-1.0 to 1.0) in Q14 format */ + int m, /* LPC order/2 */ + char *stack +) +{ + int i; + spx_word16_t b0, b1; + spx_word32_t sum; + + /*Prevents overflows*/ + if (x>16383) + x = 16383; + if (x<-16383) + x = -16383; + + /* Initialise values */ + b1=16384; + b0=x; + + /* Evaluate Chebyshev series formulation usin g iterative approach */ + sum = ADD32(EXTEND32(coef[m]), EXTEND32(MULT16_16_P14(coef[m-1],x))); + for(i=2;i<=m;i++) + { + spx_word16_t tmp=b0; + b0 = SUB16(MULT16_16_Q13(x,b0), b1); + b1 = tmp; + sum = ADD32(sum, EXTEND32(MULT16_16_P14(coef[m-i],b0))); + } + + return sum; +} +#endif + +#else + +static float cheb_poly_eva(spx_word32_t *coef, spx_word16_t x, int m, char *stack) +{ + int k; + float b0, b1, tmp; + + /* Initial conditions */ + b0=0; /* b_(m+1) */ + b1=0; /* b_(m+2) */ + + x*=2; + + /* Calculate the b_(k) */ + for(k=m;k>0;k--) + { + tmp=b0; /* tmp holds the previous value of b0 */ + b0=x*b0-b1+coef[m-k]; /* b0 holds its new value based on b0 and b1 */ + b1=tmp; /* b1 holds the previous value of b0 */ + } + + return(-b1+.5*x*b0+coef[m]); +} +#endif + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: lpc_to_lsp() + + AUTHOR......: David Rowe + DATE CREATED: 24/2/93 + + This function converts LPC coefficients to LSP + coefficients. + +\*---------------------------------------------------------------------------*/ + +#ifdef FIXED_POINT +#define SIGN_CHANGE(a,b) (((a)&0x70000000)^((b)&0x70000000)||(b==0)) +#else +#define SIGN_CHANGE(a,b) (((a)*(b))<0.0) +#endif + + +int lpc_to_lsp (spx_coef_t *a,int lpcrdr,spx_lsp_t *freq,int nb,spx_word16_t delta, char *stack) +/* float *a lpc coefficients */ +/* int lpcrdr order of LPC coefficients (10) */ +/* float *freq LSP frequencies in the x domain */ +/* int nb number of sub-intervals (4) */ +/* float delta grid spacing interval (0.02) */ + + +{ + spx_word16_t temp_xr,xl,xr,xm=0; + spx_word32_t psuml,psumr,psumm,temp_psumr/*,temp_qsumr*/; + int i,j,m,flag,k; + VARDECL(spx_word32_t *Q); /* ptrs for memory allocation */ + VARDECL(spx_word32_t *P); + VARDECL(spx_word16_t *Q16); /* ptrs for memory allocation */ + VARDECL(spx_word16_t *P16); + spx_word32_t *px; /* ptrs of respective P'(z) & Q'(z) */ + spx_word32_t *qx; + spx_word32_t *p; + spx_word32_t *q; + spx_word16_t *pt; /* ptr used for cheb_poly_eval() + whether P' or Q' */ + int roots=0; /* DR 8/2/94: number of roots found */ + flag = 1; /* program is searching for a root when, + 1 else has found one */ + m = lpcrdr/2; /* order of P'(z) & Q'(z) polynomials */ + + /* Allocate memory space for polynomials */ + ALLOC(Q, (m+1), spx_word32_t); + ALLOC(P, (m+1), spx_word32_t); + + /* determine P'(z)'s and Q'(z)'s coefficients where + P'(z) = P(z)/(1 + z^(-1)) and Q'(z) = Q(z)/(1-z^(-1)) */ + + px = P; /* initialise ptrs */ + qx = Q; + p = px; + q = qx; + +#ifdef FIXED_POINT + *px++ = LPC_SCALING; + *qx++ = LPC_SCALING; + for(i=0;i=32768) + speex_warning_int("px", *px); + if (fabs(*qx)>=32768) + speex_warning_int("qx", *qx);*/ + *px = PSHR32(*px,2); + *qx = PSHR32(*qx,2); + px++; + qx++; + } + /* The reason for this lies in the way cheb_poly_eva() is implemented for fixed-point */ + P[m] = PSHR32(P[m],3); + Q[m] = PSHR32(Q[m],3); +#else + *px++ = LPC_SCALING; + *qx++ = LPC_SCALING; + for(i=0;i= -FREQ_SCALE)){ + spx_word16_t dd; + /* Modified by JMV to provide smaller steps around x=+-1 */ +#ifdef FIXED_POINT + dd = MULT16_16_Q15(delta,SUB16(FREQ_SCALE, MULT16_16_Q14(MULT16_16_Q14(xl,xl),14000))); + if (psuml<512 && psuml>-512) + dd = PSHR16(dd,1); +#else + dd=delta*(1-.9*xl*xl); + if (fabs(psuml)<.2) + dd *= .5; +#endif + xr = SUB16(xl, dd); /* interval spacing */ + psumr = cheb_poly_eva(pt,xr,m,stack);/* poly(xl-delta_x) */ + temp_psumr = psumr; + temp_xr = xr; + + /* if no sign change increment xr and re-evaluate poly(xr). Repeat til + sign change. + if a sign change has occurred the interval is bisected and then + checked again for a sign change which determines in which + interval the zero lies in. + If there is no sign change between poly(xm) and poly(xl) set interval + between xm and xr else set interval between xl and xr and repeat till + root is located within the specified limits */ + + if(SIGN_CHANGE(psumr,psuml)) + { + roots++; + + psumm=psuml; + for(k=0;k<=nb;k++){ +#ifdef FIXED_POINT + xm = ADD16(PSHR16(xl,1),PSHR16(xr,1)); /* bisect the interval */ +#else + xm = .5*(xl+xr); /* bisect the interval */ +#endif + psumm=cheb_poly_eva(pt,xm,m,stack); + /*if(psumm*psuml>0.)*/ + if(!SIGN_CHANGE(psumm,psuml)) + { + psuml=psumm; + xl=xm; + } else { + psumr=psumm; + xr=xm; + } + } + + /* once zero is found, reset initial interval to xr */ + freq[j] = X2ANGLE(xm); + xl = xm; + flag = 0; /* reset flag for next search */ + } + else{ + psuml=temp_psumr; + xl=temp_xr; + } + } + } + return(roots); +} + +/*---------------------------------------------------------------------------*\ + + FUNCTION....: lsp_to_lpc() + + AUTHOR......: David Rowe + DATE CREATED: 24/2/93 + + Converts LSP coefficients to LPC coefficients. + +\*---------------------------------------------------------------------------*/ + +#ifdef FIXED_POINT + +void lsp_to_lpc(spx_lsp_t *freq,spx_coef_t *ak,int lpcrdr, char *stack) +/* float *freq array of LSP frequencies in the x domain */ +/* float *ak array of LPC coefficients */ +/* int lpcrdr order of LPC coefficients */ +{ + int i,j; + spx_word32_t xout1,xout2,xin; + spx_word32_t mult, a; + VARDECL(spx_word16_t *freqn); + VARDECL(spx_word32_t **xp); + VARDECL(spx_word32_t *xpmem); + VARDECL(spx_word32_t **xq); + VARDECL(spx_word32_t *xqmem); + int m = lpcrdr>>1; + + /* + + Reconstruct P(z) and Q(z) by cascading second order polynomials + in form 1 - 2cos(w)z(-1) + z(-2), where w is the LSP frequency. + In the time domain this is: + + y(n) = x(n) - 2cos(w)x(n-1) + x(n-2) + + This is what the ALLOCS below are trying to do: + + int xp[m+1][lpcrdr+1+2]; // P matrix in QIMP + int xq[m+1][lpcrdr+1+2]; // Q matrix in QIMP + + These matrices store the output of each stage on each row. The + final (m-th) row has the output of the final (m-th) cascaded + 2nd order filter. The first row is the impulse input to the + system (not written as it is known). + + The version below takes advantage of the fact that a lot of the + outputs are zero or known, for example if we put an inpulse + into the first section the "clock" it 10 times only the first 3 + outputs samples are non-zero (it's an FIR filter). + */ + + ALLOC(xp, (m+1), spx_word32_t*); + ALLOC(xpmem, (m+1)*(lpcrdr+1+2), spx_word32_t); + + ALLOC(xq, (m+1), spx_word32_t*); + ALLOC(xqmem, (m+1)*(lpcrdr+1+2), spx_word32_t); + + for(i=0; i<=m; i++) { + xp[i] = xpmem + i*(lpcrdr+1+2); + xq[i] = xqmem + i*(lpcrdr+1+2); + } + + /* work out 2cos terms in Q14 */ + + ALLOC(freqn, lpcrdr, spx_word16_t); + for (i=0;i 32767) a = 32767; + ak[j-1] = (short)a; + + } + +} + +#else + +void lsp_to_lpc(spx_lsp_t *freq,spx_coef_t *ak,int lpcrdr, char *stack) +/* float *freq array of LSP frequencies in the x domain */ +/* float *ak array of LPC coefficients */ +/* int lpcrdr order of LPC coefficients */ + + +{ + int i,j; + float xout1,xout2,xin1,xin2; + VARDECL(float *Wp); + float *pw,*n1,*n2,*n3,*n4=NULL; + VARDECL(float *x_freq); + int m = lpcrdr>>1; + + ALLOC(Wp, 4*m+2, float); + pw = Wp; + + /* initialise contents of array */ + + for(i=0;i<=4*m+1;i++){ /* set contents of buffer to 0 */ + *pw++ = 0.0; + } + + /* Set pointers up */ + + pw = Wp; + xin1 = 1.0; + xin2 = 1.0; + + ALLOC(x_freq, lpcrdr, float); + for (i=0;i0) + ak[j-1] = (xout1 + xout2)*0.5f; + *(n4+1) = xin1; + *(n4+2) = xin2; + + xin1 = 0.0; + xin2 = 0.0; + } + +} +#endif + + +#ifdef FIXED_POINT + +/*Makes sure the LSPs are stable*/ +void lsp_enforce_margin(spx_lsp_t *lsp, int len, spx_word16_t margin) +{ + int i; + spx_word16_t m = margin; + spx_word16_t m2 = 25736-margin; + + if (lsp[0]m2) + lsp[len-1]=m2; + for (i=1;ilsp[i+1]-m) + lsp[i]= SHR16(lsp[i],1) + SHR16(lsp[i+1]-m,1); + } +} + + +void lsp_interpolate(spx_lsp_t *old_lsp, spx_lsp_t *new_lsp, spx_lsp_t *interp_lsp, int len, int subframe, int nb_subframes) +{ + int i; + spx_word16_t tmp = DIV32_16(SHL32(EXTEND32(1 + subframe),14),nb_subframes); + spx_word16_t tmp2 = 16384-tmp; + for (i=0;iLSP_SCALING*(M_PI-margin)) + lsp[len-1]=LSP_SCALING*(M_PI-margin); + for (i=1;ilsp[i+1]-LSP_SCALING*margin) + lsp[i]= .5f* (lsp[i] + lsp[i+1]-LSP_SCALING*margin); + } +} + + +void lsp_interpolate(spx_lsp_t *old_lsp, spx_lsp_t *new_lsp, spx_lsp_t *interp_lsp, int len, int subframe, int nb_subframes) +{ + int i; + float tmp = (1.0f + subframe)/nb_subframes; + for (i=0;i>>= 14;\n\t" + "R3 = R3 + R5;\n\t" + + "R0 = R2;\n\t" /* R0: b0 */ + "R1 = 16384;\n\t" /* R1: b1 */ + "LOOP cpe%= LC0 = %3;\n\t" + "LOOP_BEGIN cpe%=;\n\t" + "P1 = R0;\n\t" + "R0 = R2.L * R0.L (IS) || R5 = W[P0--] (X);\n\t" + "R0 >>>= 13;\n\t" + "R0 = R0 - R1;\n\t" + "R1 = P1;\n\t" + "R5 = R5.L * R0.L (IS);\n\t" + "R5 = R5 + R4;\n\t" + "R5 >>>= 14;\n\t" + "R3 = R3 + R5;\n\t" + "LOOP_END cpe%=;\n\t" + "%0 = R3;\n\t" + : "=&d" (sum) + : "a" (x), "a" (&coef[m]), "a" (m-1) + : "R0", "R1", "R3", "R2", "R4", "R5", "P0", "P1" + ); + return sum; +} +#endif + + + diff --git a/src/libspeex/lsp_tables_nb.c b/src/libspeex/lsp_tables_nb.c new file mode 100644 index 0000000..16f2e1b --- /dev/null +++ b/src/libspeex/lsp_tables_nb.c @@ -0,0 +1,360 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: lsp_tables_nb.c + Codebooks for LSPs in narrowband CELP mode + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +const signed char cdbk_nb[640]={ +30,19,38,34,40,32,46,43,58,43, +5,-18,-25,-40,-33,-55,-52,20,34,28, +-20,-63,-97,-92,61,53,47,49,53,75, +-14,-53,-77,-79,0,-3,-5,19,22,26, +-9,-53,-55,66,90,72,85,68,74,52, +-4,-41,-58,-31,-18,-31,27,32,30,18, +24,3,8,5,-12,-3,26,28,74,63, +-2,-39,-67,-77,-106,-74,59,59,73,65, +44,40,71,72,82,83,98,88,89,60, +-6,-31,-47,-48,-13,-39,-9,7,2,79, +-1,-39,-60,-17,87,81,65,50,45,19, +-21,-67,-91,-87,-41,-50,7,18,39,74, +10,-31,-28,39,24,13,23,5,56,45, +29,10,-5,-13,-11,-35,-18,-8,-10,-8, +-25,-71,-77,-21,2,16,50,63,87,87, +5,-32,-40,-51,-68,0,12,6,54,34, +5,-12,32,52,68,64,69,59,65,45, +14,-16,-31,-40,-65,-67,41,49,47,37, +-11,-52,-75,-84,-4,57,48,42,42,33, +-11,-51,-68,-6,13,0,8,-8,26,32, +-23,-53,0,36,56,76,97,105,111,97, +-1,-28,-39,-40,-43,-54,-44,-40,-18,35, +16,-20,-19,-28,-42,29,47,38,74,45, +3,-29,-48,-62,-80,-104,-33,56,59,59, +10,17,46,72,84,101,117,123,123,106, +-7,-33,-49,-51,-70,-67,-27,-31,70,67, +-16,-62,-85,-20,82,71,86,80,85,74, +-19,-58,-75,-45,-29,-33,-18,-25,45,57, +-12,-42,-5,12,28,36,52,64,81,82, +13,-9,-27,-28,22,3,2,22,26,6, +-6,-44,-51,2,15,10,48,43,49,34, +-19,-62,-84,-89,-102,-24,8,17,61,68, +39,24,23,19,16,-5,12,15,27,15, +-8,-44,-49,-60,-18,-32,-28,52,54,62, +-8,-48,-77,-70,66,101,83,63,61,37, +-12,-50,-75,-64,33,17,13,25,15,77, +1,-42,-29,72,64,46,49,31,61,44, +-8,-47,-54,-46,-30,19,20,-1,-16,0, +16,-12,-18,-9,-26,-27,-10,-22,53,45, +-10,-47,-75,-82,-105,-109,8,25,49,77, +50,65,114,117,124,118,115,96,90,61, +-9,-45,-63,-60,-75,-57,8,11,20,29, +0,-35,-49,-43,40,47,35,40,55,38, +-24,-76,-103,-112,-27,3,23,34,52,75, +8,-29,-43,12,63,38,35,29,24,8, +25,11,1,-15,-18,-43,-7,37,40,21, +-20,-56,-19,-19,-4,-2,11,29,51,63, +-2,-44,-62,-75,-89,30,57,51,74,51, +50,46,68,64,65,52,63,55,65,43, +18,-9,-26,-35,-55,-69,3,6,8,17, +-15,-61,-86,-97,1,86,93,74,78,67, +-1,-38,-66,-48,48,39,29,25,17,-1, +13,13,29,39,50,51,69,82,97,98, +-2,-36,-46,-27,-16,-30,-13,-4,-7,-4, +25,-5,-11,-6,-25,-21,33,12,31,29, +-8,-38,-52,-63,-68,-89,-33,-1,10,74, +-2,-15,59,91,105,105,101,87,84,62, +-7,-33,-50,-35,-54,-47,25,17,82,81, +-13,-56,-83,21,58,31,42,25,72,65, +-24,-66,-91,-56,9,-2,21,10,69,75, +2,-24,11,22,25,28,38,34,48,33, +7,-29,-26,17,15,-1,14,0,-2,0, +-6,-41,-67,6,-2,-9,19,2,85,74, +-22,-67,-84,-71,-50,3,11,-9,2,62}; + +const signed char cdbk_nb_low1[320]={ +-34,-52,-15,45,2, +23,21,52,24,-33, +-9,-1,9,-44,-41, +-13,-17,44,22,-17, +-6,-4,-1,22,38, +26,16,2,50,27, +-35,-34,-9,-41,6, +0,-16,-34,51,8, +-14,-31,-49,15,-33, +45,49,33,-11,-37, +-62,-54,45,11,-5, +-72,11,-1,-12,-11, +24,27,-11,-43,46, +43,33,-12,-9,-1, +1,-4,-23,-57,-71, +11,8,16,17,-8, +-20,-31,-41,53,48, +-16,3,65,-24,-8, +-23,-32,-37,-32,-49, +-10,-17,6,38,5, +-9,-17,-46,8,52, +3,6,45,40,39, +-7,-6,-34,-74,31, +8,1,-16,43,68, +-11,-19,-31,4,6, +0,-6,-17,-16,-38, +-16,-30,2,9,-39, +-16,-1,43,-10,48, +3,3,-16,-31,-3, +62,68,43,13,3, +-10,8,20,-56,12, +12,-2,-18,22,-15, +-40,-36,1,7,41, +0,1,46,-6,-62, +-4,-12,-2,-11,-83, +-13,-2,91,33,-10, +0,4,-11,-16,79, +32,37,14,9,51, +-21,-28,-56,-34,0, +21,9,-26,11,28, +-42,-54,-23,-2,-15, +31,30,8,-39,-66, +-39,-36,31,-28,-40, +-46,35,40,22,24, +33,48,23,-34,14, +40,32,17,27,-3, +25,26,-13,-61,-17, +11,4,31,60,-6, +-26,-41,-64,13,16, +-26,54,31,-11,-23, +-9,-11,-34,-71,-21, +-34,-35,55,50,29, +-22,-27,-50,-38,57, +33,42,57,48,26, +11,0,-49,-31,26, +-4,-14,5,78,37, +17,0,-49,-12,-23, +26,14,2,2,-43, +-17,-12,10,-8,-4, +8,18,12,-6,20, +-12,-6,-13,-25,34, +15,40,49,7,8, +13,20,20,-19,-22, +-2,-8,2,51,-51}; + +const signed char cdbk_nb_low2[320]={ +-6,53,-21,-24,4, +26,17,-4,-37,25, +17,-36,-13,31,3, +-6,27,15,-10,31, +28,26,-10,-10,-40, +16,-7,15,13,41, +-9,0,-4,50,-6, +-7,14,38,22,0, +-48,2,1,-13,-19, +32,-3,-60,11,-17, +-1,-24,-34,-1,35, +-5,-27,28,44,13, +25,15,42,-11,15, +51,35,-36,20,8, +-4,-12,-29,19,-47, +49,-15,-4,16,-29, +-39,14,-30,4,25, +-9,-5,-51,-14,-3, +-40,-32,38,5,-9, +-8,-4,-1,-22,71, +-3,14,26,-18,-22, +24,-41,-25,-24,6, +23,19,-10,39,-26, +-27,65,45,2,-7, +-26,-8,22,-12,16, +15,16,-35,-5,33, +-21,-8,0,23,33, +34,6,21,36,6, +-7,-22,8,-37,-14, +31,38,11,-4,-3, +-39,-32,-8,32,-23, +-6,-12,16,20,-28, +-4,23,13,-52,-1, +22,6,-33,-40,-6, +4,-62,13,5,-26, +35,39,11,2,57, +-11,9,-20,-28,-33, +52,-5,-6,-2,22, +-14,-16,-48,35,1, +-58,20,13,33,-1, +-74,56,-18,-22,-31, +12,6,-14,4,-2, +-9,-47,10,-3,29, +-17,-5,61,14,47, +-12,2,72,-39,-17, +92,64,-53,-51,-15, +-30,-38,-41,-29,-28, +27,9,36,9,-35, +-42,81,-21,20,25, +-16,-5,-17,-35,21, +15,-28,48,2,-2, +9,-19,29,-40,30, +-18,-18,18,-16,-57, +15,-20,-12,-15,-37, +-15,33,-39,21,-22, +-13,35,11,13,-38, +-63,29,23,-27,32, +18,3,-26,42,33, +-64,-66,-17,16,56, +2,36,3,31,21, +-41,-39,8,-57,14, +37,-2,19,-36,-19, +-23,-29,-16,1,-3, +-8,-10,31,64,-65}; + +const signed char cdbk_nb_high1[320]={ +-26,-8,29,21,4, +19,-39,33,-7,-36, +56,54,48,40,29, +-4,-24,-42,-66,-43, +-60,19,-2,37,41, +-10,-37,-60,-64,18, +-22,77,73,40,25, +4,19,-19,-66,-2, +11,5,21,14,26, +-25,-86,-4,18,1, +26,-37,10,37,-1, +24,-12,-59,-11,20, +-6,34,-16,-16,42, +19,-28,-51,53,32, +4,10,62,21,-12, +-34,27,4,-48,-48, +-50,-49,31,-7,-21, +-42,-25,-4,-43,-22, +59,2,27,12,-9, +-6,-16,-8,-32,-58, +-16,-29,-5,41,23, +-30,-33,-46,-13,-10, +-38,52,52,1,-17, +-9,10,26,-25,-6, +33,-20,53,55,25, +-32,-5,-42,23,21, +66,5,-28,20,9, +75,29,-7,-42,-39, +15,3,-23,21,6, +11,1,-29,14,63, +10,54,26,-24,-51, +-49,7,-23,-51,15, +-66,1,60,25,10, +0,-30,-4,-15,17, +19,59,40,4,-5, +33,6,-22,-58,-70, +-5,23,-6,60,44, +-29,-16,-47,-29,52, +-19,50,28,16,35, +31,36,0,-21,6, +21,27,22,42,7, +-66,-40,-8,7,19, +46,0,-4,60,36, +45,-7,-29,-6,-32, +-39,2,6,-9,33, +20,-51,-34,18,-6, +19,6,11,5,-19, +-29,-2,42,-11,-45, +-21,-55,57,37,2, +-14,-67,-16,-27,-38, +69,48,19,2,-17, +20,-20,-16,-34,-17, +-25,-61,10,73,45, +16,-40,-64,-17,-29, +-22,56,17,-39,8, +-11,8,-25,-18,-13, +-19,8,54,57,36, +-17,-26,-4,6,-21, +40,42,-4,20,31, +53,10,-34,-53,31, +-17,35,0,15,-6, +-20,-63,-73,22,25, +29,17,8,-29,-39, +-69,18,15,-15,-5}; + +const signed char cdbk_nb_high2[320]={ +11,47,16,-9,-46, +-32,26,-64,34,-5, +38,-7,47,20,2, +-73,-99,-3,-45,20, +70,-52,15,-6,-7, +-82,31,21,47,51, +39,-3,9,0,-41, +-7,-15,-54,2,0, +27,-31,9,-45,-22, +-38,-24,-24,8,-33, +23,5,50,-36,-17, +-18,-51,-2,13,19, +43,12,-15,-12,61, +38,38,7,13,0, +6,-1,3,62,9, +27,22,-33,38,-35, +-9,30,-43,-9,-32, +-1,4,-4,1,-5, +-11,-8,38,31,11, +-10,-42,-21,-37,1, +43,15,-13,-35,-19, +-18,15,23,-26,59, +1,-21,53,8,-41, +-50,-14,-28,4,21, +25,-28,-40,5,-40, +-41,4,51,-33,-8, +-8,1,17,-60,12, +25,-41,17,34,43, +19,45,7,-37,24, +-15,56,-2,35,-10, +48,4,-47,-2,5, +-5,-54,5,-3,-33, +-10,30,-2,-44,-24, +-38,9,-9,42,4, +6,-56,44,-16,9, +-40,-26,18,-20,10, +28,-41,-21,-4,13, +-18,32,-30,-3,37, +15,22,28,50,-40, +3,-29,-64,7,51, +-19,-11,17,-27,-40, +-64,24,-12,-7,-27, +3,37,48,-1,2, +-9,-38,-34,46,1, +27,-6,19,-13,26, +10,34,20,25,40, +50,-6,-7,30,9, +-24,0,-23,71,-61, +22,58,-34,-4,2, +-49,-33,25,30,-8, +-6,-16,77,2,38, +-8,-35,-6,-30,56, +78,31,33,-20,13, +-39,20,22,4,21, +-8,4,-6,10,-83, +-41,9,-25,-43,15, +-7,-12,-34,-39,-37, +-33,19,30,16,-33, +42,-25,25,-68,44, +-15,-11,-4,23,50, +14,4,-39,-43,20, +-30,60,9,-20,7, +16,19,-33,37,29, +16,-35,7,38,-27}; diff --git a/src/libspeex/ltp.c b/src/libspeex/ltp.c new file mode 100644 index 0000000..0129c95 --- /dev/null +++ b/src/libspeex/ltp.c @@ -0,0 +1,839 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: ltp.c + Long-Term Prediction functions + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "ltp.h" +#include "stack_alloc.h" +#include "filters.h" +#include +#include "math_approx.h" +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + + +#ifdef _USE_SSE +#include "ltp_sse.h" +#elif defined (ARM4_ASM) || defined(ARM5E_ASM) +#include "ltp_arm4.h" +#elif defined (BFIN_ASM) +#include "ltp_bfin.h" +#endif + +#ifndef OVERRIDE_INNER_PROD +spx_word32_t inner_prod(const spx_word16_t *x, const spx_word16_t *y, int len) +{ + spx_word32_t sum=0; + len >>= 2; + while(len--) + { + spx_word32_t part=0; + part = MAC16_16(part,*x++,*y++); + part = MAC16_16(part,*x++,*y++); + part = MAC16_16(part,*x++,*y++); + part = MAC16_16(part,*x++,*y++); + /* HINT: If you had a 40-bit accumulator, you could shift only at the end */ + sum = ADD32(sum,SHR32(part,6)); + } + return sum; +} +#endif + +#ifndef OVERRIDE_PITCH_XCORR +#if 0 /* HINT: Enable this for machines with enough registers (i.e. not x86) */ +void pitch_xcorr(const spx_word16_t *_x, const spx_word16_t *_y, spx_word32_t *corr, int len, int nb_pitch, char *stack) +{ + int i,j; + for (i=0;i16383) + { + scaledown=1; + break; + } + } + /* If the weighted input is close to saturation, then we scale it down */ + if (scaledown) + { + for (i=-end;iMULT16_16(best_score[N-1],ADD16(1,ener16[i-start]))) + { + /* We can safely put it last and then check */ + best_score[N-1]=tmp; + best_ener[N-1]=ener16[i-start]+1; + pitch[N-1]=i; + /* Check if it comes in front of others */ + for (j=0;jMULT16_16(best_score[j],ADD16(1,ener16[i-start]))) + { + for (k=N-1;k>j;k--) + { + best_score[k]=best_score[k-1]; + best_ener[k]=best_ener[k-1]; + pitch[k]=pitch[k-1]; + } + best_score[j]=tmp; + best_ener[j]=ener16[i-start]+1; + pitch[j]=i; + break; + } + } + } + } + + /* Compute open-loop gain if necessary */ + if (gain) + { + for (j=0;jbest_sum && gain_sum<=max_gain) { + best_sum=sum; + best_cdbk=i; + } + } + + return best_cdbk; +} +#endif + +/** Finds the best quantized 3-tap pitch predictor by analysis by synthesis */ +static spx_word32_t pitch_gain_search_3tap( +const spx_word16_t target[], /* Target vector */ +const spx_coef_t ak[], /* LPCs for this subframe */ +const spx_coef_t awk1[], /* Weighted LPCs #1 for this subframe */ +const spx_coef_t awk2[], /* Weighted LPCs #2 for this subframe */ +spx_sig_t exc[], /* Excitation */ +const signed char *gain_cdbk, +int gain_cdbk_size, +int pitch, /* Pitch value */ +int p, /* Number of LPC coeffs */ +int nsf, /* Number of samples in subframe */ +SpeexBits *bits, +char *stack, +const spx_word16_t *exc2, +const spx_word16_t *r, +spx_word16_t *new_target, +int *cdbk_index, +int plc_tuning, +spx_word32_t cumul_gain, +int scaledown +) +{ + int i,j; + VARDECL(spx_word16_t *tmp1); + VARDECL(spx_word16_t *e); + spx_word16_t *x[3]; + spx_word32_t corr[3]; + spx_word32_t A[3][3]; + spx_word16_t gain[3]; + spx_word32_t err; + spx_word16_t max_gain=128; + int best_cdbk=0; + + ALLOC(tmp1, 3*nsf, spx_word16_t); + ALLOC(e, nsf, spx_word16_t); + + if (cumul_gain > 262144) + max_gain = 31; + + x[0]=tmp1; + x[1]=tmp1+nsf; + x[2]=tmp1+2*nsf; + + for (j=0;j=0;i--) + { + spx_word16_t e0=exc2[-pitch-1+i]; +#ifdef FIXED_POINT + /* Scale excitation down if needed (avoiding overflow) */ + if (scaledown) + e0 = SHR16(e0,1); +#endif + x[i][0]=MULT16_16_Q14(r[0], e0); + for (j=0;j30) + plc_tuning=30; +#ifdef FIXED_POINT + C[0] = SHL32(C[0],1); + C[1] = SHL32(C[1],1); + C[2] = SHL32(C[2],1); + C[3] = SHL32(C[3],1); + C[4] = SHL32(C[4],1); + C[5] = SHL32(C[5],1); + C[6] = MAC16_32_Q15(C[6],MULT16_16_16(plc_tuning,655),C[6]); + C[7] = MAC16_32_Q15(C[7],MULT16_16_16(plc_tuning,655),C[7]); + C[8] = MAC16_32_Q15(C[8],MULT16_16_16(plc_tuning,655),C[8]); + normalize16(C, C16, 32767, 9); +#else + C[6]*=.5*(1+.02*plc_tuning); + C[7]*=.5*(1+.02*plc_tuning); + C[8]*=.5*(1+.02*plc_tuning); +#endif + + best_cdbk = pitch_gain_search_3tap_vq(gain_cdbk, gain_cdbk_size, C16, max_gain); + +#ifdef FIXED_POINT + gain[0] = ADD16(32,(spx_word16_t)gain_cdbk[best_cdbk*4]); + gain[1] = ADD16(32,(spx_word16_t)gain_cdbk[best_cdbk*4+1]); + gain[2] = ADD16(32,(spx_word16_t)gain_cdbk[best_cdbk*4+2]); + /*printf ("%d %d %d %d\n",gain[0],gain[1],gain[2], best_cdbk);*/ +#else + gain[0] = 0.015625*gain_cdbk[best_cdbk*4] + .5; + gain[1] = 0.015625*gain_cdbk[best_cdbk*4+1]+ .5; + gain[2] = 0.015625*gain_cdbk[best_cdbk*4+2]+ .5; +#endif + *cdbk_index=best_cdbk; + } + + SPEEX_MEMSET(exc, 0, nsf); + for (i=0;i<3;i++) + { + int j; + int tmp1, tmp3; + int pp=pitch+1-i; + tmp1=nsf; + if (tmp1>pp) + tmp1=pp; + for (j=0;jpp+pitch) + tmp3=pp+pitch; + for (j=tmp1;jgain_bits; + gain_cdbk = params->gain_cdbk + 4*gain_cdbk_size*cdbk_offset; + + N=complexity; + if (N>10) + N=10; + if (N<1) + N=1; + + ALLOC(nbest, N, int); + params = (const ltp_params*) par; + + if (endpitch_bits); + speex_bits_pack(bits, 0, params->gain_bits); + SPEEX_MEMSET(exc, 0, nsf); + return start; + } + +#ifdef FIXED_POINT + /* Check if we need to scale everything down in the pitch search to avoid overflows */ + for (i=0;i16383) + { + scaledown=1; + break; + } + } + for (i=-end;i16383) + { + scaledown=1; + break; + } + } +#endif + if (N>end-start+1) + N=end-start+1; + if (end != start) + open_loop_nbest_pitch(sw, start, end, nsf, nbest, NULL, N, stack); + else + nbest[0] = start; + + ALLOC(best_exc, nsf, spx_sig_t); + ALLOC(new_target, nsf, spx_word16_t); + ALLOC(best_target, nsf, spx_word16_t); + + for (i=0;ipitch_bits); + speex_bits_pack(bits, best_gain_index, params->gain_bits); +#ifdef FIXED_POINT + *cumul_gain = MULT16_32_Q13(SHL16(params->gain_cdbk[4*best_gain_index+3],8), MAX32(1024,*cumul_gain)); +#else + *cumul_gain = 0.03125*MAX32(1024,*cumul_gain)*params->gain_cdbk[4*best_gain_index+3]; +#endif + /*printf ("%f\n", cumul_gain);*/ + /*printf ("encode pitch: %d %d\n", best_pitch, best_gain_index);*/ + SPEEX_COPY(exc, best_exc, nsf); + SPEEX_COPY(target, best_target, nsf); +#ifdef FIXED_POINT + /* Scale target back up if needed */ + if (scaledown) + { + for (i=0;igain_bits; + gain_cdbk = params->gain_cdbk + 4*gain_cdbk_size*cdbk_offset; + + pitch = speex_bits_unpack_unsigned(bits, params->pitch_bits); + pitch += start; + gain_index = speex_bits_unpack_unsigned(bits, params->gain_bits); + /*printf ("decode pitch: %d %d\n", pitch, gain_index);*/ +#ifdef FIXED_POINT + gain[0] = ADD16(32,(spx_word16_t)gain_cdbk[gain_index*4]); + gain[1] = ADD16(32,(spx_word16_t)gain_cdbk[gain_index*4+1]); + gain[2] = ADD16(32,(spx_word16_t)gain_cdbk[gain_index*4+2]); +#else + gain[0] = 0.015625*gain_cdbk[gain_index*4]+.5; + gain[1] = 0.015625*gain_cdbk[gain_index*4+1]+.5; + gain[2] = 0.015625*gain_cdbk[gain_index*4+2]+.5; +#endif + + if (count_lost && pitch > subframe_offset) + { + spx_word16_t gain_sum; + if (1) { +#ifdef FIXED_POINT + spx_word16_t tmp = count_lost < 4 ? last_pitch_gain : SHR16(last_pitch_gain,1); + if (tmp>62) + tmp=62; +#else + spx_word16_t tmp = count_lost < 4 ? last_pitch_gain : 0.5 * last_pitch_gain; + if (tmp>.95) + tmp=.95; +#endif + gain_sum = gain_3tap_to_1tap(gain); + + if (gain_sum > tmp) + { + spx_word16_t fact = DIV32_16(SHL32(EXTEND32(tmp),14),gain_sum); + for (i=0;i<3;i++) + gain[i]=MULT16_16_Q14(fact,gain[i]); + } + + } + + } + + *pitch_val = pitch; + gain_val[0]=gain[0]; + gain_val[1]=gain[1]; + gain_val[2]=gain[2]; + gain[0] = SHL16(gain[0],7); + gain[1] = SHL16(gain[1],7); + gain[2] = SHL16(gain[2],7); + SPEEX_MEMSET(exc_out, 0, nsf); + for (i=0;i<3;i++) + { + int j; + int tmp1, tmp3; + int pp=pitch+1-i; + tmp1=nsf; + if (tmp1>pp) + tmp1=pp; + for (j=0;jpp+pitch) + tmp3=pp+pitch; + for (j=tmp1;j63) + pitch_coef=63; +#else + if (pitch_coef>.99) + pitch_coef=.99; +#endif + for (i=0;i63) + pitch_coef=63; +#else + if (pitch_coef>.99) + pitch_coef=.99; +#endif + for (i=0;i +#include "arch.h" + +/** LTP parameters. */ +typedef struct { + const signed char *gain_cdbk; + int gain_bits; + int pitch_bits; +} ltp_params; + +#ifdef FIXED_POINT +#define gain_3tap_to_1tap(g) (ABS(g[1]) + (g[0]>0 ? g[0] : -SHR16(g[0],1)) + (g[2]>0 ? g[2] : -SHR16(g[2],1))) +#else +#define gain_3tap_to_1tap(g) (ABS(g[1]) + (g[0]>0 ? g[0] : -.5*g[0]) + (g[2]>0 ? g[2] : -.5*g[2])) +#endif + +spx_word32_t inner_prod(const spx_word16_t *x, const spx_word16_t *y, int len); +void pitch_xcorr(const spx_word16_t *_x, const spx_word16_t *_y, spx_word32_t *corr, int len, int nb_pitch, char *stack); + +void open_loop_nbest_pitch(spx_word16_t *sw, int start, int end, int len, int *pitch, spx_word16_t *gain, int N, char *stack); + + +/** Finds the best quantized 3-tap pitch predictor by analysis by synthesis */ +int pitch_search_3tap( +spx_word16_t target[], /* Target vector */ +spx_word16_t *sw, +spx_coef_t ak[], /* LPCs for this subframe */ +spx_coef_t awk1[], /* Weighted LPCs #1 for this subframe */ +spx_coef_t awk2[], /* Weighted LPCs #2 for this subframe */ +spx_sig_t exc[], /* Overlapping codebook */ +const void *par, +int start, /* Smallest pitch value allowed */ +int end, /* Largest pitch value allowed */ +spx_word16_t pitch_coef, /* Voicing (pitch) coefficient */ +int p, /* Number of LPC coeffs */ +int nsf, /* Number of samples in subframe */ +SpeexBits *bits, +char *stack, +spx_word16_t *exc2, +spx_word16_t *r, +int complexity, +int cdbk_offset, +int plc_tuning, +spx_word32_t *cumul_gain +); + +/*Unquantize adaptive codebook and update pitch contribution*/ +void pitch_unquant_3tap( +spx_word16_t exc[], /* Input excitation */ +spx_word32_t exc_out[], /* Output excitation */ +int start, /* Smallest pitch value allowed */ +int end, /* Largest pitch value allowed */ +spx_word16_t pitch_coef, /* Voicing (pitch) coefficient */ +const void *par, +int nsf, /* Number of samples in subframe */ +int *pitch_val, +spx_word16_t *gain_val, +SpeexBits *bits, +char *stack, +int lost, +int subframe_offset, +spx_word16_t last_pitch_gain, +int cdbk_offset +); + +/** Forced pitch delay and gain */ +int forced_pitch_quant( +spx_word16_t target[], /* Target vector */ +spx_word16_t *sw, +spx_coef_t ak[], /* LPCs for this subframe */ +spx_coef_t awk1[], /* Weighted LPCs #1 for this subframe */ +spx_coef_t awk2[], /* Weighted LPCs #2 for this subframe */ +spx_sig_t exc[], /* Excitation */ +const void *par, +int start, /* Smallest pitch value allowed */ +int end, /* Largest pitch value allowed */ +spx_word16_t pitch_coef, /* Voicing (pitch) coefficient */ +int p, /* Number of LPC coeffs */ +int nsf, /* Number of samples in subframe */ +SpeexBits *bits, +char *stack, +spx_word16_t *exc2, +spx_word16_t *r, +int complexity, +int cdbk_offset, +int plc_tuning, +spx_word32_t *cumul_gain +); + +/** Unquantize forced pitch delay and gain */ +void forced_pitch_unquant( +spx_word16_t exc[], /* Input excitation */ +spx_word32_t exc_out[], /* Output excitation */ +int start, /* Smallest pitch value allowed */ +int end, /* Largest pitch value allowed */ +spx_word16_t pitch_coef, /* Voicing (pitch) coefficient */ +const void *par, +int nsf, /* Number of samples in subframe */ +int *pitch_val, +spx_word16_t *gain_val, +SpeexBits *bits, +char *stack, +int lost, +int subframe_offset, +spx_word16_t last_pitch_gain, +int cdbk_offset +); diff --git a/src/libspeex/ltp_arm4.h b/src/libspeex/ltp_arm4.h new file mode 100644 index 0000000..cdb94e6 --- /dev/null +++ b/src/libspeex/ltp_arm4.h @@ -0,0 +1,187 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file ltp_arm4.h + @brief Long-Term Prediction functions (ARM4 version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_INNER_PROD +spx_word32_t inner_prod(const spx_word16_t *x, const spx_word16_t *y, int len) +{ + spx_word32_t sum1=0,sum2=0; + spx_word16_t *deadx, *deady; + int deadlen, dead1, dead2, dead3, dead4, dead5, dead6; + __asm__ __volatile__ ( + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + ".inner_prod_loop%=:\n" + "\tsub %7, %7, %7\n" + "\tsub %10, %10, %10\n" + + "\tldrsh %8, [%0], #2 \n" + "\tldrsh %9, [%1], #2 \n" + "\tmla %7, %5, %6, %7\n" + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + "\tmla %10, %8, %9, %10\n" + "\tldrsh %8, [%0], #2 \n" + "\tldrsh %9, [%1], #2 \n" + "\tmla %7, %5, %6, %7\n" + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + "\tmla %10, %8, %9, %10\n" + + "\tldrsh %8, [%0], #2 \n" + "\tldrsh %9, [%1], #2 \n" + "\tmla %7, %5, %6, %7\n" + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + "\tmla %10, %8, %9, %10\n" + "\tldrsh %8, [%0], #2 \n" + "\tldrsh %9, [%1], #2 \n" + "\tmla %7, %5, %6, %7\n" + "\tldrsh %5, [%0], #2 \n" + "\tldrsh %6, [%1], #2 \n" + "\tmla %10, %8, %9, %10\n" + + "\tsubs %4, %4, #1\n" + "\tadd %2, %2, %7, asr #5\n" + "\tadd %3, %3, %10, asr #5\n" + "\tbne .inner_prod_loop%=\n" + : "=r" (deadx), "=r" (deady), "+r" (sum1), "+r" (sum2), + "=r" (deadlen), "=r" (dead1), "=r" (dead2), "=r" (dead3), + "=r" (dead4), "=r" (dead5), "=r" (dead6) + : "0" (x), "1" (y), "4" (len>>3) + : "cc" + ); + return (sum1+sum2)>>1; +} + +#define OVERRIDE_PITCH_XCORR +void pitch_xcorr(const spx_word16_t *_x, const spx_word16_t *_y, spx_word32_t *corr, int len, int nb_pitch, char *stack) +{ + int i,j; + for (i=0;i>> 6;\n\t" + "R0 = A0;\n\t" + "%0 = R0;\n\t" + : "=m" (sum) + : "m" (x), "m" (y), "d" (len-1) + : "P0", "P1", "P2", "R0", "R1", "A0", "I0", "I1", "L0", "L1", "R3" + ); + return sum; +} + +#define OVERRIDE_PITCH_XCORR +void pitch_xcorr(const spx_word16_t *_x, const spx_word16_t *_y, spx_word32_t *corr, int len, int nb_pitch, char *stack) +{ + corr += nb_pitch - 1; + __asm__ __volatile__ ( + "P2 = %0;\n\t" + "I0 = P2;\n\t" /* x in I0 */ + "B0 = P2;\n\t" /* x in B0 */ + "R0 = %3;\n\t" /* len in R0 */ + "P3 = %3;\n\t" + "P3 += -2;\n\t" /* len in R0 */ + "P4 = %4;\n\t" /* nb_pitch in R0 */ + "R1 = R0 << 1;\n\t" /* number of bytes in x */ + "L0 = R1;\n\t" + "P0 = %1;\n\t" + + "P1 = %2;\n\t" + "B1 = P1;\n\t" + "L1 = 0;\n\t" /*Disable looping on I1*/ + + "r0 = [I0++];\n\t" + "LOOP pitch%= LC0 = P4 >> 1;\n\t" + "LOOP_BEGIN pitch%=;\n\t" + "I1 = P0;\n\t" + "A1 = A0 = 0;\n\t" + "R1 = [I1++];\n\t" + "LOOP inner_prod%= LC1 = P3 >> 1;\n\t" + "LOOP_BEGIN inner_prod%=;\n\t" + "A1 += R0.L*R1.H, A0 += R0.L*R1.L (IS) || R1.L = W[I1++];\n\t" + "A1 += R0.H*R1.L, A0 += R0.H*R1.H (IS) || R1.H = W[I1++] || R0 = [I0++];\n\t" + "LOOP_END inner_prod%=;\n\t" + "A1 += R0.L*R1.H, A0 += R0.L*R1.L (IS) || R1.L = W[I1++];\n\t" + "A1 += R0.H*R1.L, A0 += R0.H*R1.H (IS) || R0 = [I0++];\n\t" + "A0 = A0 >>> 6;\n\t" + "A1 = A1 >>> 6;\n\t" + "R2 = A0, R3 = A1;\n\t" + "[P1--] = r2;\n\t" + "[P1--] = r3;\n\t" + "P0 += 4;\n\t" + "LOOP_END pitch%=;\n\t" + "L0 = 0;\n\t" + : : "m" (_x), "m" (_y), "m" (corr), "m" (len), "m" (nb_pitch) + : "A0", "A1", "P0", "P1", "P2", "P3", "P4", "R0", "R1", "R2", "R3", "I0", "I1", "L0", "L1", "B0", "B1", "memory" + ); +} + +#define OVERRIDE_COMPUTE_PITCH_ERROR +static inline spx_word32_t compute_pitch_error(spx_word16_t *C, spx_word16_t *g, spx_word16_t pitch_control) +{ + spx_word32_t sum; + __asm__ __volatile__ + ( + "A0 = 0;\n\t" + + "R0 = W[%1++];\n\t" + "R1.L = %2.L*%5.L (IS);\n\t" + "A0 += R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %3.L*%5.L (IS);\n\t" + "A0 += R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %4.L*%5.L (IS);\n\t" + "A0 += R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %2.L*%3.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %4.L*%3.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %4.L*%2.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %2.L*%2.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %3.L*%3.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS) || R0 = W[%1++];\n\t" + + "R1.L = %4.L*%4.L (IS);\n\t" + "A0 -= R1.L*R0.L (IS);\n\t" + + "%0 = A0;\n\t" + : "=&D" (sum), "=a" (C) + : "d" (g[0]), "d" (g[1]), "d" (g[2]), "d" (pitch_control), "1" (C) + : "R0", "R1", "R2", "A0" + ); + return sum; +} + +#define OVERRIDE_OPEN_LOOP_NBEST_PITCH +#ifdef OVERRIDE_OPEN_LOOP_NBEST_PITCH +void open_loop_nbest_pitch(spx_word16_t *sw, int start, int end, int len, int *pitch, spx_word16_t *gain, int N, char *stack) +{ + int i,j,k; + VARDECL(spx_word32_t *best_score); + VARDECL(spx_word32_t *best_ener); + spx_word32_t e0; + VARDECL(spx_word32_t *corr); + VARDECL(spx_word32_t *energy); + + ALLOC(best_score, N, spx_word32_t); + ALLOC(best_ener, N, spx_word32_t); + ALLOC(corr, end-start+1, spx_word32_t); + ALLOC(energy, end-start+2, spx_word32_t); + + for (i=0;i>>= 6;\n\t" +" R1 = R1 + R2;\n\t" +" R0 >>>= 6;\n\t" +" R1 = R1 - R0;\n\t" +" R2 = MAX(R1,R3);\n\t" +"eu2: [P0++] = R2;\n\t" + : : "d" (energy), "d" (&sw[-start-1]), "d" (&sw[-start+len-1]), + "a" (end-start) + : "P0", "I1", "I2", "R0", "R1", "R2", "R3" +#if (__GNUC__ == 4) + , "LC1" +#endif + ); + + pitch_xcorr(sw, sw-end, corr, len, end-start+1, stack); + + /* FIXME: Fixed-point and floating-point code should be merged */ + { + VARDECL(spx_word16_t *corr16); + VARDECL(spx_word16_t *ener16); + ALLOC(corr16, end-start+1, spx_word16_t); + ALLOC(ener16, end-start+1, spx_word16_t); + /* Normalize to 180 so we can square it and it still fits in 16 bits */ + normalize16(corr, corr16, 180, end-start+1); + normalize16(energy, ener16, 180, end-start+1); + + if (N == 1) { + /* optimised asm to handle N==1 case */ + __asm__ __volatile__ + ( +" I0 = %1;\n\t" /* I0: corr16[] */ +" L0 = 0;\n\t" +" I1 = %2;\n\t" /* I1: energy */ +" L1 = 0;\n\t" +" R2 = -1;\n\t" /* R2: best score */ +" R3 = 0;\n\t" /* R3: best energy */ +" P0 = %4;\n\t" /* P0: best pitch */ +" P1 = %4;\n\t" /* P1: counter */ +" LSETUP (sl1, sl2) LC1 = %3;\n\t" +"sl1: R0.L = W [I0++] || R1.L = W [I1++];\n\t" +" R0 = R0.L * R0.L (IS);\n\t" +" R1 += 1;\n\t" +" R4 = R0.L * R3.L;\n\t" +" R5 = R2.L * R1.L;\n\t" +" cc = R5 < R4;\n\t" +" if cc R2 = R0;\n\t" +" if cc R3 = R1;\n\t" +" if cc P0 = P1;\n\t" +"sl2: P1 += 1;\n\t" +" %0 = P0;\n\t" + : "=&d" (pitch[0]) + : "a" (corr16), "a" (ener16), "a" (end+1-start), "d" (start) + : "P0", "P1", "I0", "I1", "R0", "R1", "R2", "R3", "R4", "R5" +#if (__GNUC__ == 4) + , "LC1" +#endif + ); + + } + else { + for (i=start;i<=end;i++) + { + spx_word16_t tmp = MULT16_16_16(corr16[i-start],corr16[i-start]); + /* Instead of dividing the tmp by the energy, we multiply on the other side */ + if (MULT16_16(tmp,best_ener[N-1])>MULT16_16(best_score[N-1],ADD16(1,ener16[i-start]))) + { + /* We can safely put it last and then check */ + best_score[N-1]=tmp; + best_ener[N-1]=ener16[i-start]+1; + pitch[N-1]=i; + /* Check if it comes in front of others */ + for (j=0;jMULT16_16(best_score[j],ADD16(1,ener16[i-start]))) + { + for (k=N-1;k>j;k--) + { + best_score[k]=best_score[k-1]; + best_ener[k]=best_ener[k-1]; + pitch[k]=pitch[k-1]; + } + best_score[j]=tmp; + best_ener[j]=ener16[i-start]+1; + pitch[j]=i; + break; + } + } + } + } + } + } + + /* Compute open-loop gain */ + if (gain) + { + for (j=0;jbest_sum && gain_sum<=max_gain) ------ (1) + + if (sum>best_sum && !(gain_sum>max_gain)) ------ (2) + + if (max_gain<=gain_sum) { ------ (3) + sum = -VERY_LARGE32; + } + if (best_sum<=sum) + + The blackin cc instructions are all of the form: + + cc = x < y (or cc = x <= y) +*/ +" R1 = B0\n\t" +" R2 = %5\n\t" +" R3 = %6\n\t" +" cc = R2 <= R1;\n\t" +" if cc R0 = R3;\n\t" +" cc = %0 <= R0;\n\t" +" if cc %0 = R0;\n\t" +" if cc %1 = P1;\n\t" + +"pgs2: P1 += 1;\n\t" + + : "=&d" (best_sum), "=&d" (best_cdbk) + : "a" (gain_cdbk), "a" (C16), "a" (gain_cdbk_size), "a" (max_gain), + "b" (-VERY_LARGE32) + : "R0", "R1", "R2", "R3", "R4", "P0", + "P1", "I1", "L1", "A0", "B0" +#if (__GNUC__ == 4) + , "LC1" +#endif + ); + + return best_cdbk; +} +#endif + diff --git a/src/libspeex/ltp_sse.h b/src/libspeex/ltp_sse.h new file mode 100644 index 0000000..bed6eaa --- /dev/null +++ b/src/libspeex/ltp_sse.h @@ -0,0 +1,92 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file ltp_sse.h + @brief Long-Term Prediction functions (SSE version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#define OVERRIDE_INNER_PROD +float inner_prod(const float *a, const float *b, int len) +{ + int i; + float ret; + __m128 sum = _mm_setzero_ps(); + for (i=0;i<(len>>2);i+=2) + { + sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(a+0), _mm_loadu_ps(b+0))); + sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(a+4), _mm_loadu_ps(b+4))); + a += 8; + b += 8; + } + sum = _mm_add_ps(sum, _mm_movehl_ps(sum, sum)); + sum = _mm_add_ss(sum, _mm_shuffle_ps(sum, sum, 0x55)); + _mm_store_ss(&ret, sum); + return ret; +} + +#define OVERRIDE_PITCH_XCORR +void pitch_xcorr(const float *_x, const float *_y, float *corr, int len, int nb_pitch, char *stack) +{ + int i, offset; + VARDECL(__m128 *x); + VARDECL(__m128 *y); + int N, L; + N = len>>2; + L = nb_pitch>>2; + ALLOC(x, N, __m128); + ALLOC(y, N+L, __m128); + for (i=0;i=(spx_int32_t)65536) + { + x >>= 16; + r += 16; + } + if (x>=256) + { + x >>= 8; + r += 8; + } + if (x>=16) + { + x >>= 4; + r += 4; + } + if (x>=4) + { + x >>= 2; + r += 2; + } + if (x>=2) + { + r += 1; + } + return r; +} + +static inline spx_int16_t spx_ilog4(spx_uint32_t x) +{ + int r=0; + if (x>=(spx_int32_t)65536) + { + x >>= 16; + r += 8; + } + if (x>=256) + { + x >>= 8; + r += 4; + } + if (x>=16) + { + x >>= 4; + r += 2; + } + if (x>=4) + { + r += 1; + } + return r; +} + +#ifdef FIXED_POINT + +/** Generate a pseudo-random number */ +static inline spx_word16_t speex_rand(spx_word16_t std, spx_int32_t *seed) +{ + spx_word32_t res; + *seed = 1664525 * *seed + 1013904223; + res = MULT16_16(EXTRACT16(SHR32(*seed,16)),std); + return EXTRACT16(PSHR32(SUB32(res, SHR32(res, 3)),14)); +} + +/* sqrt(x) ~= 0.22178 + 1.29227*x - 0.77070*x^2 + 0.25723*x^3 (for .25 < x < 1) */ +/*#define C0 3634 +#define C1 21173 +#define C2 -12627 +#define C3 4215*/ + +/* sqrt(x) ~= 0.22178 + 1.29227*x - 0.77070*x^2 + 0.25659*x^3 (for .25 < x < 1) */ +#define C0 3634 +#define C1 21173 +#define C2 -12627 +#define C3 4204 + +static inline spx_word16_t spx_sqrt(spx_word32_t x) +{ + int k; + spx_word32_t rt; + k = spx_ilog4(x)-6; + x = VSHR32(x, (k<<1)); + rt = ADD16(C0, MULT16_16_Q14(x, ADD16(C1, MULT16_16_Q14(x, ADD16(C2, MULT16_16_Q14(x, (C3))))))); + rt = VSHR32(rt,7-k); + return rt; +} + +/* log(x) ~= -2.18151 + 4.20592*x - 2.88938*x^2 + 0.86535*x^3 (for .5 < x < 1) */ + + +#define A1 16469 +#define A2 2242 +#define A3 1486 + +static inline spx_word16_t spx_acos(spx_word16_t x) +{ + int s=0; + spx_word16_t ret; + spx_word16_t sq; + if (x<0) + { + s=1; + x = NEG16(x); + } + x = SUB16(16384,x); + + x = x >> 1; + sq = MULT16_16_Q13(x, ADD16(A1, MULT16_16_Q13(x, ADD16(A2, MULT16_16_Q13(x, (A3)))))); + ret = spx_sqrt(SHL32(EXTEND32(sq),13)); + + /*ret = spx_sqrt(67108864*(-1.6129e-04 + 2.0104e+00*f + 2.7373e-01*f*f + 1.8136e-01*f*f*f));*/ + if (s) + ret = SUB16(25736,ret); + return ret; +} + + +#define K1 8192 +#define K2 -4096 +#define K3 340 +#define K4 -10 + +static inline spx_word16_t spx_cos(spx_word16_t x) +{ + spx_word16_t x2; + + if (x<12868) + { + x2 = MULT16_16_P13(x,x); + return ADD32(K1, MULT16_16_P13(x2, ADD32(K2, MULT16_16_P13(x2, ADD32(K3, MULT16_16_P13(K4, x2)))))); + } else { + x = SUB16(25736,x); + x2 = MULT16_16_P13(x,x); + return SUB32(-K1, MULT16_16_P13(x2, ADD32(K2, MULT16_16_P13(x2, ADD32(K3, MULT16_16_P13(K4, x2)))))); + } +} + +#define L1 32767 +#define L2 -7651 +#define L3 8277 +#define L4 -626 + +static inline spx_word16_t _spx_cos_pi_2(spx_word16_t x) +{ + spx_word16_t x2; + + x2 = MULT16_16_P15(x,x); + return ADD16(1,MIN16(32766,ADD32(SUB16(L1,x2), MULT16_16_P15(x2, ADD32(L2, MULT16_16_P15(x2, ADD32(L3, MULT16_16_P15(L4, x2)))))))); +} + +static inline spx_word16_t spx_cos_norm(spx_word32_t x) +{ + x = x&0x0001ffff; + if (x>SHL32(EXTEND32(1), 16)) + x = SUB32(SHL32(EXTEND32(1), 17),x); + if (x&0x00007fff) + { + if (x14) + return 0x7fffffff; + else if (integer < -15) + return 0; + frac = SHL16(x-SHL16(integer,11),3); + frac = ADD16(D0, MULT16_16_Q14(frac, ADD16(D1, MULT16_16_Q14(frac, ADD16(D2 , MULT16_16_Q14(D3,frac)))))); + return VSHR32(EXTEND32(frac), -integer-2); +} + +/* Input in Q11 format, output in Q16 */ +static inline spx_word32_t spx_exp(spx_word16_t x) +{ + if (x>21290) + return 0x7fffffff; + else if (x<-21290) + return 0; + else + return spx_exp2(MULT16_16_P14(23637,x)); +} +#define M1 32767 +#define M2 -21 +#define M3 -11943 +#define M4 4936 + +static inline spx_word16_t spx_atan01(spx_word16_t x) +{ + return MULT16_16_P15(x, ADD32(M1, MULT16_16_P15(x, ADD32(M2, MULT16_16_P15(x, ADD32(M3, MULT16_16_P15(M4, x))))))); +} + +#undef M1 +#undef M2 +#undef M3 +#undef M4 + +/* Input in Q15, output in Q14 */ +static inline spx_word16_t spx_atan(spx_word32_t x) +{ + if (x <= 32767) + { + return SHR16(spx_atan01(x),1); + } else { + int e = spx_ilog2(x); + if (e>=29) + return 25736; + x = DIV32_16(SHL32(EXTEND32(32767),29-e), EXTRACT16(SHR32(x, e-14))); + return SUB16(25736, SHR16(spx_atan01(x),1)); + } +} +#else + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#define C1 0.9999932946f +#define C2 -0.4999124376f +#define C3 0.0414877472f +#define C4 -0.0012712095f + + +#define SPX_PI_2 1.5707963268 +static inline spx_word16_t spx_cos(spx_word16_t x) +{ + if (x>16) + +#else + +static const spx_float_t MIN_LEAK = .005f; + +/* Constants for the two-path filter */ +static const spx_float_t VAR1_SMOOTH = .36f; +static const spx_float_t VAR2_SMOOTH = .7225f; +static const spx_float_t VAR1_UPDATE = .5f; +static const spx_float_t VAR2_UPDATE = .25f; +static const spx_float_t VAR_BACKTRACK = 4.f; +#define TOP16(x) (x) +#endif + + +#define PLAYBACK_DELAY 2 + +void speex_echo_get_residual(SpeexEchoState *st, spx_word32_t *Yout, int len); + + +/** Speex echo cancellation state. */ +struct SpeexEchoState_ { + int frame_size; /**< Number of samples processed each time */ + int window_size; + int M; + int cancel_count; + int adapted; + int saturated; + int screwed_up; + spx_int32_t sampling_rate; + spx_word16_t spec_average; + spx_word16_t beta0; + spx_word16_t beta_max; + spx_word32_t sum_adapt; + spx_word16_t leak_estimate; + + spx_word16_t *e; /* scratch */ + spx_word16_t *x; /* Far-end input buffer (2N) */ + spx_word16_t *X; /* Far-end buffer (M+1 frames) in frequency domain */ + spx_word16_t *input; /* scratch */ + spx_word16_t *y; /* scratch */ + spx_word16_t *last_y; + spx_word16_t *Y; /* scratch */ + spx_word16_t *E; + spx_word32_t *PHI; /* scratch */ + spx_word32_t *W; /* (Background) filter weights */ +#ifdef TWO_PATH + spx_word16_t *foreground; /* Foreground filter weights */ + spx_word32_t Davg1; /* 1st recursive average of the residual power difference */ + spx_word32_t Davg2; /* 2nd recursive average of the residual power difference */ + spx_float_t Dvar1; /* Estimated variance of 1st estimator */ + spx_float_t Dvar2; /* Estimated variance of 2nd estimator */ +#endif + spx_word32_t *power; /* Power of the far-end signal */ + spx_float_t *power_1;/* Inverse power of far-end */ + spx_word16_t *wtmp; /* scratch */ +#ifdef FIXED_POINT + spx_word16_t *wtmp2; /* scratch */ +#endif + spx_word32_t *Rf; /* scratch */ + spx_word32_t *Yf; /* scratch */ + spx_word32_t *Xf; /* scratch */ + spx_word32_t *Eh; + spx_word32_t *Yh; + spx_float_t Pey; + spx_float_t Pyy; + spx_word16_t *window; + spx_word16_t *prop; + void *fft_table; + spx_word16_t memX, memD, memE; + spx_word16_t preemph; + spx_word16_t notch_radius; + spx_mem_t notch_mem[2]; + + /* NOTE: If you only use speex_echo_cancel() and want to save some memory, remove this */ + spx_int16_t *play_buf; + int play_buf_pos; + int play_buf_started; +}; + +static inline void filter_dc_notch16(const spx_int16_t *in, spx_word16_t radius, spx_word16_t *out, int len, spx_mem_t *mem) +{ + int i; + spx_word16_t den2; +#ifdef FIXED_POINT + den2 = MULT16_16_Q15(radius,radius) + MULT16_16_Q15(QCONST16(.7,15),MULT16_16_Q15(32767-radius,32767-radius)); +#else + den2 = radius*radius + .7*(1-radius)*(1-radius); +#endif + /*printf ("%d %d %d %d %d %d\n", num[0], num[1], num[2], den[0], den[1], den[2]);*/ + for (i=0;i>= 1; + while(len--) + { + spx_word32_t part=0; + part = MAC16_16(part,*x++,*y++); + part = MAC16_16(part,*x++,*y++); + /* HINT: If you had a 40-bit accumulator, you could shift only at the end */ + sum = ADD32(sum,SHR32(part,6)); + } + return sum; +} + +/** Compute power spectrum of a half-complex (packed) vector */ +static inline void power_spectrum(const spx_word16_t *X, spx_word32_t *ps, int N) +{ + int i, j; + ps[0]=MULT16_16(X[0],X[0]); + for (i=1,j=1;i max_sum) + max_sum = prop[i]; + } + for (i=0;i +static FILE *rFile=NULL, *pFile=NULL, *oFile=NULL; + +static void dump_audio(const spx_int16_t *rec, const spx_int16_t *play, const spx_int16_t *out, int len) +{ + if (!(rFile && pFile && oFile)) + { + speex_fatal("Dump files not open"); + } + fwrite(rec, sizeof(spx_int16_t), len, rFile); + fwrite(play, sizeof(spx_int16_t), len, pFile); + fwrite(out, sizeof(spx_int16_t), len, oFile); +} +#endif + +/** Creates a new echo canceller state */ +SpeexEchoState *speex_echo_state_init(int frame_size, int filter_length) +{ + int i,N,M; + SpeexEchoState *st = (SpeexEchoState *)speex_alloc(sizeof(SpeexEchoState)); + +#ifdef DUMP_ECHO_CANCEL_DATA + if (rFile || pFile || oFile) + speex_fatal("Opening dump files twice"); + rFile = fopen("aec_rec.sw", "wb"); + pFile = fopen("aec_play.sw", "wb"); + oFile = fopen("aec_out.sw", "wb"); +#endif + + st->frame_size = frame_size; + st->window_size = 2*frame_size; + N = st->window_size; + M = st->M = (filter_length+st->frame_size-1)/frame_size; + st->cancel_count=0; + st->sum_adapt = 0; + st->saturated = 0; + st->screwed_up = 0; + /* This is the default sampling rate */ + st->sampling_rate = 8000; + st->spec_average = DIV32_16(SHL32(EXTEND32(st->frame_size), 15), st->sampling_rate); +#ifdef FIXED_POINT + st->beta0 = DIV32_16(SHL32(EXTEND32(st->frame_size), 16), st->sampling_rate); + st->beta_max = DIV32_16(SHL32(EXTEND32(st->frame_size), 14), st->sampling_rate); +#else + st->beta0 = (2.0f*st->frame_size)/st->sampling_rate; + st->beta_max = (.5f*st->frame_size)/st->sampling_rate; +#endif + st->leak_estimate = 0; + + st->fft_table = spx_fft_init(N); + + st->e = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + st->x = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + st->input = (spx_word16_t*)speex_alloc(st->frame_size*sizeof(spx_word16_t)); + st->y = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + st->last_y = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + st->Yf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + st->Rf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + st->Xf = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + st->Yh = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + st->Eh = (spx_word32_t*)speex_alloc((st->frame_size+1)*sizeof(spx_word32_t)); + + st->X = (spx_word16_t*)speex_alloc((M+1)*N*sizeof(spx_word16_t)); + st->Y = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + st->E = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + st->W = (spx_word32_t*)speex_alloc(M*N*sizeof(spx_word32_t)); +#ifdef TWO_PATH + st->foreground = (spx_word16_t*)speex_alloc(M*N*sizeof(spx_word16_t)); +#endif + st->PHI = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); + st->power = (spx_word32_t*)speex_alloc((frame_size+1)*sizeof(spx_word32_t)); + st->power_1 = (spx_float_t*)speex_alloc((frame_size+1)*sizeof(spx_float_t)); + st->window = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + st->prop = (spx_word16_t*)speex_alloc(M*sizeof(spx_word16_t)); + st->wtmp = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); +#ifdef FIXED_POINT + st->wtmp2 = (spx_word16_t*)speex_alloc(N*sizeof(spx_word16_t)); + for (i=0;i>1;i++) + { + st->window[i] = (16383-SHL16(spx_cos(DIV32_16(MULT16_16(25736,i<<1),N)),1)); + st->window[N-i-1] = st->window[i]; + } +#else + for (i=0;iwindow[i] = .5-.5*cos(2*M_PI*i/N); +#endif + for (i=0;i<=st->frame_size;i++) + st->power_1[i] = FLOAT_ONE; + for (i=0;iW[i] = 0; + { + spx_word32_t sum = 0; + /* Ratio of ~10 between adaptation rate of first and last block */ + spx_word16_t decay = SHR32(spx_exp(NEG16(DIV32_16(QCONST16(2.4,11),M))),1); + st->prop[0] = QCONST16(.7, 15); + sum = EXTEND32(st->prop[0]); + for (i=1;iprop[i] = MULT16_16_Q15(st->prop[i-1], decay); + sum = ADD32(sum, EXTEND32(st->prop[i])); + } + for (i=M-1;i>=0;i--) + { + st->prop[i] = DIV32(MULT16_16(QCONST16(.8,15), st->prop[i]),sum); + } + } + + st->memX=st->memD=st->memE=0; + st->preemph = QCONST16(.9,15); + if (st->sampling_rate<12000) + st->notch_radius = QCONST16(.9, 15); + else if (st->sampling_rate<24000) + st->notch_radius = QCONST16(.982, 15); + else + st->notch_radius = QCONST16(.992, 15); + + st->notch_mem[0] = st->notch_mem[1] = 0; + st->adapted = 0; + st->Pey = st->Pyy = FLOAT_ONE; + +#ifdef TWO_PATH + st->Davg1 = st->Davg2 = 0; + st->Dvar1 = st->Dvar2 = FLOAT_ZERO; +#endif + + st->play_buf = (spx_int16_t*)speex_alloc((PLAYBACK_DELAY+1)*st->frame_size*sizeof(spx_int16_t)); + st->play_buf_pos = PLAYBACK_DELAY*st->frame_size; + st->play_buf_started = 0; + + return st; +} + +/** Resets echo canceller state */ +void speex_echo_state_reset(SpeexEchoState *st) +{ + int i, M, N; + st->cancel_count=0; + st->screwed_up = 0; + N = st->window_size; + M = st->M; + for (i=0;iW[i] = 0; +#ifdef TWO_PATH + for (i=0;iforeground[i] = 0; +#endif + for (i=0;iX[i] = 0; + for (i=0;i<=st->frame_size;i++) + { + st->power[i] = 0; + st->power_1[i] = FLOAT_ONE; + st->Eh[i] = 0; + st->Yh[i] = 0; + } + for (i=0;iframe_size;i++) + { + st->last_y[i] = 0; + } + for (i=0;iE[i] = 0; + st->x[i] = 0; + } + st->notch_mem[0] = st->notch_mem[1] = 0; + st->memX=st->memD=st->memE=0; + + st->saturated = 0; + st->adapted = 0; + st->sum_adapt = 0; + st->Pey = st->Pyy = FLOAT_ONE; +#ifdef TWO_PATH + st->Davg1 = st->Davg2 = 0; + st->Dvar1 = st->Dvar2 = FLOAT_ZERO; +#endif + for (i=0;i<3*st->frame_size;i++) + st->play_buf[i] = 0; + st->play_buf_pos = PLAYBACK_DELAY*st->frame_size; + st->play_buf_started = 0; + +} + +/** Destroys an echo canceller state */ +void speex_echo_state_destroy(SpeexEchoState *st) +{ + spx_fft_destroy(st->fft_table); + + speex_free(st->e); + speex_free(st->x); + speex_free(st->input); + speex_free(st->y); + speex_free(st->last_y); + speex_free(st->Yf); + speex_free(st->Rf); + speex_free(st->Xf); + speex_free(st->Yh); + speex_free(st->Eh); + + speex_free(st->X); + speex_free(st->Y); + speex_free(st->E); + speex_free(st->W); +#ifdef TWO_PATH + speex_free(st->foreground); +#endif + speex_free(st->PHI); + speex_free(st->power); + speex_free(st->power_1); + speex_free(st->window); + speex_free(st->prop); + speex_free(st->wtmp); +#ifdef FIXED_POINT + speex_free(st->wtmp2); +#endif + speex_free(st->play_buf); + speex_free(st); + +#ifdef DUMP_ECHO_CANCEL_DATA + fclose(rFile); + fclose(pFile); + fclose(oFile); + rFile = pFile = oFile = NULL; +#endif +} + +void speex_echo_capture(SpeexEchoState *st, const spx_int16_t *rec, spx_int16_t *out) +{ + int i; + /*speex_warning_int("capture with fill level ", st->play_buf_pos/st->frame_size);*/ + st->play_buf_started = 1; + if (st->play_buf_pos>=st->frame_size) + { + speex_echo_cancellation(st, rec, st->play_buf, out); + st->play_buf_pos -= st->frame_size; + for (i=0;iplay_buf_pos;i++) + st->play_buf[i] = st->play_buf[i+st->frame_size]; + } else { + speex_warning("No playback frame available (your application is buggy and/or got xruns)"); + if (st->play_buf_pos!=0) + { + speex_warning("internal playback buffer corruption?"); + st->play_buf_pos = 0; + } + for (i=0;iframe_size;i++) + out[i] = rec[i]; + } +} + +void speex_echo_playback(SpeexEchoState *st, const spx_int16_t *play) +{ + /*speex_warning_int("playback with fill level ", st->play_buf_pos/st->frame_size);*/ + if (!st->play_buf_started) + { + speex_warning("discarded first playback frame"); + return; + } + if (st->play_buf_pos<=PLAYBACK_DELAY*st->frame_size) + { + int i; + for (i=0;iframe_size;i++) + st->play_buf[st->play_buf_pos+i] = play[i]; + st->play_buf_pos += st->frame_size; + if (st->play_buf_pos <= (PLAYBACK_DELAY-1)*st->frame_size) + { + speex_warning("Auto-filling the buffer (your application is buggy and/or got xruns)"); + for (i=0;iframe_size;i++) + st->play_buf[st->play_buf_pos+i] = play[i]; + st->play_buf_pos += st->frame_size; + } + } else { + speex_warning("Had to discard a playback frame (your application is buggy and/or got xruns)"); + } +} + +/** Performs echo cancellation on a frame (deprecated, last arg now ignored) */ +void speex_echo_cancel(SpeexEchoState *st, const spx_int16_t *in, const spx_int16_t *far_end, spx_int16_t *out, spx_int32_t *Yout) +{ + speex_echo_cancellation(st, in, far_end, out); +} + +/** Performs echo cancellation on a frame */ +void speex_echo_cancellation(SpeexEchoState *st, const spx_int16_t *in, const spx_int16_t *far_end, spx_int16_t *out) +{ + int i,j; + int N,M; + spx_word32_t Syy,See,Sxx,Sdd, Sff; +#ifdef TWO_PATH + spx_word32_t Dbf; + int update_foreground; +#endif + spx_word32_t Sey; + spx_word16_t ss, ss_1; + spx_float_t Pey = FLOAT_ONE, Pyy=FLOAT_ONE; + spx_float_t alpha, alpha_1; + spx_word16_t RER; + spx_word32_t tmp32; + + N = st->window_size; + M = st->M; + st->cancel_count++; +#ifdef FIXED_POINT + ss=DIV32_16(11469,M); + ss_1 = SUB16(32767,ss); +#else + ss=.35/M; + ss_1 = 1-ss; +#endif + + /* Apply a notch filter to make sure DC doesn't end up causing problems */ + filter_dc_notch16(in, st->notch_radius, st->input, st->frame_size, st->notch_mem); + /* Copy input data to buffer and apply pre-emphasis */ + for (i=0;iframe_size;i++) + { + spx_word32_t tmp32; + tmp32 = SUB32(EXTEND32(far_end[i]), EXTEND32(MULT16_16_P15(st->preemph, st->memX))); +#ifdef FIXED_POINT + /* If saturation occurs here, we need to freeze adaptation for M+1 frames (not just one) */ + if (tmp32 > 32767) + { + tmp32 = 32767; + st->saturated = M+1; + } + if (tmp32 < -32767) + { + tmp32 = -32767; + st->saturated = M+1; + } +#endif + st->x[i+st->frame_size] = EXTRACT16(tmp32); + st->memX = far_end[i]; + + tmp32 = SUB32(EXTEND32(st->input[i]), EXTEND32(MULT16_16_P15(st->preemph, st->memD))); +#ifdef FIXED_POINT + if (tmp32 > 32767) + { + tmp32 = 32767; + if (st->saturated == 0) + st->saturated = 1; + } + if (tmp32 < -32767) + { + tmp32 = -32767; + if (st->saturated == 0) + st->saturated = 1; + } +#endif + st->memD = st->input[i]; + st->input[i] = tmp32; + } + + /* Shift memory: this could be optimized eventually*/ + for (j=M-1;j>=0;j--) + { + for (i=0;iX[(j+1)*N+i] = st->X[j*N+i]; + } + + /* Convert x (far end) to frequency domain */ + spx_fft(st->fft_table, st->x, &st->X[0]); + for (i=0;ilast_y[i] = st->x[i]; + Sxx = mdf_inner_prod(st->x+st->frame_size, st->x+st->frame_size, st->frame_size); + for (i=0;iframe_size;i++) + st->x[i] = st->x[i+st->frame_size]; + /* From here on, the top part of x is used as scratch space */ + +#ifdef TWO_PATH + /* Compute foreground filter */ + spectral_mul_accum16(st->X, st->foreground, st->Y, N, M); + spx_ifft(st->fft_table, st->Y, st->e); + for (i=0;iframe_size;i++) + st->e[i] = SUB16(st->input[i], st->e[i+st->frame_size]); + Sff = mdf_inner_prod(st->e, st->e, st->frame_size); +#endif + + /* Adjust proportional adaption rate */ + mdf_adjust_prop (st->W, N, M, st->prop); + /* Compute weight gradient */ + if (st->saturated == 0) + { + for (j=M-1;j>=0;j--) + { + weighted_spectral_mul_conj(st->power_1, FLOAT_SHL(PSEUDOFLOAT(st->prop[j]),-15), &st->X[(j+1)*N], st->E, st->PHI, N); + for (i=0;iW[j*N+i] = ADD32(st->W[j*N+i], st->PHI[i]); + + } + } else { + st->saturated--; + } + + /* Update weight to prevent circular convolution (MDF / AUMDF) */ + for (j=0;jcancel_count%(M-1) == j-1) + { +#ifdef FIXED_POINT + for (i=0;iwtmp2[i] = EXTRACT16(PSHR32(st->W[j*N+i],NORMALIZE_SCALEDOWN+16)); + spx_ifft(st->fft_table, st->wtmp2, st->wtmp); + for (i=0;iframe_size;i++) + { + st->wtmp[i]=0; + } + for (i=st->frame_size;iwtmp[i]=SHL16(st->wtmp[i],NORMALIZE_SCALEUP); + } + spx_fft(st->fft_table, st->wtmp, st->wtmp2); + /* The "-1" in the shift is a sort of kludge that trades less efficient update speed for decrease noise */ + for (i=0;iW[j*N+i] -= SHL32(EXTEND32(st->wtmp2[i]),16+NORMALIZE_SCALEDOWN-NORMALIZE_SCALEUP-1); +#else + spx_ifft(st->fft_table, &st->W[j*N], st->wtmp); + for (i=st->frame_size;iwtmp[i]=0; + } + spx_fft(st->fft_table, st->wtmp, &st->W[j*N]); +#endif + } + } + + /* Compute filter response Y */ + spectral_mul_accum(st->X, st->W, st->Y, N, M); + spx_ifft(st->fft_table, st->Y, st->y); + +#ifdef TWO_PATH + /* Difference in response, this is used to estimate the variance of our residual power estimate */ + for (i=0;iframe_size;i++) + st->e[i] = SUB16(st->e[i+st->frame_size], st->y[i+st->frame_size]); + Dbf = 10+mdf_inner_prod(st->e, st->e, st->frame_size); +#endif + + for (i=0;iframe_size;i++) + st->e[i] = SUB16(st->input[i], st->y[i+st->frame_size]); + See = mdf_inner_prod(st->e, st->e, st->frame_size); +#ifndef TWO_PATH + Sff = See; +#endif + +#ifdef TWO_PATH + /* Logic for updating the foreground filter */ + + /* For two time windows, compute the mean of the energy difference, as well as the variance */ + st->Davg1 = ADD32(MULT16_32_Q15(QCONST16(.6f,15),st->Davg1), MULT16_32_Q15(QCONST16(.4f,15),SUB32(Sff,See))); + st->Davg2 = ADD32(MULT16_32_Q15(QCONST16(.85f,15),st->Davg2), MULT16_32_Q15(QCONST16(.15f,15),SUB32(Sff,See))); + st->Dvar1 = FLOAT_ADD(FLOAT_MULT(VAR1_SMOOTH, st->Dvar1), FLOAT_MUL32U(MULT16_32_Q15(QCONST16(.4f,15),Sff), MULT16_32_Q15(QCONST16(.4f,15),Dbf))); + st->Dvar2 = FLOAT_ADD(FLOAT_MULT(VAR2_SMOOTH, st->Dvar2), FLOAT_MUL32U(MULT16_32_Q15(QCONST16(.15f,15),Sff), MULT16_32_Q15(QCONST16(.15f,15),Dbf))); + + /* Equivalent float code: + st->Davg1 = .6*st->Davg1 + .4*(Sff-See); + st->Davg2 = .85*st->Davg2 + .15*(Sff-See); + st->Dvar1 = .36*st->Dvar1 + .16*Sff*Dbf; + st->Dvar2 = .7225*st->Dvar2 + .0225*Sff*Dbf; + */ + + update_foreground = 0; + /* Check if we have a statistically significant reduction in the residual echo */ + /* Note that this is *not* Gaussian, so we need to be careful about the longer tail */ + if (FLOAT_GT(FLOAT_MUL32U(SUB32(Sff,See),ABS32(SUB32(Sff,See))), FLOAT_MUL32U(Sff,Dbf))) + update_foreground = 1; + else if (FLOAT_GT(FLOAT_MUL32U(st->Davg1, ABS32(st->Davg1)), FLOAT_MULT(VAR1_UPDATE,(st->Dvar1)))) + update_foreground = 1; + else if (FLOAT_GT(FLOAT_MUL32U(st->Davg2, ABS32(st->Davg2)), FLOAT_MULT(VAR2_UPDATE,(st->Dvar2)))) + update_foreground = 1; + + /* Do we update? */ + if (update_foreground) + { + st->Davg1 = st->Davg2 = 0; + st->Dvar1 = st->Dvar2 = FLOAT_ZERO; + /* Copy background filter to foreground filter */ + for (i=0;iforeground[i] = EXTRACT16(PSHR32(st->W[i],16)); + /* Apply a smooth transition so as to not introduce blocking artifacts */ + for (i=0;iframe_size;i++) + st->e[i+st->frame_size] = MULT16_16_Q15(st->window[i+st->frame_size],st->e[i+st->frame_size]) + MULT16_16_Q15(st->window[i],st->y[i+st->frame_size]); + } else { + int reset_background=0; + /* Otherwise, check if the background filter is significantly worse */ + if (FLOAT_GT(FLOAT_MUL32U(NEG32(SUB32(Sff,See)),ABS32(SUB32(Sff,See))), FLOAT_MULT(VAR_BACKTRACK,FLOAT_MUL32U(Sff,Dbf)))) + reset_background = 1; + if (FLOAT_GT(FLOAT_MUL32U(NEG32(st->Davg1), ABS32(st->Davg1)), FLOAT_MULT(VAR_BACKTRACK,st->Dvar1))) + reset_background = 1; + if (FLOAT_GT(FLOAT_MUL32U(NEG32(st->Davg2), ABS32(st->Davg2)), FLOAT_MULT(VAR_BACKTRACK,st->Dvar2))) + reset_background = 1; + if (reset_background) + { + /* Copy foreground filter to background filter */ + for (i=0;iW[i] = SHL32(EXTEND32(st->foreground[i]),16); + /* We also need to copy the output so as to get correct adaptation */ + for (i=0;iframe_size;i++) + st->y[i+st->frame_size] = st->e[i+st->frame_size]; + for (i=0;iframe_size;i++) + st->e[i] = SUB16(st->input[i], st->y[i+st->frame_size]); + See = Sff; + st->Davg1 = st->Davg2 = 0; + st->Dvar1 = st->Dvar2 = FLOAT_ZERO; + } + } +#endif + + /* Compute error signal (for the output with de-emphasis) */ + for (i=0;iframe_size;i++) + { + spx_word32_t tmp_out; +#ifdef TWO_PATH + tmp_out = SUB32(EXTEND32(st->input[i]), EXTEND32(st->e[i+st->frame_size])); +#else + tmp_out = SUB32(EXTEND32(st->input[i]), EXTEND32(st->y[i+st->frame_size])); +#endif + /* Saturation */ + if (tmp_out>32767) + tmp_out = 32767; + else if (tmp_out<-32768) + tmp_out = -32768; + tmp_out = ADD32(tmp_out, EXTEND32(MULT16_16_P15(st->preemph, st->memE))); + /* This is an arbitrary test for saturation in the microphone signal */ + if (in[i] <= -32000 || in[i] >= 32000) + { + tmp_out = 0; + if (st->saturated == 0) + st->saturated = 1; + } + out[i] = (spx_int16_t)tmp_out; + st->memE = tmp_out; + } + +#ifdef DUMP_ECHO_CANCEL_DATA + dump_audio(in, far_end, out, st->frame_size); +#endif + + /* Compute error signal (filter update version) */ + for (i=0;iframe_size;i++) + { + st->e[i+st->frame_size] = st->e[i]; + st->e[i] = 0; + } + + /* Compute a bunch of correlations */ + Sey = mdf_inner_prod(st->e+st->frame_size, st->y+st->frame_size, st->frame_size); + Syy = mdf_inner_prod(st->y+st->frame_size, st->y+st->frame_size, st->frame_size); + Sdd = mdf_inner_prod(st->input, st->input, st->frame_size); + + /*printf ("%f %f %f %f\n", Sff, See, Syy, Sdd, st->update_cond);*/ + + /* Do some sanity check */ + if (!(Syy>=0 && Sxx>=0 && See >= 0) +#ifndef FIXED_POINT + || !(Sff < N*1e9 && Syy < N*1e9 && Sxx < N*1e9) +#endif + ) + { + /* Things have gone really bad */ + st->screwed_up += 50; + for (i=0;iframe_size;i++) + out[i] = 0; + } else if (SHR32(Sff, 2) > ADD32(Sdd, SHR32(MULT16_16(N, 10000),6))) + { + /* AEC seems to add lots of echo instead of removing it, let's see if it will improve */ + st->screwed_up++; + } else { + /* Everything's fine */ + st->screwed_up=0; + } + if (st->screwed_up>=50) + { + speex_warning("The echo canceller started acting funny and got slapped (reset). It swears it will behave now."); + speex_echo_state_reset(st); + return; + } + + /* Add a small noise floor to make sure not to have problems when dividing */ + See = MAX32(See, SHR32(MULT16_16(N, 100),6)); + + /* Convert error to frequency domain */ + spx_fft(st->fft_table, st->e, st->E); + for (i=0;iframe_size;i++) + st->y[i] = 0; + spx_fft(st->fft_table, st->y, st->Y); + + /* Compute power spectrum of far end (X), error (E) and filter response (Y) */ + power_spectrum(st->E, st->Rf, N); + power_spectrum(st->Y, st->Yf, N); + power_spectrum(st->X, st->Xf, N); + + /* Smooth far end energy estimate over time */ + for (j=0;j<=st->frame_size;j++) + st->power[j] = MULT16_32_Q15(ss_1,st->power[j]) + 1 + MULT16_32_Q15(ss,st->Xf[j]); + + /* Enable this to compute the power based only on the tail (would need to compute more + efficiently to make this really useful */ + if (0) + { + float scale2 = .5f/M; + for (j=0;j<=st->frame_size;j++) + st->power[j] = 100; + for (i=0;iX[i*N], st->Xf, N); + for (j=0;j<=st->frame_size;j++) + st->power[j] += scale2*st->Xf[j]; + } + } + + /* Compute filtered spectra and (cross-)correlations */ + for (j=st->frame_size;j>=0;j--) + { + spx_float_t Eh, Yh; + Eh = PSEUDOFLOAT(st->Rf[j] - st->Eh[j]); + Yh = PSEUDOFLOAT(st->Yf[j] - st->Yh[j]); + Pey = FLOAT_ADD(Pey,FLOAT_MULT(Eh,Yh)); + Pyy = FLOAT_ADD(Pyy,FLOAT_MULT(Yh,Yh)); +#ifdef FIXED_POINT + st->Eh[j] = MAC16_32_Q15(MULT16_32_Q15(SUB16(32767,st->spec_average),st->Eh[j]), st->spec_average, st->Rf[j]); + st->Yh[j] = MAC16_32_Q15(MULT16_32_Q15(SUB16(32767,st->spec_average),st->Yh[j]), st->spec_average, st->Yf[j]); +#else + st->Eh[j] = (1-st->spec_average)*st->Eh[j] + st->spec_average*st->Rf[j]; + st->Yh[j] = (1-st->spec_average)*st->Yh[j] + st->spec_average*st->Yf[j]; +#endif + } + + Pyy = FLOAT_SQRT(Pyy); + Pey = FLOAT_DIVU(Pey,Pyy); + + /* Compute correlation updatete rate */ + tmp32 = MULT16_32_Q15(st->beta0,Syy); + if (tmp32 > MULT16_32_Q15(st->beta_max,See)) + tmp32 = MULT16_32_Q15(st->beta_max,See); + alpha = FLOAT_DIV32(tmp32, See); + alpha_1 = FLOAT_SUB(FLOAT_ONE, alpha); + /* Update correlations (recursive average) */ + st->Pey = FLOAT_ADD(FLOAT_MULT(alpha_1,st->Pey) , FLOAT_MULT(alpha,Pey)); + st->Pyy = FLOAT_ADD(FLOAT_MULT(alpha_1,st->Pyy) , FLOAT_MULT(alpha,Pyy)); + if (FLOAT_LT(st->Pyy, FLOAT_ONE)) + st->Pyy = FLOAT_ONE; + /* We don't really hope to get better than 33 dB (MIN_LEAK-3dB) attenuation anyway */ + if (FLOAT_LT(st->Pey, FLOAT_MULT(MIN_LEAK,st->Pyy))) + st->Pey = FLOAT_MULT(MIN_LEAK,st->Pyy); + if (FLOAT_GT(st->Pey, st->Pyy)) + st->Pey = st->Pyy; + /* leak_estimate is the linear regression result */ + st->leak_estimate = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIVU(st->Pey, st->Pyy),14)); + /* This looks like a stupid bug, but it's right (because we convert from Q14 to Q15) */ + if (st->leak_estimate > 16383) + st->leak_estimate = 32767; + else + st->leak_estimate = SHL16(st->leak_estimate,1); + /*printf ("%f\n", st->leak_estimate);*/ + + /* Compute Residual to Error Ratio */ +#ifdef FIXED_POINT + tmp32 = MULT16_32_Q15(st->leak_estimate,Syy); + tmp32 = ADD32(SHR32(Sxx,13), ADD32(tmp32, SHL32(tmp32,1))); + /* Check for y in e (lower bound on RER) */ + { + spx_float_t bound = PSEUDOFLOAT(Sey); + bound = FLOAT_DIVU(FLOAT_MULT(bound, bound), PSEUDOFLOAT(ADD32(1,Syy))); + if (FLOAT_GT(bound, PSEUDOFLOAT(See))) + tmp32 = See; + else if (tmp32 < FLOAT_EXTRACT32(bound)) + tmp32 = FLOAT_EXTRACT32(bound); + } + if (tmp32 > SHR32(See,1)) + tmp32 = SHR32(See,1); + RER = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIV32(tmp32,See),15)); +#else + RER = (.0001*Sxx + 3.*MULT16_32_Q15(st->leak_estimate,Syy)) / See; + /* Check for y in e (lower bound on RER) */ + if (RER < Sey*Sey/(1+See*Syy)) + RER = Sey*Sey/(1+See*Syy); + if (RER > .5) + RER = .5; +#endif + + /* We consider that the filter has had minimal adaptation if the following is true*/ + if (!st->adapted && st->sum_adapt > SHL32(EXTEND32(M),15) && MULT16_32_Q15(st->leak_estimate,Syy) > MULT16_32_Q15(QCONST16(.03f,15),Syy)) + { + st->adapted = 1; + } + + if (st->adapted) + { + /* Normal learning rate calculation once we're past the minimal adaptation phase */ + for (i=0;i<=st->frame_size;i++) + { + spx_word32_t r, e; + /* Compute frequency-domain adaptation mask */ + r = MULT16_32_Q15(st->leak_estimate,SHL32(st->Yf[i],3)); + e = SHL32(st->Rf[i],3)+1; +#ifdef FIXED_POINT + if (r>SHR32(e,1)) + r = SHR32(e,1); +#else + if (r>.5*e) + r = .5*e; +#endif + r = MULT16_32_Q15(QCONST16(.7,15),r) + MULT16_32_Q15(QCONST16(.3,15),(spx_word32_t)(MULT16_32_Q15(RER,e))); + /*st->power_1[i] = adapt_rate*r/(e*(1+st->power[i]));*/ + st->power_1[i] = FLOAT_SHL(FLOAT_DIV32_FLOAT(r,FLOAT_MUL32U(e,st->power[i]+10)),WEIGHT_SHIFT+16); + } + } else { + /* Temporary adaption rate if filter is not yet adapted enough */ + spx_word16_t adapt_rate=0; + + if (Sxx > SHR32(MULT16_16(N, 1000),6)) + { + tmp32 = MULT16_32_Q15(QCONST16(.25f, 15), Sxx); +#ifdef FIXED_POINT + if (tmp32 > SHR32(See,2)) + tmp32 = SHR32(See,2); +#else + if (tmp32 > .25*See) + tmp32 = .25*See; +#endif + adapt_rate = FLOAT_EXTRACT16(FLOAT_SHL(FLOAT_DIV32(tmp32, See),15)); + } + for (i=0;i<=st->frame_size;i++) + st->power_1[i] = FLOAT_SHL(FLOAT_DIV32(EXTEND32(adapt_rate),ADD32(st->power[i],10)),WEIGHT_SHIFT+1); + + + /* How much have we adapted so far? */ + st->sum_adapt = ADD32(st->sum_adapt,adapt_rate); + } + + /* Save residual echo so it can be used by the nonlinear processor */ + if (st->adapted) + { + /* If the filter is adapted, take the filtered echo */ + for (i=0;iframe_size;i++) + st->last_y[i] = st->last_y[st->frame_size+i]; + for (i=0;iframe_size;i++) + st->last_y[st->frame_size+i] = in[i]-out[i]; + } else { + /* If filter isn't adapted yet, all we can do is take the far end signal directly */ + /* moved earlier: for (i=0;ilast_y[i] = st->x[i];*/ + } + +} + +/* Compute spectrum of estimated echo for use in an echo post-filter */ +void speex_echo_get_residual(SpeexEchoState *st, spx_word32_t *residual_echo, int len) +{ + int i; + spx_word16_t leak2; + int N; + + N = st->window_size; + + /* Apply hanning window (should pre-compute it)*/ + for (i=0;iy[i] = MULT16_16_Q15(st->window[i],st->last_y[i]); + + /* Compute power spectrum of the echo */ + spx_fft(st->fft_table, st->y, st->Y); + power_spectrum(st->Y, residual_echo, N); + +#ifdef FIXED_POINT + if (st->leak_estimate > 16383) + leak2 = 32767; + else + leak2 = SHL16(st->leak_estimate, 1); +#else + if (st->leak_estimate>.5) + leak2 = 1; + else + leak2 = 2*st->leak_estimate; +#endif + /* Estimate residual echo */ + for (i=0;i<=st->frame_size;i++) + residual_echo[i] = (spx_int32_t)MULT16_32_Q15(leak2,residual_echo[i]); + +} + +int speex_echo_ctl(SpeexEchoState *st, int request, void *ptr) +{ + switch(request) + { + + case SPEEX_ECHO_GET_FRAME_SIZE: + (*(int*)ptr) = st->frame_size; + break; + case SPEEX_ECHO_SET_SAMPLING_RATE: + st->sampling_rate = (*(int*)ptr); + st->spec_average = DIV32_16(SHL32(EXTEND32(st->frame_size), 15), st->sampling_rate); +#ifdef FIXED_POINT + st->beta0 = DIV32_16(SHL32(EXTEND32(st->frame_size), 16), st->sampling_rate); + st->beta_max = DIV32_16(SHL32(EXTEND32(st->frame_size), 14), st->sampling_rate); +#else + st->beta0 = (2.0f*st->frame_size)/st->sampling_rate; + st->beta_max = (.5f*st->frame_size)/st->sampling_rate; +#endif + if (st->sampling_rate<12000) + st->notch_radius = QCONST16(.9, 15); + else if (st->sampling_rate<24000) + st->notch_radius = QCONST16(.982, 15); + else + st->notch_radius = QCONST16(.992, 15); + break; + case SPEEX_ECHO_GET_SAMPLING_RATE: + (*(int*)ptr) = st->sampling_rate; + break; + default: + speex_warning_int("Unknown speex_echo_ctl request: ", request); + return -1; + } + return 0; +} diff --git a/src/libspeex/misc_bfin.h b/src/libspeex/misc_bfin.h new file mode 100644 index 0000000..77b082c --- /dev/null +++ b/src/libspeex/misc_bfin.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2005 Analog Devices */ +/** + @file misc_bfin.h + @author Jean-Marc Valin + @brief Various compatibility routines for Speex (Blackfin version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_SPEEX_MOVE +void *speex_move (void *dest, void *src, int n) +{ + __asm__ __volatile__ + ( + "L0 = 0;\n\t" + "I0 = %0;\n\t" + "R0 = [I0++];\n\t" + "LOOP move%= LC0 = %2;\n\t" + "LOOP_BEGIN move%=;\n\t" + "[%1++] = R0 || R0 = [I0++];\n\t" + "LOOP_END move%=;\n\t" + "[%1++] = R0;\n\t" + : "=a" (src), "=a" (dest) + : "a" ((n>>2)-1), "0" (src), "1" (dest) + : "R0", "I0", "L0", "memory" + ); + return dest; +} diff --git a/src/libspeex/modes.c b/src/libspeex/modes.c new file mode 100644 index 0000000..33e031e --- /dev/null +++ b/src/libspeex/modes.c @@ -0,0 +1,362 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: modes.c + + Describes the different modes of the codec + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "modes.h" +#include "ltp.h" +#include "quant_lsp.h" +#include "cb_search.h" +#include "sb_celp.h" +#include "nb_celp.h" +#include "vbr.h" +#include "arch.h" +#include + +#ifndef NULL +#define NULL 0 +#endif + + +/* Extern declarations for all codebooks we use here */ +extern const signed char gain_cdbk_nb[]; +extern const signed char gain_cdbk_lbr[]; +extern const signed char exc_5_256_table[]; +extern const signed char exc_5_64_table[]; +extern const signed char exc_8_128_table[]; +extern const signed char exc_10_32_table[]; +extern const signed char exc_10_16_table[]; +extern const signed char exc_20_32_table[]; + + +/* Parameters for Long-Term Prediction (LTP)*/ +static const ltp_params ltp_params_nb = { + gain_cdbk_nb, + 7, + 7 +}; + +/* Parameters for Long-Term Prediction (LTP)*/ +static const ltp_params ltp_params_vlbr = { + gain_cdbk_lbr, + 5, + 0 +}; + +/* Parameters for Long-Term Prediction (LTP)*/ +static const ltp_params ltp_params_lbr = { + gain_cdbk_lbr, + 5, + 7 +}; + +/* Parameters for Long-Term Prediction (LTP)*/ +static const ltp_params ltp_params_med = { + gain_cdbk_lbr, + 5, + 7 +}; + +/* Split-VQ innovation parameters for very low bit-rate narrowband */ +static const split_cb_params split_cb_nb_vlbr = { + 10, /*subvect_size*/ + 4, /*nb_subvect*/ + exc_10_16_table, /*shape_cb*/ + 4, /*shape_bits*/ + 0, +}; + +/* Split-VQ innovation parameters for very low bit-rate narrowband */ +static const split_cb_params split_cb_nb_ulbr = { + 20, /*subvect_size*/ + 2, /*nb_subvect*/ + exc_20_32_table, /*shape_cb*/ + 5, /*shape_bits*/ + 0, +}; + +/* Split-VQ innovation parameters for low bit-rate narrowband */ +static const split_cb_params split_cb_nb_lbr = { + 10, /*subvect_size*/ + 4, /*nb_subvect*/ + exc_10_32_table, /*shape_cb*/ + 5, /*shape_bits*/ + 0, +}; + + +/* Split-VQ innovation parameters narrowband */ +static const split_cb_params split_cb_nb = { + 5, /*subvect_size*/ + 8, /*nb_subvect*/ + exc_5_64_table, /*shape_cb*/ + 6, /*shape_bits*/ + 0, +}; + +/* Split-VQ innovation parameters narrowband */ +static const split_cb_params split_cb_nb_med = { + 8, /*subvect_size*/ + 5, /*nb_subvect*/ + exc_8_128_table, /*shape_cb*/ + 7, /*shape_bits*/ + 0, +}; + +/* Split-VQ innovation for low-band wideband */ +static const split_cb_params split_cb_sb = { + 5, /*subvect_size*/ + 8, /*nb_subvect*/ + exc_5_256_table, /*shape_cb*/ + 8, /*shape_bits*/ + 0, +}; + + + +/* 2150 bps "vocoder-like" mode for comfort noise */ +static const SpeexSubmode nb_submode1 = { + 0, + 1, + 0, + 0, + /* LSP quantization */ + lsp_quant_lbr, + lsp_unquant_lbr, + /* No pitch quantization */ + forced_pitch_quant, + forced_pitch_unquant, + NULL, + /* No innovation quantization (noise only) */ + noise_codebook_quant, + noise_codebook_unquant, + NULL, + -1, + 43 +}; + +/* 3.95 kbps very low bit-rate mode */ +static const SpeexSubmode nb_submode8 = { + 0, + 1, + 0, + 0, + /*LSP quantization*/ + lsp_quant_lbr, + lsp_unquant_lbr, + /*No pitch quantization*/ + forced_pitch_quant, + forced_pitch_unquant, + NULL, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb_ulbr, + QCONST16(.5,15), + 79 +}; + +/* 5.95 kbps very low bit-rate mode */ +static const SpeexSubmode nb_submode2 = { + 0, + 0, + 0, + 0, + /*LSP quantization*/ + lsp_quant_lbr, + lsp_unquant_lbr, + /*No pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_vlbr, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb_vlbr, + QCONST16(.6,15), + 119 +}; + +/* 8 kbps low bit-rate mode */ +static const SpeexSubmode nb_submode3 = { + -1, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_lbr, + lsp_unquant_lbr, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_lbr, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb_lbr, + QCONST16(.55,15), + 160 +}; + +/* 11 kbps medium bit-rate mode */ +static const SpeexSubmode nb_submode4 = { + -1, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_lbr, + lsp_unquant_lbr, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_med, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb_med, + QCONST16(.45,15), + 220 +}; + +/* 15 kbps high bit-rate mode */ +static const SpeexSubmode nb_submode5 = { + -1, + 0, + 3, + 0, + /*LSP quantization*/ + lsp_quant_nb, + lsp_unquant_nb, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_nb, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb, + QCONST16(.3,15), + 300 +}; + +/* 18.2 high bit-rate mode */ +static const SpeexSubmode nb_submode6 = { + -1, + 0, + 3, + 0, + /*LSP quantization*/ + lsp_quant_nb, + lsp_unquant_nb, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_nb, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_sb, + QCONST16(.2,15), + 364 +}; + +/* 24.6 kbps high bit-rate mode */ +static const SpeexSubmode nb_submode7 = { + -1, + 0, + 3, + 1, + /*LSP quantization*/ + lsp_quant_nb, + lsp_unquant_nb, + /*Pitch quantization*/ + pitch_search_3tap, + pitch_unquant_3tap, + <p_params_nb, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, + &split_cb_nb, + QCONST16(.1,15), + 492 +}; + + +/* Default mode for narrowband */ +static const SpeexNBMode nb_mode = { + 160, /*frameSize*/ + 40, /*subframeSize*/ + 10, /*lpcSize*/ + 17, /*pitchStart*/ + 144, /*pitchEnd*/ +#ifdef FIXED_POINT + 29491, 19661, /* gamma1, gamma2 */ +#else + 0.9, 0.6, /* gamma1, gamma2 */ +#endif + QCONST16(.0002,15), /*lpc_floor*/ + {NULL, &nb_submode1, &nb_submode2, &nb_submode3, &nb_submode4, &nb_submode5, &nb_submode6, &nb_submode7, + &nb_submode8, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + 5, + {1, 8, 2, 3, 3, 4, 4, 5, 5, 6, 7} +}; + + +/* Default mode for narrowband */ +const SpeexMode speex_nb_mode = { + &nb_mode, + nb_mode_query, + "narrowband", + 0, + 4, + &nb_encoder_init, + &nb_encoder_destroy, + &nb_encode, + &nb_decoder_init, + &nb_decoder_destroy, + &nb_decode, + &nb_encoder_ctl, + &nb_decoder_ctl, +}; + + + +int speex_mode_query(const SpeexMode *mode, int request, void *ptr) +{ + return mode->query(mode->mode, request, ptr); +} + diff --git a/src/libspeex/modes.h b/src/libspeex/modes.h new file mode 100644 index 0000000..26e2d86 --- /dev/null +++ b/src/libspeex/modes.h @@ -0,0 +1,161 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin */ +/** + @file modes.h + @brief Describes the different modes of the codec +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef MODES_H +#define MODES_H + +#include +#include +#include "arch.h" + +#define NB_SUBMODES 16 +#define NB_SUBMODE_BITS 4 + +#define SB_SUBMODES 8 +#define SB_SUBMODE_BITS 3 + +/* Used internally, NOT TO BE USED in applications */ +/** Used internally*/ +#define SPEEX_GET_PI_GAIN 100 +/** Used internally*/ +#define SPEEX_GET_EXC 101 +/** Used internally*/ +#define SPEEX_GET_INNOV 102 +/** Used internally*/ +#define SPEEX_GET_DTX_STATUS 103 +/** Used internally*/ +#define SPEEX_SET_INNOVATION_SAVE 104 +/** Used internally*/ +#define SPEEX_SET_WIDEBAND 105 + +/** Used internally*/ +#define SPEEX_GET_STACK 106 + + +/** Quantizes LSPs */ +typedef void (*lsp_quant_func)(spx_lsp_t *, spx_lsp_t *, int, SpeexBits *); + +/** Decodes quantized LSPs */ +typedef void (*lsp_unquant_func)(spx_lsp_t *, int, SpeexBits *); + + +/** Long-term predictor quantization */ +typedef int (*ltp_quant_func)(spx_word16_t *, spx_word16_t *, spx_coef_t *, spx_coef_t *, + spx_coef_t *, spx_sig_t *, const void *, int, int, spx_word16_t, + int, int, SpeexBits*, char *, spx_word16_t *, spx_word16_t *, int, int, int, spx_word32_t *); + +/** Long-term un-quantize */ +typedef void (*ltp_unquant_func)(spx_word16_t *, spx_word32_t *, int, int, spx_word16_t, const void *, int, int *, + spx_word16_t *, SpeexBits*, char*, int, int, spx_word16_t, int); + + +/** Innovation quantization function */ +typedef void (*innovation_quant_func)(spx_word16_t *, spx_coef_t *, spx_coef_t *, spx_coef_t *, const void *, int, int, + spx_sig_t *, spx_word16_t *, SpeexBits *, char *, int, int); + +/** Innovation unquantization function */ +typedef void (*innovation_unquant_func)(spx_sig_t *, const void *, int, SpeexBits*, char *, spx_int32_t *); + +/** Description of a Speex sub-mode (wither narrowband or wideband */ +typedef struct SpeexSubmode { + int lbr_pitch; /**< Set to -1 for "normal" modes, otherwise encode pitch using a global pitch and allowing a +- lbr_pitch variation (for low not-rates)*/ + int forced_pitch_gain; /**< Use the same (forced) pitch gain for all sub-frames */ + int have_subframe_gain; /**< Number of bits to use as sub-frame innovation gain */ + int double_codebook; /**< Apply innovation quantization twice for higher quality (and higher bit-rate)*/ + /*LSP functions*/ + lsp_quant_func lsp_quant; /**< LSP quantization function */ + lsp_unquant_func lsp_unquant; /**< LSP unquantization function */ + + /*Long-term predictor functions*/ + ltp_quant_func ltp_quant; /**< Long-term predictor (pitch) quantizer */ + ltp_unquant_func ltp_unquant; /**< Long-term predictor (pitch) un-quantizer */ + const void *ltp_params; /**< Pitch parameters (options) */ + + /*Quantization of innovation*/ + innovation_quant_func innovation_quant; /**< Innovation quantization */ + innovation_unquant_func innovation_unquant; /**< Innovation un-quantization */ + const void *innovation_params; /**< Innovation quantization parameters*/ + + spx_word16_t comb_gain; /**< Gain of enhancer comb filter */ + + int bits_per_frame; /**< Number of bits per frame after encoding*/ +} SpeexSubmode; + +/** Struct defining the encoding/decoding mode*/ +typedef struct SpeexNBMode { + int frameSize; /**< Size of frames used for encoding */ + int subframeSize; /**< Size of sub-frames used for encoding */ + int lpcSize; /**< Order of LPC filter */ + int pitchStart; /**< Smallest pitch value allowed */ + int pitchEnd; /**< Largest pitch value allowed */ + + spx_word16_t gamma1; /**< Perceptual filter parameter #1 */ + spx_word16_t gamma2; /**< Perceptual filter parameter #2 */ + spx_word16_t lpc_floor; /**< Noise floor for LPC analysis */ + + const SpeexSubmode *submodes[NB_SUBMODES]; /**< Sub-mode data for the mode */ + int defaultSubmode; /**< Default sub-mode to use when encoding */ + int quality_map[11]; /**< Mode corresponding to each quality setting */ +} SpeexNBMode; + + +/** Struct defining the encoding/decoding mode for SB-CELP (wideband) */ +typedef struct SpeexSBMode { + const SpeexMode *nb_mode; /**< Embedded narrowband mode */ + int frameSize; /**< Size of frames used for encoding */ + int subframeSize; /**< Size of sub-frames used for encoding */ + int lpcSize; /**< Order of LPC filter */ + spx_word16_t gamma1; /**< Perceptual filter parameter #1 */ + spx_word16_t gamma2; /**< Perceptual filter parameter #1 */ + spx_word16_t lpc_floor; /**< Noise floor for LPC analysis */ + spx_word16_t folding_gain; + + const SpeexSubmode *submodes[SB_SUBMODES]; /**< Sub-mode data for the mode */ + int defaultSubmode; /**< Default sub-mode to use when encoding */ + int low_quality_map[11]; /**< Mode corresponding to each quality setting */ + int quality_map[11]; /**< Mode corresponding to each quality setting */ +#ifndef DISABLE_VBR + const float (*vbr_thresh)[11]; +#endif + int nb_modes; +} SpeexSBMode; + +int speex_encode_native(void *state, spx_word16_t *in, SpeexBits *bits); +int speex_decode_native(void *state, SpeexBits *bits, spx_word16_t *out); + +int nb_mode_query(const void *mode, int request, void *ptr); +int wb_mode_query(const void *mode, int request, void *ptr); + +#endif diff --git a/src/libspeex/modes_wb.c b/src/libspeex/modes_wb.c new file mode 100644 index 0000000..4b575b2 --- /dev/null +++ b/src/libspeex/modes_wb.c @@ -0,0 +1,300 @@ +/* Copyright (C) 2002-2007 Jean-Marc Valin + File: modes.c + + Describes the wideband modes of the codec + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "modes.h" +#include "ltp.h" +#include "quant_lsp.h" +#include "cb_search.h" +#include "sb_celp.h" +#include "nb_celp.h" +#include "vbr.h" +#include "arch.h" +#include +#include "os_support.h" + + +#ifndef NULL +#define NULL 0 +#endif + +const SpeexMode * const speex_mode_list[SPEEX_NB_MODES] = {&speex_nb_mode, &speex_wb_mode, &speex_uwb_mode}; + +extern const signed char hexc_table[]; +extern const signed char hexc_10_32_table[]; + +#ifndef DISABLE_WIDEBAND + +/* Split-VQ innovation for high-band wideband */ +static const split_cb_params split_cb_high = { + 8, /*subvect_size*/ + 5, /*nb_subvect*/ + hexc_table, /*shape_cb*/ + 7, /*shape_bits*/ + 1, +}; + + +/* Split-VQ innovation for high-band wideband */ +static const split_cb_params split_cb_high_lbr = { + 10, /*subvect_size*/ + 4, /*nb_subvect*/ + hexc_10_32_table, /*shape_cb*/ + 5, /*shape_bits*/ + 0, +}; + +#endif + + +static const SpeexSubmode wb_submode1 = { + 0, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_high, + lsp_unquant_high, + /*Pitch quantization*/ + NULL, + NULL, + NULL, + /*No innovation quantization*/ + NULL, + NULL, + NULL, + -1, + 36 +}; + + +static const SpeexSubmode wb_submode2 = { + 0, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_high, + lsp_unquant_high, + /*Pitch quantization*/ + NULL, + NULL, + NULL, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, +#ifdef DISABLE_WIDEBAND + NULL, +#else + &split_cb_high_lbr, +#endif + -1, + 112 +}; + + +static const SpeexSubmode wb_submode3 = { + 0, + 0, + 1, + 0, + /*LSP quantization*/ + lsp_quant_high, + lsp_unquant_high, + /*Pitch quantization*/ + NULL, + NULL, + NULL, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, +#ifdef DISABLE_WIDEBAND + NULL, +#else + &split_cb_high, +#endif + -1, + 192 +}; + +static const SpeexSubmode wb_submode4 = { + 0, + 0, + 1, + 1, + /*LSP quantization*/ + lsp_quant_high, + lsp_unquant_high, + /*Pitch quantization*/ + NULL, + NULL, + NULL, + /*Innovation quantization*/ + split_cb_search_shape_sign, + split_cb_shape_sign_unquant, +#ifdef DISABLE_WIDEBAND + NULL, +#else + &split_cb_high, +#endif + -1, + 352 +}; + + +/* Split-band wideband CELP mode*/ +static const SpeexSBMode sb_wb_mode = { + &speex_nb_mode, + 160, /*frameSize*/ + 40, /*subframeSize*/ + 8, /*lpcSize*/ +#ifdef FIXED_POINT + 29491, 19661, /* gamma1, gamma2 */ +#else + 0.9, 0.6, /* gamma1, gamma2 */ +#endif + QCONST16(.0002,15), /*lpc_floor*/ + QCONST16(0.9f,15), + {NULL, &wb_submode1, &wb_submode2, &wb_submode3, &wb_submode4, NULL, NULL, NULL}, + 3, + {1, 8, 2, 3, 4, 5, 5, 6, 6, 7, 7}, + {1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 4}, +#ifndef DISABLE_VBR + vbr_hb_thresh, +#endif + 5 +}; + + +const SpeexMode speex_wb_mode = { + &sb_wb_mode, + wb_mode_query, + "wideband (sub-band CELP)", + 1, + 4, + &sb_encoder_init, + &sb_encoder_destroy, + &sb_encode, + &sb_decoder_init, + &sb_decoder_destroy, + &sb_decode, + &sb_encoder_ctl, + &sb_decoder_ctl, +}; + + + +/* "Ultra-wideband" mode stuff */ + + + +/* Split-band "ultra-wideband" (32 kbps) CELP mode*/ +static const SpeexSBMode sb_uwb_mode = { + &speex_wb_mode, + 320, /*frameSize*/ + 80, /*subframeSize*/ + 8, /*lpcSize*/ +#ifdef FIXED_POINT + 29491, 19661, /* gamma1, gamma2 */ +#else + 0.9, 0.6, /* gamma1, gamma2 */ +#endif + QCONST16(.0002,15), /*lpc_floor*/ + QCONST16(0.7f,15), + {NULL, &wb_submode1, NULL, NULL, NULL, NULL, NULL, NULL}, + 1, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, +#ifndef DISABLE_VBR + vbr_uhb_thresh, +#endif + 2 +}; + +int wb_mode_query(const void *mode, int request, void *ptr) +{ + const SpeexSBMode *m = (const SpeexSBMode*)mode; + + switch (request) + { + case SPEEX_MODE_FRAME_SIZE: + *((int*)ptr)=2*m->frameSize; + break; + case SPEEX_SUBMODE_BITS_PER_FRAME: + if (*((int*)ptr)==0) + *((int*)ptr) = SB_SUBMODE_BITS+1; + else if (m->submodes[*((int*)ptr)]==NULL) + *((int*)ptr) = -1; + else + *((int*)ptr) = m->submodes[*((int*)ptr)]->bits_per_frame; + break; + default: + speex_warning_int("Unknown wb_mode_query request: ", request); + return -1; + } + return 0; +} + + +const SpeexMode speex_uwb_mode = { + &sb_uwb_mode, + wb_mode_query, + "ultra-wideband (sub-band CELP)", + 2, + 4, + &sb_encoder_init, + &sb_encoder_destroy, + &sb_encode, + &sb_decoder_init, + &sb_decoder_destroy, + &sb_decode, + &sb_encoder_ctl, + &sb_decoder_ctl, +}; + +/* We have defined speex_lib_get_mode() as a macro in speex.h */ +#undef speex_lib_get_mode + +const SpeexMode * speex_lib_get_mode (int mode) +{ + if (mode < 0 || mode >= SPEEX_NB_MODES) return NULL; + + return speex_mode_list[mode]; +} + + + diff --git a/src/libspeex/nb_celp.c b/src/libspeex/nb_celp.c new file mode 100644 index 0000000..9dd726a --- /dev/null +++ b/src/libspeex/nb_celp.c @@ -0,0 +1,1903 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: nb_celp.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "nb_celp.h" +#include "lpc.h" +#include "lsp.h" +#include "ltp.h" +#include "quant_lsp.h" +#include "cb_search.h" +#include "filters.h" +#include "stack_alloc.h" +#include "vq.h" +#include +#include "vbr.h" +#include "arch.h" +#include "math_approx.h" +#include "os_support.h" +#include + +#ifdef VORBIS_PSYCHO +#include "vorbis_psy.h" +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#define SUBMODE(x) st->submodes[st->submodeID]->x + +/* Default size for the encoder and decoder stack (can be changed at compile time). + This does not apply when using variable-size arrays or alloca. */ +#ifndef NB_ENC_STACK +#define NB_ENC_STACK (8000*sizeof(spx_sig_t)) +#endif + +#ifndef NB_DEC_STACK +#define NB_DEC_STACK (4000*sizeof(spx_sig_t)) +#endif + + +#ifdef FIXED_POINT +const spx_word32_t ol_gain_table[32]={18900, 25150, 33468, 44536, 59265, 78865, 104946, 139653, 185838, 247297, 329081, 437913, 582736, 775454, 1031906, 1373169, 1827293, 2431601, 3235761, 4305867, 5729870, 7624808, 10146425, 13501971, 17967238, 23909222, 31816294, 42338330, 56340132, 74972501, 99766822, 132760927}; +const spx_word16_t exc_gain_quant_scal3_bound[7]={1841, 3883, 6051, 8062, 10444, 13580, 18560}; +const spx_word16_t exc_gain_quant_scal3[8]={1002, 2680, 5086, 7016, 9108, 11781, 15380, 21740}; +const spx_word16_t exc_gain_quant_scal1_bound[1]={14385}; +const spx_word16_t exc_gain_quant_scal1[2]={11546, 17224}; + +#define LSP_MARGIN 16 +#define LSP_DELTA1 6553 +#define LSP_DELTA2 1638 + +#else + +const float exc_gain_quant_scal3_bound[7]={0.112338f, 0.236980f, 0.369316f, 0.492054f, 0.637471f, 0.828874f, 1.132784f}; +const float exc_gain_quant_scal3[8]={0.061130f, 0.163546f, 0.310413f, 0.428220f, 0.555887f, 0.719055f, 0.938694f, 1.326874f}; +const float exc_gain_quant_scal1_bound[1]={0.87798f}; +const float exc_gain_quant_scal1[2]={0.70469f, 1.05127f}; + +#define LSP_MARGIN .002f +#define LSP_DELTA1 .2f +#define LSP_DELTA2 .05f + +#endif + +#ifdef VORBIS_PSYCHO +#define EXTRA_BUFFER 100 +#else +#define EXTRA_BUFFER 0 +#endif + + +#define sqr(x) ((x)*(x)) + +extern const spx_word16_t lag_window[]; +extern const spx_word16_t lpc_window[]; + +void *nb_encoder_init(const SpeexMode *m) +{ + EncState *st; + const SpeexNBMode *mode; + int i; + + mode=(const SpeexNBMode *)m->mode; + st = (EncState*)speex_alloc(sizeof(EncState)); + if (!st) + return NULL; +#if defined(VAR_ARRAYS) || defined (USE_ALLOCA) + st->stack = NULL; +#else + st->stack = (char*)speex_alloc_scratch(NB_ENC_STACK); +#endif + + st->mode=m; + + st->frameSize = mode->frameSize; + st->nbSubframes=mode->frameSize/mode->subframeSize; + st->subframeSize=mode->subframeSize; + st->windowSize = st->frameSize+st->subframeSize; + st->lpcSize = mode->lpcSize; + st->gamma1=mode->gamma1; + st->gamma2=mode->gamma2; + st->min_pitch=mode->pitchStart; + st->max_pitch=mode->pitchEnd; + st->lpc_floor = mode->lpc_floor; + + st->submodes=mode->submodes; + st->submodeID=st->submodeSelect=mode->defaultSubmode; + st->bounded_pitch = 1; + + st->encode_submode = 1; + +#ifdef VORBIS_PSYCHO + st->psy = vorbis_psy_init(8000, 256); + st->curve = (float*)speex_alloc(128*sizeof(float)); + st->old_curve = (float*)speex_alloc(128*sizeof(float)); + st->psy_window = (float*)speex_alloc(256*sizeof(float)); +#endif + + st->cumul_gain = 1024; + + /* Allocating input buffer */ + st->winBuf = (spx_word16_t*)speex_alloc((st->windowSize-st->frameSize)*sizeof(spx_word16_t)); + /* Allocating excitation buffer */ + st->excBuf = (spx_word16_t*)speex_alloc((mode->frameSize+mode->pitchEnd+2)*sizeof(spx_word16_t)); + st->exc = st->excBuf + mode->pitchEnd + 2; + st->swBuf = (spx_word16_t*)speex_alloc((mode->frameSize+mode->pitchEnd+2)*sizeof(spx_word16_t)); + st->sw = st->swBuf + mode->pitchEnd + 2; + + st->window= lpc_window; + + /* Create the window for autocorrelation (lag-windowing) */ + st->lagWindow = lag_window; + + st->old_lsp = (spx_lsp_t*)speex_alloc((st->lpcSize)*sizeof(spx_lsp_t)); + st->old_qlsp = (spx_lsp_t*)speex_alloc((st->lpcSize)*sizeof(spx_lsp_t)); + st->first = 1; + for (i=0;ilpcSize;i++) + st->old_lsp[i]= DIV32(MULT16_16(QCONST16(3.1415927f, LSP_SHIFT), i+1), st->lpcSize+1); + + st->mem_sp = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_sw = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_sw_whole = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_exc = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_exc2 = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + + st->pi_gain = (spx_word32_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word32_t)); + st->innov_rms_save = NULL; + + st->pitch = (int*)speex_alloc((st->nbSubframes)*sizeof(int)); + +#ifndef DISABLE_VBR + st->vbr = (VBRState*)speex_alloc(sizeof(VBRState)); + vbr_init(st->vbr); + st->vbr_quality = 8; + st->vbr_enabled = 0; + st->vbr_max = 0; + st->vad_enabled = 0; + st->dtx_enabled = 0; + st->dtx_count=0; + st->abr_enabled = 0; + st->abr_drift = 0; + st->abr_drift2 = 0; +#endif /* #ifndef DISABLE_VBR */ + + st->plc_tuning = 2; + st->complexity=2; + st->sampling_rate=8000; + st->isWideband = 0; + st->highpass_enabled = 1; + +#ifdef ENABLE_VALGRIND + VALGRIND_MAKE_READABLE(st, NB_ENC_STACK); +#endif + return st; +} + +void nb_encoder_destroy(void *state) +{ + EncState *st=(EncState *)state; + /* Free all allocated memory */ +#if !(defined(VAR_ARRAYS) || defined (USE_ALLOCA)) + speex_free_scratch(st->stack); +#endif + + speex_free (st->winBuf); + speex_free (st->excBuf); + speex_free (st->old_qlsp); + speex_free (st->swBuf); + + speex_free (st->old_lsp); + speex_free (st->mem_sp); + speex_free (st->mem_sw); + speex_free (st->mem_sw_whole); + speex_free (st->mem_exc); + speex_free (st->mem_exc2); + speex_free (st->pi_gain); + speex_free (st->pitch); + +#ifndef DISABLE_VBR + vbr_destroy(st->vbr); + speex_free (st->vbr); +#endif /* #ifndef DISABLE_VBR */ + +#ifdef VORBIS_PSYCHO + vorbis_psy_destroy(st->psy); + speex_free (st->curve); + speex_free (st->old_curve); + speex_free (st->psy_window); +#endif + + /*Free state memory... should be last*/ + speex_free(st); +} + +int nb_encode(void *state, void *vin, SpeexBits *bits) +{ + EncState *st; + int i, sub, roots; + int ol_pitch; + spx_word16_t ol_pitch_coef; + spx_word32_t ol_gain; + VARDECL(spx_word16_t *ringing); + VARDECL(spx_word16_t *target); + VARDECL(spx_sig_t *innov); + VARDECL(spx_word32_t *exc32); + VARDECL(spx_mem_t *mem); + VARDECL(spx_coef_t *bw_lpc1); + VARDECL(spx_coef_t *bw_lpc2); + VARDECL(spx_coef_t *lpc); + VARDECL(spx_lsp_t *lsp); + VARDECL(spx_lsp_t *qlsp); + VARDECL(spx_lsp_t *interp_lsp); + VARDECL(spx_lsp_t *interp_qlsp); + VARDECL(spx_coef_t *interp_lpc); + VARDECL(spx_coef_t *interp_qlpc); + char *stack; + VARDECL(spx_word16_t *syn_resp); + VARDECL(spx_word16_t *real_exc); + + spx_word32_t ener=0; + spx_word16_t fine_gain; + spx_word16_t *in = (spx_word16_t*)vin; + + st=(EncState *)state; + stack=st->stack; + + ALLOC(lpc, st->lpcSize, spx_coef_t); + ALLOC(bw_lpc1, st->lpcSize, spx_coef_t); + ALLOC(bw_lpc2, st->lpcSize, spx_coef_t); + ALLOC(lsp, st->lpcSize, spx_lsp_t); + ALLOC(qlsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_lsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_qlsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_lpc, st->lpcSize, spx_coef_t); + ALLOC(interp_qlpc, st->lpcSize, spx_coef_t); + + /* Move signals 1 frame towards the past */ + SPEEX_MOVE(st->excBuf, st->excBuf+st->frameSize, st->max_pitch+2); + SPEEX_MOVE(st->swBuf, st->swBuf+st->frameSize, st->max_pitch+2); + + if (st->highpass_enabled) + highpass(in, in, st->frameSize, (st->isWideband?HIGHPASS_WIDEBAND:HIGHPASS_NARROWBAND)|HIGHPASS_INPUT, st->mem_hp); + + { + VARDECL(spx_word16_t *w_sig); + VARDECL(spx_word16_t *autocorr); + ALLOC(w_sig, st->windowSize, spx_word16_t); + ALLOC(autocorr, st->lpcSize+1, spx_word16_t); + /* Window for analysis */ + for (i=0;iwindowSize-st->frameSize;i++) + w_sig[i] = EXTRACT16(SHR32(MULT16_16(st->winBuf[i],st->window[i]),SIG_SHIFT)); + for (;iwindowSize;i++) + w_sig[i] = EXTRACT16(SHR32(MULT16_16(in[i-st->windowSize+st->frameSize],st->window[i]),SIG_SHIFT)); + /* Compute auto-correlation */ + _spx_autocorr(w_sig, autocorr, st->lpcSize+1, st->windowSize); + autocorr[0] = ADD16(autocorr[0],MULT16_16_Q15(autocorr[0],st->lpc_floor)); /* Noise floor in auto-correlation domain */ + + /* Lag windowing: equivalent to filtering in the power-spectrum domain */ + for (i=0;ilpcSize+1;i++) + autocorr[i] = MULT16_16_Q14(autocorr[i],st->lagWindow[i]); + + /* Levinson-Durbin */ + _spx_lpc(lpc, autocorr, st->lpcSize); + /* LPC to LSPs (x-domain) transform */ + roots=lpc_to_lsp (lpc, st->lpcSize, lsp, 10, LSP_DELTA1, stack); + /* Check if we found all the roots */ + if (roots!=st->lpcSize) + { + /*If we can't find all LSP's, do some damage control and use previous filter*/ + for (i=0;ilpcSize;i++) + { + lsp[i]=st->old_lsp[i]; + } + } + } + + + + + /* Whole frame analysis (open-loop estimation of pitch and excitation gain) */ + { + int diff = st->windowSize-st->frameSize; + if (st->first) + for (i=0;ilpcSize;i++) + interp_lsp[i] = lsp[i]; + else + lsp_interpolate(st->old_lsp, lsp, interp_lsp, st->lpcSize, st->nbSubframes, st->nbSubframes<<1); + + lsp_enforce_margin(interp_lsp, st->lpcSize, LSP_MARGIN); + + /* Compute interpolated LPCs (unquantized) for whole frame*/ + lsp_to_lpc(interp_lsp, interp_lpc, st->lpcSize,stack); + + + /*Open-loop pitch*/ + if (!st->submodes[st->submodeID] || (st->complexity>2 && SUBMODE(have_subframe_gain)<3) || SUBMODE(forced_pitch_gain) || SUBMODE(lbr_pitch) != -1 +#ifndef DISABLE_VBR + || st->vbr_enabled || st->vad_enabled +#endif + ) + { + int nol_pitch[6]; + spx_word16_t nol_pitch_coef[6]; + + bw_lpc(st->gamma1, interp_lpc, bw_lpc1, st->lpcSize); + bw_lpc(st->gamma2, interp_lpc, bw_lpc2, st->lpcSize); + + SPEEX_COPY(st->sw, st->winBuf, diff); + SPEEX_COPY(st->sw+diff, in, st->frameSize-diff); + filter_mem16(st->sw, bw_lpc1, bw_lpc2, st->sw, st->frameSize, st->lpcSize, st->mem_sw_whole, stack); + + open_loop_nbest_pitch(st->sw, st->min_pitch, st->max_pitch, st->frameSize, + nol_pitch, nol_pitch_coef, 6, stack); + ol_pitch=nol_pitch[0]; + ol_pitch_coef = nol_pitch_coef[0]; + /*Try to remove pitch multiples*/ + for (i=1;i<6;i++) + { +#ifdef FIXED_POINT + if ((nol_pitch_coef[i]>MULT16_16_Q15(nol_pitch_coef[0],27853)) && +#else + if ((nol_pitch_coef[i]>.85*nol_pitch_coef[0]) && +#endif + (ABS(2*nol_pitch[i]-ol_pitch)<=2 || ABS(3*nol_pitch[i]-ol_pitch)<=3 || + ABS(4*nol_pitch[i]-ol_pitch)<=4 || ABS(5*nol_pitch[i]-ol_pitch)<=5)) + { + /*ol_pitch_coef=nol_pitch_coef[i];*/ + ol_pitch = nol_pitch[i]; + } + } + /*if (ol_pitch>50) + ol_pitch/=2;*/ + /*ol_pitch_coef = sqrt(ol_pitch_coef);*/ + + } else { + ol_pitch=0; + ol_pitch_coef=0; + } + + /*Compute "real" excitation*/ + SPEEX_COPY(st->exc, st->winBuf, diff); + SPEEX_COPY(st->exc+diff, in, st->frameSize-diff); + fir_mem16(st->exc, interp_lpc, st->exc, st->frameSize, st->lpcSize, st->mem_exc, stack); + + /* Compute open-loop excitation gain */ + { + spx_word16_t g = compute_rms16(st->exc, st->frameSize); + if (st->submodeID!=1 && ol_pitch>0) + ol_gain = MULT16_16(g, MULT16_16_Q14(QCONST16(1.1,14), + spx_sqrt(QCONST32(1.,28)-MULT16_32_Q15(QCONST16(.8,15),SHL32(MULT16_16(ol_pitch_coef,ol_pitch_coef),16))))); + else + ol_gain = SHL32(EXTEND32(g),SIG_SHIFT); + } + } + +#ifdef VORBIS_PSYCHO + SPEEX_MOVE(st->psy_window, st->psy_window+st->frameSize, 256-st->frameSize); + SPEEX_COPY(&st->psy_window[256-st->frameSize], in, st->frameSize); + compute_curve(st->psy, st->psy_window, st->curve); + /*print_vec(st->curve, 128, "curve");*/ + if (st->first) + SPEEX_COPY(st->old_curve, st->curve, 128); +#endif + + /*VBR stuff*/ +#ifndef DISABLE_VBR + if (st->vbr && (st->vbr_enabled||st->vad_enabled)) + { + float lsp_dist=0; + for (i=0;ilpcSize;i++) + lsp_dist += (st->old_lsp[i] - lsp[i])*(st->old_lsp[i] - lsp[i]); + lsp_dist /= LSP_SCALING*LSP_SCALING; + + if (st->abr_enabled) + { + float qual_change=0; + if (st->abr_drift2 * st->abr_drift > 0) + { + /* Only adapt if long-term and short-term drift are the same sign */ + qual_change = -.00001*st->abr_drift/(1+st->abr_count); + if (qual_change>.05) + qual_change=.05; + if (qual_change<-.05) + qual_change=-.05; + } + st->vbr_quality += qual_change; + if (st->vbr_quality>10) + st->vbr_quality=10; + if (st->vbr_quality<0) + st->vbr_quality=0; + } + + st->relative_quality = vbr_analysis(st->vbr, in, st->frameSize, ol_pitch, GAIN_SCALING_1*ol_pitch_coef); + /*if (delta_qual<0)*/ + /* delta_qual*=.1*(3+st->vbr_quality);*/ + if (st->vbr_enabled) + { + spx_int32_t mode; + int choice=0; + float min_diff=100; + mode = 8; + while (mode) + { + int v1; + float thresh; + v1=(int)floor(st->vbr_quality); + if (v1==10) + thresh = vbr_nb_thresh[mode][v1]; + else + thresh = (st->vbr_quality-v1)*vbr_nb_thresh[mode][v1+1] + (1+v1-st->vbr_quality)*vbr_nb_thresh[mode][v1]; + if (st->relative_quality > thresh && + st->relative_quality-threshrelative_quality-thresh; + } + mode--; + } + mode=choice; + if (mode==0) + { + if (st->dtx_count==0 || lsp_dist>.05 || !st->dtx_enabled || st->dtx_count>20) + { + mode=1; + st->dtx_count=1; + } else { + mode=0; + st->dtx_count++; + } + } else { + st->dtx_count=0; + } + + speex_encoder_ctl(state, SPEEX_SET_MODE, &mode); + if (st->vbr_max>0) + { + spx_int32_t rate; + speex_encoder_ctl(state, SPEEX_GET_BITRATE, &rate); + if (rate > st->vbr_max) + { + rate = st->vbr_max; + speex_encoder_ctl(state, SPEEX_SET_BITRATE, &rate); + } + } + + if (st->abr_enabled) + { + spx_int32_t bitrate; + speex_encoder_ctl(state, SPEEX_GET_BITRATE, &bitrate); + st->abr_drift+=(bitrate-st->abr_enabled); + st->abr_drift2 = .95*st->abr_drift2 + .05*(bitrate-st->abr_enabled); + st->abr_count += 1.0; + } + + } else { + /*VAD only case*/ + int mode; + if (st->relative_quality<2) + { + if (st->dtx_count==0 || lsp_dist>.05 || !st->dtx_enabled || st->dtx_count>20) + { + st->dtx_count=1; + mode=1; + } else { + mode=0; + st->dtx_count++; + } + } else { + st->dtx_count = 0; + mode=st->submodeSelect; + } + /*speex_encoder_ctl(state, SPEEX_SET_MODE, &mode);*/ + st->submodeID=mode; + } + } else { + st->relative_quality = -1; + } +#endif /* #ifndef DISABLE_VBR */ + + if (st->encode_submode) + { + /* First, transmit a zero for narrowband */ + speex_bits_pack(bits, 0, 1); + + /* Transmit the sub-mode we use for this frame */ + speex_bits_pack(bits, st->submodeID, NB_SUBMODE_BITS); + + } + + /* If null mode (no transmission), just set a couple things to zero*/ + if (st->submodes[st->submodeID] == NULL) + { + for (i=0;iframeSize;i++) + st->exc[i]=st->sw[i]=VERY_SMALL; + + for (i=0;ilpcSize;i++) + st->mem_sw[i]=0; + st->first=1; + st->bounded_pitch = 1; + + SPEEX_COPY(st->winBuf, in+2*st->frameSize-st->windowSize, st->windowSize-st->frameSize); + + /* Clear memory (no need to really compute it) */ + for (i=0;ilpcSize;i++) + st->mem_sp[i] = 0; + return 0; + + } + + /* LSP Quantization */ + if (st->first) + { + for (i=0;ilpcSize;i++) + st->old_lsp[i] = lsp[i]; + } + + + /*Quantize LSPs*/ +#if 1 /*0 for unquantized*/ + SUBMODE(lsp_quant)(lsp, qlsp, st->lpcSize, bits); +#else + for (i=0;ilpcSize;i++) + qlsp[i]=lsp[i]; +#endif + + /*If we use low bit-rate pitch mode, transmit open-loop pitch*/ + if (SUBMODE(lbr_pitch)!=-1) + { + speex_bits_pack(bits, ol_pitch-st->min_pitch, 7); + } + + if (SUBMODE(forced_pitch_gain)) + { + int quant; + /* This just damps the pitch a bit, because it tends to be too aggressive when forced */ + ol_pitch_coef = MULT16_16_Q15(QCONST16(.9,15), ol_pitch_coef); +#ifdef FIXED_POINT + quant = PSHR16(MULT16_16_16(15, ol_pitch_coef),GAIN_SHIFT); +#else + quant = (int)floor(.5+15*ol_pitch_coef*GAIN_SCALING_1); +#endif + if (quant>15) + quant=15; + if (quant<0) + quant=0; + speex_bits_pack(bits, quant, 4); + ol_pitch_coef=MULT16_16_P15(QCONST16(0.066667,15),SHL16(quant,GAIN_SHIFT)); + } + + + /*Quantize and transmit open-loop excitation gain*/ +#ifdef FIXED_POINT + { + int qe = scal_quant32(ol_gain, ol_gain_table, 32); + /*ol_gain = exp(qe/3.5)*SIG_SCALING;*/ + ol_gain = MULT16_32_Q15(28406,ol_gain_table[qe]); + speex_bits_pack(bits, qe, 5); + } +#else + { + int qe = (int)(floor(.5+3.5*log(ol_gain*1.0/SIG_SCALING))); + if (qe<0) + qe=0; + if (qe>31) + qe=31; + ol_gain = exp(qe/3.5)*SIG_SCALING; + speex_bits_pack(bits, qe, 5); + } +#endif + + + + /* Special case for first frame */ + if (st->first) + { + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + + /* Target signal */ + ALLOC(target, st->subframeSize, spx_word16_t); + ALLOC(innov, st->subframeSize, spx_sig_t); + ALLOC(exc32, st->subframeSize, spx_word32_t); + ALLOC(ringing, st->subframeSize, spx_word16_t); + ALLOC(syn_resp, st->subframeSize, spx_word16_t); + ALLOC(real_exc, st->subframeSize, spx_word16_t); + ALLOC(mem, st->lpcSize, spx_mem_t); + + /* Loop on sub-frames */ + for (sub=0;subnbSubframes;sub++) + { + int offset; + spx_word16_t *sw; + spx_word16_t *exc; + int pitch; + int response_bound = st->subframeSize; + + /* Offset relative to start of frame */ + offset = st->subframeSize*sub; + /* Excitation */ + exc=st->exc+offset; + /* Weighted signal */ + sw=st->sw+offset; + + /* LSP interpolation (quantized and unquantized) */ + lsp_interpolate(st->old_lsp, lsp, interp_lsp, st->lpcSize, sub, st->nbSubframes); + lsp_interpolate(st->old_qlsp, qlsp, interp_qlsp, st->lpcSize, sub, st->nbSubframes); + + /* Make sure the filters are stable */ + lsp_enforce_margin(interp_lsp, st->lpcSize, LSP_MARGIN); + lsp_enforce_margin(interp_qlsp, st->lpcSize, LSP_MARGIN); + + /* Compute interpolated LPCs (quantized and unquantized) */ + lsp_to_lpc(interp_lsp, interp_lpc, st->lpcSize,stack); + + lsp_to_lpc(interp_qlsp, interp_qlpc, st->lpcSize, stack); + + /* Compute analysis filter gain at w=pi (for use in SB-CELP) */ + { + spx_word32_t pi_g=LPC_SCALING; + for (i=0;ilpcSize;i+=2) + { + /*pi_g += -st->interp_qlpc[i] + st->interp_qlpc[i+1];*/ + pi_g = ADD32(pi_g, SUB32(EXTEND32(interp_qlpc[i+1]),EXTEND32(interp_qlpc[i]))); + } + st->pi_gain[sub] = pi_g; + } + +#ifdef VORBIS_PSYCHO + { + float curr_curve[128]; + float fact = ((float)sub+1.0f)/st->nbSubframes; + for (i=0;i<128;i++) + curr_curve[i] = (1.0f-fact)*st->old_curve[i] + fact*st->curve[i]; + curve_to_lpc(st->psy, curr_curve, bw_lpc1, bw_lpc2, 10); + } +#else + /* Compute bandwidth-expanded (unquantized) LPCs for perceptual weighting */ + bw_lpc(st->gamma1, interp_lpc, bw_lpc1, st->lpcSize); + if (st->gamma2>=0) + bw_lpc(st->gamma2, interp_lpc, bw_lpc2, st->lpcSize); + else + { + for (i=0;ilpcSize;i++) + bw_lpc2[i]=0; + } + /*print_vec(st->bw_lpc1, 10, "bw_lpc");*/ +#endif + + /*FIXME: This will break if we change the window size */ + speex_assert(st->windowSize-st->frameSize == st->subframeSize); + if (sub==0) + { + for (i=0;isubframeSize;i++) + real_exc[i] = sw[i] = st->winBuf[i]; + } else { + for (i=0;isubframeSize;i++) + real_exc[i] = sw[i] = in[i+((sub-1)*st->subframeSize)]; + } + fir_mem16(real_exc, interp_qlpc, real_exc, st->subframeSize, st->lpcSize, st->mem_exc2, stack); + + if (st->complexity==0) + response_bound >>= 1; + compute_impulse_response(interp_qlpc, bw_lpc1, bw_lpc2, syn_resp, response_bound, st->lpcSize, stack); + for (i=response_bound;isubframeSize;i++) + syn_resp[i]=VERY_SMALL; + + /* Compute zero response of A(z/g1) / ( A(z/g2) * A(z) ) */ + for (i=0;ilpcSize;i++) + mem[i]=SHL32(st->mem_sp[i],1); + for (i=0;isubframeSize;i++) + ringing[i] = VERY_SMALL; +#ifdef SHORTCUTS2 + iir_mem16(ringing, interp_qlpc, ringing, response_bound, st->lpcSize, mem, stack); + for (i=0;ilpcSize;i++) + mem[i]=SHL32(st->mem_sw[i],1); + filter_mem16(ringing, st->bw_lpc1, st->bw_lpc2, ringing, response_bound, st->lpcSize, mem, stack); + SPEEX_MEMSET(&ringing[response_bound], 0, st->subframeSize-response_bound); +#else + iir_mem16(ringing, interp_qlpc, ringing, st->subframeSize, st->lpcSize, mem, stack); + for (i=0;ilpcSize;i++) + mem[i]=SHL32(st->mem_sw[i],1); + filter_mem16(ringing, bw_lpc1, bw_lpc2, ringing, st->subframeSize, st->lpcSize, mem, stack); +#endif + + /* Compute weighted signal */ + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sw[i]; + filter_mem16(sw, bw_lpc1, bw_lpc2, sw, st->subframeSize, st->lpcSize, mem, stack); + + if (st->complexity==0) + for (i=0;ilpcSize;i++) + st->mem_sw[i]=mem[i]; + + /* Compute target signal (saturation prevents overflows on clipped input speech) */ + for (i=0;isubframeSize;i++) + target[i]=EXTRACT16(SATURATE(SUB32(sw[i],PSHR32(ringing[i],1)),32767)); + + /* Reset excitation */ + SPEEX_MEMSET(exc, 0, st->subframeSize); + + /* If we have a long-term predictor (otherwise, something's wrong) */ + speex_assert (SUBMODE(ltp_quant)); + { + int pit_min, pit_max; + /* Long-term prediction */ + if (SUBMODE(lbr_pitch) != -1) + { + /* Low bit-rate pitch handling */ + int margin; + margin = SUBMODE(lbr_pitch); + if (margin) + { + if (ol_pitch < st->min_pitch+margin-1) + ol_pitch=st->min_pitch+margin-1; + if (ol_pitch > st->max_pitch-margin) + ol_pitch=st->max_pitch-margin; + pit_min = ol_pitch-margin+1; + pit_max = ol_pitch+margin; + } else { + pit_min=pit_max=ol_pitch; + } + } else { + pit_min = st->min_pitch; + pit_max = st->max_pitch; + } + + /* Force pitch to use only the current frame if needed */ + if (st->bounded_pitch && pit_max>offset) + pit_max=offset; + + /* Perform pitch search */ + pitch = SUBMODE(ltp_quant)(target, sw, interp_qlpc, bw_lpc1, bw_lpc2, + exc32, SUBMODE(ltp_params), pit_min, pit_max, ol_pitch_coef, + st->lpcSize, st->subframeSize, bits, stack, + exc, syn_resp, st->complexity, 0, st->plc_tuning, &st->cumul_gain); + + st->pitch[sub]=pitch; + } + /* Quantization of innovation */ + SPEEX_MEMSET(innov, 0, st->subframeSize); + + /* FIXME: Make sure this is save from overflows (so far so good) */ + for (i=0;isubframeSize;i++) + real_exc[i] = EXTRACT16(SUB32(EXTEND32(real_exc[i]), PSHR32(exc32[i],SIG_SHIFT-1))); + + ener = SHL32(EXTEND32(compute_rms16(real_exc, st->subframeSize)),SIG_SHIFT); + + /*FIXME: Should use DIV32_16 and make sure result fits in 16 bits */ +#ifdef FIXED_POINT + { + spx_word32_t f = PDIV32(ener,PSHR32(ol_gain,SIG_SHIFT)); + if (f<=32767) + fine_gain = f; + else + fine_gain = 32767; + } +#else + fine_gain = PDIV32_16(ener,PSHR32(ol_gain,SIG_SHIFT)); +#endif + /* Calculate gain correction for the sub-frame (if any) */ + if (SUBMODE(have_subframe_gain)) + { + int qe; + if (SUBMODE(have_subframe_gain)==3) + { + qe = scal_quant(fine_gain, exc_gain_quant_scal3_bound, 8); + speex_bits_pack(bits, qe, 3); + ener=MULT16_32_Q14(exc_gain_quant_scal3[qe],ol_gain); + } else { + qe = scal_quant(fine_gain, exc_gain_quant_scal1_bound, 2); + speex_bits_pack(bits, qe, 1); + ener=MULT16_32_Q14(exc_gain_quant_scal1[qe],ol_gain); + } + } else { + ener=ol_gain; + } + + /*printf ("%f %f\n", ener, ol_gain);*/ + + /* Normalize innovation */ + signal_div(target, target, ener, st->subframeSize); + + /* Quantize innovation */ + speex_assert (SUBMODE(innovation_quant)); + { + /* Codebook search */ + SUBMODE(innovation_quant)(target, interp_qlpc, bw_lpc1, bw_lpc2, + SUBMODE(innovation_params), st->lpcSize, st->subframeSize, + innov, syn_resp, bits, stack, st->complexity, SUBMODE(double_codebook)); + + /* De-normalize innovation and update excitation */ + signal_mul(innov, innov, ener, st->subframeSize); + + for (i=0;isubframeSize;i++) + exc[i] = EXTRACT16(SATURATE32(PSHR32(ADD32(SHL32(exc32[i],1),innov[i]),SIG_SHIFT),32767)); + + /* In some (rare) modes, we do a second search (more bits) to reduce noise even more */ + if (SUBMODE(double_codebook)) { + char *tmp_stack=stack; + VARDECL(spx_sig_t *innov2); + ALLOC(innov2, st->subframeSize, spx_sig_t); + SPEEX_MEMSET(innov2, 0, st->subframeSize); + for (i=0;isubframeSize;i++) + target[i]=MULT16_16_P13(QCONST16(2.2f,13), target[i]); + SUBMODE(innovation_quant)(target, interp_qlpc, bw_lpc1, bw_lpc2, + SUBMODE(innovation_params), st->lpcSize, st->subframeSize, + innov2, syn_resp, bits, stack, st->complexity, 0); + signal_mul(innov2, innov2, MULT16_32_Q15(QCONST16(0.454545f,15),ener), st->subframeSize); + for (i=0;isubframeSize;i++) + innov[i] = ADD32(innov[i],innov2[i]); + stack = tmp_stack; + } + for (i=0;isubframeSize;i++) + exc[i] = EXTRACT16(SATURATE32(PSHR32(ADD32(SHL32(exc32[i],1),innov[i]),SIG_SHIFT),32767)); + if (st->innov_rms_save) + { + st->innov_rms_save[sub] = compute_rms(innov, st->subframeSize); + } + } + + /* Final signal synthesis from excitation */ + iir_mem16(exc, interp_qlpc, sw, st->subframeSize, st->lpcSize, st->mem_sp, stack); + + /* Compute weighted signal again, from synthesized speech (not sure it's the right thing) */ + if (st->complexity!=0) + filter_mem16(sw, bw_lpc1, bw_lpc2, sw, st->subframeSize, st->lpcSize, st->mem_sw, stack); + + } + + /* Store the LSPs for interpolation in the next frame */ + if (st->submodeID>=1) + { + for (i=0;ilpcSize;i++) + st->old_lsp[i] = lsp[i]; + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + +#ifdef VORBIS_PSYCHO + if (st->submodeID>=1) + SPEEX_COPY(st->old_curve, st->curve, 128); +#endif + + if (st->submodeID==1) + { +#ifndef DISABLE_VBR + if (st->dtx_count) + speex_bits_pack(bits, 15, 4); + else +#endif + speex_bits_pack(bits, 0, 4); + } + + /* The next frame will not be the first (Duh!) */ + st->first = 0; + SPEEX_COPY(st->winBuf, in+2*st->frameSize-st->windowSize, st->windowSize-st->frameSize); + + if (SUBMODE(innovation_quant) == noise_codebook_quant || st->submodeID==0) + st->bounded_pitch = 1; + else + st->bounded_pitch = 0; + + return 1; +} + +void *nb_decoder_init(const SpeexMode *m) +{ + DecState *st; + const SpeexNBMode *mode; + int i; + + mode=(const SpeexNBMode*)m->mode; + st = (DecState *)speex_alloc(sizeof(DecState)); + if (!st) + return NULL; +#if defined(VAR_ARRAYS) || defined (USE_ALLOCA) + st->stack = NULL; +#else + st->stack = (char*)speex_alloc_scratch(NB_DEC_STACK); +#endif + + st->mode=m; + + + st->encode_submode = 1; + + st->first=1; + /* Codec parameters, should eventually have several "modes"*/ + st->frameSize = mode->frameSize; + st->nbSubframes=mode->frameSize/mode->subframeSize; + st->subframeSize=mode->subframeSize; + st->lpcSize = mode->lpcSize; + st->min_pitch=mode->pitchStart; + st->max_pitch=mode->pitchEnd; + + st->submodes=mode->submodes; + st->submodeID=mode->defaultSubmode; + + st->lpc_enh_enabled=1; + + st->excBuf = (spx_word16_t*)speex_alloc((st->frameSize + 2*st->max_pitch + st->subframeSize + 12)*sizeof(spx_word16_t)); + st->exc = st->excBuf + 2*st->max_pitch + st->subframeSize + 6; + SPEEX_MEMSET(st->excBuf, 0, st->frameSize + st->max_pitch); + + st->interp_qlpc = (spx_coef_t*)speex_alloc(st->lpcSize*sizeof(spx_coef_t)); + st->old_qlsp = (spx_lsp_t*)speex_alloc(st->lpcSize*sizeof(spx_lsp_t)); + st->mem_sp = (spx_mem_t*)speex_alloc(st->lpcSize*sizeof(spx_mem_t)); + st->pi_gain = (spx_word32_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word32_t)); + st->last_pitch = 40; + st->count_lost=0; + st->pitch_gain_buf[0] = st->pitch_gain_buf[1] = st->pitch_gain_buf[2] = 0; + st->pitch_gain_buf_idx = 0; + st->seed = 1000; + + st->sampling_rate=8000; + st->last_ol_gain = 0; + + st->user_callback.func = &speex_default_user_handler; + st->user_callback.data = NULL; + for (i=0;i<16;i++) + st->speex_callbacks[i].func = NULL; + + st->voc_m1=st->voc_m2=st->voc_mean=0; + st->voc_offset=0; + st->dtx_enabled=0; + st->isWideband = 0; + st->highpass_enabled = 1; + +#ifdef ENABLE_VALGRIND + VALGRIND_MAKE_READABLE(st, NB_DEC_STACK); +#endif + return st; +} + +void nb_decoder_destroy(void *state) +{ + DecState *st; + st=(DecState*)state; + +#if !(defined(VAR_ARRAYS) || defined (USE_ALLOCA)) + speex_free_scratch(st->stack); +#endif + + speex_free (st->excBuf); + speex_free (st->interp_qlpc); + speex_free (st->old_qlsp); + speex_free (st->mem_sp); + speex_free (st->pi_gain); + + speex_free(state); +} + +#define median3(a, b, c) ((a) < (b) ? ((b) < (c) ? (b) : ((a) < (c) ? (c) : (a))) : ((c) < (b) ? (b) : ((c) < (a) ? (c) : (a)))) + +#ifdef FIXED_POINT +const spx_word16_t attenuation[10] = {32767, 31483, 27923, 22861, 17278, 12055, 7764, 4616, 2533, 1283}; +#else +const spx_word16_t attenuation[10] = {1., 0.961, 0.852, 0.698, 0.527, 0.368, 0.237, 0.141, 0.077, 0.039}; + +#endif + +static void nb_decode_lost(DecState *st, spx_word16_t *out, char *stack) +{ + int i; + int pitch_val; + spx_word16_t pitch_gain; + spx_word16_t fact; + spx_word16_t gain_med; + spx_word16_t innov_gain; + spx_word16_t noise_gain; + + if (st->count_lost<10) + fact = attenuation[st->count_lost]; + else + fact = 0; + + gain_med = median3(st->pitch_gain_buf[0], st->pitch_gain_buf[1], st->pitch_gain_buf[2]); + if (gain_med < st->last_pitch_gain) + st->last_pitch_gain = gain_med; + +#ifdef FIXED_POINT + pitch_gain = st->last_pitch_gain; + if (pitch_gain>54) + pitch_gain = 54; + pitch_gain = SHL16(pitch_gain, 9); +#else + pitch_gain = GAIN_SCALING_1*st->last_pitch_gain; + if (pitch_gain>.85) + pitch_gain=.85; +#endif + pitch_gain = MULT16_16_Q15(fact,pitch_gain) + VERY_SMALL; + /* FIXME: This was rms of innovation (not exc) */ + innov_gain = compute_rms16(st->exc, st->frameSize); + noise_gain = MULT16_16_Q15(innov_gain, MULT16_16_Q15(fact, SUB16(Q15ONE,MULT16_16_Q15(pitch_gain,pitch_gain)))); + /* Shift all buffers by one frame */ + SPEEX_MOVE(st->excBuf, st->excBuf+st->frameSize, 2*st->max_pitch + st->subframeSize + 12); + + + pitch_val = st->last_pitch + SHR32((spx_int32_t)speex_rand(1+st->count_lost, &st->seed),SIG_SHIFT); + if (pitch_val > st->max_pitch) + pitch_val = st->max_pitch; + if (pitch_val < st->min_pitch) + pitch_val = st->min_pitch; + for (i=0;iframeSize;i++) + { + st->exc[i]= MULT16_16_Q15(pitch_gain, (st->exc[i-pitch_val]+VERY_SMALL)) + + speex_rand(noise_gain, &st->seed); + } + + bw_lpc(QCONST16(.98,15), st->interp_qlpc, st->interp_qlpc, st->lpcSize); + iir_mem16(&st->exc[-st->subframeSize], st->interp_qlpc, out, st->frameSize, + st->lpcSize, st->mem_sp, stack); + highpass(out, out, st->frameSize, HIGHPASS_NARROWBAND|HIGHPASS_OUTPUT, st->mem_hp); + + st->first = 0; + st->count_lost++; + st->pitch_gain_buf[st->pitch_gain_buf_idx++] = PSHR16(pitch_gain,9); + if (st->pitch_gain_buf_idx > 2) /* rollover */ + st->pitch_gain_buf_idx = 0; +} + +/* Just so we don't need to carry the complete wideband mode information */ +static const int wb_skip_table[8] = {0, 36, 112, 192, 352, 0, 0, 0}; + +int nb_decode(void *state, SpeexBits *bits, void *vout) +{ + DecState *st; + int i, sub; + int pitch; + spx_word16_t pitch_gain[3]; + spx_word32_t ol_gain=0; + int ol_pitch=0; + spx_word16_t ol_pitch_coef=0; + int best_pitch=40; + spx_word16_t best_pitch_gain=0; + int wideband; + int m; + char *stack; + VARDECL(spx_sig_t *innov); + VARDECL(spx_word32_t *exc32); + VARDECL(spx_coef_t *ak); + VARDECL(spx_lsp_t *qlsp); + spx_word16_t pitch_average=0; + + spx_word16_t *out = (spx_word16_t*)vout; + VARDECL(spx_lsp_t *interp_qlsp); + + st=(DecState*)state; + stack=st->stack; + + /* Check if we're in DTX mode*/ + if (!bits && st->dtx_enabled) + { + st->submodeID=0; + } else + { + /* If bits is NULL, consider the packet to be lost (what could we do anyway) */ + if (!bits) + { + nb_decode_lost(st, out, stack); + return 0; + } + + if (st->encode_submode) + { + + /* Search for next narrowband block (handle requests, skip wideband blocks) */ + do { + if (speex_bits_remaining(bits)<5) + return -1; + wideband = speex_bits_unpack_unsigned(bits, 1); + if (wideband) /* Skip wideband block (for compatibility) */ + { + int submode; + int advance; + advance = submode = speex_bits_unpack_unsigned(bits, SB_SUBMODE_BITS); + /*speex_mode_query(&speex_wb_mode, SPEEX_SUBMODE_BITS_PER_FRAME, &advance);*/ + advance = wb_skip_table[submode]; + if (advance < 0) + { + speex_notify("Invalid mode encountered. The stream is corrupted."); + return -2; + } + advance -= (SB_SUBMODE_BITS+1); + speex_bits_advance(bits, advance); + + if (speex_bits_remaining(bits)<5) + return -1; + wideband = speex_bits_unpack_unsigned(bits, 1); + if (wideband) + { + advance = submode = speex_bits_unpack_unsigned(bits, SB_SUBMODE_BITS); + /*speex_mode_query(&speex_wb_mode, SPEEX_SUBMODE_BITS_PER_FRAME, &advance);*/ + advance = wb_skip_table[submode]; + if (advance < 0) + { + speex_notify("Invalid mode encountered. The stream is corrupted."); + return -2; + } + advance -= (SB_SUBMODE_BITS+1); + speex_bits_advance(bits, advance); + wideband = speex_bits_unpack_unsigned(bits, 1); + if (wideband) + { + speex_notify("More than two wideband layers found. The stream is corrupted."); + return -2; + } + + } + } + if (speex_bits_remaining(bits)<4) + return -1; + /* FIXME: Check for overflow */ + m = speex_bits_unpack_unsigned(bits, 4); + if (m==15) /* We found a terminator */ + { + return -1; + } else if (m==14) /* Speex in-band request */ + { + int ret = speex_inband_handler(bits, st->speex_callbacks, state); + if (ret) + return ret; + } else if (m==13) /* User in-band request */ + { + int ret = st->user_callback.func(bits, state, st->user_callback.data); + if (ret) + return ret; + } else if (m>8) /* Invalid mode */ + { + speex_notify("Invalid mode encountered. The stream is corrupted."); + return -2; + } + + } while (m>8); + + /* Get the sub-mode that was used */ + st->submodeID = m; + } + + } + + /* Shift all buffers by one frame */ + SPEEX_MOVE(st->excBuf, st->excBuf+st->frameSize, 2*st->max_pitch + st->subframeSize + 12); + + /* If null mode (no transmission), just set a couple things to zero*/ + if (st->submodes[st->submodeID] == NULL) + { + VARDECL(spx_coef_t *lpc); + ALLOC(lpc, st->lpcSize, spx_coef_t); + bw_lpc(QCONST16(0.93f,15), st->interp_qlpc, lpc, st->lpcSize); + { + spx_word16_t innov_gain=0; + /* FIXME: This was innov, not exc */ + innov_gain = compute_rms16(st->exc, st->frameSize); + for (i=0;iframeSize;i++) + st->exc[i]=speex_rand(innov_gain, &st->seed); + } + + + st->first=1; + + /* Final signal synthesis from excitation */ + iir_mem16(st->exc, lpc, out, st->frameSize, st->lpcSize, st->mem_sp, stack); + + st->count_lost=0; + return 0; + } + + ALLOC(qlsp, st->lpcSize, spx_lsp_t); + + /* Unquantize LSPs */ + SUBMODE(lsp_unquant)(qlsp, st->lpcSize, bits); + + /*Damp memory if a frame was lost and the LSP changed too much*/ + if (st->count_lost) + { + spx_word16_t fact; + spx_word32_t lsp_dist=0; + for (i=0;ilpcSize;i++) + lsp_dist = ADD32(lsp_dist, EXTEND32(ABS(st->old_qlsp[i] - qlsp[i]))); +#ifdef FIXED_POINT + fact = SHR16(19661,SHR32(lsp_dist,LSP_SHIFT+2)); +#else + fact = .6*exp(-.2*lsp_dist); +#endif + for (i=0;ilpcSize;i++) + st->mem_sp[i] = MULT16_32_Q15(fact,st->mem_sp[i]); + } + + + /* Handle first frame and lost-packet case */ + if (st->first || st->count_lost) + { + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + + /* Get open-loop pitch estimation for low bit-rate pitch coding */ + if (SUBMODE(lbr_pitch)!=-1) + { + ol_pitch = st->min_pitch+speex_bits_unpack_unsigned(bits, 7); + } + + if (SUBMODE(forced_pitch_gain)) + { + int quant; + quant = speex_bits_unpack_unsigned(bits, 4); + ol_pitch_coef=MULT16_16_P15(QCONST16(0.066667,15),SHL16(quant,GAIN_SHIFT)); + } + + /* Get global excitation gain */ + { + int qe; + qe = speex_bits_unpack_unsigned(bits, 5); +#ifdef FIXED_POINT + /* FIXME: Perhaps we could slightly lower the gain here when the output is going to saturate? */ + ol_gain = MULT16_32_Q15(28406,ol_gain_table[qe]); +#else + ol_gain = SIG_SCALING*exp(qe/3.5); +#endif + } + + ALLOC(ak, st->lpcSize, spx_coef_t); + ALLOC(innov, st->subframeSize, spx_sig_t); + ALLOC(exc32, st->subframeSize, spx_word32_t); + + if (st->submodeID==1) + { + int extra; + extra = speex_bits_unpack_unsigned(bits, 4); + + if (extra==15) + st->dtx_enabled=1; + else + st->dtx_enabled=0; + } + if (st->submodeID>1) + st->dtx_enabled=0; + + /*Loop on subframes */ + for (sub=0;subnbSubframes;sub++) + { + int offset; + spx_word16_t *exc; + spx_word16_t *sp; + spx_word16_t *innov_save = NULL; + spx_word16_t tmp; + + /* Offset relative to start of frame */ + offset = st->subframeSize*sub; + /* Excitation */ + exc=st->exc+offset; + /* Original signal */ + sp=out+offset; + if (st->innov_save) + innov_save = st->innov_save+offset; + + + /* Reset excitation */ + SPEEX_MEMSET(exc, 0, st->subframeSize); + + /*Adaptive codebook contribution*/ + speex_assert (SUBMODE(ltp_unquant)); + { + int pit_min, pit_max; + /* Handle pitch constraints if any */ + if (SUBMODE(lbr_pitch) != -1) + { + int margin; + margin = SUBMODE(lbr_pitch); + if (margin) + { +/* GT - need optimization? + if (ol_pitch < st->min_pitch+margin-1) + ol_pitch=st->min_pitch+margin-1; + if (ol_pitch > st->max_pitch-margin) + ol_pitch=st->max_pitch-margin; + pit_min = ol_pitch-margin+1; + pit_max = ol_pitch+margin; +*/ + pit_min = ol_pitch-margin+1; + if (pit_min < st->min_pitch) + pit_min = st->min_pitch; + pit_max = ol_pitch+margin; + if (pit_max > st->max_pitch) + pit_max = st->max_pitch; + } else { + pit_min = pit_max = ol_pitch; + } + } else { + pit_min = st->min_pitch; + pit_max = st->max_pitch; + } + + + + SUBMODE(ltp_unquant)(exc, exc32, pit_min, pit_max, ol_pitch_coef, SUBMODE(ltp_params), + st->subframeSize, &pitch, &pitch_gain[0], bits, stack, + st->count_lost, offset, st->last_pitch_gain, 0); + + /* Ensuring that things aren't blowing up as would happen if e.g. an encoder is + crafting packets to make us produce NaNs and slow down the decoder (vague DoS threat). + We can probably be even more aggressive and limit to 15000 or so. */ + sanitize_values32(exc32, NEG32(QCONST32(32000,SIG_SHIFT-1)), QCONST32(32000,SIG_SHIFT-1), st->subframeSize); + + tmp = gain_3tap_to_1tap(pitch_gain); + + pitch_average += tmp; + if ((tmp>best_pitch_gain&&ABS(2*best_pitch-pitch)>=3&&ABS(3*best_pitch-pitch)>=4&&ABS(4*best_pitch-pitch)>=5) + || (tmp>MULT16_16_Q15(QCONST16(.6,15),best_pitch_gain)&&(ABS(best_pitch-2*pitch)<3||ABS(best_pitch-3*pitch)<4||ABS(best_pitch-4*pitch)<5)) + || (MULT16_16_Q15(QCONST16(.67,15),tmp)>best_pitch_gain&&(ABS(2*best_pitch-pitch)<3||ABS(3*best_pitch-pitch)<4||ABS(4*best_pitch-pitch)<5)) ) + { + best_pitch = pitch; + if (tmp > best_pitch_gain) + best_pitch_gain = tmp; + } + } + + /* Unquantize the innovation */ + { + int q_energy; + spx_word32_t ener; + + SPEEX_MEMSET(innov, 0, st->subframeSize); + + /* Decode sub-frame gain correction */ + if (SUBMODE(have_subframe_gain)==3) + { + q_energy = speex_bits_unpack_unsigned(bits, 3); + ener = MULT16_32_Q14(exc_gain_quant_scal3[q_energy],ol_gain); + } else if (SUBMODE(have_subframe_gain)==1) + { + q_energy = speex_bits_unpack_unsigned(bits, 1); + ener = MULT16_32_Q14(exc_gain_quant_scal1[q_energy],ol_gain); + } else { + ener = ol_gain; + } + + speex_assert (SUBMODE(innovation_unquant)); + { + /*Fixed codebook contribution*/ + SUBMODE(innovation_unquant)(innov, SUBMODE(innovation_params), st->subframeSize, bits, stack, &st->seed); + /* De-normalize innovation and update excitation */ + + signal_mul(innov, innov, ener, st->subframeSize); + + /* Decode second codebook (only for some modes) */ + if (SUBMODE(double_codebook)) + { + char *tmp_stack=stack; + VARDECL(spx_sig_t *innov2); + ALLOC(innov2, st->subframeSize, spx_sig_t); + SPEEX_MEMSET(innov2, 0, st->subframeSize); + SUBMODE(innovation_unquant)(innov2, SUBMODE(innovation_params), st->subframeSize, bits, stack, &st->seed); + signal_mul(innov2, innov2, MULT16_32_Q15(QCONST16(0.454545f,15),ener), st->subframeSize); + for (i=0;isubframeSize;i++) + innov[i] = ADD32(innov[i], innov2[i]); + stack = tmp_stack; + } + for (i=0;isubframeSize;i++) + exc[i]=EXTRACT16(SATURATE32(PSHR32(ADD32(SHL32(exc32[i],1),innov[i]),SIG_SHIFT),32767)); + /*print_vec(exc, 40, "innov");*/ + if (innov_save) + { + for (i=0;isubframeSize;i++) + innov_save[i] = EXTRACT16(PSHR32(innov[i], SIG_SHIFT)); + } + } + + /*Vocoder mode*/ + if (st->submodeID==1) + { + spx_word16_t g=ol_pitch_coef; + g=MULT16_16_P14(QCONST16(1.5f,14),(g-QCONST16(.2f,6))); + if (g<0) + g=0; + if (g>GAIN_SCALING) + g=GAIN_SCALING; + + SPEEX_MEMSET(exc, 0, st->subframeSize); + while (st->voc_offsetsubframeSize) + { + /* exc[st->voc_offset]= g*sqrt(2*ol_pitch)*ol_gain; + Not quite sure why we need the factor of two in the sqrt */ + if (st->voc_offset>=0) + exc[st->voc_offset]=MULT16_16(spx_sqrt(MULT16_16_16(2,ol_pitch)),EXTRACT16(PSHR32(MULT16_16(g,PSHR32(ol_gain,SIG_SHIFT)),6))); + st->voc_offset+=ol_pitch; + } + st->voc_offset -= st->subframeSize; + + for (i=0;isubframeSize;i++) + { + spx_word16_t exci=exc[i]; + exc[i]= ADD16(ADD16(MULT16_16_Q15(QCONST16(.7f,15),exc[i]) , MULT16_16_Q15(QCONST16(.3f,15),st->voc_m1)), + SUB16(MULT16_16_Q15(Q15_ONE-MULT16_16_16(QCONST16(.85f,9),g),EXTRACT16(PSHR32(innov[i],SIG_SHIFT))), + MULT16_16_Q15(MULT16_16_16(QCONST16(.15f,9),g),EXTRACT16(PSHR32(st->voc_m2,SIG_SHIFT))) + )); + st->voc_m1 = exci; + st->voc_m2=innov[i]; + st->voc_mean = EXTRACT16(PSHR32(ADD32(MULT16_16(QCONST16(.8f,15),st->voc_mean), MULT16_16(QCONST16(.2f,15),exc[i])), 15)); + exc[i]-=st->voc_mean; + } + } + + } + } + + ALLOC(interp_qlsp, st->lpcSize, spx_lsp_t); + + if (st->lpc_enh_enabled && SUBMODE(comb_gain)>0 && !st->count_lost) + { + multicomb(st->exc-st->subframeSize, out, st->interp_qlpc, st->lpcSize, 2*st->subframeSize, best_pitch, 40, SUBMODE(comb_gain), stack); + multicomb(st->exc+st->subframeSize, out+2*st->subframeSize, st->interp_qlpc, st->lpcSize, 2*st->subframeSize, best_pitch, 40, SUBMODE(comb_gain), stack); + } else { + SPEEX_COPY(out, &st->exc[-st->subframeSize], st->frameSize); + } + + /* If the last packet was lost, re-scale the excitation to obtain the same energy as encoded in ol_gain */ + if (st->count_lost) + { + spx_word16_t exc_ener; + spx_word32_t gain32; + spx_word16_t gain; + exc_ener = compute_rms16 (st->exc, st->frameSize); + gain32 = PDIV32(ol_gain, ADD16(exc_ener,1)); +#ifdef FIXED_POINT + if (gain32 > 32767) + gain32 = 32767; + gain = EXTRACT16(gain32); +#else + if (gain32 > 2) + gain32=2; + gain = gain32; +#endif + for (i=0;iframeSize;i++) + { + st->exc[i] = MULT16_16_Q14(gain, st->exc[i]); + out[i]=st->exc[i-st->subframeSize]; + } + } + + /*Loop on subframes */ + for (sub=0;subnbSubframes;sub++) + { + int offset; + spx_word16_t *sp; + spx_word16_t *exc; + /* Offset relative to start of frame */ + offset = st->subframeSize*sub; + /* Original signal */ + sp=out+offset; + /* Excitation */ + exc=st->exc+offset; + + /* LSP interpolation (quantized and unquantized) */ + lsp_interpolate(st->old_qlsp, qlsp, interp_qlsp, st->lpcSize, sub, st->nbSubframes); + + /* Make sure the LSP's are stable */ + lsp_enforce_margin(interp_qlsp, st->lpcSize, LSP_MARGIN); + + /* Compute interpolated LPCs (unquantized) */ + lsp_to_lpc(interp_qlsp, ak, st->lpcSize, stack); + + /* Compute analysis filter at w=pi */ + { + spx_word32_t pi_g=LPC_SCALING; + for (i=0;ilpcSize;i+=2) + { + /*pi_g += -st->interp_qlpc[i] + st->interp_qlpc[i+1];*/ + pi_g = ADD32(pi_g, SUB32(EXTEND32(ak[i+1]),EXTEND32(ak[i]))); + } + st->pi_gain[sub] = pi_g; + } + + iir_mem16(sp, st->interp_qlpc, sp, st->subframeSize, st->lpcSize, + st->mem_sp, stack); + + for (i=0;ilpcSize;i++) + st->interp_qlpc[i] = ak[i]; + + } + + if (st->highpass_enabled) + highpass(out, out, st->frameSize, (st->isWideband?HIGHPASS_WIDEBAND:HIGHPASS_NARROWBAND)|HIGHPASS_OUTPUT, st->mem_hp); + /*for (i=0;iframeSize;i++) + printf ("%d\n", (int)st->frame[i]);*/ + + /* Tracking output level */ + st->level = 1+PSHR32(ol_gain,SIG_SHIFT); + st->max_level = MAX16(MULT16_16_Q15(QCONST16(.99f,15), st->max_level), st->level); + st->min_level = MIN16(ADD16(1,MULT16_16_Q14(QCONST16(1.01f,14), st->min_level)), st->level); + if (st->max_level < st->min_level+1) + st->max_level = st->min_level+1; + /*printf ("%f %f %f %d\n", og, st->min_level, st->max_level, update);*/ + + /* Store the LSPs for interpolation in the next frame */ + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + + /* The next frame will not be the first (Duh!) */ + st->first = 0; + st->count_lost=0; + st->last_pitch = best_pitch; +#ifdef FIXED_POINT + st->last_pitch_gain = PSHR16(pitch_average,2); +#else + st->last_pitch_gain = .25*pitch_average; +#endif + st->pitch_gain_buf[st->pitch_gain_buf_idx++] = st->last_pitch_gain; + if (st->pitch_gain_buf_idx > 2) /* rollover */ + st->pitch_gain_buf_idx = 0; + + st->last_ol_gain = ol_gain; + + return 0; +} + +int nb_encoder_ctl(void *state, int request, void *ptr) +{ + EncState *st; + st=(EncState*)state; + switch(request) + { + case SPEEX_GET_FRAME_SIZE: + (*(spx_int32_t*)ptr) = st->frameSize; + break; + case SPEEX_SET_LOW_MODE: + case SPEEX_SET_MODE: + st->submodeSelect = st->submodeID = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_LOW_MODE: + case SPEEX_GET_MODE: + (*(spx_int32_t*)ptr) = st->submodeID; + break; +#ifndef DISABLE_VBR + case SPEEX_SET_VBR: + st->vbr_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_VBR: + (*(spx_int32_t*)ptr) = st->vbr_enabled; + break; + case SPEEX_SET_VAD: + st->vad_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_VAD: + (*(spx_int32_t*)ptr) = st->vad_enabled; + break; + case SPEEX_SET_DTX: + st->dtx_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_DTX: + (*(spx_int32_t*)ptr) = st->dtx_enabled; + break; + case SPEEX_SET_ABR: + st->abr_enabled = (*(spx_int32_t*)ptr); + st->vbr_enabled = st->abr_enabled!=0; + if (st->vbr_enabled) + { + spx_int32_t i=10; + spx_int32_t rate, target; + float vbr_qual; + target = (*(spx_int32_t*)ptr); + while (i>=0) + { + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &i); + speex_encoder_ctl(st, SPEEX_GET_BITRATE, &rate); + if (rate <= target) + break; + i--; + } + vbr_qual=i; + if (vbr_qual<0) + vbr_qual=0; + speex_encoder_ctl(st, SPEEX_SET_VBR_QUALITY, &vbr_qual); + st->abr_count=0; + st->abr_drift=0; + st->abr_drift2=0; + } + + break; + case SPEEX_GET_ABR: + (*(spx_int32_t*)ptr) = st->abr_enabled; + break; +#endif /* #ifndef DISABLE_VBR */ +#if !defined(DISABLE_VBR) && !defined(DISABLE_FLOAT_API) + case SPEEX_SET_VBR_QUALITY: + st->vbr_quality = (*(float*)ptr); + break; + case SPEEX_GET_VBR_QUALITY: + (*(float*)ptr) = st->vbr_quality; + break; +#endif /* !defined(DISABLE_VBR) && !defined(DISABLE_FLOAT_API) */ + case SPEEX_SET_QUALITY: + { + int quality = (*(spx_int32_t*)ptr); + if (quality < 0) + quality = 0; + if (quality > 10) + quality = 10; + st->submodeSelect = st->submodeID = ((const SpeexNBMode*)(st->mode->mode))->quality_map[quality]; + } + break; + case SPEEX_SET_COMPLEXITY: + st->complexity = (*(spx_int32_t*)ptr); + if (st->complexity<0) + st->complexity=0; + break; + case SPEEX_GET_COMPLEXITY: + (*(spx_int32_t*)ptr) = st->complexity; + break; + case SPEEX_SET_BITRATE: + { + spx_int32_t i=10; + spx_int32_t rate, target; + target = (*(spx_int32_t*)ptr); + while (i>=0) + { + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &i); + speex_encoder_ctl(st, SPEEX_GET_BITRATE, &rate); + if (rate <= target) + break; + i--; + } + } + break; + case SPEEX_GET_BITRATE: + if (st->submodes[st->submodeID]) + (*(spx_int32_t*)ptr) = st->sampling_rate*SUBMODE(bits_per_frame)/st->frameSize; + else + (*(spx_int32_t*)ptr) = st->sampling_rate*(NB_SUBMODE_BITS+1)/st->frameSize; + break; + case SPEEX_SET_SAMPLING_RATE: + st->sampling_rate = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_SAMPLING_RATE: + (*(spx_int32_t*)ptr)=st->sampling_rate; + break; + case SPEEX_RESET_STATE: + { + int i; + st->bounded_pitch = 1; + st->first = 1; + for (i=0;ilpcSize;i++) + st->old_lsp[i]= DIV32(MULT16_16(QCONST16(3.1415927f, LSP_SHIFT), i+1), st->lpcSize+1); + for (i=0;ilpcSize;i++) + st->mem_sw[i]=st->mem_sw_whole[i]=st->mem_sp[i]=st->mem_exc[i]=0; + for (i=0;iframeSize+st->max_pitch+1;i++) + st->excBuf[i]=st->swBuf[i]=0; + for (i=0;iwindowSize-st->frameSize;i++) + st->winBuf[i]=0; + } + break; + case SPEEX_SET_SUBMODE_ENCODING: + st->encode_submode = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_SUBMODE_ENCODING: + (*(spx_int32_t*)ptr) = st->encode_submode; + break; + case SPEEX_GET_LOOKAHEAD: + (*(spx_int32_t*)ptr)=(st->windowSize-st->frameSize); + break; + case SPEEX_SET_PLC_TUNING: + st->plc_tuning = (*(spx_int32_t*)ptr); + if (st->plc_tuning>100) + st->plc_tuning=100; + break; + case SPEEX_GET_PLC_TUNING: + (*(spx_int32_t*)ptr)=(st->plc_tuning); + break; +#ifndef DISABLE_VBR + case SPEEX_SET_VBR_MAX_BITRATE: + st->vbr_max = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_VBR_MAX_BITRATE: + (*(spx_int32_t*)ptr) = st->vbr_max; + break; +#endif /* #ifndef DISABLE_VBR */ + case SPEEX_SET_HIGHPASS: + st->highpass_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_HIGHPASS: + (*(spx_int32_t*)ptr) = st->highpass_enabled; + break; + + /* This is all internal stuff past this point */ + case SPEEX_GET_PI_GAIN: + { + int i; + spx_word32_t *g = (spx_word32_t*)ptr; + for (i=0;inbSubframes;i++) + g[i]=st->pi_gain[i]; + } + break; + case SPEEX_GET_EXC: + { + int i; + for (i=0;inbSubframes;i++) + ((spx_word16_t*)ptr)[i] = compute_rms16(st->exc+i*st->subframeSize, st->subframeSize); + } + break; +#ifndef DISABLE_VBR + case SPEEX_GET_RELATIVE_QUALITY: + (*(float*)ptr)=st->relative_quality; + break; +#endif /* #ifndef DISABLE_VBR */ + case SPEEX_SET_INNOVATION_SAVE: + st->innov_rms_save = (spx_word16_t*)ptr; + break; + case SPEEX_SET_WIDEBAND: + st->isWideband = *((spx_int32_t*)ptr); + break; + case SPEEX_GET_STACK: + *((char**)ptr) = st->stack; + break; + default: + speex_warning_int("Unknown nb_ctl request: ", request); + return -1; + } + return 0; +} + +int nb_decoder_ctl(void *state, int request, void *ptr) +{ + DecState *st; + st=(DecState*)state; + switch(request) + { + case SPEEX_SET_LOW_MODE: + case SPEEX_SET_MODE: + st->submodeID = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_LOW_MODE: + case SPEEX_GET_MODE: + (*(spx_int32_t*)ptr) = st->submodeID; + break; + case SPEEX_SET_ENH: + st->lpc_enh_enabled = *((spx_int32_t*)ptr); + break; + case SPEEX_GET_ENH: + *((spx_int32_t*)ptr) = st->lpc_enh_enabled; + break; + case SPEEX_GET_FRAME_SIZE: + (*(spx_int32_t*)ptr) = st->frameSize; + break; + case SPEEX_GET_BITRATE: + if (st->submodes[st->submodeID]) + (*(spx_int32_t*)ptr) = st->sampling_rate*SUBMODE(bits_per_frame)/st->frameSize; + else + (*(spx_int32_t*)ptr) = st->sampling_rate*(NB_SUBMODE_BITS+1)/st->frameSize; + break; + case SPEEX_SET_SAMPLING_RATE: + st->sampling_rate = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_SAMPLING_RATE: + (*(spx_int32_t*)ptr)=st->sampling_rate; + break; + case SPEEX_SET_HANDLER: + { + SpeexCallback *c = (SpeexCallback*)ptr; + st->speex_callbacks[c->callback_id].func=c->func; + st->speex_callbacks[c->callback_id].data=c->data; + st->speex_callbacks[c->callback_id].callback_id=c->callback_id; + } + break; + case SPEEX_SET_USER_HANDLER: + { + SpeexCallback *c = (SpeexCallback*)ptr; + st->user_callback.func=c->func; + st->user_callback.data=c->data; + st->user_callback.callback_id=c->callback_id; + } + break; + case SPEEX_RESET_STATE: + { + int i; + for (i=0;ilpcSize;i++) + st->mem_sp[i]=0; + for (i=0;iframeSize + st->max_pitch + 1;i++) + st->excBuf[i]=0; + } + break; + case SPEEX_SET_SUBMODE_ENCODING: + st->encode_submode = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_SUBMODE_ENCODING: + (*(spx_int32_t*)ptr) = st->encode_submode; + break; + case SPEEX_GET_LOOKAHEAD: + (*(spx_int32_t*)ptr)=st->subframeSize; + break; + case SPEEX_SET_HIGHPASS: + st->highpass_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_GET_HIGHPASS: + (*(spx_int32_t*)ptr) = st->highpass_enabled; + break; + /* FIXME: Convert to fixed-point and re-enable even when float API is disabled */ +#ifndef DISABLE_FLOAT_API + case SPEEX_GET_ACTIVITY: + { + float ret; + ret = log(st->level/st->min_level)/log(st->max_level/st->min_level); + if (ret>1) + ret = 1; + /* Done in a strange way to catch NaNs as well */ + if (!(ret > 0)) + ret = 0; + /*printf ("%f %f %f %f\n", st->level, st->min_level, st->max_level, ret);*/ + (*(spx_int32_t*)ptr) = (int)(100*ret); + } + break; +#endif + case SPEEX_GET_PI_GAIN: + { + int i; + spx_word32_t *g = (spx_word32_t*)ptr; + for (i=0;inbSubframes;i++) + g[i]=st->pi_gain[i]; + } + break; + case SPEEX_GET_EXC: + { + int i; + for (i=0;inbSubframes;i++) + ((spx_word16_t*)ptr)[i] = compute_rms16(st->exc+i*st->subframeSize, st->subframeSize); + } + break; + case SPEEX_GET_DTX_STATUS: + *((spx_int32_t*)ptr) = st->dtx_enabled; + break; + case SPEEX_SET_INNOVATION_SAVE: + st->innov_save = (spx_word16_t*)ptr; + break; + case SPEEX_SET_WIDEBAND: + st->isWideband = *((spx_int32_t*)ptr); + break; + case SPEEX_GET_STACK: + *((char**)ptr) = st->stack; + break; + default: + speex_warning_int("Unknown nb_ctl request: ", request); + return -1; + } + return 0; +} diff --git a/src/libspeex/nb_celp.h b/src/libspeex/nb_celp.h new file mode 100644 index 0000000..14c776f --- /dev/null +++ b/src/libspeex/nb_celp.h @@ -0,0 +1,203 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin */ +/** + @file nb_celp.h + @brief Narrowband CELP encoder/decoder +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef NB_CELP_H +#define NB_CELP_H + +#include "modes.h" +#include +#include +#include "vbr.h" +#include "filters.h" + +#ifdef VORBIS_PSYCHO +#include "vorbis_psy.h" +#endif + +/**Structure representing the full state of the narrowband encoder*/ +typedef struct EncState { + const SpeexMode *mode; /**< Mode corresponding to the state */ + int first; /**< Is this the first frame? */ + int frameSize; /**< Size of frames */ + int subframeSize; /**< Size of sub-frames */ + int nbSubframes; /**< Number of sub-frames */ + int windowSize; /**< Analysis (LPC) window length */ + int lpcSize; /**< LPC order */ + int min_pitch; /**< Minimum pitch value allowed */ + int max_pitch; /**< Maximum pitch value allowed */ + + spx_word32_t cumul_gain; /**< Product of previously used pitch gains (Q10) */ + int bounded_pitch; /**< Next frame should not rely on previous frames for pitch */ + int ol_pitch; /**< Open-loop pitch */ + int ol_voiced; /**< Open-loop voiced/non-voiced decision */ + int *pitch; + +#ifdef VORBIS_PSYCHO + VorbisPsy *psy; + float *psy_window; + float *curve; + float *old_curve; +#endif + + spx_word16_t gamma1; /**< Perceptual filter: A(z/gamma1) */ + spx_word16_t gamma2; /**< Perceptual filter: A(z/gamma2) */ + spx_word16_t lpc_floor; /**< Noise floor multiplier for A[0] in LPC analysis*/ + char *stack; /**< Pseudo-stack allocation for temporary memory */ + spx_word16_t *winBuf; /**< Input buffer (original signal) */ + spx_word16_t *excBuf; /**< Excitation buffer */ + spx_word16_t *exc; /**< Start of excitation frame */ + spx_word16_t *swBuf; /**< Weighted signal buffer */ + spx_word16_t *sw; /**< Start of weighted signal frame */ + const spx_word16_t *window; /**< Temporary (Hanning) window */ + const spx_word16_t *lagWindow; /**< Window applied to auto-correlation */ + spx_lsp_t *old_lsp; /**< LSPs for previous frame */ + spx_lsp_t *old_qlsp; /**< Quantized LSPs for previous frame */ + spx_mem_t *mem_sp; /**< Filter memory for signal synthesis */ + spx_mem_t *mem_sw; /**< Filter memory for perceptually-weighted signal */ + spx_mem_t *mem_sw_whole; /**< Filter memory for perceptually-weighted signal (whole frame)*/ + spx_mem_t *mem_exc; /**< Filter memory for excitation (whole frame) */ + spx_mem_t *mem_exc2; /**< Filter memory for excitation (whole frame) */ + spx_mem_t mem_hp[2]; /**< High-pass filter memory */ + spx_word32_t *pi_gain; /**< Gain of LPC filter at theta=pi (fe/2) */ + spx_word16_t *innov_rms_save; /**< If non-NULL, innovation RMS is copied here */ + +#ifndef DISABLE_VBR + VBRState *vbr; /**< State of the VBR data */ + float vbr_quality; /**< Quality setting for VBR encoding */ + float relative_quality; /**< Relative quality that will be needed by VBR */ + spx_int32_t vbr_enabled; /**< 1 for enabling VBR, 0 otherwise */ + spx_int32_t vbr_max; /**< Max bit-rate allowed in VBR mode */ + int vad_enabled; /**< 1 for enabling VAD, 0 otherwise */ + int dtx_enabled; /**< 1 for enabling DTX, 0 otherwise */ + int dtx_count; /**< Number of consecutive DTX frames */ + spx_int32_t abr_enabled; /**< ABR setting (in bps), 0 if off */ + float abr_drift; + float abr_drift2; + float abr_count; +#endif /* #ifndef DISABLE_VBR */ + + int complexity; /**< Complexity setting (0-10 from least complex to most complex) */ + spx_int32_t sampling_rate; + int plc_tuning; + int encode_submode; + const SpeexSubmode * const *submodes; /**< Sub-mode data */ + int submodeID; /**< Activated sub-mode */ + int submodeSelect; /**< Mode chosen by the user (may differ from submodeID if VAD is on) */ + int isWideband; /**< Is this used as part of the embedded wideband codec */ + int highpass_enabled; /**< Is the input filter enabled */ +} EncState; + +/**Structure representing the full state of the narrowband decoder*/ +typedef struct DecState { + const SpeexMode *mode; /**< Mode corresponding to the state */ + int first; /**< Is this the first frame? */ + int count_lost; /**< Was the last frame lost? */ + int frameSize; /**< Size of frames */ + int subframeSize; /**< Size of sub-frames */ + int nbSubframes; /**< Number of sub-frames */ + int lpcSize; /**< LPC order */ + int min_pitch; /**< Minimum pitch value allowed */ + int max_pitch; /**< Maximum pitch value allowed */ + spx_int32_t sampling_rate; + + spx_word16_t last_ol_gain; /**< Open-loop gain for previous frame */ + + char *stack; /**< Pseudo-stack allocation for temporary memory */ + spx_word16_t *excBuf; /**< Excitation buffer */ + spx_word16_t *exc; /**< Start of excitation frame */ + spx_lsp_t *old_qlsp; /**< Quantized LSPs for previous frame */ + spx_coef_t *interp_qlpc; /**< Interpolated quantized LPCs */ + spx_mem_t *mem_sp; /**< Filter memory for synthesis signal */ + spx_mem_t mem_hp[2]; /**< High-pass filter memory */ + spx_word32_t *pi_gain; /**< Gain of LPC filter at theta=pi (fe/2) */ + spx_word16_t *innov_save; /** If non-NULL, innovation is copied here */ + + spx_word16_t level; + spx_word16_t max_level; + spx_word16_t min_level; + + /* This is used in packet loss concealment */ + int last_pitch; /**< Pitch of last correctly decoded frame */ + spx_word16_t last_pitch_gain; /**< Pitch gain of last correctly decoded frame */ + spx_word16_t pitch_gain_buf[3]; /**< Pitch gain of last decoded frames */ + int pitch_gain_buf_idx; /**< Tail of the buffer */ + spx_int32_t seed; /** Seed used for random number generation */ + + int encode_submode; + const SpeexSubmode * const *submodes; /**< Sub-mode data */ + int submodeID; /**< Activated sub-mode */ + int lpc_enh_enabled; /**< 1 when LPC enhancer is on, 0 otherwise */ + SpeexCallback speex_callbacks[SPEEX_MAX_CALLBACKS]; + + SpeexCallback user_callback; + + /*Vocoder data*/ + spx_word16_t voc_m1; + spx_word32_t voc_m2; + spx_word16_t voc_mean; + int voc_offset; + + int dtx_enabled; + int isWideband; /**< Is this used as part of the embedded wideband codec */ + int highpass_enabled; /**< Is the input filter enabled */ +} DecState; + +/** Initializes encoder state*/ +void *nb_encoder_init(const SpeexMode *m); + +/** De-allocates encoder state resources*/ +void nb_encoder_destroy(void *state); + +/** Encodes one frame*/ +int nb_encode(void *state, void *in, SpeexBits *bits); + + +/** Initializes decoder state*/ +void *nb_decoder_init(const SpeexMode *m); + +/** De-allocates decoder state resources*/ +void nb_decoder_destroy(void *state); + +/** Decodes one frame*/ +int nb_decode(void *state, SpeexBits *bits, void *out); + +/** ioctl-like function for controlling a narrowband encoder */ +int nb_encoder_ctl(void *state, int request, void *ptr); + +/** ioctl-like function for controlling a narrowband decoder */ +int nb_decoder_ctl(void *state, int request, void *ptr); + + +#endif diff --git a/src/libspeex/os_support.h b/src/libspeex/os_support.h new file mode 100644 index 0000000..6b74b0c --- /dev/null +++ b/src/libspeex/os_support.h @@ -0,0 +1,169 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: os_support.h + This is the (tiny) OS abstraction layer. Aside from math.h, this is the + only place where system headers are allowed. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef OS_SUPPORT_H +#define OS_SUPPORT_H + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef OS_SUPPORT_CUSTOM +#include "os_support_custom.h" +#endif + +/** Speex wrapper for calloc. To do your own dynamic allocation, all you need to do is replace this function, speex_realloc and speex_free + NOTE: speex_alloc needs to CLEAR THE MEMORY */ +#ifndef OVERRIDE_SPEEX_ALLOC +static inline void *speex_alloc (int size) +{ + /* WARNING: this is not equivalent to malloc(). If you want to use malloc() + or your own allocator, YOU NEED TO CLEAR THE MEMORY ALLOCATED. Otherwise + you will experience strange bugs */ + return calloc(size,1); +} +#endif + +/** Same as speex_alloc, except that the area is only needed inside a Speex call (might cause problem with wideband though) */ +#ifndef OVERRIDE_SPEEX_ALLOC_SCRATCH +static inline void *speex_alloc_scratch (int size) +{ + /* Scratch space doesn't need to be cleared */ + return calloc(size,1); +} +#endif + +/** Speex wrapper for realloc. To do your own dynamic allocation, all you need to do is replace this function, speex_alloc and speex_free */ +#ifndef OVERRIDE_SPEEX_REALLOC +static inline void *speex_realloc (void *ptr, int size) +{ + return realloc(ptr, size); +} +#endif + +/** Speex wrapper for calloc. To do your own dynamic allocation, all you need to do is replace this function, speex_realloc and speex_alloc */ +#ifndef OVERRIDE_SPEEX_FREE +static inline void speex_free (void *ptr) +{ + free(ptr); +} +#endif + +/** Same as speex_free, except that the area is only needed inside a Speex call (might cause problem with wideband though) */ +#ifndef OVERRIDE_SPEEX_FREE_SCRATCH +static inline void speex_free_scratch (void *ptr) +{ + free(ptr); +} +#endif + +/** Copy n bytes of memory from src to dst. The 0* term provides compile-time type checking */ +#ifndef OVERRIDE_SPEEX_COPY +#define SPEEX_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) +#endif + +/** Copy n bytes of memory from src to dst, allowing overlapping regions. The 0* term + provides compile-time type checking */ +#ifndef OVERRIDE_SPEEX_MOVE +#define SPEEX_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) +#endif + +/** Set n bytes of memory to value of c, starting at address s */ +#ifndef OVERRIDE_SPEEX_MEMSET +#define SPEEX_MEMSET(dst, c, n) (memset((dst), (c), (n)*sizeof(*(dst)))) +#endif + + +#ifndef OVERRIDE_SPEEX_FATAL +static inline void _speex_fatal(const char *str, const char *file, int line) +{ + fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str); + exit(1); +} +#endif + +#ifndef OVERRIDE_SPEEX_WARNING +static inline void speex_warning(const char *str) +{ +#ifndef DISABLE_WARNINGS + fprintf (stderr, "warning: %s\n", str); +#endif +} +#endif + +#ifndef OVERRIDE_SPEEX_WARNING_INT +static inline void speex_warning_int(const char *str, int val) +{ +#ifndef DISABLE_WARNINGS + fprintf (stderr, "warning: %s %d\n", str, val); +#endif +} +#endif + +#ifndef OVERRIDE_SPEEX_NOTIFY +static inline void speex_notify(const char *str) +{ +#ifndef DISABLE_NOTIFICATIONS + fprintf (stderr, "notification: %s\n", str); +#endif +} +#endif + +#ifndef OVERRIDE_SPEEX_PUTC +/** Speex wrapper for putc */ +static inline void _speex_putc(int ch, void *file) +{ + FILE *f = (FILE *)file; + fprintf(f, "%c", ch); +} +#endif + +#define speex_fatal(str) _speex_fatal(str, __FILE__, __LINE__); +#define speex_assert(cond) {if (!(cond)) {speex_fatal("assertion failed: " #cond);}} + +#ifndef RELEASE +static inline void print_vec(float *vec, int len, char *name) +{ + int i; + printf ("%s ", name); + for (i=0;i +#include "speex/speex_preprocess.h" +#include "speex/speex_echo.h" +#include "arch.h" +#include "fftwrap.h" +#include "filterbank.h" +#include "math_approx.h" +#include "os_support.h" + +#ifndef M_PI +#define M_PI 3.14159263 +#endif + +#define LOUDNESS_EXP 5.f +#define AMP_SCALE .001f +#define AMP_SCALE_1 1000.f + +#define NB_BANDS 24 + +#define SPEECH_PROB_START_DEFAULT QCONST16(0.35f,15) +#define SPEECH_PROB_CONTINUE_DEFAULT QCONST16(0.20f,15) +#define NOISE_SUPPRESS_DEFAULT -15 +#define ECHO_SUPPRESS_DEFAULT -40 +#define ECHO_SUPPRESS_ACTIVE_DEFAULT -15 + +#ifndef NULL +#define NULL 0 +#endif + +#define SQR(x) ((x)*(x)) +#define SQR16(x) (MULT16_16((x),(x))) +#define SQR16_Q15(x) (MULT16_16_Q15((x),(x))) + +#ifdef FIXED_POINT +static inline spx_word16_t DIV32_16_Q8(spx_word32_t a, spx_word32_t b) +{ + if (SHR32(a,7) >= b) + { + return 32767; + } else { + if (b>=QCONST32(1,23)) + { + a = SHR32(a,8); + b = SHR32(b,8); + } + if (b>=QCONST32(1,19)) + { + a = SHR32(a,4); + b = SHR32(b,4); + } + if (b>=QCONST32(1,15)) + { + a = SHR32(a,4); + b = SHR32(b,4); + } + a = SHL32(a,8); + return PDIV32_16(a,b); + } + +} +static inline spx_word16_t DIV32_16_Q15(spx_word32_t a, spx_word32_t b) +{ + if (SHR32(a,15) >= b) + { + return 32767; + } else { + if (b>=QCONST32(1,23)) + { + a = SHR32(a,8); + b = SHR32(b,8); + } + if (b>=QCONST32(1,19)) + { + a = SHR32(a,4); + b = SHR32(b,4); + } + if (b>=QCONST32(1,15)) + { + a = SHR32(a,4); + b = SHR32(b,4); + } + a = SHL32(a,15)-a; + return DIV32_16(a,b); + } +} +#define SNR_SCALING 256.f +#define SNR_SCALING_1 0.0039062f +#define SNR_SHIFT 8 + +#define FRAC_SCALING 32767.f +#define FRAC_SCALING_1 3.0518e-05 +#define FRAC_SHIFT 1 + +#define EXPIN_SCALING 2048.f +#define EXPIN_SCALING_1 0.00048828f +#define EXPIN_SHIFT 11 +#define EXPOUT_SCALING_1 1.5259e-05 + +#define NOISE_SHIFT 7 + +#else + +#define DIV32_16_Q8(a,b) ((a)/(b)) +#define DIV32_16_Q15(a,b) ((a)/(b)) +#define SNR_SCALING 1.f +#define SNR_SCALING_1 1.f +#define SNR_SHIFT 0 +#define FRAC_SCALING 1.f +#define FRAC_SCALING_1 1.f +#define FRAC_SHIFT 0 +#define NOISE_SHIFT 0 + +#define EXPIN_SCALING 1.f +#define EXPIN_SCALING_1 1.f +#define EXPOUT_SCALING_1 1.f + +#endif + +/** Speex pre-processor state. */ +struct SpeexPreprocessState_ { + /* Basic info */ + int frame_size; /**< Number of samples processed each time */ + int ps_size; /**< Number of points in the power spectrum */ + int sampling_rate; /**< Sampling rate of the input/output */ + int nbands; + FilterBank *bank; + + /* Parameters */ + int denoise_enabled; + int vad_enabled; + int dereverb_enabled; + spx_word16_t reverb_decay; + spx_word16_t reverb_level; + spx_word16_t speech_prob_start; + spx_word16_t speech_prob_continue; + int noise_suppress; + int echo_suppress; + int echo_suppress_active; + SpeexEchoState *echo_state; + + /* DSP-related arrays */ + spx_word16_t *frame; /**< Processing frame (2*ps_size) */ + spx_word16_t *ft; /**< Processing frame in freq domain (2*ps_size) */ + spx_word32_t *ps; /**< Current power spectrum */ + spx_word16_t *gain2; /**< Adjusted gains */ + spx_word16_t *gain_floor; /**< Minimum gain allowed */ + spx_word16_t *window; /**< Analysis/Synthesis window */ + spx_word32_t *noise; /**< Noise estimate */ + spx_word32_t *reverb_estimate; /**< Estimate of reverb energy */ + spx_word32_t *old_ps; /**< Power spectrum for last frame */ + spx_word16_t *gain; /**< Ephraim Malah gain */ + spx_word16_t *prior; /**< A-priori SNR */ + spx_word16_t *post; /**< A-posteriori SNR */ + + spx_word32_t *S; /**< Smoothed power spectrum */ + spx_word32_t *Smin; /**< See Cohen paper */ + spx_word32_t *Stmp; /**< See Cohen paper */ + int *update_prob; /**< Probability of speech presence for noise update */ + + spx_word16_t *zeta; /**< Smoothed a priori SNR */ + spx_word32_t *echo_noise; + spx_word32_t *residual_echo; + + /* Misc */ + spx_word16_t *inbuf; /**< Input buffer (overlapped analysis) */ + spx_word16_t *outbuf; /**< Output buffer (for overlap and add) */ + + /* AGC stuff, only for floating point for now */ +#ifndef FIXED_POINT + int agc_enabled; + float agc_level; + float loudness_accum; + float *loudness_weight; /**< Perceptual loudness curve */ + float loudness; /**< Loudness estimate */ + float agc_gain; /**< Current AGC gain */ + int nb_loudness_adapt; /**< Number of frames used for loudness adaptation so far */ + float max_gain; /**< Maximum gain allowed */ + float max_increase_step; /**< Maximum increase in gain from one frame to another */ + float max_decrease_step; /**< Maximum decrease in gain from one frame to another */ + float prev_loudness; /**< Loudness of previous frame */ + float init_max; /**< Current gain limit during initialisation */ +#endif + int nb_adapt; /**< Number of frames used for adaptation so far */ + int was_speech; + int min_count; /**< Number of frames processed so far */ + void *fft_lookup; /**< Lookup table for the FFT */ +#ifdef FIXED_POINT + int frame_shift; +#endif +}; + + +static void conj_window(spx_word16_t *w, int len) +{ + int i; + for (i=0;i19) + return ADD32(EXTEND32(Q15_ONE),EXTEND32(DIV32_16(QCONST32(.1296,23), SHR32(xx,EXPIN_SHIFT-SNR_SHIFT)))); + frac = SHL32(xx-SHL32(ind,10),5); + return SHL32(DIV32_16(PSHR32(MULT16_16(Q15_ONE-frac,table[ind]) + MULT16_16(frac,table[ind+1]),7),(spx_sqrt(SHL32(xx,15)+6711))),7); +} + +static inline spx_word16_t qcurve(spx_word16_t x) +{ + x = MAX16(x, 1); + return DIV32_16(SHL32(EXTEND32(32767),9),ADD16(512,MULT16_16_Q15(QCONST16(.60f,15),DIV32_16(32767,x)))); +} + +/* Compute the gain floor based on different floors for the background noise and residual echo */ +static void compute_gain_floor(int noise_suppress, int effective_echo_suppress, spx_word32_t *noise, spx_word32_t *echo, spx_word16_t *gain_floor, int len) +{ + int i; + + if (noise_suppress > effective_echo_suppress) + { + spx_word16_t noise_gain, gain_ratio; + noise_gain = EXTRACT16(MIN32(Q15_ONE,SHR32(spx_exp(MULT16_16(QCONST16(0.11513,11),noise_suppress)),1))); + gain_ratio = EXTRACT16(MIN32(Q15_ONE,SHR32(spx_exp(MULT16_16(QCONST16(.2302585f,11),effective_echo_suppress-noise_suppress)),1))); + + /* gain_floor = sqrt [ (noise*noise_floor + echo*echo_floor) / (noise+echo) ] */ + for (i=0;i19) + return FRAC_SCALING*(1+.1296/x); + frac = 2*x-integer; + return FRAC_SCALING*((1-frac)*table[ind] + frac*table[ind+1])/sqrt(x+.0001f); +} + +static inline spx_word16_t qcurve(spx_word16_t x) +{ + return 1.f/(1.f+.15f/(SNR_SCALING_1*x)); +} + +static void compute_gain_floor(int noise_suppress, int effective_echo_suppress, spx_word32_t *noise, spx_word32_t *echo, spx_word16_t *gain_floor, int len) +{ + int i; + float echo_floor; + float noise_floor; + + noise_floor = exp(.2302585f*noise_suppress); + echo_floor = exp(.2302585f*effective_echo_suppress); + + /* Compute the gain floor based on different floors for the background noise and residual echo */ + for (i=0;iframe_size = frame_size; + + /* Round ps_size down to the nearest power of two */ +#if 0 + i=1; + st->ps_size = st->frame_size; + while(1) + { + if (st->ps_size & ~i) + { + st->ps_size &= ~i; + i<<=1; + } else { + break; + } + } + + + if (st->ps_size < 3*st->frame_size/4) + st->ps_size = st->ps_size * 3 / 2; +#else + st->ps_size = st->frame_size; +#endif + + N = st->ps_size; + N3 = 2*N - st->frame_size; + N4 = st->frame_size - N3; + + st->sampling_rate = sampling_rate; + st->denoise_enabled = 1; + st->vad_enabled = 0; + st->dereverb_enabled = 0; + st->reverb_decay = 0; + st->reverb_level = 0; + st->noise_suppress = NOISE_SUPPRESS_DEFAULT; + st->echo_suppress = ECHO_SUPPRESS_DEFAULT; + st->echo_suppress_active = ECHO_SUPPRESS_ACTIVE_DEFAULT; + + st->speech_prob_start = SPEECH_PROB_START_DEFAULT; + st->speech_prob_continue = SPEECH_PROB_CONTINUE_DEFAULT; + + st->echo_state = NULL; + + st->nbands = NB_BANDS; + M = st->nbands; + st->bank = filterbank_new(M, sampling_rate, N, 1); + + st->frame = (spx_word16_t*)speex_alloc(2*N*sizeof(spx_word16_t)); + st->window = (spx_word16_t*)speex_alloc(2*N*sizeof(spx_word16_t)); + st->ft = (spx_word16_t*)speex_alloc(2*N*sizeof(spx_word16_t)); + + st->ps = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->noise = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->echo_noise = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->residual_echo = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->reverb_estimate = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->old_ps = (spx_word32_t*)speex_alloc((N+M)*sizeof(spx_word32_t)); + st->prior = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->post = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->gain = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->gain2 = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->gain_floor = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + st->zeta = (spx_word16_t*)speex_alloc((N+M)*sizeof(spx_word16_t)); + + st->S = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); + st->Smin = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); + st->Stmp = (spx_word32_t*)speex_alloc(N*sizeof(spx_word32_t)); + st->update_prob = (int*)speex_alloc(N*sizeof(int)); + + st->inbuf = (spx_word16_t*)speex_alloc(N3*sizeof(spx_word16_t)); + st->outbuf = (spx_word16_t*)speex_alloc(N3*sizeof(spx_word16_t)); + + conj_window(st->window, 2*N3); + for (i=2*N3;i<2*st->ps_size;i++) + st->window[i]=Q15_ONE; + + if (N4>0) + { + for (i=N3-1;i>=0;i--) + { + st->window[i+N3+N4]=st->window[i+N3]; + st->window[i+N3]=1; + } + } + for (i=0;inoise[i]=QCONST32(1.f,NOISE_SHIFT); + st->reverb_estimate[i]=0; + st->old_ps[i]=1; + st->gain[i]=Q15_ONE; + st->post[i]=SHL16(1, SNR_SHIFT); + st->prior[i]=SHL16(1, SNR_SHIFT); + } + + for (i=0;iupdate_prob[i] = 1; + for (i=0;iinbuf[i]=0; + st->outbuf[i]=0; + } +#ifndef FIXED_POINT + st->agc_enabled = 0; + st->agc_level = 8000; + st->loudness_weight = (float*)speex_alloc(N*sizeof(float)); + for (i=0;iloudness_weight[i] = .5f*(1.f/(1.f+ff/8000.f))+1.f*exp(-.5f*(ff-3800.f)*(ff-3800.f)/9e5f);*/ + st->loudness_weight[i] = .35f-.35f*ff/16000.f+.73f*exp(-.5f*(ff-3800)*(ff-3800)/9e5f); + if (st->loudness_weight[i]<.01f) + st->loudness_weight[i]=.01f; + st->loudness_weight[i] *= st->loudness_weight[i]; + } + /*st->loudness = pow(AMP_SCALE*st->agc_level,LOUDNESS_EXP);*/ + st->loudness = 1e-15; + st->agc_gain = 1; + st->nb_loudness_adapt = 0; + st->max_gain = 30; + st->max_increase_step = exp(0.11513f * 12.*st->frame_size / st->sampling_rate); + st->max_decrease_step = exp(-0.11513f * 40.*st->frame_size / st->sampling_rate); + st->prev_loudness = 1; + st->init_max = 1; +#endif + st->was_speech = 0; + + st->fft_lookup = spx_fft_init(2*N); + + st->nb_adapt=0; + st->min_count=0; + return st; +} + +void speex_preprocess_state_destroy(SpeexPreprocessState *st) +{ + speex_free(st->frame); + speex_free(st->ft); + speex_free(st->ps); + speex_free(st->gain2); + speex_free(st->gain_floor); + speex_free(st->window); + speex_free(st->noise); + speex_free(st->reverb_estimate); + speex_free(st->old_ps); + speex_free(st->gain); + speex_free(st->prior); + speex_free(st->post); +#ifndef FIXED_POINT + speex_free(st->loudness_weight); +#endif + speex_free(st->echo_noise); + speex_free(st->residual_echo); + + speex_free(st->S); + speex_free(st->Smin); + speex_free(st->Stmp); + speex_free(st->update_prob); + speex_free(st->zeta); + + speex_free(st->inbuf); + speex_free(st->outbuf); + + spx_fft_destroy(st->fft_lookup); + filterbank_destroy(st->bank); + speex_free(st); +} + +/* FIXME: The AGC doesn't work yet with fixed-point*/ +#ifndef FIXED_POINT +static void speex_compute_agc(SpeexPreprocessState *st, spx_word16_t Pframe, spx_word16_t *ft) +{ + int i; + int N = st->ps_size; + float target_gain; + float loudness=1.f; + float rate; + + for (i=2;ips[i]* st->loudness_weight[i]; + } + loudness=sqrt(loudness); + /*if (loudness < 2*pow(st->loudness, 1.0/LOUDNESS_EXP) && + loudness*2 > pow(st->loudness, 1.0/LOUDNESS_EXP))*/ + if (Pframe>.3f) + { + st->nb_loudness_adapt++; + /*rate=2.0f*Pframe*Pframe/(1+st->nb_loudness_adapt);*/ + rate = .03*Pframe*Pframe; + st->loudness = (1-rate)*st->loudness + (rate)*pow(AMP_SCALE*loudness, LOUDNESS_EXP); + st->loudness_accum = (1-rate)*st->loudness_accum + rate; + if (st->init_max < st->max_gain && st->nb_adapt > 20) + st->init_max *= 1.f + .1f*Pframe*Pframe; + } + /*printf ("%f %f %f %f\n", Pframe, loudness, pow(st->loudness, 1.0f/LOUDNESS_EXP), st->loudness2);*/ + + target_gain = AMP_SCALE*st->agc_level*pow(st->loudness/(1e-4+st->loudness_accum), -1.0f/LOUDNESS_EXP); + + if ((Pframe>.5 && st->nb_adapt > 20) || target_gain < st->agc_gain) + { + if (target_gain > st->max_increase_step*st->agc_gain) + target_gain = st->max_increase_step*st->agc_gain; + if (target_gain < st->max_decrease_step*st->agc_gain && loudness < 10*st->prev_loudness) + target_gain = st->max_decrease_step*st->agc_gain; + if (target_gain > st->max_gain) + target_gain = st->max_gain; + if (target_gain > st->init_max) + target_gain = st->init_max; + + st->agc_gain = target_gain; + } + /*fprintf (stderr, "%f %f %f\n", loudness, (float)AMP_SCALE_1*pow(st->loudness, 1.0f/LOUDNESS_EXP), st->agc_gain);*/ + + for (i=0;i<2*N;i++) + ft[i] *= st->agc_gain; + st->prev_loudness = loudness; +} +#endif + +static void preprocess_analysis(SpeexPreprocessState *st, spx_int16_t *x) +{ + int i; + int N = st->ps_size; + int N3 = 2*N - st->frame_size; + int N4 = st->frame_size - N3; + spx_word32_t *ps=st->ps; + + /* 'Build' input frame */ + for (i=0;iframe[i]=st->inbuf[i]; + for (i=0;iframe_size;i++) + st->frame[N3+i]=x[i]; + + /* Update inbuf */ + for (i=0;iinbuf[i]=x[N4+i]; + + /* Windowing */ + for (i=0;i<2*N;i++) + st->frame[i] = MULT16_16_Q15(st->frame[i], st->window[i]); + +#ifdef FIXED_POINT + { + spx_word16_t max_val=0; + for (i=0;i<2*N;i++) + max_val = MAX16(max_val, ABS16(st->frame[i])); + st->frame_shift = 14-spx_ilog2(EXTEND32(max_val)); + for (i=0;i<2*N;i++) + st->frame[i] = SHL16(st->frame[i], st->frame_shift); + } +#endif + + /* Perform FFT */ + spx_fft(st->fft_lookup, st->frame, st->ft); + + /* Power spectrum */ + ps[0]=MULT16_16(st->ft[0],st->ft[0]); + for (i=1;ift[2*i-1],st->ft[2*i-1]) + MULT16_16(st->ft[2*i],st->ft[2*i]); + for (i=0;ips[i] = PSHR32(st->ps[i], 2*st->frame_shift); + + filterbank_compute_bank32(st->bank, ps, ps+N); +} + +static void update_noise_prob(SpeexPreprocessState *st) +{ + int i; + int min_range; + int N = st->ps_size; + + for (i=1;iS[i] = MULT16_32_Q15(QCONST16(.8f,15),st->S[i]) + MULT16_32_Q15(QCONST16(.05f,15),st->ps[i-1]) + + MULT16_32_Q15(QCONST16(.1f,15),st->ps[i]) + MULT16_32_Q15(QCONST16(.05f,15),st->ps[i+1]); + st->S[0] = MULT16_32_Q15(QCONST16(.8f,15),st->S[0]) + MULT16_32_Q15(QCONST16(.2f,15),st->ps[0]); + st->S[N-1] = MULT16_32_Q15(QCONST16(.8f,15),st->S[N-1]) + MULT16_32_Q15(QCONST16(.2f,15),st->ps[N-1]); + + if (st->nb_adapt==1) + { + for (i=0;iSmin[i] = st->Stmp[i] = 0; + } + + if (st->nb_adapt < 100) + min_range = 15; + else if (st->nb_adapt < 1000) + min_range = 50; + else if (st->nb_adapt < 10000) + min_range = 150; + else + min_range = 300; + if (st->min_count > min_range) + { + st->min_count = 0; + for (i=0;iSmin[i] = MIN32(st->Stmp[i], st->S[i]); + st->Stmp[i] = st->S[i]; + } + } else { + for (i=0;iSmin[i] = MIN32(st->Smin[i], st->S[i]); + st->Stmp[i] = MIN32(st->Stmp[i], st->S[i]); + } + } + for (i=0;iS[i]) > ADD32(st->Smin[i],EXTEND32(20))) + st->update_prob[i] = 1; + else + st->update_prob[i] = 0; + /*fprintf (stderr, "%f ", st->S[i]/st->Smin[i]);*/ + /*fprintf (stderr, "%f ", st->update_prob[i]);*/ + } + +} + +#define NOISE_OVERCOMPENS 1. + +void speex_echo_get_residual(SpeexEchoState *st, spx_word32_t *Yout, int len); + +int speex_preprocess(SpeexPreprocessState *st, spx_int16_t *x, spx_int32_t *echo) +{ + return speex_preprocess_run(st, x); +} + +int speex_preprocess_run(SpeexPreprocessState *st, spx_int16_t *x) +{ + int i; + int M; + int N = st->ps_size; + int N3 = 2*N - st->frame_size; + int N4 = st->frame_size - N3; + spx_word32_t *ps=st->ps; + spx_word32_t Zframe; + spx_word16_t Pframe; + spx_word16_t beta, beta_1; + spx_word16_t effective_echo_suppress; + + st->nb_adapt++; + if (st->nb_adapt>20000) + st->nb_adapt = 20000; + st->min_count++; + + beta = MAX16(QCONST16(.03,15),DIV32_16(Q15_ONE,st->nb_adapt)); + beta_1 = Q15_ONE-beta; + M = st->nbands; + /* Deal with residual echo if provided */ + if (st->echo_state) + { + speex_echo_get_residual(st->echo_state, st->residual_echo, N); +#ifndef FIXED_POINT + /* If there are NaNs or ridiculous values, it'll show up in the DC and we just reset everything to zero */ + if (!(st->residual_echo[0] >=0 && st->residual_echo[0]residual_echo[i] = 0; + } +#endif + for (i=0;iecho_noise[i] = MAX32(MULT16_32_Q15(QCONST16(.6f,15),st->echo_noise[i]), st->residual_echo[i]); + filterbank_compute_bank32(st->bank, st->echo_noise, st->echo_noise+N); + } else { + for (i=0;iecho_noise[i] = 0; + } + preprocess_analysis(st, x); + + update_noise_prob(st); + + /* Noise estimation always updated for the 10 first frames */ + /*if (st->nb_adapt<10) + { + for (i=1;iupdate_prob[i] = 0; + } + */ + + /* Update the noise estimate for the frequencies where it can be */ + for (i=0;iupdate_prob[i] || st->ps[i] < PSHR32(st->noise[i], NOISE_SHIFT)) + st->noise[i] = MAX32(EXTEND32(0),MULT16_32_Q15(beta_1,st->noise[i]) + MULT16_32_Q15(beta,SHL32(st->ps[i],NOISE_SHIFT))); + } + filterbank_compute_bank32(st->bank, st->noise, st->noise+N); + + /* Special case for first frame */ + if (st->nb_adapt==1) + for (i=0;iold_ps[i] = ps[i]; + + /* Compute a posteriori SNR */ + for (i=0;inoise[i],NOISE_SHIFT)) , st->echo_noise[i]) , st->reverb_estimate[i]); + + /* A posteriori SNR = ps/noise - 1*/ + st->post[i] = SUB16(DIV32_16_Q8(ps[i],tot_noise), QCONST16(1.f,SNR_SHIFT)); + st->post[i]=MIN16(st->post[i], QCONST16(100.f,SNR_SHIFT)); + + /* Computing update gamma = .1 + .9*(old/(old+noise))^2 */ + gamma = QCONST16(.1f,15)+MULT16_16_Q15(QCONST16(.89f,15),SQR16_Q15(DIV32_16_Q15(st->old_ps[i],ADD32(st->old_ps[i],tot_noise)))); + + /* A priori SNR update = gamma*max(0,post) + (1-gamma)*old/noise */ + st->prior[i] = EXTRACT16(PSHR32(ADD32(MULT16_16(gamma,MAX16(0,st->post[i])), MULT16_16(Q15_ONE-gamma,DIV32_16_Q8(st->old_ps[i],tot_noise))), 15)); + st->prior[i]=MIN16(st->prior[i], QCONST16(100.f,SNR_SHIFT)); + } + + /*print_vec(st->post, N+M, "");*/ + + /* Recursive average of the a priori SNR. A bit smoothed for the psd components */ + st->zeta[0] = PSHR32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[0]), MULT16_16(QCONST16(.3f,15),st->prior[0])),15); + for (i=1;izeta[i] = PSHR32(ADD32(ADD32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[i]), MULT16_16(QCONST16(.15f,15),st->prior[i])), + MULT16_16(QCONST16(.075f,15),st->prior[i-1])), MULT16_16(QCONST16(.075f,15),st->prior[i+1])),15); + for (i=N-1;izeta[i] = PSHR32(ADD32(MULT16_16(QCONST16(.7f,15),st->zeta[i]), MULT16_16(QCONST16(.3f,15),st->prior[i])),15); + + /* Speech probability of presence for the entire frame is based on the average filterbank a priori SNR */ + Zframe = 0; + for (i=N;izeta[i])); + Pframe = QCONST16(.1f,15)+MULT16_16_Q15(QCONST16(.899f,15),qcurve(DIV32_16(Zframe,st->nbands))); + + effective_echo_suppress = EXTRACT16(PSHR32(ADD32(MULT16_16(SUB16(Q15_ONE,Pframe), st->echo_suppress), MULT16_16(Pframe, st->echo_suppress_active)),15)); + + compute_gain_floor(st->noise_suppress, effective_echo_suppress, st->noise+N, st->echo_noise+N, st->gain_floor+N, M); + + /* Compute Ephraim & Malah gain speech probability of presence for each critical band (Bark scale) + Technically this is actually wrong because the EM gaim assumes a slightly different probability + distribution */ + for (i=N;iprior[i]), 15), ADD16(st->prior[i], SHL32(1,SNR_SHIFT))); + theta = MULT16_32_P15(prior_ratio, QCONST32(1.f,EXPIN_SHIFT)+SHL32(EXTEND32(st->post[i]),EXPIN_SHIFT-SNR_SHIFT)); + + MM = hypergeom_gain(theta); + /* Gain with bound */ + st->gain[i] = EXTRACT16(MIN32(Q15_ONE, MULT16_32_Q15(prior_ratio, MM))); + /* Save old Bark power spectrum */ + st->old_ps[i] = MULT16_32_P15(QCONST16(.2f,15),st->old_ps[i]) + MULT16_32_P15(MULT16_16_P15(QCONST16(.8f,15),SQR16_Q15(st->gain[i])),ps[i]); + + P1 = QCONST16(.199f,15)+MULT16_16_Q15(QCONST16(.8f,15),qcurve (st->zeta[i])); + q = Q15_ONE-MULT16_16_Q15(Pframe,P1); +#ifdef FIXED_POINT + theta = MIN32(theta, EXTEND32(32767)); +/*Q8*/tmp = MULT16_16_Q15((SHL32(1,SNR_SHIFT)+st->prior[i]),EXTRACT16(MIN32(Q15ONE,SHR32(spx_exp(-EXTRACT16(theta)),1)))); + tmp = MIN16(QCONST16(3.,SNR_SHIFT), tmp); /* Prevent overflows in the next line*/ +/*Q8*/tmp = EXTRACT16(PSHR32(MULT16_16(PDIV32_16(SHL32(EXTEND32(q),8),(Q15_ONE-q)),tmp),8)); + st->gain2[i]=DIV32_16(SHL32(EXTEND32(32767),SNR_SHIFT), ADD16(256,tmp)); +#else + st->gain2[i]=1/(1.f + (q/(1.f-q))*(1+st->prior[i])*exp(-theta)); +#endif + } + /* Convert the EM gains and speech prob to linear frequency */ + filterbank_compute_psd16(st->bank,st->gain2+N, st->gain2); + filterbank_compute_psd16(st->bank,st->gain+N, st->gain); + + /* Use 1 for linear gain resolution (best) or 0 for Bark gain resolution (faster) */ + if (1) + { + filterbank_compute_psd16(st->bank,st->gain_floor+N, st->gain_floor); + + /* Compute gain according to the Ephraim-Malah algorithm -- linear frequency */ + for (i=0;iprior[i]), 15), ADD16(st->prior[i], SHL32(1,SNR_SHIFT))); + theta = MULT16_32_P15(prior_ratio, QCONST32(1.f,EXPIN_SHIFT)+SHL32(EXTEND32(st->post[i]),EXPIN_SHIFT-SNR_SHIFT)); + + /* Optimal estimator for loudness domain */ + MM = hypergeom_gain(theta); + /* EM gain with bound */ + g = EXTRACT16(MIN32(Q15_ONE, MULT16_32_Q15(prior_ratio, MM))); + /* Interpolated speech probability of presence */ + p = st->gain2[i]; + + /* Constrain the gain to be close to the Bark scale gain */ + if (MULT16_16_Q15(QCONST16(.333f,15),g) > st->gain[i]) + g = MULT16_16(3,st->gain[i]); + st->gain[i] = g; + + /* Save old power spectrum */ + st->old_ps[i] = MULT16_32_P15(QCONST16(.2f,15),st->old_ps[i]) + MULT16_32_P15(MULT16_16_P15(QCONST16(.8f,15),SQR16_Q15(st->gain[i])),ps[i]); + + /* Apply gain floor */ + if (st->gain[i] < st->gain_floor[i]) + st->gain[i] = st->gain_floor[i]; + + /* Exponential decay model for reverberation (unused) */ + /*st->reverb_estimate[i] = st->reverb_decay*st->reverb_estimate[i] + st->reverb_decay*st->reverb_level*st->gain[i]*st->gain[i]*st->ps[i];*/ + + /* Take into account speech probability of presence (loudness domain MMSE estimator) */ + /* gain2 = [p*sqrt(gain)+(1-p)*sqrt(gain _floor) ]^2 */ + tmp = MULT16_16_P15(p,spx_sqrt(SHL32(EXTEND32(st->gain[i]),15))) + MULT16_16_P15(SUB16(Q15_ONE,p),spx_sqrt(SHL32(EXTEND32(st->gain_floor[i]),15))); + st->gain2[i]=SQR16_Q15(tmp); + + /* Use this if you want a log-domain MMSE estimator instead */ + /*st->gain2[i] = pow(st->gain[i], p) * pow(st->gain_floor[i],1.f-p);*/ + } + } else { + for (i=N;igain2[i]; + st->gain[i] = MAX16(st->gain[i], st->gain_floor[i]); + tmp = MULT16_16_P15(p,spx_sqrt(SHL32(EXTEND32(st->gain[i]),15))) + MULT16_16_P15(SUB16(Q15_ONE,p),spx_sqrt(SHL32(EXTEND32(st->gain_floor[i]),15))); + st->gain2[i]=SQR16_Q15(tmp); + } + filterbank_compute_psd16(st->bank,st->gain2+N, st->gain2); + } + + /* If noise suppression is off, don't apply the gain (but then why call this in the first place!) */ + if (!st->denoise_enabled) + { + for (i=0;igain2[i]=Q15_ONE; + } + + /* Apply computed gain */ + for (i=1;ift[2*i-1] = MULT16_16_P15(st->gain2[i],st->ft[2*i-1]); + st->ft[2*i] = MULT16_16_P15(st->gain2[i],st->ft[2*i]); + } + st->ft[0] = MULT16_16_P15(st->gain2[0],st->ft[0]); + st->ft[2*N-1] = MULT16_16_P15(st->gain2[N-1],st->ft[2*N-1]); + + /*FIXME: This *will* not work for fixed-point */ +#ifndef FIXED_POINT + if (st->agc_enabled) + speex_compute_agc(st, Pframe, st->ft); +#endif + + /* Inverse FFT with 1/N scaling */ + spx_ifft(st->fft_lookup, st->ft, st->frame); + /* Scale back to original (lower) amplitude */ + for (i=0;i<2*N;i++) + st->frame[i] = PSHR16(st->frame[i], st->frame_shift); + + /*FIXME: This *will* not work for fixed-point */ +#ifndef FIXED_POINT + if (st->agc_enabled) + { + float max_sample=0; + for (i=0;i<2*N;i++) + if (fabs(st->frame[i])>max_sample) + max_sample = fabs(st->frame[i]); + if (max_sample>28000.f) + { + float damp = 28000.f/max_sample; + for (i=0;i<2*N;i++) + st->frame[i] *= damp; + } + } +#endif + + /* Synthesis window (for WOLA) */ + for (i=0;i<2*N;i++) + st->frame[i] = MULT16_16_Q15(st->frame[i], st->window[i]); + + /* Perform overlap and add */ + for (i=0;ioutbuf[i] + st->frame[i]; + for (i=0;iframe[N3+i]; + + /* Update outbuf */ + for (i=0;ioutbuf[i] = st->frame[st->frame_size+i]; + + /* FIXME: This VAD is a kludge */ + if (st->vad_enabled) + { + if (Pframe > st->speech_prob_start || (st->was_speech && Pframe > st->speech_prob_continue)) + { + st->was_speech=1; + return 1; + } else + { + st->was_speech=0; + return 0; + } + } else { + return 1; + } +} + +void speex_preprocess_estimate_update(SpeexPreprocessState *st, spx_int16_t *x) +{ + int i; + int N = st->ps_size; + int N3 = 2*N - st->frame_size; + int M; + spx_word32_t *ps=st->ps; + + M = st->nbands; + st->min_count++; + + preprocess_analysis(st, x); + + update_noise_prob(st); + + for (i=1;iupdate_prob[i] || st->ps[i] < PSHR32(st->noise[i],NOISE_SHIFT)) + { + st->noise[i] = MULT16_32_Q15(QCONST16(.95f,15),st->noise[i]) + MULT16_32_Q15(QCONST16(.05f,15),SHL32(st->ps[i],NOISE_SHIFT)); + } + } + + for (i=0;ioutbuf[i] = MULT16_16_Q15(x[st->frame_size-N3+i],st->window[st->frame_size+i]); + + /* Save old power spectrum */ + for (i=0;iold_ps[i] = ps[i]; + + for (i=0;ireverb_estimate[i] = MULT16_32_Q15(st->reverb_decay, st->reverb_estimate[i]); +} + + +int speex_preprocess_ctl(SpeexPreprocessState *state, int request, void *ptr) +{ + int i; + SpeexPreprocessState *st; + st=(SpeexPreprocessState*)state; + switch(request) + { + case SPEEX_PREPROCESS_SET_DENOISE: + st->denoise_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_DENOISE: + (*(spx_int32_t*)ptr) = st->denoise_enabled; + break; +#ifndef FIXED_POINT + case SPEEX_PREPROCESS_SET_AGC: + st->agc_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_AGC: + (*(spx_int32_t*)ptr) = st->agc_enabled; + break; +#ifndef DISABLE_FLOAT_API + case SPEEX_PREPROCESS_SET_AGC_LEVEL: + st->agc_level = (*(float*)ptr); + if (st->agc_level<1) + st->agc_level=1; + if (st->agc_level>32768) + st->agc_level=32768; + break; + case SPEEX_PREPROCESS_GET_AGC_LEVEL: + (*(float*)ptr) = st->agc_level; + break; +#endif /* #ifndef DISABLE_FLOAT_API */ + case SPEEX_PREPROCESS_SET_AGC_INCREMENT: + st->max_increase_step = exp(0.11513f * (*(spx_int32_t*)ptr)*st->frame_size / st->sampling_rate); + break; + case SPEEX_PREPROCESS_GET_AGC_INCREMENT: + (*(spx_int32_t*)ptr) = floor(.5+8.6858*log(st->max_increase_step)*st->sampling_rate/st->frame_size); + break; + case SPEEX_PREPROCESS_SET_AGC_DECREMENT: + st->max_decrease_step = exp(0.11513f * (*(spx_int32_t*)ptr)*st->frame_size / st->sampling_rate); + break; + case SPEEX_PREPROCESS_GET_AGC_DECREMENT: + (*(spx_int32_t*)ptr) = floor(.5+8.6858*log(st->max_decrease_step)*st->sampling_rate/st->frame_size); + break; + case SPEEX_PREPROCESS_SET_AGC_MAX_GAIN: + st->max_gain = exp(0.11513f * (*(spx_int32_t*)ptr)); + break; + case SPEEX_PREPROCESS_GET_AGC_MAX_GAIN: + (*(spx_int32_t*)ptr) = floor(.5+8.6858*log(st->max_gain)); + break; +#endif + case SPEEX_PREPROCESS_SET_VAD: + speex_warning("The VAD has been replaced by a hack pending a complete rewrite"); + st->vad_enabled = (*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_VAD: + (*(spx_int32_t*)ptr) = st->vad_enabled; + break; + + case SPEEX_PREPROCESS_SET_DEREVERB: + st->dereverb_enabled = (*(spx_int32_t*)ptr); + for (i=0;ips_size;i++) + st->reverb_estimate[i]=0; + break; + case SPEEX_PREPROCESS_GET_DEREVERB: + (*(spx_int32_t*)ptr) = st->dereverb_enabled; + break; + + case SPEEX_PREPROCESS_SET_DEREVERB_LEVEL: + /* FIXME: Re-enable when de-reverberation is actually enabled again */ + /*st->reverb_level = (*(float*)ptr);*/ + break; + case SPEEX_PREPROCESS_GET_DEREVERB_LEVEL: + /* FIXME: Re-enable when de-reverberation is actually enabled again */ + /*(*(float*)ptr) = st->reverb_level;*/ + break; + + case SPEEX_PREPROCESS_SET_DEREVERB_DECAY: + /* FIXME: Re-enable when de-reverberation is actually enabled again */ + /*st->reverb_decay = (*(float*)ptr);*/ + break; + case SPEEX_PREPROCESS_GET_DEREVERB_DECAY: + /* FIXME: Re-enable when de-reverberation is actually enabled again */ + /*(*(float*)ptr) = st->reverb_decay;*/ + break; + + case SPEEX_PREPROCESS_SET_PROB_START: + *(spx_int32_t*)ptr = MIN32(100,MAX32(0, *(spx_int32_t*)ptr)); + st->speech_prob_start = DIV32_16(MULT16_16(Q15ONE,*(spx_int32_t*)ptr), 100); + break; + case SPEEX_PREPROCESS_GET_PROB_START: + (*(spx_int32_t*)ptr) = MULT16_16_Q15(st->speech_prob_start, 100); + break; + + case SPEEX_PREPROCESS_SET_PROB_CONTINUE: + *(spx_int32_t*)ptr = MIN32(100,MAX32(0, *(spx_int32_t*)ptr)); + st->speech_prob_continue = DIV32_16(MULT16_16(Q15ONE,*(spx_int32_t*)ptr), 100); + break; + case SPEEX_PREPROCESS_GET_PROB_CONTINUE: + (*(spx_int32_t*)ptr) = MULT16_16_Q15(st->speech_prob_continue, 100); + break; + + case SPEEX_PREPROCESS_SET_NOISE_SUPPRESS: + st->noise_suppress = -ABS(*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_NOISE_SUPPRESS: + (*(spx_int32_t*)ptr) = st->noise_suppress; + break; + case SPEEX_PREPROCESS_SET_ECHO_SUPPRESS: + st->echo_suppress = -ABS(*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_ECHO_SUPPRESS: + (*(spx_int32_t*)ptr) = st->echo_suppress; + break; + case SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE: + st->echo_suppress_active = -ABS(*(spx_int32_t*)ptr); + break; + case SPEEX_PREPROCESS_GET_ECHO_SUPPRESS_ACTIVE: + (*(spx_int32_t*)ptr) = st->echo_suppress_active; + break; + case SPEEX_PREPROCESS_SET_ECHO_STATE: + st->echo_state = (SpeexEchoState*)ptr; + break; + case SPEEX_PREPROCESS_GET_ECHO_STATE: + ptr = (void*)st->echo_state; + break; +#ifndef FIXED_POINT + case SPEEX_PREPROCESS_GET_AGC_LOUDNESS: + (*(spx_int32_t*)ptr) = pow(st->loudness, 1.0/LOUDNESS_EXP); + break; +#endif + + default: + speex_warning_int("Unknown speex_preprocess_ctl request: ", request); + return -1; + } + return 0; +} diff --git a/src/libspeex/pseudofloat.h b/src/libspeex/pseudofloat.h new file mode 100644 index 0000000..fa841a0 --- /dev/null +++ b/src/libspeex/pseudofloat.h @@ -0,0 +1,379 @@ +/* Copyright (C) 2005 Jean-Marc Valin */ +/** + @file pseudofloat.h + @brief Pseudo-floating point + * This header file provides a lightweight floating point type for + * use on fixed-point platforms when a large dynamic range is + * required. The new type is not compatible with the 32-bit IEEE format, + * it is not even remotely as accurate as 32-bit floats, and is not + * even guaranteed to produce even remotely correct results for code + * other than Speex. It makes all kinds of shortcuts that are acceptable + * for Speex, but may not be acceptable for your application. You're + * quite welcome to reuse this code and improve it, but don't assume + * it works out of the box. Most likely, it doesn't. + */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef PSEUDOFLOAT_H +#define PSEUDOFLOAT_H + +#include "arch.h" +#include "os_support.h" +#include "math_approx.h" +#include + +#ifdef FIXED_POINT + +typedef struct { + spx_int16_t m; + spx_int16_t e; +} spx_float_t; + +static const spx_float_t FLOAT_ZERO = {0,0}; +static const spx_float_t FLOAT_ONE = {16384,-14}; +static const spx_float_t FLOAT_HALF = {16384,-15}; + +#define MIN(a,b) ((a)<(b)?(a):(b)) +static inline spx_float_t PSEUDOFLOAT(spx_int32_t x) +{ + int e=0; + int sign=0; + if (x<0) + { + sign = 1; + x = -x; + } + if (x==0) + { + spx_float_t r = {0,0}; + return r; + } + e = spx_ilog2(ABS32(x))-14; + x = VSHR32(x, e); + if (sign) + { + spx_float_t r; + r.m = -x; + r.e = e; + return r; + } + else + { + spx_float_t r; + r.m = x; + r.e = e; + return r; + } +} + + +static inline spx_float_t FLOAT_ADD(spx_float_t a, spx_float_t b) +{ + spx_float_t r; + if (a.m==0) + return b; + else if (b.m==0) + return a; + if ((a).e > (b).e) + { + r.m = ((a).m>>1) + ((b).m>>MIN(15,(a).e-(b).e+1)); + r.e = (a).e+1; + } + else + { + r.m = ((b).m>>1) + ((a).m>>MIN(15,(b).e-(a).e+1)); + r.e = (b).e+1; + } + if (r.m>0) + { + if (r.m<16384) + { + r.m<<=1; + r.e-=1; + } + } else { + if (r.m>-16384) + { + r.m<<=1; + r.e-=1; + } + } + /*printf ("%f + %f = %f\n", REALFLOAT(a), REALFLOAT(b), REALFLOAT(r));*/ + return r; +} + +static inline spx_float_t FLOAT_SUB(spx_float_t a, spx_float_t b) +{ + spx_float_t r; + if (a.m==0) + return b; + else if (b.m==0) + return a; + if ((a).e > (b).e) + { + r.m = ((a).m>>1) - ((b).m>>MIN(15,(a).e-(b).e+1)); + r.e = (a).e+1; + } + else + { + r.m = ((a).m>>MIN(15,(b).e-(a).e+1)) - ((b).m>>1); + r.e = (b).e+1; + } + if (r.m>0) + { + if (r.m<16384) + { + r.m<<=1; + r.e-=1; + } + } else { + if (r.m>-16384) + { + r.m<<=1; + r.e-=1; + } + } + /*printf ("%f + %f = %f\n", REALFLOAT(a), REALFLOAT(b), REALFLOAT(r));*/ + return r; +} + +static inline int FLOAT_LT(spx_float_t a, spx_float_t b) +{ + if (a.m==0) + return b.m>0; + else if (b.m==0) + return a.m<0; + if ((a).e > (b).e) + return ((a).m>>1) < ((b).m>>MIN(15,(a).e-(b).e+1)); + else + return ((b).m>>1) > ((a).m>>MIN(15,(b).e-(a).e+1)); + +} + +static inline int FLOAT_GT(spx_float_t a, spx_float_t b) +{ + return FLOAT_LT(b,a); +} + +static inline spx_float_t FLOAT_MULT(spx_float_t a, spx_float_t b) +{ + spx_float_t r; + r.m = (spx_int16_t)((spx_int32_t)(a).m*(b).m>>15); + r.e = (a).e+(b).e+15; + if (r.m>0) + { + if (r.m<16384) + { + r.m<<=1; + r.e-=1; + } + } else { + if (r.m>-16384) + { + r.m<<=1; + r.e-=1; + } + } + /*printf ("%f * %f = %f\n", REALFLOAT(a), REALFLOAT(b), REALFLOAT(r));*/ + return r; +} + +static inline spx_float_t FLOAT_AMULT(spx_float_t a, spx_float_t b) +{ + spx_float_t r; + r.m = (spx_int16_t)((spx_int32_t)(a).m*(b).m>>15); + r.e = (a).e+(b).e+15; + return r; +} + + +static inline spx_float_t FLOAT_SHL(spx_float_t a, int b) +{ + spx_float_t r; + r.m = a.m; + r.e = a.e+b; + return r; +} + +static inline spx_int16_t FLOAT_EXTRACT16(spx_float_t a) +{ + if (a.e<0) + return EXTRACT16((EXTEND32(a.m)+(EXTEND32(1)<<(-a.e-1)))>>-a.e); + else + return a.m<>-a.e; + else + return EXTEND32(a.m)<=SHL32(EXTEND32(b.m-1),15)) + { + a >>= 1; + e++; + } + r.m = DIV32_16(a,b.m); + r.e = e-b.e; + return r; +} + + +/* Do NOT attempt to divide by a negative number */ +static inline spx_float_t FLOAT_DIV32(spx_word32_t a, spx_word32_t b) +{ + int e0=0,e=0; + spx_float_t r; + if (a==0) + { + return FLOAT_ZERO; + } + if (b>32767) + { + e0 = spx_ilog2(b)-14; + b = VSHR32(b, e0); + e0 = -e0; + } + e = spx_ilog2(ABS32(a))-spx_ilog2(b-1)-15; + a = VSHR32(a, e); + if (ABS32(a)>=SHL32(EXTEND32(b-1),15)) + { + a >>= 1; + e++; + } + e += e0; + r.m = DIV32_16(a,b); + r.e = e; + return r; +} + +/* Do NOT attempt to divide by a negative number */ +static inline spx_float_t FLOAT_DIVU(spx_float_t a, spx_float_t b) +{ + int e=0; + spx_int32_t num; + spx_float_t r; + if (b.m<=0) + { + speex_warning_int("Attempted to divide by", b.m); + return FLOAT_ONE; + } + num = a.m; + a.m = ABS16(a.m); + while (a.m >= b.m) + { + e++; + a.m >>= 1; + } + num = num << (15-e); + r.m = DIV32_16(num,b.m); + r.e = a.e-b.e-15+e; + return r; +} + +static inline spx_float_t FLOAT_SQRT(spx_float_t a) +{ + spx_float_t r; + spx_int32_t m; + m = SHL32(EXTEND32(a.m), 14); + r.e = a.e - 14; + if (r.e & 1) + { + r.e -= 1; + m <<= 1; + } + r.e >>= 1; + r.m = spx_sqrt(m); + return r; +} + +#else + +#define spx_float_t float +#define FLOAT_ZERO 0.f +#define FLOAT_ONE 1.f +#define FLOAT_HALF 0.5f +#define PSEUDOFLOAT(x) (x) +#define FLOAT_MULT(a,b) ((a)*(b)) +#define FLOAT_AMULT(a,b) ((a)*(b)) +#define FLOAT_MUL32(a,b) ((a)*(b)) +#define FLOAT_DIV32(a,b) ((a)/(b)) +#define FLOAT_EXTRACT16(a) (a) +#define FLOAT_EXTRACT32(a) (a) +#define FLOAT_ADD(a,b) ((a)+(b)) +#define FLOAT_SUB(a,b) ((a)-(b)) +#define REALFLOAT(x) (x) +#define FLOAT_DIV32_FLOAT(a,b) ((a)/(b)) +#define FLOAT_MUL32U(a,b) ((a)*(b)) +#define FLOAT_SHL(a,b) (a) +#define FLOAT_LT(a,b) ((a)<(b)) +#define FLOAT_GT(a,b) ((a)>(b)) +#define FLOAT_DIVU(a,b) ((a)/(b)) +#define FLOAT_SQRT(a) (spx_sqrt(a)) + +#endif + +#endif diff --git a/src/libspeex/quant_lsp.c b/src/libspeex/quant_lsp.c new file mode 100644 index 0000000..e624d1a --- /dev/null +++ b/src/libspeex/quant_lsp.c @@ -0,0 +1,385 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: quant_lsp.c + LSP vector quantization + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "quant_lsp.h" +#include "os_support.h" +#include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#include "arch.h" + +#ifdef BFIN_ASM +#include "quant_lsp_bfin.h" +#endif + +#ifdef FIXED_POINT + +#define LSP_LINEAR(i) (SHL16(i+1,11)) +#define LSP_LINEAR_HIGH(i) (ADD16(MULT16_16_16(i,2560),6144)) +#define LSP_DIV_256(x) (SHL16((spx_word16_t)x, 5)) +#define LSP_DIV_512(x) (SHL16((spx_word16_t)x, 4)) +#define LSP_DIV_1024(x) (SHL16((spx_word16_t)x, 3)) +#define LSP_PI 25736 + +#else + +#define LSP_LINEAR(i) (.25*(i)+.25) +#define LSP_LINEAR_HIGH(i) (.3125*(i)+.75) +#define LSP_SCALE 256. +#define LSP_DIV_256(x) (0.0039062*(x)) +#define LSP_DIV_512(x) (0.0019531*(x)) +#define LSP_DIV_1024(x) (0.00097656*(x)) +#define LSP_PI M_PI + +#endif + +static void compute_quant_weights(spx_lsp_t *qlsp, spx_word16_t *quant_weight, int order) +{ + int i; + spx_word16_t tmp1, tmp2; + for (i=0;i tmp2 ? tmp1 : tmp2; + }*/ + + for (i=0;i +#include "arch.h" + +#define MAX_LSP_SIZE 20 + +#define NB_CDBK_SIZE 64 +#define NB_CDBK_SIZE_LOW1 64 +#define NB_CDBK_SIZE_LOW2 64 +#define NB_CDBK_SIZE_HIGH1 64 +#define NB_CDBK_SIZE_HIGH2 64 + +/*Narrowband codebooks*/ +extern const signed char cdbk_nb[]; +extern const signed char cdbk_nb_low1[]; +extern const signed char cdbk_nb_low2[]; +extern const signed char cdbk_nb_high1[]; +extern const signed char cdbk_nb_high2[]; + +/* Quantizes narrowband LSPs with 30 bits */ +void lsp_quant_nb(spx_lsp_t *lsp, spx_lsp_t *qlsp, int order, SpeexBits *bits); + +/* Decodes quantized narrowband LSPs */ +void lsp_unquant_nb(spx_lsp_t *lsp, int order, SpeexBits *bits); + +/* Quantizes low bit-rate narrowband LSPs with 18 bits */ +void lsp_quant_lbr(spx_lsp_t *lsp, spx_lsp_t *qlsp, int order, SpeexBits *bits); + +/* Decodes quantized low bit-rate narrowband LSPs */ +void lsp_unquant_lbr(spx_lsp_t *lsp, int order, SpeexBits *bits); + +/* Quantizes high-band LSPs with 12 bits */ +void lsp_quant_high(spx_lsp_t *lsp, spx_lsp_t *qlsp, int order, SpeexBits *bits); + +/* Decodes high-band LSPs */ +void lsp_unquant_high(spx_lsp_t *lsp, int order, SpeexBits *bits); + +#endif diff --git a/src/libspeex/quant_lsp_bfin.h b/src/libspeex/quant_lsp_bfin.h new file mode 100644 index 0000000..c884078 --- /dev/null +++ b/src/libspeex/quant_lsp_bfin.h @@ -0,0 +1,165 @@ +/* Copyright (C) 2006 David Rowe */ +/** + @file quant_lsp_bfin.h + @author David Rowe + @brief Various compatibility routines for Speex (Blackfin version) +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_LSP_QUANT +#ifdef OVERRIDE_LSP_QUANT + +/* + Note http://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html + well tell you all the magic resgister constraints used below + for gcc in-line asm. +*/ + +static int lsp_quant( + spx_word16_t *x, + const signed char *cdbk, + int nbVec, + int nbDim +) +{ + int j; + spx_word32_t best_dist=1<<30; + int best_id=0; + + __asm__ __volatile__ + ( +" %0 = 1 (X);\n\t" /* %0: best_dist */ +" %0 <<= 30;\n\t" +" %1 = 0 (X);\n\t" /* %1: best_i */ +" P2 = %3\n\t" /* P2: ptr to cdbk */ +" R5 = 0;\n\t" /* R5: best cb entry */ + +" R0 = %5;\n\t" /* set up circ addr */ +" R0 <<= 1;\n\t" +" L0 = R0;\n\t" +" I0 = %2;\n\t" /* %2: &x[0] */ +" B0 = %2;\n\t" + +" R2.L = W [I0++];\n\t" +" LSETUP (lq1, lq2) LC0 = %4;\n\t" +"lq1: R3 = 0;\n\t" /* R3: dist */ +" LSETUP (lq3, lq4) LC1 = %5;\n\t" +"lq3: R1 = B [P2++] (X);\n\t" +" R1 <<= 5;\n\t" +" R0.L = R2.L - R1.L || R2.L = W [I0++];\n\t" +" R0 = R0.L*R0.L;\n\t" +"lq4: R3 = R3 + R0;\n\t" + +" cc =R3<%0;\n\t" +" if cc %0=R3;\n\t" +" if cc %1=R5;\n\t" +"lq2: R5 += 1;\n\t" +" L0 = 0;\n\t" + : "=&d" (best_dist), "=&d" (best_id) + : "a" (x), "b" (cdbk), "a" (nbVec), "a" (nbDim) + : "I0", "P2", "R0", "R1", "R2", "R3", "R5", "L0", "B0", "A0" + ); + + for (j=0;j>> 16;\n\t" +" R1 = (A1 += R2.L*R0.H) (IS);\n\t" +"lwq4: R3 = R3 + R1;\n\t" + +" cc =R3<%0;\n\t" +" if cc %0=R3;\n\t" +" if cc %1=R5;\n\t" +"lwq2: R5 += 1;\n\t" +" L0 = 0;\n\t" +" L1 = 0;\n\t" + : "=&d" (best_dist), "=&d" (best_id) + : "a" (x), "a" (weight), "b" (cdbk), "a" (nbVec), "a" (nbDim) + : "I0", "I1", "P2", "R0", "R1", "R2", "R3", "R5", "A1", + "L0", "L1", "B0", "B1" + ); + + for (j=0;j +static void *speex_alloc (int size) {return calloc(size,1);} +static void *speex_realloc (void *ptr, int size) {return realloc(ptr, size);} +static void speex_free (void *ptr) {free(ptr);} +#include "speex_resampler.h" +#include "arch.h" +#else /* OUTSIDE_SPEEX */ + +#include "speex/speex_resampler.h" +#include "arch.h" +#include "os_support.h" +#endif /* OUTSIDE_SPEEX */ + +#include + +#ifndef M_PI +#define M_PI 3.14159263 +#endif + +#ifdef FIXED_POINT +#define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x))) +#else +#define WORD2INT(x) ((x) < -32767.5f ? -32768 : ((x) > 32766.5f ? 32767 : floor(.5+(x)))) +#endif + +/*#define float double*/ +#define FILTER_SIZE 64 +#define OVERSAMPLE 8 + +#define IMAX(a,b) ((a) > (b) ? (a) : (b)) +#define IMIN(a,b) ((a) < (b) ? (a) : (b)) + +#ifndef NULL +#define NULL 0 +#endif + +typedef int (*resampler_basic_func)(SpeexResamplerState *, spx_uint32_t , const spx_word16_t *, spx_uint32_t *, spx_word16_t *, spx_uint32_t *); + +struct SpeexResamplerState_ { + spx_uint32_t in_rate; + spx_uint32_t out_rate; + spx_uint32_t num_rate; + spx_uint32_t den_rate; + + int quality; + spx_uint32_t nb_channels; + spx_uint32_t filt_len; + spx_uint32_t mem_alloc_size; + int int_advance; + int frac_advance; + float cutoff; + spx_uint32_t oversample; + int initialised; + int started; + + /* These are per-channel */ + spx_int32_t *last_sample; + spx_uint32_t *samp_frac_num; + spx_uint32_t *magic_samples; + + spx_word16_t *mem; + spx_word16_t *sinc_table; + spx_uint32_t sinc_table_length; + resampler_basic_func resampler_ptr; + + int in_stride; + int out_stride; +} ; + +static double kaiser12_table[68] = { + 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076, + 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014, + 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601, + 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014, + 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490, + 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546, + 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178, + 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947, + 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058, + 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438, + 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734, + 0.00001000, 0.00000000}; +/* +static double kaiser12_table[36] = { + 0.99440475, 1.00000000, 0.99440475, 0.97779076, 0.95066529, 0.91384741, + 0.86843014, 0.81573067, 0.75723148, 0.69451601, 0.62920216, 0.56287762, + 0.49704014, 0.43304576, 0.37206735, 0.31506490, 0.26276832, 0.21567274, + 0.17404546, 0.13794294, 0.10723616, 0.08164178, 0.06075685, 0.04409466, + 0.03111947, 0.02127838, 0.01402878, 0.00886058, 0.00531256, 0.00298291, + 0.00153438, 0.00069463, 0.00025272, 0.0000527734, 0.00000500, 0.00000000}; +*/ +static double kaiser10_table[36] = { + 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446, + 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347, + 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962, + 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451, + 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739, + 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000}; + +static double kaiser8_table[36] = { + 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200, + 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126, + 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272, + 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758, + 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490, + 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000}; + +static double kaiser6_table[36] = { + 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003, + 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565, + 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561, + 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058, + 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600, + 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000}; + +struct FuncDef { + double *table; + int oversample; +}; + +static struct FuncDef _KAISER12 = {kaiser12_table, 64}; +#define KAISER12 (&_KAISER12) +/*static struct FuncDef _KAISER12 = {kaiser12_table, 32}; +#define KAISER12 (&_KAISER12)*/ +static struct FuncDef _KAISER10 = {kaiser10_table, 32}; +#define KAISER10 (&_KAISER10) +static struct FuncDef _KAISER8 = {kaiser8_table, 32}; +#define KAISER8 (&_KAISER8) +static struct FuncDef _KAISER6 = {kaiser6_table, 32}; +#define KAISER6 (&_KAISER6) + +struct QualityMapping { + int base_length; + int oversample; + float downsample_bandwidth; + float upsample_bandwidth; + struct FuncDef *window_func; +}; + + +/* This table maps conversion quality to internal parameters. There are two + reasons that explain why the up-sampling bandwidth is larger than the + down-sampling bandwidth: + 1) When up-sampling, we can assume that the spectrum is already attenuated + close to the Nyquist rate (from an A/D or a previous resampling filter) + 2) Any aliasing that occurs very close to the Nyquist rate will be masked + by the sinusoids/noise just below the Nyquist rate (guaranteed only for + up-sampling). +*/ +static const struct QualityMapping quality_map[11] = { + { 8, 4, 0.830f, 0.860f, KAISER6 }, /* Q0 */ + { 16, 4, 0.850f, 0.880f, KAISER6 }, /* Q1 */ + { 32, 4, 0.882f, 0.910f, KAISER6 }, /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */ + { 48, 8, 0.895f, 0.917f, KAISER8 }, /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */ + { 64, 8, 0.921f, 0.940f, KAISER8 }, /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */ + { 80, 16, 0.922f, 0.940f, KAISER10}, /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */ + { 96, 16, 0.940f, 0.945f, KAISER10}, /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */ + {128, 16, 0.950f, 0.950f, KAISER10}, /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */ + {160, 16, 0.960f, 0.960f, KAISER10}, /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */ + {192, 32, 0.968f, 0.968f, KAISER12}, /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */ + {256, 32, 0.975f, 0.975f, KAISER12}, /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */ +}; +/*8,24,40,56,80,104,128,160,200,256,320*/ +static double compute_func(float x, struct FuncDef *func) +{ + float y, frac; + double interp[4]; + int ind; + y = x*func->oversample; + ind = (int)floor(y); + frac = (y-ind); + /* CSE with handle the repeated powers */ + interp[3] = -0.1666666667*frac + 0.1666666667*(frac*frac*frac); + interp[2] = frac + 0.5*(frac*frac) - 0.5*(frac*frac*frac); + /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/ + interp[0] = -0.3333333333*frac + 0.5*(frac*frac) - 0.1666666667*(frac*frac*frac); + /* Just to make sure we don't have rounding problems */ + interp[1] = 1.f-interp[3]-interp[2]-interp[0]; + + /*sum = frac*accum[1] + (1-frac)*accum[2];*/ + return interp[0]*func->table[ind] + interp[1]*func->table[ind+1] + interp[2]*func->table[ind+2] + interp[3]*func->table[ind+3]; +} + +#if 0 +#include +int main(int argc, char **argv) +{ + int i; + for (i=0;i<256;i++) + { + printf ("%f\n", compute_func(i/256., KAISER12)); + } + return 0; +} +#endif + +#ifdef FIXED_POINT +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx = x * cutoff; + if (fabs(x)<1e-6f) + return WORD2INT(32768.*cutoff); + else if (fabs(x) > .5f*N) + return 0; + /*FIXME: Can it really be any slower than this? */ + return WORD2INT(32768.*cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func)); +} +#else +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx = x * cutoff; + if (fabs(x)<1e-6) + return cutoff; + else if (fabs(x) > .5*N) + return 0; + /*FIXME: Can it really be any slower than this? */ + return cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func); +} +#endif + +#ifdef FIXED_POINT +static void cubic_coef(spx_word16_t x, spx_word16_t interp[4]) +{ + /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation + but I know it's MMSE-optimal on a sinc */ + spx_word16_t x2, x3; + x2 = MULT16_16_P15(x, x); + x3 = MULT16_16_P15(x, x2); + interp[0] = PSHR32(MULT16_16(QCONST16(-0.16667f, 15),x) + MULT16_16(QCONST16(0.16667f, 15),x3),15); + interp[1] = EXTRACT16(EXTEND32(x) + SHR32(SUB32(EXTEND32(x2),EXTEND32(x3)),1)); + interp[3] = PSHR32(MULT16_16(QCONST16(-0.33333f, 15),x) + MULT16_16(QCONST16(.5f,15),x2) - MULT16_16(QCONST16(0.16667f, 15),x3),15); + /* Just to make sure we don't have rounding problems */ + interp[2] = Q15_ONE-interp[0]-interp[1]-interp[3]; + if (interp[2]<32767) + interp[2]+=1; +} +#else +static void cubic_coef(spx_word16_t frac, spx_word16_t interp[4]) +{ + /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation + but I know it's MMSE-optimal on a sinc */ + interp[0] = -0.16667f*frac + 0.16667f*frac*frac*frac; + interp[1] = frac + 0.5f*frac*frac - 0.5f*frac*frac*frac; + /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/ + interp[3] = -0.33333f*frac + 0.5f*frac*frac - 0.16667f*frac*frac*frac; + /* Just to make sure we don't have rounding problems */ + interp[2] = 1.-interp[0]-interp[1]-interp[3]; +} +#endif + +static int resampler_basic_direct_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + mem = st->mem + channel_index * st->mem_alloc_size; + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + int j; + spx_word32_t sum=0; + + /* We already have all the filter coefficients pre-computed in the table */ + const spx_word16_t *ptr; + /* Do the memory part */ + for (j=0;last_sample-N+1+j < 0;j++) + { + sum += MULT16_16(mem[last_sample+j],st->sinc_table[samp_frac_num*st->filt_len+j]); + } + + /* Do the new part */ + if (in != NULL) + { + ptr = in+st->in_stride*(last_sample-N+1+j); + for (;jsinc_table[samp_frac_num*st->filt_len+j]); + ptr += st->in_stride; + } + } + + *out = PSHR32(sum,15); + out += st->out_stride; + out_sample++; + last_sample += st->int_advance; + samp_frac_num += st->frac_advance; + if (samp_frac_num >= st->den_rate) + { + samp_frac_num -= st->den_rate; + last_sample++; + } + } + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +#ifdef FIXED_POINT +#else +/* This is the same as the previous function, except with a double-precision accumulator */ +static int resampler_basic_direct_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + mem = st->mem + channel_index * st->mem_alloc_size; + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + int j; + double sum=0; + + /* We already have all the filter coefficients pre-computed in the table */ + const spx_word16_t *ptr; + /* Do the memory part */ + for (j=0;last_sample-N+1+j < 0;j++) + { + sum += MULT16_16(mem[last_sample+j],(double)st->sinc_table[samp_frac_num*st->filt_len+j]); + } + + /* Do the new part */ + if (in != NULL) + { + ptr = in+st->in_stride*(last_sample-N+1+j); + for (;jsinc_table[samp_frac_num*st->filt_len+j]); + ptr += st->in_stride; + } + } + + *out = sum; + out += st->out_stride; + out_sample++; + last_sample += st->int_advance; + samp_frac_num += st->frac_advance; + if (samp_frac_num >= st->den_rate) + { + samp_frac_num -= st->den_rate; + last_sample++; + } + } + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} +#endif + +static int resampler_basic_interpolate_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + mem = st->mem + channel_index * st->mem_alloc_size; + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + int j; + spx_word32_t sum=0; + + /* We need to interpolate the sinc filter */ + spx_word32_t accum[4] = {0.f,0.f, 0.f, 0.f}; + spx_word16_t interp[4]; + const spx_word16_t *ptr; + int offset; + spx_word16_t frac; + offset = samp_frac_num*st->oversample/st->den_rate; +#ifdef FIXED_POINT + frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate); +#else + frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate; +#endif + /* This code is written like this to make it easy to optimise with SIMD. + For most DSPs, it would be best to split the loops in two because most DSPs + have only two accumulators */ + for (j=0;last_sample-N+1+j < 0;j++) + { + spx_word16_t curr_mem = mem[last_sample+j]; + accum[0] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + + if (in != NULL) + { + ptr = in+st->in_stride*(last_sample-N+1+j); + /* Do the new part */ + for (;jin_stride; + accum[0] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + } + cubic_coef(frac, interp); + sum = MULT16_32_Q15(interp[0],accum[0]) + MULT16_32_Q15(interp[1],accum[1]) + MULT16_32_Q15(interp[2],accum[2]) + MULT16_32_Q15(interp[3],accum[3]); + + *out = PSHR32(sum,15); + out += st->out_stride; + out_sample++; + last_sample += st->int_advance; + samp_frac_num += st->frac_advance; + if (samp_frac_num >= st->den_rate) + { + samp_frac_num -= st->den_rate; + last_sample++; + } + } + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +#ifdef FIXED_POINT +#else +/* This is the same as the previous function, except with a double-precision accumulator */ +static int resampler_basic_interpolate_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + mem = st->mem + channel_index * st->mem_alloc_size; + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + int j; + spx_word32_t sum=0; + + /* We need to interpolate the sinc filter */ + double accum[4] = {0.f,0.f, 0.f, 0.f}; + float interp[4]; + const spx_word16_t *ptr; + float alpha = ((float)samp_frac_num)/st->den_rate; + int offset = samp_frac_num*st->oversample/st->den_rate; + float frac = alpha*st->oversample - offset; + /* This code is written like this to make it easy to optimise with SIMD. + For most DSPs, it would be best to split the loops in two because most DSPs + have only two accumulators */ + for (j=0;last_sample-N+1+j < 0;j++) + { + double curr_mem = mem[last_sample+j]; + accum[0] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + if (in != NULL) + { + ptr = in+st->in_stride*(last_sample-N+1+j); + /* Do the new part */ + for (;jin_stride; + accum[0] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + } + cubic_coef(frac, interp); + sum = interp[0]*accum[0] + interp[1]*accum[1] + interp[2]*accum[2] + interp[3]*accum[3]; + + *out = PSHR32(sum,15); + out += st->out_stride; + out_sample++; + last_sample += st->int_advance; + samp_frac_num += st->frac_advance; + if (samp_frac_num >= st->den_rate) + { + samp_frac_num -= st->den_rate; + last_sample++; + } + } + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} +#endif + +static void update_filter(SpeexResamplerState *st) +{ + spx_uint32_t old_length; + + old_length = st->filt_len; + st->oversample = quality_map[st->quality].oversample; + st->filt_len = quality_map[st->quality].base_length; + + if (st->num_rate > st->den_rate) + { + /* down-sampling */ + st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate; + /* FIXME: divide the numerator and denominator by a certain amount if they're too large */ + st->filt_len = st->filt_len*st->num_rate / st->den_rate; + /* Round down to make sure we have a multiple of 4 */ + st->filt_len &= (~0x3); + if (2*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (4*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (8*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (16*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (st->oversample < 1) + st->oversample = 1; + } else { + /* up-sampling */ + st->cutoff = quality_map[st->quality].upsample_bandwidth; + } + + /* Choose the resampling type that requires the least amount of memory */ + if (st->den_rate <= st->oversample) + { + spx_uint32_t i; + if (!st->sinc_table) + st->sinc_table = (spx_word16_t *)speex_alloc(st->filt_len*st->den_rate*sizeof(spx_word16_t)); + else if (st->sinc_table_length < st->filt_len*st->den_rate) + { + st->sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,st->filt_len*st->den_rate*sizeof(spx_word16_t)); + st->sinc_table_length = st->filt_len*st->den_rate; + } + for (i=0;iden_rate;i++) + { + spx_int32_t j; + for (j=0;jfilt_len;j++) + { + st->sinc_table[i*st->filt_len+j] = sinc(st->cutoff,((j-(spx_int32_t)st->filt_len/2+1)-((float)i)/st->den_rate), st->filt_len, quality_map[st->quality].window_func); + } + } +#ifdef FIXED_POINT + st->resampler_ptr = resampler_basic_direct_single; +#else + if (st->quality>8) + st->resampler_ptr = resampler_basic_direct_double; + else + st->resampler_ptr = resampler_basic_direct_single; +#endif + /*fprintf (stderr, "resampler uses direct sinc table and normalised cutoff %f\n", cutoff);*/ + } else { + spx_int32_t i; + if (!st->sinc_table) + st->sinc_table = (spx_word16_t *)speex_alloc((st->filt_len*st->oversample+8)*sizeof(spx_word16_t)); + else if (st->sinc_table_length < st->filt_len*st->oversample+8) + { + st->sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,(st->filt_len*st->oversample+8)*sizeof(spx_word16_t)); + st->sinc_table_length = st->filt_len*st->oversample+8; + } + for (i=-4;i<(spx_int32_t)(st->oversample*st->filt_len+4);i++) + st->sinc_table[i+4] = sinc(st->cutoff,(i/(float)st->oversample - st->filt_len/2), st->filt_len, quality_map[st->quality].window_func); +#ifdef FIXED_POINT + st->resampler_ptr = resampler_basic_interpolate_single; +#else + if (st->quality>8) + st->resampler_ptr = resampler_basic_interpolate_double; + else + st->resampler_ptr = resampler_basic_interpolate_single; +#endif + /*fprintf (stderr, "resampler uses interpolated sinc table and normalised cutoff %f\n", cutoff);*/ + } + st->int_advance = st->num_rate/st->den_rate; + st->frac_advance = st->num_rate%st->den_rate; + + + /* Here's the place where we update the filter memory to take into account + the change in filter length. It's probably the messiest part of the code + due to handling of lots of corner cases. */ + if (!st->mem) + { + spx_uint32_t i; + st->mem = (spx_word16_t*)speex_alloc(st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t)); + for (i=0;inb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + st->mem_alloc_size = st->filt_len-1; + /*speex_warning("init filter");*/ + } else if (!st->started) + { + spx_uint32_t i; + st->mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t)); + for (i=0;inb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + st->mem_alloc_size = st->filt_len-1; + /*speex_warning("reinit filter");*/ + } else if (st->filt_len > old_length) + { + spx_int32_t i; + /* Increase the filter length */ + /*speex_warning("increase filter size");*/ + int old_alloc_size = st->mem_alloc_size; + if (st->filt_len-1 > st->mem_alloc_size) + { + st->mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t)); + st->mem_alloc_size = st->filt_len-1; + } + for (i=st->nb_channels-1;i>=0;i--) + { + spx_int32_t j; + spx_uint32_t olen = old_length; + /*if (st->magic_samples[i])*/ + { + /* Try and remove the magic samples as if nothing had happened */ + + /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */ + olen = old_length + 2*st->magic_samples[i]; + for (j=old_length-2+st->magic_samples[i];j>=0;j--) + st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j]; + for (j=0;jmagic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = 0; + st->magic_samples[i] = 0; + } + if (st->filt_len > olen) + { + /* If the new filter length is still bigger than the "augmented" length */ + /* Copy data going backward */ + for (j=0;jmem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)]; + /* Then put zeros for lack of anything better */ + for (;jfilt_len-1;j++) + st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0; + /* Adjust last_sample */ + st->last_sample[i] += (st->filt_len - olen)/2; + } else { + /* Put back some of the magic! */ + st->magic_samples[i] = (olen - st->filt_len)/2; + for (j=0;jfilt_len-1+st->magic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + } + } + } else if (st->filt_len < old_length) + { + spx_uint32_t i; + /* Reduce filter length, this a bit tricky. We need to store some of the memory as "magic" + samples so they can be used directly as input the next time(s) */ + for (i=0;inb_channels;i++) + { + spx_uint32_t j; + spx_uint32_t old_magic = st->magic_samples[i]; + st->magic_samples[i] = (old_length - st->filt_len)/2; + /* We must copy some of the memory that's no longer used */ + /* Copy data going backward */ + for (j=0;jfilt_len-1+st->magic_samples[i]+old_magic;j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + st->magic_samples[i] += old_magic; + } + } + +} + +SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + return speex_resampler_init_frac(nb_channels, in_rate, out_rate, in_rate, out_rate, quality, err); +} + +SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + spx_uint32_t i; + SpeexResamplerState *st; + if (quality > 10 || quality < 0) + { + if (err) + *err = RESAMPLER_ERR_INVALID_ARG; + return NULL; + } + st = (SpeexResamplerState *)speex_alloc(sizeof(SpeexResamplerState)); + st->initialised = 0; + st->started = 0; + st->in_rate = 0; + st->out_rate = 0; + st->num_rate = 0; + st->den_rate = 0; + st->quality = -1; + st->sinc_table_length = 0; + st->mem_alloc_size = 0; + st->filt_len = 0; + st->mem = 0; + st->resampler_ptr = 0; + + st->cutoff = 1.f; + st->nb_channels = nb_channels; + st->in_stride = 1; + st->out_stride = 1; + + /* Per channel data */ + st->last_sample = (spx_int32_t*)speex_alloc(nb_channels*sizeof(int)); + st->magic_samples = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(int)); + st->samp_frac_num = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(int)); + for (i=0;ilast_sample[i] = 0; + st->magic_samples[i] = 0; + st->samp_frac_num[i] = 0; + } + + speex_resampler_set_quality(st, quality); + speex_resampler_set_rate_frac(st, ratio_num, ratio_den, in_rate, out_rate); + + + update_filter(st); + + st->initialised = 1; + if (err) + *err = RESAMPLER_ERR_SUCCESS; + + return st; +} + +void speex_resampler_destroy(SpeexResamplerState *st) +{ + speex_free(st->mem); + speex_free(st->sinc_table); + speex_free(st->last_sample); + speex_free(st->magic_samples); + speex_free(st->samp_frac_num); + speex_free(st); +} + + + +static int speex_resampler_process_native(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int j=0; + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + spx_uint32_t tmp_out_len = 0; + mem = st->mem + channel_index * st->mem_alloc_size; + st->started = 1; + + /* Handle the case where we have samples left from a reduction in filter length */ + if (st->magic_samples[channel_index]) + { + int istride_save; + spx_uint32_t tmp_in_len; + spx_uint32_t tmp_magic; + + istride_save = st->in_stride; + tmp_in_len = st->magic_samples[channel_index]; + tmp_out_len = *out_len; + /* magic_samples needs to be set to zero to avoid infinite recursion */ + tmp_magic = st->magic_samples[channel_index]; + st->magic_samples[channel_index] = 0; + st->in_stride = 1; + speex_resampler_process_native(st, channel_index, mem+N-1, &tmp_in_len, out, &tmp_out_len); + st->in_stride = istride_save; + /*speex_warning_int("extra samples:", tmp_out_len);*/ + /* If we couldn't process all "magic" input samples, save the rest for next time */ + if (tmp_in_len < tmp_magic) + { + spx_uint32_t i; + st->magic_samples[channel_index] = tmp_magic-tmp_in_len; + for (i=0;imagic_samples[channel_index];i++) + mem[N-1+i]=mem[N-1+i+tmp_in_len]; + } + out += tmp_out_len*st->out_stride; + *out_len -= tmp_out_len; + } + + /* Call the right resampler through the function ptr */ + out_sample = st->resampler_ptr(st, channel_index, in, in_len, out, out_len); + + if (st->last_sample[channel_index] < (spx_int32_t)*in_len) + *in_len = st->last_sample[channel_index]; + *out_len = out_sample+tmp_out_len; + st->last_sample[channel_index] -= *in_len; + + for (j=0;jin_stride*(j+*in_len-N+1)]; + } else { + for (;jin_stride; + ostride_save = st->out_stride; + if (in != NULL) + { + for (i=0;i<*in_len;i++) + x[i] = WORD2INT(in[i*st->in_stride]); + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, x, in_len, y, out_len); + } else { + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, NULL, in_len, y, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + for (i=0;i<*out_len;i++) + out[i*st->out_stride] = y[i]; +#else + spx_word16_t x[FIXED_STACK_ALLOC]; + spx_word16_t y[FIXED_STACK_ALLOC]; + spx_uint32_t ilen=*in_len, olen=*out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + while (ilen && olen) + { + spx_uint32_t ichunk, ochunk; + ichunk = ilen; + ochunk = olen; + if (ichunk>FIXED_STACK_ALLOC) + ichunk=FIXED_STACK_ALLOC; + if (ochunk>FIXED_STACK_ALLOC) + ochunk=FIXED_STACK_ALLOC; + if (in != NULL) + { + for (i=0;iin_stride]); + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, x, &ichunk, y, &ochunk); + } else { + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, NULL, &ichunk, y, &ochunk); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + for (i=0;iout_stride] = y[i]; + out += ochunk; + in += ichunk; + ilen -= ichunk; + olen -= ochunk; + } + *in_len -= ilen; + *out_len -= olen; +#endif + return RESAMPLER_ERR_SUCCESS; +} +int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +{ + return speex_resampler_process_native(st, channel_index, in, in_len, out, out_len); +} +#else +int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +{ + return speex_resampler_process_native(st, channel_index, in, in_len, out, out_len); +} +int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; +#ifdef VAR_ARRAYS + spx_word16_t x[*in_len]; + spx_word16_t y[*out_len]; + /*VARDECL(spx_word16_t *x); + VARDECL(spx_word16_t *y); + ALLOC(x, *in_len, spx_word16_t); + ALLOC(y, *out_len, spx_word16_t);*/ + istride_save = st->in_stride; + ostride_save = st->out_stride; + if (in != NULL) + { + for (i=0;i<*in_len;i++) + x[i] = in[i*st->in_stride]; + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, x, in_len, y, out_len); + } else { + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, NULL, in_len, y, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + for (i=0;i<*out_len;i++) + out[i*st->out_stride] = WORD2INT(y[i]); +#else + spx_word16_t x[FIXED_STACK_ALLOC]; + spx_word16_t y[FIXED_STACK_ALLOC]; + spx_uint32_t ilen=*in_len, olen=*out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + while (ilen && olen) + { + spx_uint32_t ichunk, ochunk; + ichunk = ilen; + ochunk = olen; + if (ichunk>FIXED_STACK_ALLOC) + ichunk=FIXED_STACK_ALLOC; + if (ochunk>FIXED_STACK_ALLOC) + ochunk=FIXED_STACK_ALLOC; + if (in != NULL) + { + for (i=0;iin_stride]; + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, x, &ichunk, y, &ochunk); + } else { + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, NULL, &ichunk, y, &ochunk); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + for (i=0;iout_stride] = WORD2INT(y[i]); + out += ochunk; + in += ichunk; + ilen -= ichunk; + olen -= ochunk; + } + *in_len -= ilen; + *out_len -= olen; +#endif + return RESAMPLER_ERR_SUCCESS; +} +#endif + +int speex_resampler_process_interleaved_float(SpeexResamplerState *st, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_len = *out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + for (i=0;inb_channels;i++) + { + *out_len = bak_len; + if (in != NULL) + speex_resampler_process_float(st, i, in+i, in_len, out+i, out_len); + else + speex_resampler_process_float(st, i, NULL, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + return RESAMPLER_ERR_SUCCESS; +} + + +int speex_resampler_process_interleaved_int(SpeexResamplerState *st, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_len = *out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + for (i=0;inb_channels;i++) + { + *out_len = bak_len; + if (in != NULL) + speex_resampler_process_int(st, i, in+i, in_len, out+i, out_len); + else + speex_resampler_process_int(st, i, NULL, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + return RESAMPLER_ERR_SUCCESS; +} + +int speex_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + return speex_resampler_set_rate_frac(st, in_rate, out_rate, in_rate, out_rate); +} + +void speex_resampler_get_rate(SpeexResamplerState *st, spx_uint32_t *in_rate, spx_uint32_t *out_rate) +{ + *in_rate = st->in_rate; + *out_rate = st->out_rate; +} + +int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + spx_uint32_t fact; + spx_uint32_t old_den; + spx_uint32_t i; + if (st->in_rate == in_rate && st->out_rate == out_rate && st->num_rate == ratio_num && st->den_rate == ratio_den) + return RESAMPLER_ERR_SUCCESS; + + old_den = st->den_rate; + st->in_rate = in_rate; + st->out_rate = out_rate; + st->num_rate = ratio_num; + st->den_rate = ratio_den; + /* FIXME: This is terribly inefficient, but who cares (at least for now)? */ + for (fact=2;fact<=IMIN(st->num_rate, st->den_rate);fact++) + { + while ((st->num_rate % fact == 0) && (st->den_rate % fact == 0)) + { + st->num_rate /= fact; + st->den_rate /= fact; + } + } + + if (old_den > 0) + { + for (i=0;inb_channels;i++) + { + st->samp_frac_num[i]=st->samp_frac_num[i]*st->den_rate/old_den; + /* Safety net */ + if (st->samp_frac_num[i] >= st->den_rate) + st->samp_frac_num[i] = st->den_rate-1; + } + } + + if (st->initialised) + update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +void speex_resampler_get_ratio(SpeexResamplerState *st, spx_uint32_t *ratio_num, spx_uint32_t *ratio_den) +{ + *ratio_num = st->num_rate; + *ratio_den = st->den_rate; +} + +int speex_resampler_set_quality(SpeexResamplerState *st, int quality) +{ + if (quality > 10 || quality < 0) + return RESAMPLER_ERR_INVALID_ARG; + if (st->quality == quality) + return RESAMPLER_ERR_SUCCESS; + st->quality = quality; + if (st->initialised) + update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +void speex_resampler_get_quality(SpeexResamplerState *st, int *quality) +{ + *quality = st->quality; +} + +void speex_resampler_set_input_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->in_stride = stride; +} + +void speex_resampler_get_input_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->in_stride; +} + +void speex_resampler_set_output_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->out_stride = stride; +} + +void speex_resampler_get_output_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->out_stride; +} + +int speex_resampler_get_input_latency(SpeexResamplerState *st) +{ + return st->filt_len / 2; +} + +int speex_resampler_get_output_latency(SpeexResamplerState *st) +{ + return ((st->filt_len / 2) * st->den_rate + (st->num_rate >> 1)) / st->num_rate; +} + +int speex_resampler_skip_zeros(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;inb_channels;i++) + st->last_sample[i] = st->filt_len/2; + return RESAMPLER_ERR_SUCCESS; +} + +int speex_resampler_reset_mem(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;inb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + return RESAMPLER_ERR_SUCCESS; +} + +const char *speex_resampler_strerror(int err) +{ + switch (err) + { + case RESAMPLER_ERR_SUCCESS: + return "Success."; + case RESAMPLER_ERR_ALLOC_FAILED: + return "Memory allocation failed."; + case RESAMPLER_ERR_BAD_STATE: + return "Bad resampler state."; + case RESAMPLER_ERR_INVALID_ARG: + return "Invalid argument."; + case RESAMPLER_ERR_PTR_OVERLAP: + return "Input and output buffers overlap."; + default: + return "Unknown error. Bad error code or strange version mismatch."; + } +} diff --git a/src/libspeex/sb_celp.c b/src/libspeex/sb_celp.c new file mode 100644 index 0000000..6c19ff8 --- /dev/null +++ b/src/libspeex/sb_celp.c @@ -0,0 +1,1488 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: sb_celp.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "sb_celp.h" +#include "filters.h" +#include "lpc.h" +#include "lsp.h" +#include "stack_alloc.h" +#include "cb_search.h" +#include "quant_lsp.h" +#include "vq.h" +#include "ltp.h" +#include "arch.h" +#include "math_approx.h" +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + +/* Default size for the encoder and decoder stack (can be changed at compile time). + This does not apply when using variable-size arrays or alloca. */ +#ifndef SB_ENC_STACK +#define SB_ENC_STACK (10000*sizeof(spx_sig_t)) +#endif + +#ifndef SB_DEC_STACK +#define SB_DEC_STACK (6000*sizeof(spx_sig_t)) +#endif + + +#ifdef DISABLE_WIDEBAND +void *sb_encoder_init(const SpeexMode *m) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return NULL; +} +void sb_encoder_destroy(void *state) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); +} +int sb_encode(void *state, void *vin, SpeexBits *bits) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return -2; +} +void *sb_decoder_init(const SpeexMode *m) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return NULL; +} +void sb_decoder_destroy(void *state) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); +} +int sb_decode(void *state, SpeexBits *bits, void *vout) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return -2; +} +int sb_encoder_ctl(void *state, int request, void *ptr) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return -2; +} +int sb_decoder_ctl(void *state, int request, void *ptr) +{ + speex_fatal("Wideband and Ultra-wideband are disabled"); + return -2; +} +#else + + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#define sqr(x) ((x)*(x)) + +#define SUBMODE(x) st->submodes[st->submodeID]->x + +#ifdef FIXED_POINT +static const spx_word16_t gc_quant_bound[16] = {125, 164, 215, 282, 370, 484, 635, 832, 1090, 1428, 1871, 2452, 3213, 4210, 5516, 7228}; +static const spx_word16_t fold_quant_bound[32] = { + 39, 44, 50, 57, 64, 73, 83, 94, + 106, 120, 136, 154, 175, 198, 225, 255, + 288, 327, 370, 420, 476, 539, 611, 692, + 784, 889, 1007, 1141, 1293, 1465, 1660, 1881}; +#define LSP_MARGIN 410 +#define LSP_DELTA1 6553 +#define LSP_DELTA2 1638 + +#else + +static const spx_word16_t gc_quant_bound[16] = { + 0.97979, 1.28384, 1.68223, 2.20426, 2.88829, 3.78458, 4.95900, 6.49787, + 8.51428, 11.15642, 14.61846, 19.15484, 25.09895, 32.88761, 43.09325, 56.46588}; +static const spx_word16_t fold_quant_bound[32] = { + 0.30498, 0.34559, 0.39161, 0.44375, 0.50283, 0.56979, 0.64565, 0.73162, + 0.82903, 0.93942, 1.06450, 1.20624, 1.36685, 1.54884, 1.75506, 1.98875, + 2.25355, 2.55360, 2.89361, 3.27889, 3.71547, 4.21018, 4.77076, 5.40598, + 6.12577, 6.94141, 7.86565, 8.91295, 10.09969, 11.44445, 12.96826, 14.69497}; + +#define LSP_MARGIN .05 +#define LSP_DELTA1 .2 +#define LSP_DELTA2 .05 + +#endif + +#define QMF_ORDER 64 + +#ifdef FIXED_POINT +static const spx_word16_t h0[64] = {2, -7, -7, 18, 15, -39, -25, 75, 35, -130, -41, 212, 38, -327, -17, 483, -32, -689, 124, 956, -283, -1307, 543, 1780, -973, -2467, 1733, 3633, -3339, -6409, 9059, 30153, 30153, 9059, -6409, -3339, 3633, 1733, -2467, -973, 1780, 543, -1307, -283, 956, 124, -689, -32, 483, -17, -327, 38, 212, -41, -130, 35, 75, -25, -39, 15, 18, -7, -7, 2}; + +#else +static const float h0[64] = { + 3.596189e-05f, -0.0001123515f, + -0.0001104587f, 0.0002790277f, + 0.0002298438f, -0.0005953563f, + -0.0003823631f, 0.00113826f, + 0.0005308539f, -0.001986177f, + -0.0006243724f, 0.003235877f, + 0.0005743159f, -0.004989147f, + -0.0002584767f, 0.007367171f, + -0.0004857935f, -0.01050689f, + 0.001894714f, 0.01459396f, + -0.004313674f, -0.01994365f, + 0.00828756f, 0.02716055f, + -0.01485397f, -0.03764973f, + 0.026447f, 0.05543245f, + -0.05095487f, -0.09779096f, + 0.1382363f, 0.4600981f, + 0.4600981f, 0.1382363f, + -0.09779096f, -0.05095487f, + 0.05543245f, 0.026447f, + -0.03764973f, -0.01485397f, + 0.02716055f, 0.00828756f, + -0.01994365f, -0.004313674f, + 0.01459396f, 0.001894714f, + -0.01050689f, -0.0004857935f, + 0.007367171f, -0.0002584767f, + -0.004989147f, 0.0005743159f, + 0.003235877f, -0.0006243724f, + -0.001986177f, 0.0005308539f, + 0.00113826f, -0.0003823631f, + -0.0005953563f, 0.0002298438f, + 0.0002790277f, -0.0001104587f, + -0.0001123515f, 3.596189e-05f +}; + +#endif + +extern const spx_word16_t lag_window[]; +extern const spx_word16_t lpc_window[]; + + +void *sb_encoder_init(const SpeexMode *m) +{ + int i; + spx_int32_t tmp; + SBEncState *st; + const SpeexSBMode *mode; + + st = (SBEncState*)speex_alloc(sizeof(SBEncState)); + if (!st) + return NULL; + st->mode = m; + mode = (const SpeexSBMode*)m->mode; + + + st->st_low = speex_encoder_init(mode->nb_mode); +#if defined(VAR_ARRAYS) || defined (USE_ALLOCA) + st->stack = NULL; +#else + /*st->stack = (char*)speex_alloc_scratch(SB_ENC_STACK);*/ + speex_encoder_ctl(st->st_low, SPEEX_GET_STACK, &st->stack); +#endif + + st->full_frame_size = 2*mode->frameSize; + st->frame_size = mode->frameSize; + st->subframeSize = mode->subframeSize; + st->nbSubframes = mode->frameSize/mode->subframeSize; + st->windowSize = st->frame_size+st->subframeSize; + st->lpcSize=mode->lpcSize; + + st->encode_submode = 1; + st->submodes=mode->submodes; + st->submodeSelect = st->submodeID=mode->defaultSubmode; + + tmp=9; + speex_encoder_ctl(st->st_low, SPEEX_SET_QUALITY, &tmp); + tmp=1; + speex_encoder_ctl(st->st_low, SPEEX_SET_WIDEBAND, &tmp); + + st->lpc_floor = mode->lpc_floor; + st->gamma1=mode->gamma1; + st->gamma2=mode->gamma2; + st->first=1; + + st->high=(spx_word16_t*)speex_alloc((st->windowSize-st->frame_size)*sizeof(spx_word16_t)); + + st->h0_mem=(spx_word16_t*)speex_alloc((QMF_ORDER)*sizeof(spx_word16_t)); + st->h1_mem=(spx_word16_t*)speex_alloc((QMF_ORDER)*sizeof(spx_word16_t)); + + st->window= lpc_window; + + st->lagWindow = lag_window; + + st->old_lsp = (spx_lsp_t*)speex_alloc(st->lpcSize*sizeof(spx_lsp_t)); + st->old_qlsp = (spx_lsp_t*)speex_alloc(st->lpcSize*sizeof(spx_lsp_t)); + st->interp_qlpc = (spx_coef_t*)speex_alloc(st->lpcSize*sizeof(spx_coef_t)); + st->pi_gain = (spx_word32_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word32_t)); + st->exc_rms = (spx_word16_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word16_t)); + st->innov_rms_save = NULL; + + st->mem_sp = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_sp2 = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + st->mem_sw = (spx_mem_t*)speex_alloc((st->lpcSize)*sizeof(spx_mem_t)); + + for (i=0;ilpcSize;i++) + st->old_lsp[i]= DIV32(MULT16_16(QCONST16(3.1415927f, LSP_SHIFT), i+1), st->lpcSize+1); + +#ifndef DISABLE_VBR + st->vbr_quality = 8; + st->vbr_enabled = 0; + st->vbr_max = 0; + st->vbr_max_high = 20000; /* We just need a big value here */ + st->vad_enabled = 0; + st->abr_enabled = 0; + st->relative_quality=0; +#endif /* #ifndef DISABLE_VBR */ + + st->complexity=2; + speex_encoder_ctl(st->st_low, SPEEX_GET_SAMPLING_RATE, &st->sampling_rate); + st->sampling_rate*=2; +#ifdef ENABLE_VALGRIND + VALGRIND_MAKE_READABLE(st, (st->stack-(char*)st)); +#endif + return st; +} + +void sb_encoder_destroy(void *state) +{ + SBEncState *st=(SBEncState*)state; + + speex_encoder_destroy(st->st_low); +#if !(defined(VAR_ARRAYS) || defined (USE_ALLOCA)) + /*speex_free_scratch(st->stack);*/ +#endif + + speex_free(st->high); + + speex_free(st->h0_mem); + speex_free(st->h1_mem); + + speex_free(st->old_lsp); + speex_free(st->old_qlsp); + speex_free(st->interp_qlpc); + speex_free(st->pi_gain); + speex_free(st->exc_rms); + + speex_free(st->mem_sp); + speex_free(st->mem_sp2); + speex_free(st->mem_sw); + + + speex_free(st); +} + + +int sb_encode(void *state, void *vin, SpeexBits *bits) +{ + SBEncState *st; + int i, roots, sub; + char *stack; + VARDECL(spx_mem_t *mem); + VARDECL(spx_sig_t *innov); + VARDECL(spx_word16_t *target); + VARDECL(spx_word16_t *syn_resp); + VARDECL(spx_word32_t *low_pi_gain); + spx_word16_t *low; + spx_word16_t *high; + VARDECL(spx_word16_t *low_exc_rms); + VARDECL(spx_word16_t *low_innov_rms); + const SpeexSBMode *mode; + spx_int32_t dtx; + spx_word16_t *in = (spx_word16_t*)vin; + spx_word16_t e_low=0, e_high=0; + VARDECL(spx_coef_t *lpc); + VARDECL(spx_coef_t *interp_lpc); + VARDECL(spx_coef_t *bw_lpc1); + VARDECL(spx_coef_t *bw_lpc2); + VARDECL(spx_lsp_t *lsp); + VARDECL(spx_lsp_t *qlsp); + VARDECL(spx_lsp_t *interp_lsp); + VARDECL(spx_lsp_t *interp_qlsp); + + st = (SBEncState*)state; + stack=st->stack; + mode = (const SpeexSBMode*)(st->mode->mode); + low = in; + high = in+st->frame_size; + + /* High-band buffering / sync with low band */ + /* Compute the two sub-bands by filtering with QMF h0*/ + qmf_decomp(in, h0, low, high, st->full_frame_size, QMF_ORDER, st->h0_mem, stack); + +#ifndef DISABLE_VBR + if (st->vbr_enabled || st->vad_enabled) + { + /* Need to compute things here before the signal is trashed by the encoder */ + /*FIXME: Are the two signals (low, high) in sync? */ + e_low = compute_rms16(low, st->frame_size); + e_high = compute_rms16(high, st->frame_size); + } +#endif /* #ifndef DISABLE_VBR */ + + ALLOC(low_innov_rms, st->nbSubframes, spx_word16_t); + speex_encoder_ctl(st->st_low, SPEEX_SET_INNOVATION_SAVE, low_innov_rms); + /* Encode the narrowband part*/ + speex_encode_native(st->st_low, low, bits); + + high = high - (st->windowSize-st->frame_size); + SPEEX_COPY(high, st->high, st->windowSize-st->frame_size); + SPEEX_COPY(st->high, &high[st->frame_size], st->windowSize-st->frame_size); + + + ALLOC(low_pi_gain, st->nbSubframes, spx_word32_t); + ALLOC(low_exc_rms, st->nbSubframes, spx_word16_t); + speex_encoder_ctl(st->st_low, SPEEX_GET_PI_GAIN, low_pi_gain); + speex_encoder_ctl(st->st_low, SPEEX_GET_EXC, low_exc_rms); + + speex_encoder_ctl(st->st_low, SPEEX_GET_LOW_MODE, &dtx); + + if (dtx==0) + dtx=1; + else + dtx=0; + + ALLOC(lpc, st->lpcSize, spx_coef_t); + ALLOC(interp_lpc, st->lpcSize, spx_coef_t); + ALLOC(bw_lpc1, st->lpcSize, spx_coef_t); + ALLOC(bw_lpc2, st->lpcSize, spx_coef_t); + + ALLOC(lsp, st->lpcSize, spx_lsp_t); + ALLOC(qlsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_lsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_qlsp, st->lpcSize, spx_lsp_t); + + { + VARDECL(spx_word16_t *autocorr); + VARDECL(spx_word16_t *w_sig); + ALLOC(autocorr, st->lpcSize+1, spx_word16_t); + ALLOC(w_sig, st->windowSize, spx_word16_t); + /* Window for analysis */ + /* FIXME: This is a kludge */ + if (st->subframeSize==80) + { + for (i=0;iwindowSize;i++) + w_sig[i] = EXTRACT16(SHR32(MULT16_16(high[i],st->window[i>>1]),SIG_SHIFT)); + } else { + for (i=0;iwindowSize;i++) + w_sig[i] = EXTRACT16(SHR32(MULT16_16(high[i],st->window[i]),SIG_SHIFT)); + } + /* Compute auto-correlation */ + _spx_autocorr(w_sig, autocorr, st->lpcSize+1, st->windowSize); + autocorr[0] = ADD16(autocorr[0],MULT16_16_Q15(autocorr[0],st->lpc_floor)); /* Noise floor in auto-correlation domain */ + + /* Lag windowing: equivalent to filtering in the power-spectrum domain */ + for (i=0;ilpcSize+1;i++) + autocorr[i] = MULT16_16_Q14(autocorr[i],st->lagWindow[i]); + + /* Levinson-Durbin */ + _spx_lpc(lpc, autocorr, st->lpcSize); + } + + /* LPC to LSPs (x-domain) transform */ + roots=lpc_to_lsp (lpc, st->lpcSize, lsp, 10, LSP_DELTA1, stack); + if (roots!=st->lpcSize) + { + roots = lpc_to_lsp (lpc, st->lpcSize, lsp, 10, LSP_DELTA2, stack); + if (roots!=st->lpcSize) { + /*If we can't find all LSP's, do some damage control and use a flat filter*/ + for (i=0;ilpcSize;i++) + { + lsp[i]=st->old_lsp[i]; + } + } + } + +#ifndef DISABLE_VBR + /* VBR code */ + if ((st->vbr_enabled || st->vad_enabled) && !dtx) + { + float ratio; + if (st->abr_enabled) + { + float qual_change=0; + if (st->abr_drift2 * st->abr_drift > 0) + { + /* Only adapt if long-term and short-term drift are the same sign */ + qual_change = -.00001*st->abr_drift/(1+st->abr_count); + if (qual_change>.1) + qual_change=.1; + if (qual_change<-.1) + qual_change=-.1; + } + st->vbr_quality += qual_change; + if (st->vbr_quality>10) + st->vbr_quality=10; + if (st->vbr_quality<0) + st->vbr_quality=0; + } + + + ratio = 2*log((1.f+e_high)/(1.f+e_low)); + + speex_encoder_ctl(st->st_low, SPEEX_GET_RELATIVE_QUALITY, &st->relative_quality); + if (ratio<-4) + ratio=-4; + if (ratio>2) + ratio=2; + /*if (ratio>-2)*/ + if (st->vbr_enabled) + { + spx_int32_t modeid; + modeid = mode->nb_modes-1; + st->relative_quality+=1.0*(ratio+2); + if (st->relative_quality<-1) + st->relative_quality=-1; + while (modeid) + { + int v1; + float thresh; + v1=(int)floor(st->vbr_quality); + if (v1==10) + thresh = mode->vbr_thresh[modeid][v1]; + else + thresh = (st->vbr_quality-v1) * mode->vbr_thresh[modeid][v1+1] + + (1+v1-st->vbr_quality) * mode->vbr_thresh[modeid][v1]; + if (st->relative_quality >= thresh && st->sampling_rate*st->submodes[modeid]->bits_per_frame/st->full_frame_size <= st->vbr_max_high) + break; + modeid--; + } + speex_encoder_ctl(state, SPEEX_SET_HIGH_MODE, &modeid); + if (st->abr_enabled) + { + spx_int32_t bitrate; + speex_encoder_ctl(state, SPEEX_GET_BITRATE, &bitrate); + st->abr_drift+=(bitrate-st->abr_enabled); + st->abr_drift2 = .95*st->abr_drift2 + .05*(bitrate-st->abr_enabled); + st->abr_count += 1.0; + } + + } else { + /* VAD only */ + int modeid; + if (st->relative_quality<2.0) + modeid=1; + else + modeid=st->submodeSelect; + /*speex_encoder_ctl(state, SPEEX_SET_MODE, &mode);*/ + st->submodeID=modeid; + + } + /*fprintf (stderr, "%f %f\n", ratio, low_qual);*/ + } +#endif /* #ifndef DISABLE_VBR */ + + if (st->encode_submode) + { + speex_bits_pack(bits, 1, 1); + if (dtx) + speex_bits_pack(bits, 0, SB_SUBMODE_BITS); + else + speex_bits_pack(bits, st->submodeID, SB_SUBMODE_BITS); + } + + /* If null mode (no transmission), just set a couple things to zero*/ + if (dtx || st->submodes[st->submodeID] == NULL) + { + for (i=0;iframe_size;i++) + high[i]=VERY_SMALL; + + for (i=0;ilpcSize;i++) + st->mem_sw[i]=0; + st->first=1; + + /* Final signal synthesis from excitation */ + iir_mem16(high, st->interp_qlpc, high, st->frame_size, st->lpcSize, st->mem_sp, stack); + + if (dtx) + return 0; + else + return 1; + } + + + /* LSP quantization */ + SUBMODE(lsp_quant)(lsp, qlsp, st->lpcSize, bits); + + if (st->first) + { + for (i=0;ilpcSize;i++) + st->old_lsp[i] = lsp[i]; + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + + ALLOC(mem, st->lpcSize, spx_mem_t); + ALLOC(syn_resp, st->subframeSize, spx_word16_t); + ALLOC(innov, st->subframeSize, spx_sig_t); + ALLOC(target, st->subframeSize, spx_word16_t); + + for (sub=0;subnbSubframes;sub++) + { + VARDECL(spx_word16_t *exc); + VARDECL(spx_word16_t *res); + VARDECL(spx_word16_t *sw); + spx_word16_t *sp; + spx_word16_t filter_ratio; /*Q7*/ + int offset; + spx_word32_t rl, rh; /*Q13*/ + spx_word16_t eh=0; + + offset = st->subframeSize*sub; + sp=high+offset; + ALLOC(exc, st->subframeSize, spx_word16_t); + ALLOC(res, st->subframeSize, spx_word16_t); + ALLOC(sw, st->subframeSize, spx_word16_t); + + /* LSP interpolation (quantized and unquantized) */ + lsp_interpolate(st->old_lsp, lsp, interp_lsp, st->lpcSize, sub, st->nbSubframes); + lsp_interpolate(st->old_qlsp, qlsp, interp_qlsp, st->lpcSize, sub, st->nbSubframes); + + lsp_enforce_margin(interp_lsp, st->lpcSize, LSP_MARGIN); + lsp_enforce_margin(interp_qlsp, st->lpcSize, LSP_MARGIN); + + lsp_to_lpc(interp_lsp, interp_lpc, st->lpcSize,stack); + lsp_to_lpc(interp_qlsp, st->interp_qlpc, st->lpcSize, stack); + + bw_lpc(st->gamma1, interp_lpc, bw_lpc1, st->lpcSize); + bw_lpc(st->gamma2, interp_lpc, bw_lpc2, st->lpcSize); + + /* Compute mid-band (4000 Hz for wideband) response of low-band and high-band + filters */ + st->pi_gain[sub]=LPC_SCALING; + rh = LPC_SCALING; + for (i=0;ilpcSize;i+=2) + { + rh += st->interp_qlpc[i+1] - st->interp_qlpc[i]; + st->pi_gain[sub] += st->interp_qlpc[i] + st->interp_qlpc[i+1]; + } + + rl = low_pi_gain[sub]; +#ifdef FIXED_POINT + filter_ratio=EXTRACT16(SATURATE(PDIV32(SHL32(ADD32(rl,82),7),ADD32(82,rh)),32767)); +#else + filter_ratio=(rl+.01)/(rh+.01); +#endif + + /* Compute "real excitation" */ + fir_mem16(sp, st->interp_qlpc, exc, st->subframeSize, st->lpcSize, st->mem_sp2, stack); + /* Compute energy of low-band and high-band excitation */ + + eh = compute_rms16(exc, st->subframeSize); + + if (!SUBMODE(innovation_quant)) {/* 1 for spectral folding excitation, 0 for stochastic */ + spx_word32_t g; /*Q7*/ + spx_word16_t el; /*Q0*/ + el = low_innov_rms[sub]; + + /* Gain to use if we want to use the low-band excitation for high-band */ + g=PDIV32(MULT16_16(filter_ratio,eh),EXTEND32(ADD16(1,el))); + +#if 0 + { + char *tmp_stack=stack; + float *tmp_sig; + float g2; + ALLOC(tmp_sig, st->subframeSize, spx_sig_t); + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sp[i]; + iir_mem2(st->low_innov+offset, st->interp_qlpc, tmp_sig, st->subframeSize, st->lpcSize, mem); + g2 = compute_rms(sp, st->subframeSize)/(.01+compute_rms(tmp_sig, st->subframeSize)); + /*fprintf (stderr, "gains: %f %f\n", g, g2);*/ + g = g2; + stack = tmp_stack; + } +#endif + + /*print_vec(&g, 1, "gain factor");*/ + /* Gain quantization */ + { + int quant = scal_quant(g, fold_quant_bound, 32); + /*speex_warning_int("tata", quant);*/ + if (quant<0) + quant=0; + if (quant>31) + quant=31; + speex_bits_pack(bits, quant, 5); + } + if (st->innov_rms_save) + { + st->innov_rms_save[sub] = eh; + } + st->exc_rms[sub] = eh; + } else { + spx_word16_t gc; /*Q7*/ + spx_word32_t scale; /*Q14*/ + spx_word16_t el; /*Q0*/ + el = low_exc_rms[sub]; /*Q0*/ + + gc = PDIV32_16(MULT16_16(filter_ratio,1+eh),1+el); + + /* This is a kludge that cleans up a historical bug */ + if (st->subframeSize==80) + gc = MULT16_16_P15(QCONST16(0.70711f,15),gc); + /*printf ("%f %f %f %f\n", el, eh, filter_ratio, gc);*/ + { + int qgc = scal_quant(gc, gc_quant_bound, 16); + speex_bits_pack(bits, qgc, 4); + gc = MULT16_16_Q15(QCONST16(0.87360,15),gc_quant_bound[qgc]); + } + if (st->subframeSize==80) + gc = MULT16_16_P14(QCONST16(1.4142f,14), gc); + + scale = SHL32(MULT16_16(PDIV32_16(SHL32(EXTEND32(gc),SIG_SHIFT-6),filter_ratio),(1+el)),6); + + compute_impulse_response(st->interp_qlpc, bw_lpc1, bw_lpc2, syn_resp, st->subframeSize, st->lpcSize, stack); + + + /* Reset excitation */ + for (i=0;isubframeSize;i++) + res[i]=VERY_SMALL; + + /* Compute zero response (ringing) of A(z/g1) / ( A(z/g2) * Aq(z) ) */ + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sp[i]; + iir_mem16(res, st->interp_qlpc, res, st->subframeSize, st->lpcSize, mem, stack); + + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sw[i]; + filter_mem16(res, bw_lpc1, bw_lpc2, res, st->subframeSize, st->lpcSize, mem, stack); + + /* Compute weighted signal */ + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sw[i]; + filter_mem16(sp, bw_lpc1, bw_lpc2, sw, st->subframeSize, st->lpcSize, mem, stack); + + /* Compute target signal */ + for (i=0;isubframeSize;i++) + target[i]=SUB16(sw[i],res[i]); + + signal_div(target, target, scale, st->subframeSize); + + /* Reset excitation */ + SPEEX_MEMSET(innov, 0, st->subframeSize); + + /*print_vec(target, st->subframeSize, "\ntarget");*/ + SUBMODE(innovation_quant)(target, st->interp_qlpc, bw_lpc1, bw_lpc2, + SUBMODE(innovation_params), st->lpcSize, st->subframeSize, + innov, syn_resp, bits, stack, st->complexity, SUBMODE(double_codebook)); + /*print_vec(target, st->subframeSize, "after");*/ + + signal_mul(innov, innov, scale, st->subframeSize); + + if (SUBMODE(double_codebook)) { + char *tmp_stack=stack; + VARDECL(spx_sig_t *innov2); + ALLOC(innov2, st->subframeSize, spx_sig_t); + SPEEX_MEMSET(innov2, 0, st->subframeSize); + for (i=0;isubframeSize;i++) + target[i]=MULT16_16_P13(QCONST16(2.5f,13), target[i]); + + SUBMODE(innovation_quant)(target, st->interp_qlpc, bw_lpc1, bw_lpc2, + SUBMODE(innovation_params), st->lpcSize, st->subframeSize, + innov2, syn_resp, bits, stack, st->complexity, 0); + signal_mul(innov2, innov2, MULT16_32_P15(QCONST16(0.4f,15),scale), st->subframeSize); + + for (i=0;isubframeSize;i++) + innov[i] = ADD32(innov[i],innov2[i]); + stack = tmp_stack; + } + for (i=0;isubframeSize;i++) + exc[i] = PSHR32(innov[i],SIG_SHIFT); + + if (st->innov_rms_save) + { + st->innov_rms_save[sub] = MULT16_16_Q15(QCONST16(.70711f, 15), compute_rms(innov, st->subframeSize)); + } + st->exc_rms[sub] = compute_rms16(exc, st->subframeSize); + + + } + + + /*Keep the previous memory*/ + for (i=0;ilpcSize;i++) + mem[i]=st->mem_sp[i]; + /* Final signal synthesis from excitation */ + iir_mem16(exc, st->interp_qlpc, sp, st->subframeSize, st->lpcSize, st->mem_sp, stack); + + /* Compute weighted signal again, from synthesized speech (not sure it's the right thing) */ + filter_mem16(sp, bw_lpc1, bw_lpc2, sw, st->subframeSize, st->lpcSize, st->mem_sw, stack); + } + + for (i=0;ilpcSize;i++) + st->old_lsp[i] = lsp[i]; + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + + st->first=0; + + return 1; +} + + + + + +void *sb_decoder_init(const SpeexMode *m) +{ + spx_int32_t tmp; + SBDecState *st; + const SpeexSBMode *mode; + st = (SBDecState*)speex_alloc(sizeof(SBDecState)); + if (!st) + return NULL; + st->mode = m; + mode=(const SpeexSBMode*)m->mode; + st->encode_submode = 1; + + st->st_low = speex_decoder_init(mode->nb_mode); +#if defined(VAR_ARRAYS) || defined (USE_ALLOCA) + st->stack = NULL; +#else + /*st->stack = (char*)speex_alloc_scratch(SB_DEC_STACK);*/ + speex_decoder_ctl(st->st_low, SPEEX_GET_STACK, &st->stack); +#endif + + st->full_frame_size = 2*mode->frameSize; + st->frame_size = mode->frameSize; + st->subframeSize = mode->subframeSize; + st->nbSubframes = mode->frameSize/mode->subframeSize; + st->lpcSize=mode->lpcSize; + speex_decoder_ctl(st->st_low, SPEEX_GET_SAMPLING_RATE, &st->sampling_rate); + st->sampling_rate*=2; + tmp=1; + speex_decoder_ctl(st->st_low, SPEEX_SET_WIDEBAND, &tmp); + + st->submodes=mode->submodes; + st->submodeID=mode->defaultSubmode; + + st->first=1; + + st->g0_mem = (spx_word16_t*)speex_alloc((QMF_ORDER)*sizeof(spx_word16_t)); + st->g1_mem = (spx_word16_t*)speex_alloc((QMF_ORDER)*sizeof(spx_word16_t)); + + st->excBuf = (spx_word16_t*)speex_alloc((st->subframeSize)*sizeof(spx_word16_t)); + + st->old_qlsp = (spx_lsp_t*)speex_alloc((st->lpcSize)*sizeof(spx_lsp_t)); + st->interp_qlpc = (spx_coef_t*)speex_alloc(st->lpcSize*sizeof(spx_coef_t)); + + st->pi_gain = (spx_word32_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word32_t)); + st->exc_rms = (spx_word16_t*)speex_alloc((st->nbSubframes)*sizeof(spx_word16_t)); + st->mem_sp = (spx_mem_t*)speex_alloc((2*st->lpcSize)*sizeof(spx_mem_t)); + + st->innov_save = NULL; + + + st->lpc_enh_enabled=0; + st->seed = 1000; + +#ifdef ENABLE_VALGRIND + VALGRIND_MAKE_READABLE(st, (st->stack-(char*)st)); +#endif + return st; +} + +void sb_decoder_destroy(void *state) +{ + SBDecState *st; + st = (SBDecState*)state; + speex_decoder_destroy(st->st_low); +#if !(defined(VAR_ARRAYS) || defined (USE_ALLOCA)) + /*speex_free_scratch(st->stack);*/ +#endif + + speex_free(st->g0_mem); + speex_free(st->g1_mem); + speex_free(st->excBuf); + speex_free(st->old_qlsp); + speex_free(st->interp_qlpc); + speex_free(st->pi_gain); + speex_free(st->exc_rms); + speex_free(st->mem_sp); + + speex_free(state); +} + +static void sb_decode_lost(SBDecState *st, spx_word16_t *out, int dtx, char *stack) +{ + int i; + int saved_modeid=0; + + if (dtx) + { + saved_modeid=st->submodeID; + st->submodeID=1; + } else { + bw_lpc(QCONST16(0.99f,15), st->interp_qlpc, st->interp_qlpc, st->lpcSize); + } + + st->first=1; + + + /* Final signal synthesis from excitation */ + if (!dtx) + { + st->last_ener = MULT16_16_Q15(QCONST16(.9f,15),st->last_ener); + } + for (i=0;iframe_size;i++) + out[i+st->frame_size] = speex_rand(st->last_ener, &st->seed); + + iir_mem16(out+st->frame_size, st->interp_qlpc, out+st->frame_size, st->frame_size, st->lpcSize, + st->mem_sp, stack); + + + /* Reconstruct the original */ + qmf_synth(out, out+st->frame_size, h0, out, st->full_frame_size, QMF_ORDER, st->g0_mem, st->g1_mem, stack); + if (dtx) + { + st->submodeID=saved_modeid; + } + + return; +} + +int sb_decode(void *state, SpeexBits *bits, void *vout) +{ + int i, sub; + SBDecState *st; + int wideband; + int ret; + char *stack; + VARDECL(spx_word32_t *low_pi_gain); + VARDECL(spx_word16_t *low_exc_rms); + VARDECL(spx_coef_t *ak); + VARDECL(spx_lsp_t *qlsp); + VARDECL(spx_lsp_t *interp_qlsp); + spx_int32_t dtx; + const SpeexSBMode *mode; + spx_word16_t *out = (spx_word16_t*)vout; + spx_word16_t *low_innov_alias; + spx_word32_t exc_ener_sum = 0; + + st = (SBDecState*)state; + stack=st->stack; + mode = (const SpeexSBMode*)(st->mode->mode); + + low_innov_alias = out+st->frame_size; + speex_decoder_ctl(st->st_low, SPEEX_SET_INNOVATION_SAVE, low_innov_alias); + /* Decode the low-band */ + ret = speex_decode_native(st->st_low, bits, out); + + speex_decoder_ctl(st->st_low, SPEEX_GET_DTX_STATUS, &dtx); + + /* If error decoding the narrowband part, propagate error */ + if (ret!=0) + { + return ret; + } + + if (!bits) + { + sb_decode_lost(st, out, dtx, stack); + return 0; + } + + if (st->encode_submode) + { + + /*Check "wideband bit"*/ + if (speex_bits_remaining(bits)>0) + wideband = speex_bits_peek(bits); + else + wideband = 0; + if (wideband) + { + /*Regular wideband frame, read the submode*/ + wideband = speex_bits_unpack_unsigned(bits, 1); + st->submodeID = speex_bits_unpack_unsigned(bits, SB_SUBMODE_BITS); + } else + { + /*Was a narrowband frame, set "null submode"*/ + st->submodeID = 0; + } + if (st->submodeID != 0 && st->submodes[st->submodeID] == NULL) + { + speex_notify("Invalid mode encountered. The stream is corrupted."); + return -2; + } + } + + /* If null mode (no transmission), just set a couple things to zero*/ + if (st->submodes[st->submodeID] == NULL) + { + if (dtx) + { + sb_decode_lost(st, out, 1, stack); + return 0; + } + + for (i=0;iframe_size;i++) + out[st->frame_size+i]=VERY_SMALL; + + st->first=1; + + /* Final signal synthesis from excitation */ + iir_mem16(out+st->frame_size, st->interp_qlpc, out+st->frame_size, st->frame_size, st->lpcSize, st->mem_sp, stack); + + qmf_synth(out, out+st->frame_size, h0, out, st->full_frame_size, QMF_ORDER, st->g0_mem, st->g1_mem, stack); + + return 0; + + } + + ALLOC(low_pi_gain, st->nbSubframes, spx_word32_t); + ALLOC(low_exc_rms, st->nbSubframes, spx_word16_t); + speex_decoder_ctl(st->st_low, SPEEX_GET_PI_GAIN, low_pi_gain); + speex_decoder_ctl(st->st_low, SPEEX_GET_EXC, low_exc_rms); + + ALLOC(qlsp, st->lpcSize, spx_lsp_t); + ALLOC(interp_qlsp, st->lpcSize, spx_lsp_t); + SUBMODE(lsp_unquant)(qlsp, st->lpcSize, bits); + + if (st->first) + { + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + } + + ALLOC(ak, st->lpcSize, spx_coef_t); + + for (sub=0;subnbSubframes;sub++) + { + VARDECL(spx_word32_t *exc); + spx_word16_t *innov_save=NULL; + spx_word16_t *sp; + spx_word16_t filter_ratio; + spx_word16_t el=0; + int offset; + spx_word32_t rl=0,rh=0; + + offset = st->subframeSize*sub; + sp=out+st->frame_size+offset; + ALLOC(exc, st->subframeSize, spx_word32_t); + /* Pointer for saving innovation */ + if (st->innov_save) + { + innov_save = st->innov_save+2*offset; + SPEEX_MEMSET(innov_save, 0, 2*st->subframeSize); + } + + /* LSP interpolation */ + lsp_interpolate(st->old_qlsp, qlsp, interp_qlsp, st->lpcSize, sub, st->nbSubframes); + + lsp_enforce_margin(interp_qlsp, st->lpcSize, LSP_MARGIN); + + /* LSP to LPC */ + lsp_to_lpc(interp_qlsp, ak, st->lpcSize, stack); + + /* Calculate reponse ratio between the low and high filter in the middle + of the band (4000 Hz) */ + + st->pi_gain[sub]=LPC_SCALING; + rh = LPC_SCALING; + for (i=0;ilpcSize;i+=2) + { + rh += ak[i+1] - ak[i]; + st->pi_gain[sub] += ak[i] + ak[i+1]; + } + + rl = low_pi_gain[sub]; +#ifdef FIXED_POINT + filter_ratio=EXTRACT16(SATURATE(PDIV32(SHL32(ADD32(rl,82),7),ADD32(82,rh)),32767)); +#else + filter_ratio=(rl+.01)/(rh+.01); +#endif + + SPEEX_MEMSET(exc, 0, st->subframeSize); + if (!SUBMODE(innovation_unquant)) + { + spx_word32_t g; + int quant; + + quant = speex_bits_unpack_unsigned(bits, 5); + g= spx_exp(MULT16_16(QCONST16(.125f,11),(quant-10))); + + g = PDIV32(g, filter_ratio); + + for (i=0;isubframeSize;i+=2) + { + exc[i]=SHL32(MULT16_32_P15(MULT16_16_Q15(mode->folding_gain,low_innov_alias[offset+i]),SHL32(g,6)),SIG_SHIFT); + exc[i+1]=NEG32(SHL32(MULT16_32_P15(MULT16_16_Q15(mode->folding_gain,low_innov_alias[offset+i+1]),SHL32(g,6)),SIG_SHIFT)); + } + + } else { + spx_word16_t gc; + spx_word32_t scale; + int qgc = speex_bits_unpack_unsigned(bits, 4); + + el = low_exc_rms[sub]; + gc = MULT16_16_Q15(QCONST16(0.87360,15),gc_quant_bound[qgc]); + + if (st->subframeSize==80) + gc = MULT16_16_P14(QCONST16(1.4142f,14),gc); + + scale = SHL32(PDIV32(SHL32(MULT16_16(gc, el),3), filter_ratio),SIG_SHIFT-3); + SUBMODE(innovation_unquant)(exc, SUBMODE(innovation_params), st->subframeSize, + bits, stack, &st->seed); + + signal_mul(exc,exc,scale,st->subframeSize); + + if (SUBMODE(double_codebook)) { + char *tmp_stack=stack; + VARDECL(spx_sig_t *innov2); + ALLOC(innov2, st->subframeSize, spx_sig_t); + SPEEX_MEMSET(innov2, 0, st->subframeSize); + SUBMODE(innovation_unquant)(innov2, SUBMODE(innovation_params), st->subframeSize, + bits, stack, &st->seed); + signal_mul(innov2, innov2, MULT16_32_P15(QCONST16(0.4f,15),scale), st->subframeSize); + for (i=0;isubframeSize;i++) + exc[i] = ADD32(exc[i],innov2[i]); + stack = tmp_stack; + } + + } + + if (st->innov_save) + { + for (i=0;isubframeSize;i++) + innov_save[2*i]=EXTRACT16(PSHR32(exc[i],SIG_SHIFT)); + } + + iir_mem16(st->excBuf, st->interp_qlpc, sp, st->subframeSize, st->lpcSize, + st->mem_sp, stack); + for (i=0;isubframeSize;i++) + st->excBuf[i]=EXTRACT16(PSHR32(exc[i],SIG_SHIFT)); + for (i=0;ilpcSize;i++) + st->interp_qlpc[i] = ak[i]; + st->exc_rms[sub] = compute_rms16(st->excBuf, st->subframeSize); + exc_ener_sum = ADD32(exc_ener_sum, DIV32(MULT16_16(st->exc_rms[sub],st->exc_rms[sub]), st->nbSubframes)); + } + st->last_ener = spx_sqrt(exc_ener_sum); + + qmf_synth(out, out+st->frame_size, h0, out, st->full_frame_size, QMF_ORDER, st->g0_mem, st->g1_mem, stack); + for (i=0;ilpcSize;i++) + st->old_qlsp[i] = qlsp[i]; + + st->first=0; + + return 0; +} + + +int sb_encoder_ctl(void *state, int request, void *ptr) +{ + SBEncState *st; + st=(SBEncState*)state; + switch(request) + { + case SPEEX_GET_FRAME_SIZE: + (*(spx_int32_t*)ptr) = st->full_frame_size; + break; + case SPEEX_SET_HIGH_MODE: + st->submodeSelect = st->submodeID = (*(spx_int32_t*)ptr); + break; + case SPEEX_SET_LOW_MODE: + speex_encoder_ctl(st->st_low, SPEEX_SET_LOW_MODE, ptr); + break; + case SPEEX_SET_DTX: + speex_encoder_ctl(st->st_low, SPEEX_SET_DTX, ptr); + break; + case SPEEX_GET_DTX: + speex_encoder_ctl(st->st_low, SPEEX_GET_DTX, ptr); + break; + case SPEEX_GET_LOW_MODE: + speex_encoder_ctl(st->st_low, SPEEX_GET_LOW_MODE, ptr); + break; + case SPEEX_SET_MODE: + speex_encoder_ctl(st, SPEEX_SET_QUALITY, ptr); + break; +#ifndef DISABLE_VBR + case SPEEX_SET_VBR: + st->vbr_enabled = (*(spx_int32_t*)ptr); + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR, ptr); + break; + case SPEEX_GET_VBR: + (*(spx_int32_t*)ptr) = st->vbr_enabled; + break; + case SPEEX_SET_VAD: + st->vad_enabled = (*(spx_int32_t*)ptr); + speex_encoder_ctl(st->st_low, SPEEX_SET_VAD, ptr); + break; + case SPEEX_GET_VAD: + (*(spx_int32_t*)ptr) = st->vad_enabled; + break; +#endif /* #ifndef DISABLE_VBR */ +#if !defined(DISABLE_VBR) && !defined(DISABLE_FLOAT_API) + case SPEEX_SET_VBR_QUALITY: + { + spx_int32_t q; + float qual = (*(float*)ptr)+.6; + st->vbr_quality = (*(float*)ptr); + if (qual>10) + qual=10; + q=(int)floor(.5+*(float*)ptr); + if (q>10) + q=10; + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR_QUALITY, &qual); + speex_encoder_ctl(state, SPEEX_SET_QUALITY, &q); + break; + } + case SPEEX_GET_VBR_QUALITY: + (*(float*)ptr) = st->vbr_quality; + break; +#endif /* #if !defined(DISABLE_VBR) && !defined(DISABLE_FLOAT_API) */ +#ifndef DISABLE_VBR + case SPEEX_SET_ABR: + st->abr_enabled = (*(spx_int32_t*)ptr); + st->vbr_enabled = st->abr_enabled!=0; + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR, &st->vbr_enabled); + if (st->vbr_enabled) + { + spx_int32_t i=10, rate, target; + float vbr_qual; + target = (*(spx_int32_t*)ptr); + while (i>=0) + { + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &i); + speex_encoder_ctl(st, SPEEX_GET_BITRATE, &rate); + if (rate <= target) + break; + i--; + } + vbr_qual=i; + if (vbr_qual<0) + vbr_qual=0; + speex_encoder_ctl(st, SPEEX_SET_VBR_QUALITY, &vbr_qual); + st->abr_count=0; + st->abr_drift=0; + st->abr_drift2=0; + } + + break; + case SPEEX_GET_ABR: + (*(spx_int32_t*)ptr) = st->abr_enabled; + break; +#endif /* #ifndef DISABLE_VBR */ + + case SPEEX_SET_QUALITY: + { + spx_int32_t nb_qual; + int quality = (*(spx_int32_t*)ptr); + if (quality < 0) + quality = 0; + if (quality > 10) + quality = 10; + st->submodeSelect = st->submodeID = ((const SpeexSBMode*)(st->mode->mode))->quality_map[quality]; + nb_qual = ((const SpeexSBMode*)(st->mode->mode))->low_quality_map[quality]; + speex_encoder_ctl(st->st_low, SPEEX_SET_MODE, &nb_qual); + } + break; + case SPEEX_SET_COMPLEXITY: + speex_encoder_ctl(st->st_low, SPEEX_SET_COMPLEXITY, ptr); + st->complexity = (*(spx_int32_t*)ptr); + if (st->complexity<1) + st->complexity=1; + break; + case SPEEX_GET_COMPLEXITY: + (*(spx_int32_t*)ptr) = st->complexity; + break; + case SPEEX_SET_BITRATE: + { + spx_int32_t i=10; + spx_int32_t rate, target; + target = (*(spx_int32_t*)ptr); + while (i>=0) + { + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &i); + speex_encoder_ctl(st, SPEEX_GET_BITRATE, &rate); + if (rate <= target) + break; + i--; + } + } + break; + case SPEEX_GET_BITRATE: + speex_encoder_ctl(st->st_low, request, ptr); + /*fprintf (stderr, "before: %d\n", (*(int*)ptr));*/ + if (st->submodes[st->submodeID]) + (*(spx_int32_t*)ptr) += st->sampling_rate*SUBMODE(bits_per_frame)/st->full_frame_size; + else + (*(spx_int32_t*)ptr) += st->sampling_rate*(SB_SUBMODE_BITS+1)/st->full_frame_size; + /*fprintf (stderr, "after: %d\n", (*(int*)ptr));*/ + break; + case SPEEX_SET_SAMPLING_RATE: + { + spx_int32_t tmp=(*(spx_int32_t*)ptr); + st->sampling_rate = tmp; + tmp>>=1; + speex_encoder_ctl(st->st_low, SPEEX_SET_SAMPLING_RATE, &tmp); + } + break; + case SPEEX_GET_SAMPLING_RATE: + (*(spx_int32_t*)ptr)=st->sampling_rate; + break; + case SPEEX_RESET_STATE: + { + int i; + st->first = 1; + for (i=0;ilpcSize;i++) + st->old_lsp[i]= DIV32(MULT16_16(QCONST16(3.1415927f, LSP_SHIFT), i+1), st->lpcSize+1); + for (i=0;ilpcSize;i++) + st->mem_sw[i]=st->mem_sp[i]=st->mem_sp2[i]=0; + for (i=0;ih0_mem[i]=st->h1_mem[i]=0; + } + break; + case SPEEX_SET_SUBMODE_ENCODING: + st->encode_submode = (*(spx_int32_t*)ptr); + speex_encoder_ctl(st->st_low, SPEEX_SET_SUBMODE_ENCODING, ptr); + break; + case SPEEX_GET_SUBMODE_ENCODING: + (*(spx_int32_t*)ptr) = st->encode_submode; + break; + case SPEEX_GET_LOOKAHEAD: + speex_encoder_ctl(st->st_low, SPEEX_GET_LOOKAHEAD, ptr); + (*(spx_int32_t*)ptr) = 2*(*(spx_int32_t*)ptr) + QMF_ORDER - 1; + break; + case SPEEX_SET_PLC_TUNING: + speex_encoder_ctl(st->st_low, SPEEX_SET_PLC_TUNING, ptr); + break; + case SPEEX_GET_PLC_TUNING: + speex_encoder_ctl(st->st_low, SPEEX_GET_PLC_TUNING, ptr); + break; +#ifndef DISABLE_VBR + case SPEEX_SET_VBR_MAX_BITRATE: + { + st->vbr_max = (*(spx_int32_t*)ptr); + if (SPEEX_SET_VBR_MAX_BITRATE<1) + { + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR_MAX_BITRATE, &st->vbr_max); + st->vbr_max_high = 17600; + } else { + spx_int32_t low_rate; + if (st->vbr_max >= 42200) + { + st->vbr_max_high = 17600; + } else if (st->vbr_max >= 27800) + { + st->vbr_max_high = 9600; + } else if (st->vbr_max > 20600) + { + st->vbr_max_high = 5600; + } else { + st->vbr_max_high = 1800; + } + if (st->subframeSize==80) + st->vbr_max_high = 1800; + low_rate = st->vbr_max - st->vbr_max_high; + speex_encoder_ctl(st->st_low, SPEEX_SET_VBR_MAX_BITRATE, &low_rate); + } + } + break; + case SPEEX_GET_VBR_MAX_BITRATE: + (*(spx_int32_t*)ptr) = st->vbr_max; + break; +#endif /* #ifndef DISABLE_VBR */ + case SPEEX_SET_HIGHPASS: + speex_encoder_ctl(st->st_low, SPEEX_SET_HIGHPASS, ptr); + break; + case SPEEX_GET_HIGHPASS: + speex_encoder_ctl(st->st_low, SPEEX_GET_HIGHPASS, ptr); + break; + + + /* This is all internal stuff past this point */ + case SPEEX_GET_PI_GAIN: + { + int i; + spx_word32_t *g = (spx_word32_t*)ptr; + for (i=0;inbSubframes;i++) + g[i]=st->pi_gain[i]; + } + break; + case SPEEX_GET_EXC: + { + int i; + for (i=0;inbSubframes;i++) + ((spx_word16_t*)ptr)[i] = st->exc_rms[i]; + } + break; +#ifndef DISABLE_VBR + case SPEEX_GET_RELATIVE_QUALITY: + (*(float*)ptr)=st->relative_quality; + break; +#endif /* #ifndef DISABLE_VBR */ + case SPEEX_SET_INNOVATION_SAVE: + st->innov_rms_save = (spx_word16_t*)ptr; + break; + case SPEEX_SET_WIDEBAND: + speex_encoder_ctl(st->st_low, SPEEX_SET_WIDEBAND, ptr); + break; + case SPEEX_GET_STACK: + *((char**)ptr) = st->stack; + break; + default: + speex_warning_int("Unknown nb_ctl request: ", request); + return -1; + } + return 0; +} + +int sb_decoder_ctl(void *state, int request, void *ptr) +{ + SBDecState *st; + st=(SBDecState*)state; + switch(request) + { + case SPEEX_SET_HIGH_MODE: + st->submodeID = (*(spx_int32_t*)ptr); + break; + case SPEEX_SET_LOW_MODE: + speex_decoder_ctl(st->st_low, SPEEX_SET_LOW_MODE, ptr); + break; + case SPEEX_GET_LOW_MODE: + speex_decoder_ctl(st->st_low, SPEEX_GET_LOW_MODE, ptr); + break; + case SPEEX_GET_FRAME_SIZE: + (*(spx_int32_t*)ptr) = st->full_frame_size; + break; + case SPEEX_SET_ENH: + speex_decoder_ctl(st->st_low, request, ptr); + st->lpc_enh_enabled = *((spx_int32_t*)ptr); + break; + case SPEEX_GET_ENH: + *((spx_int32_t*)ptr) = st->lpc_enh_enabled; + break; + case SPEEX_SET_MODE: + case SPEEX_SET_QUALITY: + { + spx_int32_t nb_qual; + int quality = (*(spx_int32_t*)ptr); + if (quality < 0) + quality = 0; + if (quality > 10) + quality = 10; + st->submodeID = ((const SpeexSBMode*)(st->mode->mode))->quality_map[quality]; + nb_qual = ((const SpeexSBMode*)(st->mode->mode))->low_quality_map[quality]; + speex_decoder_ctl(st->st_low, SPEEX_SET_MODE, &nb_qual); + } + break; + case SPEEX_GET_BITRATE: + speex_decoder_ctl(st->st_low, request, ptr); + if (st->submodes[st->submodeID]) + (*(spx_int32_t*)ptr) += st->sampling_rate*SUBMODE(bits_per_frame)/st->full_frame_size; + else + (*(spx_int32_t*)ptr) += st->sampling_rate*(SB_SUBMODE_BITS+1)/st->full_frame_size; + break; + case SPEEX_SET_SAMPLING_RATE: + { + spx_int32_t tmp=(*(spx_int32_t*)ptr); + st->sampling_rate = tmp; + tmp>>=1; + speex_decoder_ctl(st->st_low, SPEEX_SET_SAMPLING_RATE, &tmp); + } + break; + case SPEEX_GET_SAMPLING_RATE: + (*(spx_int32_t*)ptr)=st->sampling_rate; + break; + case SPEEX_SET_HANDLER: + speex_decoder_ctl(st->st_low, SPEEX_SET_HANDLER, ptr); + break; + case SPEEX_SET_USER_HANDLER: + speex_decoder_ctl(st->st_low, SPEEX_SET_USER_HANDLER, ptr); + break; + case SPEEX_RESET_STATE: + { + int i; + for (i=0;i<2*st->lpcSize;i++) + st->mem_sp[i]=0; + for (i=0;ig0_mem[i]=st->g1_mem[i]=0; + st->last_ener=0; + } + break; + case SPEEX_SET_SUBMODE_ENCODING: + st->encode_submode = (*(spx_int32_t*)ptr); + speex_decoder_ctl(st->st_low, SPEEX_SET_SUBMODE_ENCODING, ptr); + break; + case SPEEX_GET_SUBMODE_ENCODING: + (*(spx_int32_t*)ptr) = st->encode_submode; + break; + case SPEEX_GET_LOOKAHEAD: + speex_decoder_ctl(st->st_low, SPEEX_GET_LOOKAHEAD, ptr); + (*(spx_int32_t*)ptr) = 2*(*(spx_int32_t*)ptr); + break; + case SPEEX_SET_HIGHPASS: + speex_decoder_ctl(st->st_low, SPEEX_SET_HIGHPASS, ptr); + break; + case SPEEX_GET_HIGHPASS: + speex_decoder_ctl(st->st_low, SPEEX_GET_HIGHPASS, ptr); + break; + case SPEEX_GET_ACTIVITY: + speex_decoder_ctl(st->st_low, SPEEX_GET_ACTIVITY, ptr); + break; + case SPEEX_GET_PI_GAIN: + { + int i; + spx_word32_t *g = (spx_word32_t*)ptr; + for (i=0;inbSubframes;i++) + g[i]=st->pi_gain[i]; + } + break; + case SPEEX_GET_EXC: + { + int i; + for (i=0;inbSubframes;i++) + ((spx_word16_t*)ptr)[i] = st->exc_rms[i]; + } + break; + case SPEEX_GET_DTX_STATUS: + speex_decoder_ctl(st->st_low, SPEEX_GET_DTX_STATUS, ptr); + break; + case SPEEX_SET_INNOVATION_SAVE: + st->innov_save = (spx_word16_t*)ptr; + break; + case SPEEX_SET_WIDEBAND: + speex_decoder_ctl(st->st_low, SPEEX_SET_WIDEBAND, ptr); + break; + case SPEEX_GET_STACK: + *((char**)ptr) = st->stack; + break; + default: + speex_warning_int("Unknown nb_ctl request: ", request); + return -1; + } + return 0; +} + +#endif + diff --git a/src/libspeex/sb_celp.h b/src/libspeex/sb_celp.h new file mode 100644 index 0000000..e8c3761 --- /dev/null +++ b/src/libspeex/sb_celp.h @@ -0,0 +1,155 @@ +/* Copyright (C) 2002-2006 Jean-Marc Valin */ +/** + @file sb_celp.h + @brief Sub-band CELP mode used for wideband encoding +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SB_CELP_H +#define SB_CELP_H + +#include "modes.h" +#include +#include "nb_celp.h" + +/**Structure representing the full state of the sub-band encoder*/ +typedef struct SBEncState { + const SpeexMode *mode; /**< Pointer to the mode (containing for vtable info) */ + void *st_low; /**< State of the low-band (narrowband) encoder */ + int full_frame_size; /**< Length of full-band frames*/ + int frame_size; /**< Length of high-band frames*/ + int subframeSize; /**< Length of high-band sub-frames*/ + int nbSubframes; /**< Number of high-band sub-frames*/ + int windowSize; /**< Length of high-band LPC window*/ + int lpcSize; /**< Order of high-band LPC analysis */ + int first; /**< First frame? */ + spx_word16_t lpc_floor; /**< Controls LPC analysis noise floor */ + spx_word16_t gamma1; /**< Perceptual weighting coef 1 */ + spx_word16_t gamma2; /**< Perceptual weighting coef 2 */ + + char *stack; /**< Temporary allocation stack */ + spx_word16_t *high; /**< High-band signal (buffer) */ + spx_word16_t *h0_mem, *h1_mem; + + const spx_word16_t *window; /**< LPC analysis window */ + const spx_word16_t *lagWindow; /**< Auto-correlation window */ + spx_lsp_t *old_lsp; /**< LSPs of previous frame */ + spx_lsp_t *old_qlsp; /**< Quantized LSPs of previous frame */ + spx_coef_t *interp_qlpc; /**< Interpolated quantized LPCs for current sub-frame */ + + spx_mem_t *mem_sp; /**< Synthesis signal memory */ + spx_mem_t *mem_sp2; + spx_mem_t *mem_sw; /**< Perceptual signal memory */ + spx_word32_t *pi_gain; + spx_word16_t *exc_rms; + spx_word16_t *innov_rms_save; /**< If non-NULL, innovation is copied here */ + +#ifndef DISABLE_VBR + float vbr_quality; /**< Quality setting for VBR encoding */ + int vbr_enabled; /**< 1 for enabling VBR, 0 otherwise */ + spx_int32_t vbr_max; /**< Max bit-rate allowed in VBR mode (total) */ + spx_int32_t vbr_max_high; /**< Max bit-rate allowed in VBR mode for the high-band */ + spx_int32_t abr_enabled; /**< ABR setting (in bps), 0 if off */ + float abr_drift; + float abr_drift2; + float abr_count; + int vad_enabled; /**< 1 for enabling VAD, 0 otherwise */ + float relative_quality; +#endif /* #ifndef DISABLE_VBR */ + + int encode_submode; + const SpeexSubmode * const *submodes; + int submodeID; + int submodeSelect; + int complexity; + spx_int32_t sampling_rate; + +} SBEncState; + + +/**Structure representing the full state of the sub-band decoder*/ +typedef struct SBDecState { + const SpeexMode *mode; /**< Pointer to the mode (containing for vtable info) */ + void *st_low; /**< State of the low-band (narrowband) encoder */ + int full_frame_size; + int frame_size; + int subframeSize; + int nbSubframes; + int lpcSize; + int first; + spx_int32_t sampling_rate; + int lpc_enh_enabled; + + char *stack; + spx_word16_t *g0_mem, *g1_mem; + + spx_word16_t *excBuf; + spx_lsp_t *old_qlsp; + spx_coef_t *interp_qlpc; + + spx_mem_t *mem_sp; + spx_word32_t *pi_gain; + spx_word16_t *exc_rms; + spx_word16_t *innov_save; /** If non-NULL, innovation is copied here */ + + spx_word16_t last_ener; + spx_int32_t seed; + + int encode_submode; + const SpeexSubmode * const *submodes; + int submodeID; +} SBDecState; + + +/**Initializes encoder state*/ +void *sb_encoder_init(const SpeexMode *m); + +/**De-allocates encoder state resources*/ +void sb_encoder_destroy(void *state); + +/**Encodes one frame*/ +int sb_encode(void *state, void *in, SpeexBits *bits); + + +/**Initializes decoder state*/ +void *sb_decoder_init(const SpeexMode *m); + +/**De-allocates decoder state resources*/ +void sb_decoder_destroy(void *state); + +/**Decodes one frame*/ +int sb_decode(void *state, SpeexBits *bits, void *out); + +int sb_encoder_ctl(void *state, int request, void *ptr); + +int sb_decoder_ctl(void *state, int request, void *ptr); + +#endif diff --git a/src/libspeex/smallft.c b/src/libspeex/smallft.c new file mode 100644 index 0000000..5c26d01 --- /dev/null +++ b/src/libspeex/smallft.c @@ -0,0 +1,1261 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: *unnormalized* fft transform + last mod: $Id: smallft.c,v 1.19 2003/10/08 05:12:37 jm Exp $ + + ********************************************************************/ + +/* FFT implementation from OggSquish, minus cosine transforms, + * minus all but radix 2/4 case. In Vorbis we only need this + * cut-down version. + * + * To do more than just power-of-two sized vectors, see the full + * version I wrote for NetLib. + * + * Note that the packing is a little strange; rather than the FFT r/i + * packing following R_0, I_n, R_1, I_1, R_2, I_2 ... R_n-1, I_n-1, + * it follows R_0, R_1, I_1, R_2, I_2 ... R_n-1, I_n-1, I_n like the + * FORTRAN version + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "smallft.h" +#include "arch.h" +#include "os_support.h" + +static void drfti1(int n, float *wa, int *ifac){ + static int ntryh[4] = { 4,2,3,5 }; + static float tpi = 6.28318530717958648f; + float arg,argh,argld,fi; + int ntry=0,i,j=-1; + int k1, l1, l2, ib; + int ld, ii, ip, is, nq, nr; + int ido, ipm, nfm1; + int nl=n; + int nf=0; + + L101: + j++; + if (j < 4) + ntry=ntryh[j]; + else + ntry+=2; + + L104: + nq=nl/ntry; + nr=nl-ntry*nq; + if (nr!=0) goto L101; + + nf++; + ifac[nf+1]=ntry; + nl=nq; + if(ntry!=2)goto L107; + if(nf==1)goto L107; + + for (i=1;i>1; + ipp2=ip; + idp2=ido; + nbd=(ido-1)>>1; + t0=l1*ido; + t10=ip*ido; + + if(ido==1)goto L119; + for(ik=0;ikl1){ + for(j=1;j>1; + ipp2=ip; + ipph=(ip+1)>>1; + if(idol1)goto L139; + + is= -ido-1; + t1=0; + for(j=1;jn==1)return; + drftf1(l->n,data,l->trigcache,l->trigcache+l->n,l->splitcache); +} + +void spx_drft_backward(struct drft_lookup *l,float *data){ + if (l->n==1)return; + drftb1(l->n,data,l->trigcache,l->trigcache+l->n,l->splitcache); +} + +void spx_drft_init(struct drft_lookup *l,int n) +{ + l->n=n; + l->trigcache=(float*)speex_alloc(3*n*sizeof(*l->trigcache)); + l->splitcache=(int*)speex_alloc(32*sizeof(*l->splitcache)); + fdrffti(n, l->trigcache, l->splitcache); +} + +void spx_drft_clear(struct drft_lookup *l) +{ + if(l) + { + if(l->trigcache) + speex_free(l->trigcache); + if(l->splitcache) + speex_free(l->splitcache); + } +} diff --git a/src/libspeex/smallft.h b/src/libspeex/smallft.h new file mode 100644 index 0000000..446e2f6 --- /dev/null +++ b/src/libspeex/smallft.h @@ -0,0 +1,46 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: fft transform + last mod: $Id: smallft.h,v 1.3 2003/09/16 18:35:45 jm Exp $ + + ********************************************************************/ +/** + @file smallft.h + @brief Discrete Rotational Fourier Transform (DRFT) +*/ + +#ifndef _V_SMFT_H_ +#define _V_SMFT_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** Discrete Rotational Fourier Transform lookup */ +struct drft_lookup{ + int n; + float *trigcache; + int *splitcache; +}; + +extern void spx_drft_forward(struct drft_lookup *l,float *data); +extern void spx_drft_backward(struct drft_lookup *l,float *data); +extern void spx_drft_init(struct drft_lookup *l,int n); +extern void spx_drft_clear(struct drft_lookup *l); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libspeex/speex.c b/src/libspeex/speex.c new file mode 100644 index 0000000..78e1a7a --- /dev/null +++ b/src/libspeex/speex.c @@ -0,0 +1,250 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: speex.c + + Basic Speex functions + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "modes.h" +#include +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + +#define MAX_IN_SAMPLES 640 + + + +void *speex_encoder_init(const SpeexMode *mode) +{ + return mode->enc_init(mode); +} + +void *speex_decoder_init(const SpeexMode *mode) +{ + return mode->dec_init(mode); +} + +void speex_encoder_destroy(void *state) +{ + (*((SpeexMode**)state))->enc_destroy(state); +} + +void speex_decoder_destroy(void *state) +{ + (*((SpeexMode**)state))->dec_destroy(state); +} + + + +int speex_encode_native(void *state, spx_word16_t *in, SpeexBits *bits) +{ + return (*((SpeexMode**)state))->enc(state, in, bits); +} + +int speex_decode_native(void *state, SpeexBits *bits, spx_word16_t *out) +{ + return (*((SpeexMode**)state))->dec(state, bits, out); +} + + + +#ifdef FIXED_POINT + +#ifndef DISABLE_FLOAT_API +int speex_encode(void *state, float *in, SpeexBits *bits) +{ + int i; + spx_int32_t N; + spx_int16_t short_in[MAX_IN_SAMPLES]; + speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &N); + for (i=0;i32767.f) + short_in[i] = 32767; + else if (in[i]<-32768.f) + short_in[i] = -32768; + else + short_in[i] = (spx_int16_t)floor(.5+in[i]); + } + return (*((SpeexMode**)state))->enc(state, short_in, bits); +} +#endif /* #ifndef DISABLE_FLOAT_API */ + +int speex_encode_int(void *state, spx_int16_t *in, SpeexBits *bits) +{ + SpeexMode *mode; + mode = *(SpeexMode**)state; + return (mode)->enc(state, in, bits); +} + +#ifndef DISABLE_FLOAT_API +int speex_decode(void *state, SpeexBits *bits, float *out) +{ + int i, ret; + spx_int32_t N; + spx_int16_t short_out[MAX_IN_SAMPLES]; + speex_decoder_ctl(state, SPEEX_GET_FRAME_SIZE, &N); + ret = (*((SpeexMode**)state))->dec(state, bits, short_out); + for (i=0;idec(state, bits, out); +} + +#else + +int speex_encode(void *state, float *in, SpeexBits *bits) +{ + return (*((SpeexMode**)state))->enc(state, in, bits); +} + +int speex_encode_int(void *state, spx_int16_t *in, SpeexBits *bits) +{ + int i; + spx_int32_t N; + float float_in[MAX_IN_SAMPLES]; + speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &N); + for (i=0;ienc(state, float_in, bits); +} + +int speex_decode(void *state, SpeexBits *bits, float *out) +{ + return (*((SpeexMode**)state))->dec(state, bits, out); +} + +int speex_decode_int(void *state, SpeexBits *bits, spx_int16_t *out) +{ + int i; + spx_int32_t N; + float float_out[MAX_IN_SAMPLES]; + int ret; + speex_decoder_ctl(state, SPEEX_GET_FRAME_SIZE, &N); + ret = (*((SpeexMode**)state))->dec(state, bits, float_out); + for (i=0;i32767.f) + out[i] = 32767; + else if (float_out[i]<-32768.f) + out[i] = -32768; + else + out[i] = (spx_int16_t)floor(.5+float_out[i]); + } + return ret; +} +#endif + + + +int speex_encoder_ctl(void *state, int request, void *ptr) +{ + return (*((SpeexMode**)state))->enc_ctl(state, request, ptr); +} + +int speex_decoder_ctl(void *state, int request, void *ptr) +{ + return (*((SpeexMode**)state))->dec_ctl(state, request, ptr); +} + + + +int nb_mode_query(const void *mode, int request, void *ptr) +{ + const SpeexNBMode *m = (const SpeexNBMode*)mode; + + switch (request) + { + case SPEEX_MODE_FRAME_SIZE: + *((int*)ptr)=m->frameSize; + break; + case SPEEX_SUBMODE_BITS_PER_FRAME: + if (*((int*)ptr)==0) + *((int*)ptr) = NB_SUBMODE_BITS+1; + else if (m->submodes[*((int*)ptr)]==NULL) + *((int*)ptr) = -1; + else + *((int*)ptr) = m->submodes[*((int*)ptr)]->bits_per_frame; + break; + default: + speex_warning_int("Unknown nb_mode_query request: ", request); + return -1; + } + return 0; +} + + + +int speex_lib_ctl(int request, void *ptr) +{ + switch (request) + { + case SPEEX_LIB_GET_MAJOR_VERSION: + *((int*)ptr) = SPEEX_MAJOR_VERSION; + break; + case SPEEX_LIB_GET_MINOR_VERSION: + *((int*)ptr) = SPEEX_MINOR_VERSION; + break; + case SPEEX_LIB_GET_MICRO_VERSION: + *((int*)ptr) = SPEEX_MICRO_VERSION; + break; + case SPEEX_LIB_GET_EXTRA_VERSION: + *((const char**)ptr) = SPEEX_EXTRA_VERSION; + break; + case SPEEX_LIB_GET_VERSION_STRING: + *((const char**)ptr) = SPEEX_VERSION; + break; + /*case SPEEX_LIB_SET_ALLOC_FUNC: + break; + case SPEEX_LIB_GET_ALLOC_FUNC: + break; + case SPEEX_LIB_SET_FREE_FUNC: + break; + case SPEEX_LIB_GET_FREE_FUNC: + break;*/ + default: + speex_warning_int("Unknown wb_mode_query request: ", request); + return -1; + } + return 0; +} diff --git a/src/libspeex/speex_callbacks.c b/src/libspeex/speex_callbacks.c new file mode 100644 index 0000000..d1158b2 --- /dev/null +++ b/src/libspeex/speex_callbacks.c @@ -0,0 +1,144 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File speex_callbacks.c + Callback handling and in-band signalling + + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "arch.h" +#include "os_support.h" + +int speex_inband_handler(SpeexBits *bits, SpeexCallback *callback_list, void *state) +{ + int id; + SpeexCallback *callback; + /*speex_bits_advance(bits, 5);*/ + id=speex_bits_unpack_unsigned(bits, 4); + callback = callback_list+id; + + if (callback->func) + { + return callback->func(bits, state, callback->data); + } else + /*If callback is not registered, skip the right number of bits*/ + { + int adv; + if (id<2) + adv = 1; + else if (id<8) + adv = 4; + else if (id<10) + adv = 8; + else if (id<12) + adv = 16; + else if (id<14) + adv = 32; + else + adv = 64; + speex_bits_advance(bits, adv); + } + return 0; +} + +int speex_std_mode_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t m; + m = speex_bits_unpack_unsigned(bits, 4); + speex_encoder_ctl(data, SPEEX_SET_MODE, &m); + return 0; +} + +int speex_std_low_mode_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t m; + m = speex_bits_unpack_unsigned(bits, 4); + speex_encoder_ctl(data, SPEEX_SET_LOW_MODE, &m); + return 0; +} + +int speex_std_high_mode_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t m; + m = speex_bits_unpack_unsigned(bits, 4); + speex_encoder_ctl(data, SPEEX_SET_HIGH_MODE, &m); + return 0; +} + +#ifndef DISABLE_VBR +int speex_std_vbr_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t vbr; + vbr = speex_bits_unpack_unsigned(bits, 1); + speex_encoder_ctl(data, SPEEX_SET_VBR, &vbr); + return 0; +} +#endif /* #ifndef DISABLE_VBR */ + +int speex_std_enh_request_handler(SpeexBits *bits, void *state, void *data) +{ + spx_int32_t enh; + enh = speex_bits_unpack_unsigned(bits, 1); + speex_decoder_ctl(data, SPEEX_SET_ENH, &enh); + return 0; +} + +#ifndef DISABLE_VBR +int speex_std_vbr_quality_request_handler(SpeexBits *bits, void *state, void *data) +{ + float qual; + qual = speex_bits_unpack_unsigned(bits, 4); + speex_encoder_ctl(data, SPEEX_SET_VBR_QUALITY, &qual); + return 0; +} +#endif /* #ifndef DISABLE_VBR */ + +int speex_std_char_handler(SpeexBits *bits, void *state, void *data) +{ + unsigned char ch; + ch = speex_bits_unpack_unsigned(bits, 8); + _speex_putc(ch, data); + /*printf("speex_std_char_handler ch=%x\n", ch);*/ + return 0; +} + + + +/* Default handler for user callbacks: skip it */ +int speex_default_user_handler(SpeexBits *bits, void *state, void *data) +{ + int req_size = speex_bits_unpack_unsigned(bits, 4); + speex_bits_advance(bits, 5+8*req_size); + return 0; +} diff --git a/src/libspeex/speex_header.c b/src/libspeex/speex_header.c new file mode 100644 index 0000000..e9dd228 --- /dev/null +++ b/src/libspeex/speex_header.c @@ -0,0 +1,188 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: speex_header.c + Describes the Speex header + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "arch.h" +#include +#include +#include "os_support.h" + +#ifndef NULL +#define NULL 0 +#endif + +/** Convert little endian */ +static inline spx_int32_t le_int(spx_int32_t i) +{ +#if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) ) + spx_uint32_t ui, ret; + ui = i; + ret = ui>>24; + ret |= (ui>>8)&0x0000ff00; + ret |= (ui<<8)&0x00ff0000; + ret |= (ui<<24); + return ret; +#else + return i; +#endif +} + +#define ENDIAN_SWITCH(x) {x=le_int(x);} + + +/* +typedef struct SpeexHeader { + char speex_string[8]; + char speex_version[SPEEX_HEADER_VERSION_LENGTH]; + int speex_version_id; + int header_size; + int rate; + int mode; + int mode_bitstream_version; + int nb_channels; + int bitrate; + int frame_size; + int vbr; + int frames_per_packet; + int extra_headers; + int reserved1; + int reserved2; +} SpeexHeader; +*/ + +void speex_init_header(SpeexHeader *header, int rate, int nb_channels, const SpeexMode *m) +{ + int i; + const char *h="Speex "; + /* + strncpy(header->speex_string, "Speex ", 8); + strncpy(header->speex_version, SPEEX_VERSION, SPEEX_HEADER_VERSION_LENGTH-1); + header->speex_version[SPEEX_HEADER_VERSION_LENGTH-1]=0; + */ + for (i=0;i<8;i++) + header->speex_string[i]=h[i]; + for (i=0;ispeex_version[i]=SPEEX_VERSION[i]; + for (;ispeex_version[i]=0; + + header->speex_version_id = 1; + header->header_size = sizeof(SpeexHeader); + + header->rate = rate; + header->mode = m->modeID; + header->mode_bitstream_version = m->bitstream_version; + if (m->modeID<0) + speex_warning("This mode is meant to be used alone"); + header->nb_channels = nb_channels; + header->bitrate = -1; + speex_mode_query(m, SPEEX_MODE_FRAME_SIZE, &header->frame_size); + header->vbr = 0; + + header->frames_per_packet = 0; + header->extra_headers = 0; + header->reserved1 = 0; + header->reserved2 = 0; +} + +char *speex_header_to_packet(SpeexHeader *header, int *size) +{ + SpeexHeader *le_header; + le_header = (SpeexHeader*)speex_alloc(sizeof(SpeexHeader)); + + SPEEX_COPY(le_header, header, 1); + + /*Make sure everything is now little-endian*/ + ENDIAN_SWITCH(le_header->speex_version_id); + ENDIAN_SWITCH(le_header->header_size); + ENDIAN_SWITCH(le_header->rate); + ENDIAN_SWITCH(le_header->mode); + ENDIAN_SWITCH(le_header->mode_bitstream_version); + ENDIAN_SWITCH(le_header->nb_channels); + ENDIAN_SWITCH(le_header->bitrate); + ENDIAN_SWITCH(le_header->frame_size); + ENDIAN_SWITCH(le_header->vbr); + ENDIAN_SWITCH(le_header->frames_per_packet); + ENDIAN_SWITCH(le_header->extra_headers); + + *size = sizeof(SpeexHeader); + return (char *)le_header; +} + +SpeexHeader *speex_packet_to_header(char *packet, int size) +{ + int i; + SpeexHeader *le_header; + const char *h = "Speex "; + for (i=0;i<8;i++) + if (packet[i]!=h[i]) + { + speex_notify("This doesn't look like a Speex file"); + return NULL; + } + + /*FIXME: Do we allow larger headers?*/ + if (size < (int)sizeof(SpeexHeader)) + { + speex_notify("Speex header too small"); + return NULL; + } + + le_header = (SpeexHeader*)speex_alloc(sizeof(SpeexHeader)); + + SPEEX_COPY(le_header, (SpeexHeader*)packet, 1); + + /*Make sure everything is converted correctly from little-endian*/ + ENDIAN_SWITCH(le_header->speex_version_id); + ENDIAN_SWITCH(le_header->header_size); + ENDIAN_SWITCH(le_header->rate); + ENDIAN_SWITCH(le_header->mode); + ENDIAN_SWITCH(le_header->mode_bitstream_version); + ENDIAN_SWITCH(le_header->nb_channels); + ENDIAN_SWITCH(le_header->bitrate); + ENDIAN_SWITCH(le_header->frame_size); + ENDIAN_SWITCH(le_header->vbr); + ENDIAN_SWITCH(le_header->frames_per_packet); + ENDIAN_SWITCH(le_header->extra_headers); + + return le_header; + +} + +void speex_header_free(void *ptr) +{ + speex_free(ptr); +} diff --git a/src/libspeex/stack_alloc.h b/src/libspeex/stack_alloc.h new file mode 100644 index 0000000..5264e66 --- /dev/null +++ b/src/libspeex/stack_alloc.h @@ -0,0 +1,115 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file stack_alloc.h + @brief Temporary memory allocation on stack +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef STACK_ALLOC_H +#define STACK_ALLOC_H + +#ifdef USE_ALLOCA +# ifdef WIN32 +# include +# else +# ifdef HAVE_ALLOCA_H +# include +# else +# include +# endif +# endif +#endif + +/** + * @def ALIGN(stack, size) + * + * Aligns the stack to a 'size' boundary + * + * @param stack Stack + * @param size New size boundary + */ + +/** + * @def PUSH(stack, size, type) + * + * Allocates 'size' elements of type 'type' on the stack + * + * @param stack Stack + * @param size Number of elements + * @param type Type of element + */ + +/** + * @def VARDECL(var) + * + * Declare variable on stack + * + * @param var Variable to declare + */ + +/** + * @def ALLOC(var, size, type) + * + * Allocate 'size' elements of 'type' on stack + * + * @param var Name of variable to allocate + * @param size Number of elements + * @param type Type of element + */ + +#ifdef ENABLE_VALGRIND + +#include + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) + +#define PUSH(stack, size, type) (VALGRIND_MAKE_NOACCESS(stack, 1000),ALIGN((stack),sizeof(type)),VALGRIND_MAKE_WRITABLE(stack, ((size)*sizeof(type))),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type)))) + +#else + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) + +#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type)))) + +#endif + +#if defined(VAR_ARRAYS) +#define VARDECL(var) +#define ALLOC(var, size, type) type var[size] +#elif defined(USE_ALLOCA) +#define VARDECL(var) var +#define ALLOC(var, size, type) var = alloca(sizeof(type)*(size)) +#else +#define VARDECL(var) var +#define ALLOC(var, size, type) var = PUSH(stack, size, type) +#endif + + +#endif diff --git a/src/libspeex/stereo.c b/src/libspeex/stereo.c new file mode 100644 index 0000000..bc6e1ba --- /dev/null +++ b/src/libspeex/stereo.c @@ -0,0 +1,296 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: stereo.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "math_approx.h" +#include "vq.h" +#include +#include "os_support.h" + +typedef struct RealSpeexStereoState { + spx_word32_t balance; /**< Left/right balance info */ + spx_word32_t e_ratio; /**< Ratio of energies: E(left+right)/[E(left)+E(right)] */ + spx_word32_t smooth_left; /**< Smoothed left channel gain */ + spx_word32_t smooth_right; /**< Smoothed right channel gain */ + spx_uint32_t reserved1; /**< Reserved for future use */ + spx_int32_t reserved2; /**< Reserved for future use */ +} RealSpeexStereoState; + + +/*float e_ratio_quant[4] = {1, 1.26, 1.587, 2};*/ +#ifndef FIXED_POINT +static const float e_ratio_quant[4] = {.25f, .315f, .397f, .5f}; +static const float e_ratio_quant_bounds[3] = {0.2825f, 0.356f, 0.4485f}; +#else +static const spx_word16_t e_ratio_quant[4] = {8192, 10332, 13009, 16384}; +static const spx_word16_t e_ratio_quant_bounds[3] = {9257, 11665, 14696}; +static const spx_word16_t balance_bounds[31] = {18, 23, 30, 38, 49, 63, 81, 104, + 134, 172, 221, 284, 364, 468, 600, 771, + 990, 1271, 1632, 2096, 2691, 3455, 4436, 5696, + 7314, 9392, 12059, 15484, 19882, 25529, 32766}; +#endif + +/* This is an ugly compatibility hack that properly resets the stereo state + In case it it compiled in fixed-point, but initialised with the deprecated + floating point static initialiser */ +#ifdef FIXED_POINT +#define COMPATIBILITY_HACK(s) do {if ((s)->reserved1 != 0xdeadbeef) speex_stereo_state_reset((SpeexStereoState*)s); } while (0); +#else +#define COMPATIBILITY_HACK(s) +#endif + +SpeexStereoState *speex_stereo_state_init() +{ + SpeexStereoState *stereo = speex_alloc(sizeof(SpeexStereoState)); + speex_stereo_state_reset(stereo); + return stereo; +} + +void speex_stereo_state_reset(SpeexStereoState *_stereo) +{ + RealSpeexStereoState *stereo = (RealSpeexStereoState*)_stereo; +#ifdef FIXED_POINT + stereo->balance = 65536; + stereo->e_ratio = 16384; + stereo->smooth_left = 16384; + stereo->smooth_right = 16384; + stereo->reserved1 = 0xdeadbeef; + stereo->reserved2 = 0; +#else + stereo->balance = 1.0f; + stereo->e_ratio = .5f; + stereo->smooth_left = 1.f; + stereo->smooth_right = 1.f; + stereo->reserved1 = 0; + stereo->reserved2 = 0; +#endif +} + +void speex_stereo_state_destroy(SpeexStereoState *stereo) +{ + speex_free(stereo); +} + +#ifndef DISABLE_FLOAT_API +void speex_encode_stereo(float *data, int frame_size, SpeexBits *bits) +{ + int i, tmp; + float e_left=0, e_right=0, e_tot=0; + float balance, e_ratio; + for (i=0;i0) + speex_bits_pack(bits, 0, 1); + else + speex_bits_pack(bits, 1, 1); + balance=floor(.5+fabs(balance)); + if (balance>30) + balance=31; + + speex_bits_pack(bits, (int)balance, 5); + + /* FIXME: this is a hack */ + tmp=scal_quant(e_ratio*Q15_ONE, e_ratio_quant_bounds, 4); + speex_bits_pack(bits, tmp, 2); +} +#endif /* #ifndef DISABLE_FLOAT_API */ + +void speex_encode_stereo_int(spx_int16_t *data, int frame_size, SpeexBits *bits) +{ + int i, tmp; + spx_word32_t e_left=0, e_right=0, e_tot=0; + spx_word32_t balance, e_ratio; + spx_word32_t largest, smallest; + int balance_id; +#ifdef FIXED_POINT + int shift; +#endif + + /* In band marker */ + speex_bits_pack(bits, 14, 5); + /* Stereo marker */ + speex_bits_pack(bits, SPEEX_INBAND_STEREO, 4); + + for (i=0;i e_right) + { + speex_bits_pack(bits, 0, 1); + largest = e_left; + smallest = e_right; + } else { + speex_bits_pack(bits, 1, 1); + largest = e_right; + smallest = e_left; + } + + /* Balance quantization */ +#ifdef FIXED_POINT + shift = spx_ilog2(largest)-15; + largest = VSHR32(largest, shift-4); + smallest = VSHR32(smallest, shift); + balance = DIV32(largest, ADD32(smallest, 1)); + if (balance > 32767) + balance = 32767; + balance_id = scal_quant(EXTRACT16(balance), balance_bounds, 32); +#else + balance=(largest+1.)/(smallest+1.); + balance=4*log(balance); + balance_id=floor(.5+fabs(balance)); + if (balance_id>30) + balance_id=31; +#endif + + speex_bits_pack(bits, balance_id, 5); + + /* "coherence" quantisation */ +#ifdef FIXED_POINT + shift = spx_ilog2(e_tot); + e_tot = VSHR32(e_tot, shift-25); + e_left = VSHR32(e_left, shift-10); + e_right = VSHR32(e_right, shift-10); + e_ratio = DIV32(e_tot, e_left+e_right+1); +#else + e_ratio = e_tot/(1.+e_left+e_right); +#endif + + tmp=scal_quant(EXTRACT16(e_ratio), e_ratio_quant_bounds, 4); + /*fprintf (stderr, "%d %d %d %d\n", largest, smallest, balance_id, e_ratio);*/ + speex_bits_pack(bits, tmp, 2); +} + +#ifndef DISABLE_FLOAT_API +void speex_decode_stereo(float *data, int frame_size, SpeexStereoState *_stereo) +{ + int i; + spx_word32_t balance; + spx_word16_t e_left, e_right, e_ratio; + RealSpeexStereoState *stereo = (RealSpeexStereoState*)_stereo; + + COMPATIBILITY_HACK(stereo); + + balance=stereo->balance; + e_ratio=stereo->e_ratio; + + /* These two are Q14, with max value just below 2. */ + e_right = DIV32(QCONST32(1., 22), spx_sqrt(MULT16_32_Q15(e_ratio, ADD32(QCONST32(1., 16), balance)))); + e_left = SHR32(MULT16_16(spx_sqrt(balance), e_right), 8); + + for (i=frame_size-1;i>=0;i--) + { + spx_word16_t tmp=data[i]; + stereo->smooth_left = EXTRACT16(PSHR32(MAC16_16(MULT16_16(stereo->smooth_left, QCONST16(0.98, 15)), e_left, QCONST16(0.02, 15)), 15)); + stereo->smooth_right = EXTRACT16(PSHR32(MAC16_16(MULT16_16(stereo->smooth_right, QCONST16(0.98, 15)), e_right, QCONST16(0.02, 15)), 15)); + data[2*i] = (float)MULT16_16_P14(stereo->smooth_left, tmp); + data[2*i+1] = (float)MULT16_16_P14(stereo->smooth_right, tmp); + } +} +#endif /* #ifndef DISABLE_FLOAT_API */ + +void speex_decode_stereo_int(spx_int16_t *data, int frame_size, SpeexStereoState *_stereo) +{ + int i; + spx_word32_t balance; + spx_word16_t e_left, e_right, e_ratio; + RealSpeexStereoState *stereo = (RealSpeexStereoState*)_stereo; + + COMPATIBILITY_HACK(stereo); + + balance=stereo->balance; + e_ratio=stereo->e_ratio; + + /* These two are Q14, with max value just below 2. */ + e_right = DIV32(QCONST32(1., 22), spx_sqrt(MULT16_32_Q15(e_ratio, ADD32(QCONST32(1., 16), balance)))); + e_left = SHR32(MULT16_16(spx_sqrt(balance), e_right), 8); + + for (i=frame_size-1;i>=0;i--) + { + spx_int16_t tmp=data[i]; + stereo->smooth_left = EXTRACT16(PSHR32(MAC16_16(MULT16_16(stereo->smooth_left, QCONST16(0.98, 15)), e_left, QCONST16(0.02, 15)), 15)); + stereo->smooth_right = EXTRACT16(PSHR32(MAC16_16(MULT16_16(stereo->smooth_right, QCONST16(0.98, 15)), e_right, QCONST16(0.02, 15)), 15)); + data[2*i] = (spx_int16_t)MULT16_16_P14(stereo->smooth_left, tmp); + data[2*i+1] = (spx_int16_t)MULT16_16_P14(stereo->smooth_right, tmp); + } +} + +int speex_std_stereo_request_handler(SpeexBits *bits, void *state, void *data) +{ + RealSpeexStereoState *stereo; + spx_word16_t sign=1, dexp; + int tmp; + + stereo = (RealSpeexStereoState*)data; + + COMPATIBILITY_HACK(stereo); + + if (speex_bits_unpack_unsigned(bits, 1)) + sign=-1; + dexp = speex_bits_unpack_unsigned(bits, 5); +#ifndef FIXED_POINT + stereo->balance = exp(sign*.25*dexp); +#else + stereo->balance = spx_exp(MULT16_16(sign, SHL16(dexp, 9))); +#endif + tmp = speex_bits_unpack_unsigned(bits, 2); + stereo->e_ratio = e_ratio_quant[tmp]; + + return 0; +} diff --git a/src/libspeex/testdenoise.c b/src/libspeex/testdenoise.c new file mode 100644 index 0000000..42644cb --- /dev/null +++ b/src/libspeex/testdenoise.c @@ -0,0 +1,44 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#define NN 160 + +int main() +{ + short in[NN]; + int i; + SpeexPreprocessState *st; + int count=0; + float f; + + st = speex_preprocess_state_init(NN, 8000); + i=1; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DENOISE, &i); + i=0; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_AGC, &i); + f=8000; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_AGC_LEVEL, &f); + i=0; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB, &i); + f=.0; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB_DECAY, &f); + f=.0; + speex_preprocess_ctl(st, SPEEX_PREPROCESS_SET_DEREVERB_LEVEL, &f); + while (1) + { + int vad; + fread(in, sizeof(short), NN, stdin); + if (feof(stdin)) + break; + vad = speex_preprocess_run(st, in); + /*fprintf (stderr, "%d\n", vad);*/ + fwrite(in, sizeof(short), NN, stdout); + count++; + } + speex_preprocess_state_destroy(st); + return 0; +} diff --git a/src/libspeex/testecho.c b/src/libspeex/testecho.c new file mode 100644 index 0000000..5ae855f --- /dev/null +++ b/src/libspeex/testecho.c @@ -0,0 +1,53 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include "speex/speex_echo.h" +#include "speex/speex_preprocess.h" + + +#define NN 128 +#define TAIL 1024 + +int main(int argc, char **argv) +{ + FILE *echo_fd, *ref_fd, *e_fd; + short echo_buf[NN], ref_buf[NN], e_buf[NN]; + SpeexEchoState *st; + SpeexPreprocessState *den; + int sampleRate = 8000; + + if (argc != 4) + { + fprintf(stderr, "testecho mic_signal.sw speaker_signal.sw output.sw\n"); + exit(1); + } + echo_fd = fopen(argv[2], "rb"); + ref_fd = fopen(argv[1], "rb"); + e_fd = fopen(argv[3], "wb"); + + st = speex_echo_state_init(NN, TAIL); + den = speex_preprocess_state_init(NN, sampleRate); + speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate); + speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_STATE, st); + + while (!feof(ref_fd) && !feof(echo_fd)) + { + fread(ref_buf, sizeof(short), NN, ref_fd); + fread(echo_buf, sizeof(short), NN, echo_fd); + speex_echo_cancellation(st, ref_buf, echo_buf, e_buf); + speex_preprocess_run(den, e_buf); + fwrite(e_buf, sizeof(short), NN, e_fd); + } + speex_echo_state_destroy(st); + speex_preprocess_state_destroy(den); + fclose(e_fd); + fclose(echo_fd); + fclose(ref_fd); + return 0; +} diff --git a/src/libspeex/testenc.c b/src/libspeex/testenc.c new file mode 100644 index 0000000..44c132f --- /dev/null +++ b/src/libspeex/testenc.c @@ -0,0 +1,146 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#ifdef FIXED_DEBUG +extern long long spx_mips; +#endif + +#define FRAME_SIZE 160 +#include +int main(int argc, char **argv) +{ + char *inFile, *outFile, *bitsFile; + FILE *fin, *fout, *fbits=NULL; + short in_short[FRAME_SIZE]; + short out_short[FRAME_SIZE]; + int snr_frames = 0; + char cbits[200]; + int nbBits; + int i; + void *st; + void *dec; + SpeexBits bits; + spx_int32_t tmp; + int bitCount=0; + spx_int32_t skip_group_delay; + SpeexCallback callback; + + st = speex_encoder_init(speex_lib_get_mode(SPEEX_MODEID_NB)); + dec = speex_decoder_init(speex_lib_get_mode(SPEEX_MODEID_NB)); + + /* BEGIN: You probably don't need the following in a real application */ + callback.callback_id = SPEEX_INBAND_CHAR; + callback.func = speex_std_char_handler; + callback.data = stderr; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + callback.callback_id = SPEEX_INBAND_MODE_REQUEST; + callback.func = speex_std_mode_request_handler; + callback.data = st; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + /* END of unnecessary stuff */ + + tmp=1; + speex_decoder_ctl(dec, SPEEX_SET_ENH, &tmp); + tmp=0; + speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp); + tmp=8; + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &tmp); + tmp=1; + speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &tmp); + + /* Turn this off if you want to measure SNR (on by default) */ + tmp=1; + speex_encoder_ctl(st, SPEEX_SET_HIGHPASS, &tmp); + speex_decoder_ctl(dec, SPEEX_SET_HIGHPASS, &tmp); + + speex_encoder_ctl(st, SPEEX_GET_LOOKAHEAD, &skip_group_delay); + speex_decoder_ctl(dec, SPEEX_GET_LOOKAHEAD, &tmp); + skip_group_delay += tmp; + + if (argc != 4 && argc != 3) + { + fprintf (stderr, "Usage: encode [in file] [out file] [bits file]\nargc = %d", argc); + exit(1); + } + inFile = argv[1]; + fin = fopen(inFile, "rb"); + outFile = argv[2]; + fout = fopen(outFile, "wb+"); + if (argc==4) + { + bitsFile = argv[3]; + fbits = fopen(bitsFile, "wb"); + } + speex_bits_init(&bits); + while (!feof(fin)) + { + fread(in_short, sizeof(short), FRAME_SIZE, fin); + if (feof(fin)) + break; + speex_bits_reset(&bits); + + speex_encode_int(st, in_short, &bits); + nbBits = speex_bits_write(&bits, cbits, 200); + bitCount+=bits.nbBits; + + if (argc==4) + fwrite(cbits, 1, nbBits, fbits); + speex_bits_rewind(&bits); + + speex_decode_int(dec, &bits, out_short); + speex_bits_reset(&bits); + + fwrite(&out_short[skip_group_delay], sizeof(short), FRAME_SIZE-skip_group_delay, fout); + skip_group_delay = 0; + } + fprintf (stderr, "Total encoded size: %d bits\n", bitCount); + speex_encoder_destroy(st); + speex_decoder_destroy(dec); + speex_bits_destroy(&bits); + +#ifndef DISABLE_FLOAT_API + { + float sigpow,errpow,snr, seg_snr=0; + sigpow = 0; + errpow = 0; + + /* This code just computes SNR, so you don't need it either */ + rewind(fin); + rewind(fout); + + while ( FRAME_SIZE == fread(in_short, sizeof(short), FRAME_SIZE, fin) + && + FRAME_SIZE == fread(out_short, sizeof(short), FRAME_SIZE,fout) ) + { + float s=0, e=0; + for (i=0;i +#include +#include +#include + +#ifdef FIXED_DEBUG +extern long long spx_mips; +#endif + +#define FRAME_SIZE 640 +#include +int main(int argc, char **argv) +{ + char *inFile, *outFile, *bitsFile; + FILE *fin, *fout, *fbits=NULL; + short in_short[FRAME_SIZE]; + short out_short[FRAME_SIZE]; + float in_float[FRAME_SIZE]; + float sigpow,errpow,snr, seg_snr=0; + int snr_frames = 0; + char cbits[200]; + int nbBits; + int i; + void *st; + void *dec; + SpeexBits bits; + spx_int32_t tmp; + int bitCount=0; + spx_int32_t skip_group_delay; + SpeexCallback callback; + + sigpow = 0; + errpow = 0; + + st = speex_encoder_init(speex_lib_get_mode(SPEEX_MODEID_UWB)); + dec = speex_decoder_init(speex_lib_get_mode(SPEEX_MODEID_UWB)); + + callback.callback_id = SPEEX_INBAND_CHAR; + callback.func = speex_std_char_handler; + callback.data = stderr; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + callback.callback_id = SPEEX_INBAND_MODE_REQUEST; + callback.func = speex_std_mode_request_handler; + callback.data = st; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + tmp=0; + speex_decoder_ctl(dec, SPEEX_SET_ENH, &tmp); + tmp=0; + speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp); + tmp=7; + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &tmp); + tmp=1; + speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &tmp); + + speex_encoder_ctl(st, SPEEX_GET_LOOKAHEAD, &skip_group_delay); + speex_decoder_ctl(dec, SPEEX_GET_LOOKAHEAD, &tmp); + skip_group_delay += tmp; + + + if (argc != 4 && argc != 3) + { + fprintf (stderr, "Usage: encode [in file] [out file] [bits file]\nargc = %d", argc); + exit(1); + } + inFile = argv[1]; + fin = fopen(inFile, "rb"); + outFile = argv[2]; + fout = fopen(outFile, "wb+"); + if (argc==4) + { + bitsFile = argv[3]; + fbits = fopen(bitsFile, "wb"); + } + speex_bits_init(&bits); + while (!feof(fin)) + { + fread(in_short, sizeof(short), FRAME_SIZE, fin); + if (feof(fin)) + break; + for (i=0;i +#include +#include +#include + +#ifdef FIXED_DEBUG +extern long long spx_mips; +#endif + +#define FRAME_SIZE 320 +#include +int main(int argc, char **argv) +{ + char *inFile, *outFile, *bitsFile; + FILE *fin, *fout, *fbits=NULL; + short in_short[FRAME_SIZE]; + short out_short[FRAME_SIZE]; + float sigpow,errpow,snr, seg_snr=0; + int snr_frames = 0; + char cbits[200]; + int nbBits; + int i; + void *st; + void *dec; + SpeexBits bits; + spx_int32_t tmp; + int bitCount=0; + spx_int32_t skip_group_delay; + SpeexCallback callback; + + sigpow = 0; + errpow = 0; + + st = speex_encoder_init(speex_lib_get_mode(SPEEX_MODEID_WB)); + dec = speex_decoder_init(speex_lib_get_mode(SPEEX_MODEID_WB)); + + callback.callback_id = SPEEX_INBAND_CHAR; + callback.func = speex_std_char_handler; + callback.data = stderr; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + callback.callback_id = SPEEX_INBAND_MODE_REQUEST; + callback.func = speex_std_mode_request_handler; + callback.data = st; + speex_decoder_ctl(dec, SPEEX_SET_HANDLER, &callback); + + tmp=1; + speex_decoder_ctl(dec, SPEEX_SET_ENH, &tmp); + tmp=0; + speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp); + tmp=8; + speex_encoder_ctl(st, SPEEX_SET_QUALITY, &tmp); + tmp=3; + speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &tmp); + /*tmp=3; + speex_encoder_ctl(st, SPEEX_SET_HIGH_MODE, &tmp); + tmp=6; + speex_encoder_ctl(st, SPEEX_SET_LOW_MODE, &tmp); +*/ + + speex_encoder_ctl(st, SPEEX_GET_LOOKAHEAD, &skip_group_delay); + speex_decoder_ctl(dec, SPEEX_GET_LOOKAHEAD, &tmp); + skip_group_delay += tmp; + + + if (argc != 4 && argc != 3) + { + fprintf (stderr, "Usage: encode [in file] [out file] [bits file]\nargc = %d", argc); + exit(1); + } + inFile = argv[1]; + fin = fopen(inFile, "rb"); + outFile = argv[2]; + fout = fopen(outFile, "wb+"); + if (argc==4) + { + bitsFile = argv[3]; + fbits = fopen(bitsFile, "wb"); + } + speex_bits_init(&bits); + while (!feof(fin)) + { + fread(in_short, sizeof(short), FRAME_SIZE, fin); + if (feof(fin)) + break; + speex_bits_reset(&bits); + + speex_encode_int(st, in_short, &bits); + nbBits = speex_bits_write(&bits, cbits, 200); + bitCount+=bits.nbBits; + + if (argc==4) + fwrite(cbits, 1, nbBits, fbits); + speex_bits_rewind(&bits); + + speex_decode_int(dec, &bits, out_short); + speex_bits_reset(&bits); + + fwrite(&out_short[skip_group_delay], sizeof(short), FRAME_SIZE-skip_group_delay, fout); + skip_group_delay = 0; + } + fprintf (stderr, "Total encoded size: %d bits\n", bitCount); + speex_encoder_destroy(st); + speex_decoder_destroy(dec); + speex_bits_destroy(&bits); + + rewind(fin); + rewind(fout); + + while ( FRAME_SIZE == fread(in_short, sizeof(short), FRAME_SIZE, fin) + && + FRAME_SIZE == fread(out_short, sizeof(short), FRAME_SIZE,fout) ) + { + float s=0, e=0; + for (i=0;i + + +#define sqr(x) ((x)*(x)) + +#define MIN_ENERGY 6000 +#define NOISE_POW .3 + +#ifndef DISABLE_VBR + +const float vbr_nb_thresh[9][11]={ + {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}, /* CNG */ + { 4.0f, 2.5f, 2.0f, 1.2f, 0.5f, 0.0f, -0.5f, -0.7f, -0.8f, -0.9f, -1.0f}, /* 2 kbps */ + {10.0f, 6.5f, 5.2f, 4.5f, 3.9f, 3.5f, 3.0f, 2.5f, 2.3f, 1.8f, 1.0f}, /* 6 kbps */ + {11.0f, 8.8f, 7.5f, 6.5f, 5.0f, 3.9f, 3.9f, 3.9f, 3.5f, 3.0f, 1.0f}, /* 8 kbps */ + {11.0f, 11.0f, 9.9f, 8.5f, 7.0f, 6.0f, 4.5f, 4.0f, 4.0f, 4.0f, 2.0f}, /* 11 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 9.5f, 8.5f, 8.0f, 7.0f, 6.0f, 5.0f, 3.0f}, /* 15 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 9.5f, 8.5f, 7.0f, 6.0f, 5.0f}, /* 18 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 9.8f, 9.5f, 7.5f}, /* 24 kbps */ + { 7.0f, 4.5f, 3.7f, 3.0f, 2.5f, 2.0f, 1.8f, 1.5f, 1.0f, 0.0f, 0.0f} /* 4 kbps */ +}; + + +const float vbr_hb_thresh[5][11]={ + {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}, /* silence */ + {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}, /* 2 kbps */ + {11.0f, 11.0f, 9.5f, 8.5f, 7.5f, 6.0f, 5.0f, 3.9f, 3.0f, 2.0f, 1.0f}, /* 6 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 9.5f, 8.7f, 7.8f, 7.0f, 6.5f, 4.0f}, /* 10 kbps */ + {11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 11.0f, 9.8f, 7.5f, 5.5f} /* 18 kbps */ +}; + +const float vbr_uhb_thresh[2][11]={ + {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}, /* silence */ + { 3.9f, 2.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f} /* 2 kbps */ +}; + +void vbr_init(VBRState *vbr) +{ + int i; + + vbr->average_energy=0; + vbr->last_energy=1; + vbr->accum_sum=0; + vbr->energy_alpha=.1; + vbr->soft_pitch=0; + vbr->last_pitch_coef=0; + vbr->last_quality=0; + + vbr->noise_accum = .05*pow(MIN_ENERGY, NOISE_POW); + vbr->noise_accum_count=.05; + vbr->noise_level=vbr->noise_accum/vbr->noise_accum_count; + vbr->consec_noise=0; + + + for (i=0;ilast_log_energy[i] = log(MIN_ENERGY); +} + + +/* + This function should analyse the signal and decide how critical the + coding error will be perceptually. The following factors should be + taken into account: + + -Attacks (positive energy derivative) should be coded with more bits + + -Stationary voiced segments should receive more bits + + -Segments with (very) low absolute energy should receive less bits (maybe + only shaped noise?) + + -DTX for near-zero energy? + + -Stationary fricative segments should have less bits + + -Temporal masking: when energy slope is decreasing, decrease the bit-rate + + -Decrease bit-rate for males (low pitch)? + + -(wideband only) less bits in the high-band when signal is very + non-stationary (harder to notice high-frequency noise)??? + +*/ + +float vbr_analysis(VBRState *vbr, spx_word16_t *sig, int len, int pitch, float pitch_coef) +{ + int i; + float ener=0, ener1=0, ener2=0; + float qual=7; + int va; + float log_energy; + float non_st=0; + float voicing; + float pow_ener; + + for (i=0;i>1;i++) + ener1 += ((float)sig[i])*sig[i]; + + for (i=len>>1;ilast_log_energy[i]); + non_st = non_st/(30*VBR_MEMORY_SIZE); + if (non_st>1) + non_st=1; + + voicing = 3*(pitch_coef-.4)*fabs(pitch_coef-.4); + vbr->average_energy = (1-vbr->energy_alpha)*vbr->average_energy + vbr->energy_alpha*ener; + vbr->noise_level=vbr->noise_accum/vbr->noise_accum_count; + pow_ener = pow(ener,NOISE_POW); + if (vbr->noise_accum_count<.06 && ener>MIN_ENERGY) + vbr->noise_accum = .05*pow_ener; + + if ((voicing<.3 && non_st < .2 && pow_ener < 1.2*vbr->noise_level) + || (voicing<.3 && non_st < .05 && pow_ener < 1.5*vbr->noise_level) + || (voicing<.4 && non_st < .05 && pow_ener < 1.2*vbr->noise_level) + || (voicing<0 && non_st < .05)) + { + float tmp; + va = 0; + vbr->consec_noise++; + if (pow_ener > 3*vbr->noise_level) + tmp = 3*vbr->noise_level; + else + tmp = pow_ener; + if (vbr->consec_noise>=4) + { + vbr->noise_accum = .95*vbr->noise_accum + .05*tmp; + vbr->noise_accum_count = .95*vbr->noise_accum_count + .05; + } + } else { + va = 1; + vbr->consec_noise=0; + } + + if (pow_ener < vbr->noise_level && ener>MIN_ENERGY) + { + vbr->noise_accum = .95*vbr->noise_accum + .05*pow_ener; + vbr->noise_accum_count = .95*vbr->noise_accum_count + .05; + } + + /* Checking for very low absolute energy */ + if (ener < 30000) + { + qual -= .7; + if (ener < 10000) + qual-=.7; + if (ener < 3000) + qual-=.7; + } else { + float short_diff, long_diff; + short_diff = log((ener+1)/(1+vbr->last_energy)); + long_diff = log((ener+1)/(1+vbr->average_energy)); + /*fprintf (stderr, "%f %f\n", short_diff, long_diff);*/ + + if (long_diff<-5) + long_diff=-5; + if (long_diff>2) + long_diff=2; + + if (long_diff>0) + qual += .6*long_diff; + if (long_diff<0) + qual += .5*long_diff; + if (short_diff>0) + { + if (short_diff>5) + short_diff=5; + qual += .5*short_diff; + } + /* Checking for energy increases */ + if (ener2 > 1.6*ener1) + qual += .5; + } + vbr->last_energy = ener; + vbr->soft_pitch = .6*vbr->soft_pitch + .4*pitch_coef; + qual += 2.2*((pitch_coef-.4) + (vbr->soft_pitch-.4)); + + if (qual < vbr->last_quality) + qual = .5*qual + .5*vbr->last_quality; + if (qual<4) + qual=4; + if (qual>10) + qual=10; + + /* + if (vbr->consec_noise>=2) + qual-=1.3; + if (vbr->consec_noise>=5) + qual-=1.3; + if (vbr->consec_noise>=12) + qual-=1.3; + */ + if (vbr->consec_noise>=3) + qual=4; + + if (vbr->consec_noise) + qual -= 1.0 * (log(3.0 + vbr->consec_noise)-log(3)); + if (qual<0) + qual=0; + + if (ener<60000) + { + if (vbr->consec_noise>2) + qual-=0.5*(log(3.0 + vbr->consec_noise)-log(3)); + if (ener<10000&&vbr->consec_noise>2) + qual-=0.5*(log(3.0 + vbr->consec_noise)-log(3)); + if (qual<0) + qual=0; + qual += .3*log(.0001+ener/60000.0); + } + if (qual<-1) + qual=-1; + + /*printf ("%f %f %f %f %d\n", qual, voicing, non_st, pow_ener/(.01+vbr->noise_level), va);*/ + + vbr->last_pitch_coef = pitch_coef; + vbr->last_quality = qual; + + for (i=VBR_MEMORY_SIZE-1;i>0;i--) + vbr->last_log_energy[i] = vbr->last_log_energy[i-1]; + vbr->last_log_energy[0] = log_energy; + + /*printf ("VBR: %f %f %f %d %f\n", (float)(log_energy-log(vbr->average_energy+MIN_ENERGY)), non_st, voicing, va, vbr->noise_level);*/ + + return qual; +} + +void vbr_destroy(VBRState *vbr) +{ +} + +#endif /* #ifndef DISABLE_VBR */ diff --git a/src/libspeex/vbr.h b/src/libspeex/vbr.h new file mode 100644 index 0000000..ff1e3e4 --- /dev/null +++ b/src/libspeex/vbr.h @@ -0,0 +1,70 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file vbr.h + @brief Variable Bit-Rate (VBR) related routines +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef VBR_H +#define VBR_H + +#include "arch.h" + +#define VBR_MEMORY_SIZE 5 + +extern const float vbr_nb_thresh[9][11]; +extern const float vbr_hb_thresh[5][11]; +extern const float vbr_uhb_thresh[2][11]; + +/** VBR state. */ +typedef struct VBRState { + float energy_alpha; + float average_energy; + float last_energy; + float last_log_energy[VBR_MEMORY_SIZE]; + float accum_sum; + float last_pitch_coef; + float soft_pitch; + float last_quality; + float noise_level; + float noise_accum; + float noise_accum_count; + int consec_noise; +} VBRState; + +void vbr_init(VBRState *vbr); + +float vbr_analysis(VBRState *vbr, spx_word16_t *sig, int len, int pitch, float pitch_coef); + +void vbr_destroy(VBRState *vbr); + +#endif diff --git a/src/libspeex/vorbis_psy.h b/src/libspeex/vorbis_psy.h new file mode 100644 index 0000000..6871057 --- /dev/null +++ b/src/libspeex/vorbis_psy.h @@ -0,0 +1,97 @@ +/* Copyright (C) 2005 Jean-Marc Valin, CSIRO, Christopher Montgomery + File: vorbis_psy.h + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VORBIS_PSY_H +#define VORBIS_PSY_H + +#ifdef VORBIS_PSYCHO + +#include "smallft.h" +#define P_BANDS 17 /* 62Hz to 16kHz */ +#define NOISE_COMPAND_LEVELS 40 + + +#define todB(x) ((x)>1e-13?log((x)*(x))*4.34294480f:-30) +#define fromdB(x) (exp((x)*.11512925f)) + +/* The bark scale equations are approximations, since the original + table was somewhat hand rolled. The below are chosen to have the + best possible fit to the rolled tables, thus their somewhat odd + appearance (these are more accurate and over a longer range than + the oft-quoted bark equations found in the texts I have). The + approximations are valid from 0 - 30kHz (nyquist) or so. + + all f in Hz, z in Bark */ + +#define toBARK(n) (13.1f*atan(.00074f*(n))+2.24f*atan((n)*(n)*1.85e-8f)+1e-4f*(n)) +#define fromBARK(z) (102.f*(z)-2.f*pow(z,2.f)+.4f*pow(z,3.f)+pow(1.46f,z)-1.f) + +/* Frequency to octave. We arbitrarily declare 63.5 Hz to be octave + 0.0 */ + +#define toOC(n) (log(n)*1.442695f-5.965784f) +#define fromOC(o) (exp(((o)+5.965784f)*.693147f)) + + +typedef struct { + + float noisewindowlo; + float noisewindowhi; + int noisewindowlomin; + int noisewindowhimin; + int noisewindowfixed; + float noiseoff[P_BANDS]; + float noisecompand[NOISE_COMPAND_LEVELS]; + +} VorbisPsyInfo; + + + +typedef struct { + int n; + int rate; + struct drft_lookup lookup; + VorbisPsyInfo *vi; + + float *window; + float *noiseoffset; + long *bark; + +} VorbisPsy; + + +VorbisPsy *vorbis_psy_init(int rate, int size); +void vorbis_psy_destroy(VorbisPsy *psy); +void compute_curve(VorbisPsy *psy, float *audio, float *curve); +void curve_to_lpc(VorbisPsy *psy, float *curve, float *awk1, float *awk2, int ord); + +#endif +#endif diff --git a/src/libspeex/vq.c b/src/libspeex/vq.c new file mode 100644 index 0000000..609f124 --- /dev/null +++ b/src/libspeex/vq.c @@ -0,0 +1,147 @@ +/* Copyright (C) 2002 Jean-Marc Valin + File: vq.c + Vector quantization + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "vq.h" +#include "stack_alloc.h" +#include "arch.h" + +#ifdef _USE_SSE +#include +#include "vq_sse.h" +#elif defined(SHORTCUTS) && (defined(ARM4_ASM) || defined(ARM5E_ASM)) +#include "vq_arm4.h" +#elif defined(BFIN_ASM) +#include "vq_bfin.h" +#endif + + +int scal_quant(spx_word16_t in, const spx_word16_t *boundary, int entries) +{ + int i=0; + while (iboundary[0]) + { + boundary++; + i++; + } + return i; +} + +int scal_quant32(spx_word32_t in, const spx_word32_t *boundary, int entries) +{ + int i=0; + while (iboundary[0]) + { + boundary++; + i++; + } + return i; +} + + +#ifndef OVERRIDE_VQ_NBEST +/*Finds the indices of the n-best entries in a codebook*/ +void vq_nbest(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j,k,used; + used = 0; + for (i=0;i= 1) && (k > used || dist < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist; + nbest[k]=i; + used++; + } + } +} +#endif + + + + +#ifndef OVERRIDE_VQ_NBEST_SIGN +/*Finds the indices of the n-best entries in a codebook with sign*/ +void vq_nbest_sign(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j,k, sign, used; + used=0; + for (i=0;i0) + { + sign=0; + dist=-dist; + } else + { + sign=1; + } +#ifdef FIXED_POINT + dist = ADD32(dist,SHR32(E[i],1)); +#else + dist = ADD32(dist,.5f*E[i]); +#endif + if (i= 1) && (k > used || dist < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist; + nbest[k]=i; + used++; + if (sign) + nbest[k]+=entries; + } + } +} +#endif diff --git a/src/libspeex/vq.h b/src/libspeex/vq.h new file mode 100644 index 0000000..5a4ced2 --- /dev/null +++ b/src/libspeex/vq.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2002 Jean-Marc Valin */ +/** + @file vq.h + @brief Vector quantization +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VQ_H +#define VQ_H + +#include "arch.h" + +int scal_quant(spx_word16_t in, const spx_word16_t *boundary, int entries); +int scal_quant32(spx_word32_t in, const spx_word32_t *boundary, int entries); + +#ifdef _USE_SSE +#include +void vq_nbest(spx_word16_t *in, const __m128 *codebook, int len, int entries, __m128 *E, int N, int *nbest, spx_word32_t *best_dist, char *stack); + +void vq_nbest_sign(spx_word16_t *in, const __m128 *codebook, int len, int entries, __m128 *E, int N, int *nbest, spx_word32_t *best_dist, char *stack); +#else +void vq_nbest(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack); + +void vq_nbest_sign(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack); +#endif + +#endif diff --git a/src/libspeex/vq_arm4.h b/src/libspeex/vq_arm4.h new file mode 100644 index 0000000..585b861 --- /dev/null +++ b/src/libspeex/vq_arm4.h @@ -0,0 +1,115 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file vq_arm4.h + @brief ARM4-optimized vq routine +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_VQ_NBEST +void vq_nbest(spx_word16_t *in, const spx_word16_t *codebook, int len, int entries, spx_word32_t *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j; + for (i=0;i>= 1;\n\t" + "A0 = %0;\n\t" + "R0.L = W[%1++%7] || R1.L = W[I0++];\n\t" + "LOOP vq_loop%= LC1 = %5;\n\t" + "LOOP_BEGIN vq_loop%=;\n\t" + "%0 = (A0 -= R0.L*R1.L) (IS) || R0.L = W[%1++%7] || R1.L = W[I0++];\n\t" + "LOOP_END vq_loop%=;\n\t" + "%0 = (A0 -= R0.L*R1.L) (IS);\n\t" + "cc = %0 < %2;\n\t" + "if cc %2 = %0;\n\t" + "if cc %3 = R2;\n\t" + "R2 += 1;\n\t" + "LOOP_END entries_loop%=;\n\t" + : "=&D" (dist), "=&a" (codebook), "=&d" (best_dist[0]), "=&d" (nbest[0]), "=&a" (E) + : "a" (len-1), "a" (in), "a" (2), "d" (entries), "d" (len<<1), "1" (codebook), "4" (E), "2" (best_dist[0]), "3" (nbest[0]) + : "R0", "R1", "R2", "I0", "L0", "B0", "A0", "cc", "memory" + ); + } + } else { + int i,k,used; + used = 0; + for (i=0;i>= 1;\n\t" + "A0 = %0;\n\t" + "I0 = %3;\n\t" + "L0 = 0;\n\t" + "R0.L = W[%1++%4] || R1.L = W[I0++];\n\t" + "LOOP vq_loop%= LC0 = %2;\n\t" + "LOOP_BEGIN vq_loop%=;\n\t" + "%0 = (A0 -= R0.L*R1.L) (IS) || R0.L = W[%1++%4] || R1.L = W[I0++];\n\t" + "LOOP_END vq_loop%=;\n\t" + "%0 = (A0 -= R0.L*R1.L) (IS);\n\t" + : "=D" (dist), "=a" (codebook) + : "a" (len-1), "a" (in), "a" (2), "1" (codebook), "0" (E[i]) + : "R0", "R1", "I0", "L0", "A0" + ); + if (i= 1) && (k > used || dist < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist; + nbest[k]=i; + used++; + } + } + } +} diff --git a/src/libspeex/vq_sse.h b/src/libspeex/vq_sse.h new file mode 100644 index 0000000..00a42ce --- /dev/null +++ b/src/libspeex/vq_sse.h @@ -0,0 +1,120 @@ +/* Copyright (C) 2004 Jean-Marc Valin */ +/** + @file vq_sse.h + @brief SSE-optimized vq routine +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define OVERRIDE_VQ_NBEST +void vq_nbest(spx_word16_t *_in, const __m128 *codebook, int len, int entries, __m128 *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j,k,used; + VARDECL(float *dist); + VARDECL(__m128 *in); + __m128 half; + used = 0; + ALLOC(dist, entries, float); + half = _mm_set_ps1(.5f); + ALLOC(in, len, __m128); + for (i=0;i>2;i++) + { + __m128 d = _mm_mul_ps(E[i], half); + for (j=0;j= 1) && (k > used || dist[i] < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist[i]; + nbest[k]=i; + used++; + } + } +} + + + + +#define OVERRIDE_VQ_NBEST_SIGN +void vq_nbest_sign(spx_word16_t *_in, const __m128 *codebook, int len, int entries, __m128 *E, int N, int *nbest, spx_word32_t *best_dist, char *stack) +{ + int i,j,k,used; + VARDECL(float *dist); + VARDECL(__m128 *in); + __m128 half; + used = 0; + ALLOC(dist, entries, float); + half = _mm_set_ps1(.5f); + ALLOC(in, len, __m128); + for (i=0;i>2;i++) + { + __m128 d = _mm_setzero_ps(); + for (j=0;j0) + { + sign=0; + dist[i]=-dist[i]; + } else + { + sign=1; + } + dist[i] += .5f*((float*)E)[i]; + if (i= 1) && (k > used || dist[i] < best_dist[k-1]); k--) + { + best_dist[k]=best_dist[k-1]; + nbest[k] = nbest[k-1]; + } + best_dist[k]=dist[i]; + nbest[k]=i; + used++; + if (sign) + nbest[k]+=entries; + } + } +} diff --git a/src/libspeex/window.c b/src/libspeex/window.c new file mode 100644 index 0000000..ac042d4 --- /dev/null +++ b/src/libspeex/window.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2006 Jean-Marc Valin + File: window.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "arch.h" + +#ifdef FIXED_POINT +const spx_word16_t lag_window[11] = { + 16384, 16337, 16199, 15970, 15656, 15260, 14790, 14254, 13659, 13015, 12330 +}; + +const spx_word16_t lpc_window[200] = { +1310, 1313, 1321, 1333, 1352, 1375, 1403, 1436, +1475, 1518, 1567, 1621, 1679, 1743, 1811, 1884, +1962, 2044, 2132, 2224, 2320, 2421, 2526, 2636, +2750, 2868, 2990, 3116, 3246, 3380, 3518, 3659, +3804, 3952, 4104, 4259, 4417, 4578, 4742, 4909, +5079, 5251, 5425, 5602, 5781, 5963, 6146, 6331, +6518, 6706, 6896, 7087, 7280, 7473, 7668, 7863, +8059, 8256, 8452, 8650, 8847, 9044, 9241, 9438, +9635, 9831, 10026, 10220, 10414, 10606, 10797, 10987, +11176, 11363, 11548, 11731, 11912, 12091, 12268, 12443, +12615, 12785, 12952, 13116, 13277, 13435, 13590, 13742, +13890, 14035, 14176, 14314, 14448, 14578, 14704, 14826, +14944, 15058, 15168, 15273, 15374, 15470, 15562, 15649, +15732, 15810, 15883, 15951, 16015, 16073, 16127, 16175, +16219, 16257, 16291, 16319, 16342, 16360, 16373, 16381, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384, +16384, 16384, 16384, 16361, 16294, 16183, 16028, 15830, +15588, 15304, 14979, 14613, 14207, 13763, 13282, 12766, +12215, 11631, 11016, 10373, 9702, 9007, 8289, 7551, +6797, 6028, 5251, 4470, 3695, 2943, 2248, 1696 +}; +#else +const spx_word16_t lag_window[11] = { + 1.00000, 0.99716, 0.98869, 0.97474, 0.95554, 0.93140, 0.90273, 0.86998, 0.83367, 0.79434, 0.75258 +}; + +const spx_word16_t lpc_window[200] = { + 0.080000f, 0.080158f, 0.080630f, 0.081418f, 0.082520f, 0.083935f, 0.085663f, 0.087703f, + 0.090052f, 0.092710f, 0.095674f, 0.098943f, 0.102514f, 0.106385f, 0.110553f, 0.115015f, + 0.119769f, 0.124811f, 0.130137f, 0.135744f, 0.141628f, 0.147786f, 0.154212f, 0.160902f, + 0.167852f, 0.175057f, 0.182513f, 0.190213f, 0.198153f, 0.206328f, 0.214731f, 0.223357f, + 0.232200f, 0.241254f, 0.250513f, 0.259970f, 0.269619f, 0.279453f, 0.289466f, 0.299651f, + 0.310000f, 0.320507f, 0.331164f, 0.341965f, 0.352901f, 0.363966f, 0.375151f, 0.386449f, + 0.397852f, 0.409353f, 0.420943f, 0.432615f, 0.444361f, 0.456172f, 0.468040f, 0.479958f, + 0.491917f, 0.503909f, 0.515925f, 0.527959f, 0.540000f, 0.552041f, 0.564075f, 0.576091f, + 0.588083f, 0.600042f, 0.611960f, 0.623828f, 0.635639f, 0.647385f, 0.659057f, 0.670647f, + 0.682148f, 0.693551f, 0.704849f, 0.716034f, 0.727099f, 0.738035f, 0.748836f, 0.759493f, + 0.770000f, 0.780349f, 0.790534f, 0.800547f, 0.810381f, 0.820030f, 0.829487f, 0.838746f, + 0.847800f, 0.856643f, 0.865269f, 0.873672f, 0.881847f, 0.889787f, 0.897487f, 0.904943f, + 0.912148f, 0.919098f, 0.925788f, 0.932214f, 0.938372f, 0.944256f, 0.949863f, 0.955189f, + 0.960231f, 0.964985f, 0.969447f, 0.973615f, 0.977486f, 0.981057f, 0.984326f, 0.987290f, + 0.989948f, 0.992297f, 0.994337f, 0.996065f, 0.997480f, 0.998582f, 0.999370f, 0.999842f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f, + 1.000000f, 1.000000f, 1.000000f, 0.998640f, 0.994566f, 0.987787f, 0.978324f, 0.966203f, + 0.951458f, 0.934131f, 0.914270f, 0.891931f, 0.867179f, 0.840084f, 0.810723f, 0.779182f, + 0.745551f, 0.709930f, 0.672424f, 0.633148f, 0.592223f, 0.549781f, 0.505964f, 0.460932f, + 0.414863f, 0.367968f, 0.320511f, 0.272858f, 0.225569f, 0.179655f, 0.137254f, 0.103524f +}; +#endif diff --git a/src/master/Makefile b/src/master/Makefile new file mode 100644 index 0000000..a3a3f87 --- /dev/null +++ b/src/master/Makefile @@ -0,0 +1,64 @@ +BD_DEBUG=debug-$(PLATFORM)-$(ARCH) +BD_RELEASE=release-$(PLATFORM)-$(ARCH) + +ifndef PLATFORM + PLATFORM=$(shell uname|sed -e s/_.*//|tr '[:upper:]' '[:lower:]') +endif +ifndef ARCH + ARCH=$(shell uname -m | sed -e s/i.86/x86/) +endif + +ifeq ($(PLATFORM),mingw32) + BINEXT=.exe + RELEASE_LDFLAGS=-lwsock32 + DEBUG_LDFLAGS=-lwsock32 + RM=rm -f + MKDIR=mkdir +else + BINEXT= + RELEASE_LDFLAGS=-ltdb + DEBUG_LDFLAGS=-ltdb + RM=rm -f + MKDIR=mkdir +endif + +CC=gcc +RELEASE_CFLAGS=-Wall -O2 +DEBUG_CFLAGS=-g +OBJECTS= \ + $(BD)/master.o \ + $(BD)/messages.o \ + $(BD)/stats.o \ + $(BD)/servers.o + +release: makedirs + $(MAKE) $(BD_RELEASE)/tremmaster BD=$(BD_RELEASE) \ + CFLAGS="$(CFLAGS) $(RELEASE_CFLAGS)" LDFLAGS="$(LDFLAGS) $(RELEASE_LDFLAGS)" + +debug: makedirs + $(MAKE) $(BD_DEBUG)/tremmaster BD=$(BD_DEBUG) \ + CFLAGS="$(CFLAGS) $(DEBUG_CFLAGS)" LDFLAGS="$(LDFLAGS) $(DEBUG_LDFLAGS)" + +all: release debug + +$(BD)/%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +$(BD)/tremmaster: $(OBJECTS) + $(CC) -o $@ $(OBJECTS) $(LDFLAGS) + +clean: + -$(RM) $(BD_DEBUG)/* + -$(RM) $(BD_RELEASE)/* + +makedirs: + @if [ ! -d $(BD_RELEASE) ];then $(MKDIR) $(BD_RELEASE);fi + @if [ ! -d $(BD_DEBUG) ];then $(MKDIR) $(BD_DEBUG);fi + +.PHONY: all clean release debug makedirs + +# for f in *.c ; do cpp -MM ${f} -MT\$\(BD\)/${f%.c}.o ; done +$(BD)/master.o: master.c common.h messages.h servers.h +$(BD)/messages.o: messages.c common.h messages.h servers.h +$(BD)/servers.o: servers.c common.h servers.h +$(BD)/stats.o: stats.c common.h diff --git a/src/master/common.h b/src/master/common.h new file mode 100644 index 0000000..8c5c3b6 --- /dev/null +++ b/src/master/common.h @@ -0,0 +1,91 @@ +/* + common.h + + Common header file for dpmaster + + Copyright (C) 2004-2005 Mathieu Olivier + + 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 _COMMON_H_ +#define _COMMON_H_ + + +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +# include +#else +# include +# include +# include +# include +#endif + + +// ---------- Types ---------- // + +// A few basic types +typedef enum {qfalse, qtrue} qboolean; +typedef unsigned char qbyte; + +// The various messages levels +typedef enum +{ + MSG_NOPRINT, // used by "max_msg_level" (= no printings) + MSG_ERROR, // errors + MSG_WARNING, // warnings + MSG_NORMAL, // standard messages + MSG_DEBUG // for debugging purpose +} msg_level_t; + + +// ---------- Public variables ---------- // + +// The master socket +extern int inSock; +extern int outSock; + +// The current time (updated every time we receive a packet) +extern time_t crt_time; + +// Maximum level for a message to be printed +extern msg_level_t max_msg_level; + +// Peer address. We rebuild it every time we receive a new packet +extern char peer_address [128]; + + +// ---------- Public functions ---------- // + +// Win32 uses a different name for some standard functions +#ifdef WIN32 +# define snprintf _snprintf +#endif + +// Print a message to screen, depending on its verbose level +int MsgPrint (msg_level_t msg_level, const char* format, ...); + +void RecordClientStat( const char *address, const char *version, const char *renderer ); +void RecordGameStat( const char *address, const char *dataText ); + +#endif // #ifndef _COMMON_H_ diff --git a/src/master/master.c b/src/master/master.c new file mode 100644 index 0000000..fd6e66f --- /dev/null +++ b/src/master/master.c @@ -0,0 +1,877 @@ +/* + master.c + + A master server for Tremulous + + Copyright (C) 2002-2005 Mathieu Olivier + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include +#include + +#ifndef WIN32 +# include +# include +#endif + +#include "common.h" +#include "messages.h" +#include "servers.h" + + +// ---------- Constants ---------- // + +// Version of dpmaster +#define VERSION "1.6" + +// Default master port +#define DEFAULT_MASTER_PORT 30710 + +// Maximum and minimum sizes for a valid packet +#define MAX_PACKET_SIZE 2048 +#define MIN_PACKET_SIZE 5 + +#ifndef WIN32 +// Default path we use for chroot +# define DEFAULT_JAIL_PATH "/var/empty/" + +// User we use by default for dropping super-user privileges +# define DEFAULT_LOW_PRIV_USER "nobody" +#endif + + +// ---------- Types ---------- // + +#ifdef WIN32 +typedef int socklen_t; +#endif + + +// ---------- Private variables ---------- // + +// The port we use +static unsigned short master_port = DEFAULT_MASTER_PORT; + +// Local address we listen on, if any +static const char* listen_name = NULL; +static struct in_addr listen_addr; + +#ifndef WIN32 +// On UNIX systems, we can run as a daemon +static qboolean daemon_mode = qfalse; + +// Path we use for chroot +static const char* jail_path = DEFAULT_JAIL_PATH; + +// Low privileges user +static const char* low_priv_user = DEFAULT_LOW_PRIV_USER; +#endif + + +// ---------- Public variables ---------- // + +// The master socket +int inSock = -1; +int outSock = -1; + +// The current time (updated every time we receive a packet) +time_t crt_time; + +// Maximum level for a message to be printed +msg_level_t max_msg_level = MSG_NORMAL; + +// Peer address. We rebuild it every time we receive a new packet +char peer_address [128]; + + +// ---------- Private functions ---------- // + +/* +==================== +PrintPacket + +Print the contents of a packet on stdout +==================== +*/ +static void PrintPacket (const char* packet, size_t length) +{ + size_t i; + + // Exceptionally, we use MSG_NOPRINT here because if the function is + // called, the user probably wants this text to be displayed + // whatever the maximum message level is. + MsgPrint (MSG_NOPRINT, "\""); + + for (i = 0; i < length; i++) + { + char c = packet[i]; + if (c == '\\') + MsgPrint (MSG_NOPRINT, "\\\\"); + else if (c == '\"') + MsgPrint (MSG_NOPRINT, "\""); + else if (c >= 32 && (qbyte)c <= 127) + MsgPrint (MSG_NOPRINT, "%c", c); + else + MsgPrint (MSG_NOPRINT, "\\x%02X", c); + } + + MsgPrint (MSG_NOPRINT, "\" (%u bytes)\n", length); +} + + +/* +==================== +SysInit + +System dependent initializations +==================== +*/ +static qboolean SysInit (void) +{ +#ifdef WIN32 + WSADATA winsockdata; + + if (WSAStartup (MAKEWORD (1, 1), &winsockdata)) + { + MsgPrint (MSG_ERROR, "ERROR: can't initialize winsocks\n"); + return qfalse; + } +#endif + + return qtrue; +} + + +/* +==================== +UnsecureInit + +System independent initializations, called BEFORE the security initializations. +We need this intermediate step because DNS requests may not be able to resolve +after the security initializations, due to chroot. +==================== +*/ +static qboolean UnsecureInit (void) +{ + // Resolve the address mapping list + if (! Sv_ResolveAddressMappings ()) + return qfalse; + + // Resolve the listen address if one was specified + if (listen_name != NULL) + { + struct hostent* itf; + + itf = gethostbyname (listen_name); + if (itf == NULL) + { + MsgPrint (MSG_ERROR, "ERROR: can't resolve %s\n", listen_name); + return qfalse; + } + if (itf->h_addrtype != AF_INET) + { + MsgPrint (MSG_ERROR, "ERROR: %s is not an IPv4 address\n", + listen_name); + return qfalse; + } + + memcpy (&listen_addr.s_addr, itf->h_addr, + sizeof (listen_addr.s_addr)); + } + + return qtrue; +} + + +/* +==================== +SecInit + +Security initializations (system dependent) +==================== +*/ +static qboolean SecInit (void) +{ +#ifndef WIN32 + // Should we run as a daemon? + if (daemon_mode && daemon (0, 0)) + { + MsgPrint (MSG_NOPRINT, "ERROR: daemonization failed (%s)\n", + strerror (errno)); + return qfalse; + } + + // UNIX allows us to be completely paranoid, so let's go for it + if (geteuid () == 0) + { + struct passwd* pw; + + MsgPrint (MSG_WARNING, + "WARNING: running with super-user privileges\n"); + + // We must get the account infos before the calls to chroot and chdir + pw = getpwnam (low_priv_user); + if (pw == NULL) + { + MsgPrint (MSG_ERROR, "ERROR: can't get user \"%s\" properties\n", + low_priv_user); + return qfalse; + } + + // Chroot ourself + MsgPrint (MSG_NORMAL, " - chrooting myself to %s... ", jail_path); + if (chroot (jail_path) || chdir ("/")) + { + MsgPrint (MSG_ERROR, "FAILED (%s)\n", strerror (errno)); + return qfalse; + } + MsgPrint (MSG_NORMAL, "succeeded\n"); + + // Switch to lower privileges + MsgPrint (MSG_NORMAL, " - switching to user \"%s\" privileges... ", + low_priv_user); + if (setgid (pw->pw_gid) || setuid (pw->pw_uid)) + { + MsgPrint (MSG_ERROR, "FAILED (%s)\n", strerror (errno)); + return qfalse; + } + MsgPrint (MSG_NORMAL, "succeeded (UID: %u, GID: %u)\n", + pw->pw_uid, pw->pw_gid); + + MsgPrint (MSG_NORMAL, "\n"); + } +#endif + + return qtrue; +} + + +/* +==================== +ParseCommandLine + +Parse the options passed by the command line +==================== +*/ +static qboolean ParseCommandLine (int argc, const char* argv []) +{ + int ind = 1; + unsigned int vlevel = max_msg_level; + qboolean valid_options = qtrue; + + while (ind < argc && valid_options) + { + // If it doesn't even look like an option, why bother? + if (argv[ind][0] != '-') + valid_options = qfalse; + + else switch (argv[ind][1]) + { +#ifndef WIN32 + // Daemon mode + case 'D': + daemon_mode = qtrue; + break; +#endif + + // Help + case 'h': + valid_options = qfalse; + break; + + // Hash size + case 'H': + ind++; + if (ind < argc) + valid_options = Sv_SetHashSize (atoi (argv[ind])); + else + valid_options = qfalse; + break; + +#ifndef WIN32 + // Jail path + case 'j': + ind++; + if (ind < argc) + jail_path = argv[ind]; + else + valid_options = qfalse; + break; +#endif + + // Listen address + case 'l': + ind++; + if (ind >= argc || argv[ind][0] == '\0') + valid_options = qfalse; + else + listen_name = argv[ind]; + break; + + // Address mapping + case 'm': + ind++; + if (ind < argc) + valid_options = Sv_AddAddressMapping (argv[ind]); + else + valid_options = qfalse; + break; + + // Maximum number of servers + case 'n': + ind++; + if (ind < argc) + valid_options = Sv_SetMaxNbServers (atoi (argv[ind])); + else + valid_options = qfalse; + break; + + // Port number + case 'p': + { + unsigned short port_num = 0; + ind++; + if (ind < argc) + port_num = atoi (argv[ind]); + if (!port_num) + valid_options = qfalse; + else + master_port = port_num; + break; + } + +#ifndef WIN32 + // Low privileges user + case 'u': + ind++; + if (ind < argc) + low_priv_user = argv[ind]; + else + valid_options = qfalse; + break; +#endif + + // Verbose level + case 'v': + // If a verbose level has been specified + if (ind + 1 < argc && argv[ind + 1][0] != '-') + { + ind++; + vlevel = atoi (argv[ind]); + if (vlevel > MSG_DEBUG) + valid_options = qfalse; + } + else + vlevel = MSG_DEBUG; + break; + + default: + valid_options = qfalse; + } + + ind++; + } + + // If the command line is OK, we can set the verbose level now + if (valid_options) + { +#ifndef WIN32 + // If we run as a daemon, don't bother printing anything + if (daemon_mode) + max_msg_level = MSG_NOPRINT; + else +#endif + max_msg_level = vlevel; + } + + return valid_options; +} + + +/* +==================== +PrintHelp + +Print the command line syntax and the available options +==================== +*/ +static void PrintHelp (void) +{ + MsgPrint (MSG_ERROR, + "Syntax: dpmaster [options]\n" + "Available options are:\n" +#ifndef WIN32 + " -D : run as a daemon\n" +#endif + " -h : this help\n" + " -H : hash size in bits, up to %u (default: %u)\n" +#ifndef WIN32 + " -j : use as chroot path (default: %s)\n" + " only available when running with super-user privileges\n" +#endif + " -l

: listen on local address
\n" + " -m = : map address to when sending it to clients\n" + " addresses can contain a port number (ex: myaddr.net:1234)\n" + " -n : maximum number of servers recorded (default: %u)\n" + " -p : use port (default: %u)\n" +#ifndef WIN32 + " -u : use privileges (default: %s)\n" + " only available when running with super-user privileges\n" +#endif + " -v [verbose_lvl] : verbose level, up to %u (default: %u; no value means max)\n" + "\n", + MAX_HASH_SIZE, DEFAULT_HASH_SIZE, +#ifndef WIN32 + DEFAULT_JAIL_PATH, +#endif + DEFAULT_MAX_NB_SERVERS, + DEFAULT_MASTER_PORT, +#ifndef WIN32 + DEFAULT_LOW_PRIV_USER, +#endif + MSG_DEBUG, MSG_NORMAL); +} + + +/* +==================== +SecureInit + +System independent initializations, called AFTER the security initializations +==================== +*/ +static qboolean SecureInit (void) +{ + struct sockaddr_in address; + + // Init the time and the random seed + crt_time = time (NULL); + srand (crt_time); + + // Initialize the server list and hash table + if (!Sv_Init ()) + return qfalse; + + // Open the socket + inSock = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (inSock < 0) + { + MsgPrint (MSG_ERROR, "ERROR: socket creation failed (%s)\n", + strerror (errno)); + return qfalse; + } + + outSock = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (outSock < 0) + { + MsgPrint (MSG_ERROR, "ERROR: socket creation failed (%s)\n", + strerror (errno)); + return qfalse; + } + + // Bind it to the master port + memset (&address, 0, sizeof (address)); + address.sin_family = AF_INET; + if (listen_name != NULL) + { + MsgPrint (MSG_NORMAL, "Listening on address %s (%s)\n", + listen_name, inet_ntoa (listen_addr)); + address.sin_addr.s_addr = listen_addr.s_addr; + } + else + address.sin_addr.s_addr = htonl (INADDR_ANY); + + address.sin_port = htons (master_port); + if (bind (inSock, (struct sockaddr*)&address, sizeof (address)) != 0) + { + MsgPrint (MSG_ERROR, "ERROR: socket binding failed (%s)\n", + strerror (errno)); +#ifdef WIN32 + closesocket (inSock); +#else + close (inSock); +#endif + return qfalse; + } + MsgPrint (MSG_NORMAL, "Listening on UDP port %hu\n", + ntohs (address.sin_port)); + + // Deliberately use a different port for outgoing traffic in order + // to confuse NAT UDP "connection" tracking and thus delist servers + // hidden by NAT + address.sin_port = htons (master_port+1); + if (bind (outSock, (struct sockaddr*)&address, sizeof (address)) != 0) + { + MsgPrint (MSG_ERROR, "ERROR: socket binding failed (%s)\n", + strerror (errno)); +#ifdef WIN32 + closesocket (outSock); +#else + close (outSock); +#endif + return qfalse; + } + + return qtrue; +} + +static qboolean exitNow = qfalse; + +/* +=============== +cleanUp + +Clean up +=============== +*/ +static void cleanUp( int signal ) +{ + MsgPrint( MSG_NORMAL, "Caught signal %d, exiting...\n", signal ); + + exitNow = qtrue; +} + +#define ADDRESS_LENGTH 16 +static const char *ignoreFile = "ignore.txt"; + +typedef struct +{ + char address[ ADDRESS_LENGTH ]; // Dotted quad +} ignoreAddress_t; + +#define PARSE_INTERVAL 10 // seconds + +static time_t lastParseTime = 0; +static int numIgnoreAddresses = 0; +static ignoreAddress_t *ignoreAddresses = NULL; + +/* +==================== +parseIgnoreAddress +==================== +*/ +static qboolean parseIgnoreAddress( void ) +{ + int numAllocIgnoreAddresses = 1; + FILE *f = NULL; + int i; + + // Only reparse periodically + if( crt_time - lastParseTime < PARSE_INTERVAL ) + return qtrue; + + lastParseTime = time( NULL ); + + // Free existing list + if( ignoreAddresses != NULL ) + { + free( ignoreAddresses ); + ignoreAddresses = NULL; + } + + numIgnoreAddresses = 0; + ignoreAddresses = malloc( sizeof( ignoreAddress_t ) * numAllocIgnoreAddresses ); + + // Alloc failed, fail parsing + if( ignoreAddresses == NULL ) + return qfalse; + + f = fopen( ignoreFile, "r" ); + + if( !f ) + { + free( ignoreAddresses ); + ignoreAddresses = NULL; + return qfalse; + } + + while( !feof( f ) ) + { + char c; + char buffer[ ADDRESS_LENGTH ]; + + i = 0; + + // Skip whitespace + do + { + c = fgetc( f ); + } + while( c != EOF && isspace( c ) ); + + if( c != EOF ) + { + do + { + if( i >= ADDRESS_LENGTH ) + { + buffer[ i - 1 ] = '\0'; + break; + } + + buffer[ i ] = c; + + if( isspace( c ) ) + { + buffer[ i ] = '\0'; + break; + } + + i++; + } while( ( c = fgetc( f ) ) != EOF ); + + strcpy( ignoreAddresses[ numIgnoreAddresses ].address, buffer ); + + numIgnoreAddresses++; + + // Make list bigger + if( numIgnoreAddresses >= numAllocIgnoreAddresses ) + { + ignoreAddress_t *new; + + numAllocIgnoreAddresses *= 2; + new = realloc( ignoreAddresses, + sizeof( ignoreAddress_t ) * numAllocIgnoreAddresses ); + + // Alloc failed, fail parsing + if( new == NULL ) + { + fclose( f ); + free( ignoreAddresses ); + ignoreAddresses = NULL; + return qfalse; + } + + ignoreAddresses = new; + } + } + } + + fclose( f ); + + return qtrue; +} + +/* +==================== +ignoreAddress + +Check whether or not to ignore a specific address +==================== +*/ +static qboolean ignoreAddress( const char *address ) +{ + int i; + + if( !parseIgnoreAddress( ) ) + { + // Couldn't parse, allow the address + return qfalse; + } + + for( i = 0; i < numIgnoreAddresses; i++ ) + { + if( strcmp( address, ignoreAddresses[ i ].address ) == 0 ) + break; + } + + // It matched + if( i < numIgnoreAddresses ) + return qtrue; + + return qfalse; +} + +/* +==================== +max + +Maximum of two ints +==================== +*/ +static inline int max( int a, int b ) +{ + return a > b ? a : b; +} + +/* +==================== +main + +Main function +==================== +*/ +int main (int argc, const char* argv []) +{ + struct sockaddr_in address; + socklen_t addrlen; + int nb_bytes; + int sock; + char packet [MAX_PACKET_SIZE + 1]; // "+ 1" because we append a '\0' + qboolean valid_options; + fd_set rfds; + struct timeval tv; + + + signal( SIGINT, cleanUp ); + signal( SIGTERM, cleanUp ); + + // Get the options from the command line + valid_options = ParseCommandLine (argc, argv); + + MsgPrint (MSG_NORMAL, + "tremmaster (version " VERSION " " __DATE__ " " __TIME__ ")\n" ); + + // If there was a mistake in the command line, print the help and exit + if (!valid_options) + { + PrintHelp (); + return EXIT_FAILURE; + } + + // Initializations + if (!SysInit () || !UnsecureInit () || !SecInit () || !SecureInit ()) + return EXIT_FAILURE; + MsgPrint (MSG_NORMAL, "\n"); + + // Until the end of times... + while( !exitNow ) + { + FD_ZERO( &rfds ); + FD_SET( inSock, &rfds ); + FD_SET( outSock, &rfds ); + tv.tv_sec = tv.tv_usec = 0; + + // Check for new data every 100ms + if( select( max( inSock, outSock ) + 1, &rfds, NULL, NULL, &tv ) <= 0 ) + { +#ifdef _WIN32 + Sleep( 100 ); +#else + usleep( 100000 ); +#endif + continue; + } + + if( FD_ISSET( inSock, &rfds ) ) + sock = inSock; + else if( FD_ISSET( outSock, &rfds ) ) + sock = outSock; + else + continue; + + // Get the next valid message + addrlen = sizeof (address); + nb_bytes = recvfrom (sock, packet, sizeof (packet) - 1, 0, + (struct sockaddr*)&address, &addrlen); + if (nb_bytes <= 0) + { + MsgPrint (MSG_WARNING, + "WARNING: \"recvfrom\" returned %d\n", nb_bytes); + continue; + } + + // If we may have to print something, rebuild the peer address buffer + if (max_msg_level != MSG_NOPRINT) + snprintf (peer_address, sizeof (peer_address), "%s:%hu", + inet_ntoa (address.sin_addr), ntohs (address.sin_port)); + + // Ignore abusers + if( ignoreAddress( inet_ntoa( address.sin_addr ) ) ) + { + server_t* abuser = Sv_GetByAddr( &address, qfalse ); + if( abuser != NULL ) + { + abuser->timeout = crt_time - 1; + Sv_GetByAddr( &address, qfalse ); + MsgPrint( MSG_WARNING, "WARNING: removing abuser %s\n", peer_address ); + } + continue; + } + + // We print the packet contents if necessary + // TODO: print the current time here + if (max_msg_level >= MSG_DEBUG) + { + MsgPrint (MSG_DEBUG, "New packet received from %s: ", + peer_address); + PrintPacket (packet, nb_bytes); + } + + // A few sanity checks + if (nb_bytes < MIN_PACKET_SIZE) + { + MsgPrint (MSG_WARNING, + "WARNING: rejected packet from %s (size = %d bytes)\n", + peer_address, nb_bytes); + continue; + } + if (*((unsigned int*)packet) != 0xFFFFFFFF) + { + MsgPrint (MSG_WARNING, + "WARNING: rejected packet from %s (invalid header)\n", + peer_address); + continue; + } + if( ntohs( address.sin_port ) < 1024 ) + { + MsgPrint (MSG_WARNING, + "WARNING: rejected packet from %s (source port = 0)\n", + peer_address); + continue; + } + + // Append a '\0' to make the parsing easier and update the current time + packet[nb_bytes] = '\0'; + crt_time = time (NULL); + + // Call HandleMessage with the remaining contents + HandleMessage (packet + 4, nb_bytes - 4, &address); + } + + return 0; +} + + +// ---------- Public functions ---------- // + +/* +==================== +MsgPrint + +Print a message to screen, depending on its verbose level +==================== +*/ +int MsgPrint (msg_level_t msg_level, const char* format, ...) +{ + va_list args; + int result; + + // If the message level is above the maximum level, don't print it + if (msg_level > max_msg_level) + return 0; + + va_start (args, format); + result = vprintf (format, args); + va_end (args); + + fflush (stdout); + + return result; +} diff --git a/src/master/messages.c b/src/master/messages.c new file mode 100644 index 0000000..a86e795 --- /dev/null +++ b/src/master/messages.c @@ -0,0 +1,574 @@ +/* + messages.c + + Message management for tremmaster + + Copyright (C) 2004 Mathieu Olivier + + 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 "common.h" +#include "messages.h" +#include "servers.h" + + +// ---------- Constants ---------- // + +// Timeouts (in secondes) +#define TIMEOUT_HEARTBEAT 2 +#define TIMEOUT_INFORESPONSE (15 * 60) + +// Period of validity for a challenge string (in secondes) +#define TIMEOUT_CHALLENGE 2 + +// Maximum size of a reponse packet +#define MAX_PACKET_SIZE 1400 + + +// Types of messages (with samples): + +// "heartbeat Tremulous\n" +#define S2M_HEARTBEAT "heartbeat" + +// "gamestat " +#define S2M_GAMESTAT "gamestat" + +// "getinfo A_Challenge" +#define M2S_GETINFO "getinfo" + +// "infoResponse\n\\pure\\1\\..." +#define S2M_INFORESPONSE "infoResponse\x0A" + +// "getservers 67 empty full" +#define C2M_GETSERVERS "getservers " + +// "getserversResponse\\...(6 bytes)...\\...(6 bytes)...\\EOT\0\0\0" +#define M2C_GETSERVERSREPONSE "getserversResponse" + +#define C2M_GETMOTD "getmotd" +#define M2C_MOTD "motd " + + +// ---------- Private functions ---------- // + +/* +==================== +SearchInfostring + +Search an infostring for the value of a key +==================== +*/ +static char* SearchInfostring (const char* infostring, const char* key) +{ + static char value [256]; + char crt_key [256]; + size_t value_ind, key_ind; + char c; + + if (*infostring++ != '\\') + return NULL; + + value_ind = 0; + for (;;) + { + key_ind = 0; + + // Get the key name + for (;;) + { + c = *infostring++; + + if (c == '\0') + return NULL; + if (c == '\\' || key_ind == sizeof (crt_key) - 1) + { + crt_key[key_ind] = '\0'; + break; + } + + crt_key[key_ind++] = c; + } + + // If it's the key we are looking for, save it in "value" + if (!strcmp (crt_key, key)) + { + for (;;) + { + c = *infostring++; + + if (c == '\0' || c == '\\' || value_ind == sizeof (value) - 1) + { + value[value_ind] = '\0'; + return value; + } + + value[value_ind++] = c; + } + } + + // Else, skip the value + for (;;) + { + c = *infostring++; + + if (c == '\0') + return NULL; + if (c == '\\') + break; + } + } +} + + +/* +==================== +BuildChallenge + +Build a challenge string for a "getinfo" message +==================== +*/ +static const char* BuildChallenge (void) +{ + static char challenge [CHALLENGE_MAX_LENGTH]; + size_t ind; + size_t length = CHALLENGE_MIN_LENGTH - 1; // We start at the minimum size + + // ... then we add a random number of characters + length += rand () % (CHALLENGE_MAX_LENGTH - CHALLENGE_MIN_LENGTH + 1); + + for (ind = 0; ind < length; ind++) + { + char c; + do + { + c = 33 + rand () % (126 - 33 + 1); // -> c = 33..126 + } while (c == '\\' || c == ';' || c == '"' || c == '%' || c == '/'); + + challenge[ind] = c; + } + + challenge[length] = '\0'; + return challenge; +} + + +/* +==================== +SendGetInfo + +Send a "getinfo" message to a server +==================== +*/ +static void SendGetInfo (server_t* server) +{ + char msg [64] = "\xFF\xFF\xFF\xFF" M2S_GETINFO " "; + + if (!server->challenge_timeout || server->challenge_timeout < crt_time) + { + strncpy (server->challenge, BuildChallenge (), + sizeof (server->challenge) - 1); + server->challenge_timeout = crt_time + TIMEOUT_CHALLENGE; + } + + strncat (msg, server->challenge, sizeof (msg) - strlen (msg) - 1); + sendto (outSock, msg, strlen (msg), 0, + (const struct sockaddr*)&server->address, + sizeof (server->address)); + + MsgPrint (MSG_DEBUG, "%s <--- getinfo with challenge \"%s\"\n", + peer_address, server->challenge); +} + + +/* +==================== +HandleGetServers + +Parse getservers requests and send the appropriate response +==================== +*/ +static void HandleGetServers (const char* msg, const struct sockaddr_in* addr) +{ + const char* packetheader = "\xFF\xFF\xFF\xFF" M2C_GETSERVERSREPONSE "\\"; + const size_t headersize = strlen (packetheader); + char packet [MAX_PACKET_SIZE]; + size_t packetind; + server_t* sv; + unsigned int protocol; + unsigned int sv_addr; + unsigned short sv_port; + qboolean no_empty; + qboolean no_full; + unsigned int numServers = 0; + + // Check if there's a name before the protocol number + // In this case, the message comes from a DarkPlaces-compatible client + protocol = atoi (msg); + + MsgPrint (MSG_NORMAL, "%s ---> getservers( protocol version %d )\n", + peer_address, protocol ); + + no_empty = (strstr (msg, "empty") == NULL); + no_full = (strstr (msg, "full") == NULL); + + // Initialize the packet contents with the header + packetind = headersize; + memcpy(packet, packetheader, headersize); + + // Add every relevant server + for (sv = Sv_GetFirst (); /* see below */; sv = Sv_GetNext ()) + { + // If we're done, or if the packet is full, send the packet + if (sv == NULL || packetind > sizeof (packet) - (7 + 6)) + { + // End Of Transmission + packet[packetind ] = 'E'; + packet[packetind + 1] = 'O'; + packet[packetind + 2] = 'T'; + packet[packetind + 3] = '\0'; + packet[packetind + 4] = '\0'; + packet[packetind + 5] = '\0'; + packetind += 6; + + // Send the packet to the client + sendto (inSock, packet, packetind, 0, (const struct sockaddr*)addr, + sizeof (*addr)); + + MsgPrint (MSG_DEBUG, "%s <--- getserversResponse (%u servers)\n", + peer_address, numServers); + + // If we're done + if (sv == NULL) + return; + + // Reset the packet index (no need to change the header) + packetind = headersize; + } + + sv_addr = ntohl (sv->address.sin_addr.s_addr); + sv_port = ntohs (sv->address.sin_port); + + // Extra debugging info + if (max_msg_level >= MSG_DEBUG) + { + MsgPrint (MSG_DEBUG, + "Comparing server: IP:\"%u.%u.%u.%u:%hu\", p:%u, c:%hu\n", + sv_addr >> 24, (sv_addr >> 16) & 0xFF, + (sv_addr >> 8) & 0xFF, sv_addr & 0xFF, + sv_port, sv->protocol, sv->nbclients ); + + if (sv->protocol != protocol) + MsgPrint (MSG_DEBUG, + "Reject: protocol %u != requested %u\n", + sv->protocol, protocol); + if (sv->nbclients == 0 && no_empty) + MsgPrint (MSG_DEBUG, + "Reject: nbclients is %hu/%hu && no_empty\n", + sv->nbclients, sv->maxclients); + if (sv->nbclients == sv->maxclients && no_full) + MsgPrint (MSG_DEBUG, + "Reject: nbclients is %hu/%hu && no_full\n", + sv->nbclients, sv->maxclients); + } + + // Check protocol, options + if (sv->protocol != protocol || + (sv->nbclients == 0 && no_empty) || + (sv->nbclients == sv->maxclients && no_full)) + { + + // Skip it + continue; + } + + // Use the address mapping associated with the server, if any + if (sv->addrmap != NULL) + { + const addrmap_t* addrmap = sv->addrmap; + + sv_addr = ntohl (addrmap->to.sin_addr.s_addr); + if (addrmap->to.sin_port != 0) + sv_port = ntohs (addrmap->to.sin_port); + + MsgPrint (MSG_DEBUG, + "Server address mapped to %u.%u.%u.%u:%hu\n", + sv_addr >> 24, (sv_addr >> 16) & 0xFF, + (sv_addr >> 8) & 0xFF, sv_addr & 0xFF, + sv_port); + } + + // IP address + packet[packetind ] = sv_addr >> 24; + packet[packetind + 1] = (sv_addr >> 16) & 0xFF; + packet[packetind + 2] = (sv_addr >> 8) & 0xFF; + packet[packetind + 3] = sv_addr & 0xFF; + + // Port + packet[packetind + 4] = sv_port >> 8; + packet[packetind + 5] = sv_port & 0xFF; + + // Trailing '\' + packet[packetind + 6] = '\\'; + + MsgPrint (MSG_DEBUG, " - Sending server %u.%u.%u.%u:%hu\n", + (qbyte)packet[packetind ], (qbyte)packet[packetind + 1], + (qbyte)packet[packetind + 2], (qbyte)packet[packetind + 3], + sv_port); + + packetind += 7; + numServers++; + } +} + + +/* +==================== +HandleInfoResponse + +Parse infoResponse messages +==================== +*/ +static void HandleInfoResponse (server_t* server, const char* msg) +{ + char* value; + unsigned int new_protocol = 0, new_maxclients = 0; + + MsgPrint (MSG_DEBUG, "%s ---> infoResponse\n", peer_address); + + // Check the challenge + if (!server->challenge_timeout || server->challenge_timeout < crt_time) + { + MsgPrint (MSG_WARNING, + "WARNING: infoResponse with obsolete challenge from %s\n", + peer_address); + return; + } + value = SearchInfostring (msg, "challenge"); + if (!value || strcmp (value, server->challenge)) + { + MsgPrint (MSG_ERROR, "ERROR: invalid challenge from %s (%s)\n", + peer_address, value); + return; + } + // some people enjoy making it hard to find them + value = SearchInfostring (msg, "hostname"); + if (!value || !value[0]) + { + MsgPrint (MSG_ERROR, "ERROR: no hostname from %s\n", + peer_address, value); + return; + } + + // Check and save the values of "protocol" and "maxclients" + value = SearchInfostring (msg, "protocol"); + if (value) + new_protocol = atoi (value); + value = SearchInfostring (msg, "sv_maxclients"); + if (value) + new_maxclients = atoi (value); + if (!new_protocol || !new_maxclients) + { + MsgPrint (MSG_ERROR, + "ERROR: invalid infoResponse from %s (protocol: %d, maxclients: %d)\n", + peer_address, new_protocol, new_maxclients); + return; + } + server->protocol = new_protocol; + server->maxclients = new_maxclients; + + // Save some other useful values + value = SearchInfostring (msg, "clients"); + if (value) + server->nbclients = atoi (value); + + // Set a new timeout + server->timeout = crt_time + TIMEOUT_INFORESPONSE; +} + + +#define CHALLENGE_KEY "challenge\\" +#define MOTD_KEY "motd\\" + +/* +==================== +HandleGetMotd + +Parse getservers requests and send the appropriate response +==================== +*/ +static void HandleGetMotd( const char* msg, const struct sockaddr_in* addr ) +{ + const char *packetheader = "\xFF\xFF\xFF\xFF" M2C_MOTD "\""; + const size_t headersize = strlen (packetheader); + char packet[ MAX_PACKET_SIZE ]; + char challenge[ MAX_PACKET_SIZE ]; + const char *motd = ""; //FIXME + size_t packetind; + char *value; + char version[ 1024 ], renderer[ 1024 ]; + + MsgPrint( MSG_DEBUG, "%s ---> getmotd\n", peer_address ); + + value = SearchInfostring( msg, "challenge" ); + if( !value ) + { + MsgPrint( MSG_ERROR, "ERROR: invalid challenge from %s (%s)\n", + peer_address, value ); + return; + } + + strncpy( challenge, value, sizeof(challenge)-1 ); + challenge[sizeof(challenge)-1] = '\0'; + + value = SearchInfostring( msg, "renderer" ); + if( value ) + { + strncpy( renderer, value, sizeof(renderer)-1 ); + renderer[sizeof(renderer)-1] = '\0'; + MsgPrint( MSG_DEBUG, "%s is using renderer %s\n", peer_address, value ); + } + + value = SearchInfostring( msg, "version" ); + if( value ) + { + strncpy( version, value, sizeof(version)-1 ); + version[sizeof(version)-1] = '\0'; + MsgPrint( MSG_DEBUG, "%s is using version %s\n", peer_address, value ); + } + +#ifndef _WIN32 + RecordClientStat( peer_address, version, renderer ); +#endif + + // Initialize the packet contents with the header + packetind = headersize; + memcpy( packet, packetheader, headersize ); + + strncpy( &packet[ packetind ], CHALLENGE_KEY, MAX_PACKET_SIZE - packetind - 2 ); + packetind += strlen( CHALLENGE_KEY ); + + strncpy( &packet[ packetind ], challenge, MAX_PACKET_SIZE - packetind - 2 ); + packetind += strlen( challenge ); + packet[ packetind++ ] = '\\'; + + strncpy( &packet[ packetind ], MOTD_KEY, MAX_PACKET_SIZE - packetind - 2 ); + packetind += strlen( MOTD_KEY ); + + strncpy( &packet[ packetind ], motd, MAX_PACKET_SIZE - packetind - 2 ); + packetind += strlen( motd ); + + if (packetind > MAX_PACKET_SIZE - 2) + packetind = MAX_PACKET_SIZE - 2; + + packet[ packetind++ ] = '\"'; + packet[ packetind++ ] = '\0'; + + MsgPrint( MSG_DEBUG, "%s <--- motd\n", peer_address ); + + // Send the packet to the client + sendto( inSock, packet, packetind, 0, (const struct sockaddr*)addr, + sizeof( *addr ) ); +} + +/* +==================== +HandleGameStat +==================== +*/ +static void HandleGameStat( const char* msg, const struct sockaddr_in* addr ) +{ +#ifndef _WIN32 + RecordGameStat( peer_address, msg ); +#endif +} + +// ---------- Public functions ---------- // + +/* +==================== +HandleMessage + +Parse a packet to figure out what to do with it +==================== +*/ +void HandleMessage (const char* msg, size_t length, + const struct sockaddr_in* address) +{ + server_t* server; + + // If it's an heartbeat + if (!strncmp (S2M_HEARTBEAT, msg, strlen (S2M_HEARTBEAT))) + { + char gameId [64]; + + // Extract the game id + sscanf (msg + strlen (S2M_HEARTBEAT) + 1, "%63s", gameId); + MsgPrint (MSG_DEBUG, "%s ---> heartbeat (%s)\n", + peer_address, gameId); + + // Get the server in the list (add it to the list if necessary) + server = Sv_GetByAddr (address, qtrue); + if (server == NULL) + return; + + server->active = qtrue; + + // If we haven't yet received any infoResponse from this server, + // we let it some more time to contact us. After that, only + // infoResponse messages can update the timeout value. + if (!server->maxclients) + server->timeout = crt_time + TIMEOUT_HEARTBEAT; + + // Ask for some infos + SendGetInfo (server); + } + + // If it's an infoResponse message + else if (!strncmp (S2M_INFORESPONSE, msg, strlen (S2M_INFORESPONSE))) + { + server = Sv_GetByAddr (address, qfalse); + if (server == NULL) + return; + + HandleInfoResponse (server, msg + strlen (S2M_INFORESPONSE)); + } + + // If it's a getservers request + else if (!strncmp (C2M_GETSERVERS, msg, strlen (C2M_GETSERVERS))) + { + HandleGetServers (msg + strlen (C2M_GETSERVERS), address); + } + + // If it's a getmotd request + else if (!strncmp (C2M_GETMOTD, msg, strlen (C2M_GETMOTD))) + { + HandleGetMotd (msg + strlen (C2M_GETMOTD), address); + } + + // If it's a game statistic + else if( !strncmp( S2M_GAMESTAT, msg, strlen ( S2M_GAMESTAT ) ) ) + { + server = Sv_GetByAddr(address, qfalse); + if (server == NULL) + return; + if( crt_time - server->lastGameStat > MIN_GAMESTAT_DELAY ) + HandleGameStat( msg + strlen( S2M_GAMESTAT ), address ); + else + MsgPrint( MSG_NORMAL, "%s flooding GAMESTAT messages\n", peer_address ); + server->lastGameStat = crt_time; + } +} diff --git a/src/master/messages.h b/src/master/messages.h new file mode 100644 index 0000000..93b5271 --- /dev/null +++ b/src/master/messages.h @@ -0,0 +1,35 @@ +/* + messages.h + + Message management for dpmaster + + Copyright (C) 2004 Mathieu Olivier + + 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 _MESSAGES_H_ +#define _MESSAGES_H_ + + +// ---------- Public functions ---------- // + +// Parse a packet to figure out what to do with it +void HandleMessage (const char* msg, size_t length, + const struct sockaddr_in* address); + + +#endif // #ifndef _MESSAGES_H_ diff --git a/src/master/servers.c b/src/master/servers.c new file mode 100644 index 0000000..a8412a1 --- /dev/null +++ b/src/master/servers.c @@ -0,0 +1,666 @@ +/* + servers.c + + Server list and address mapping management for dpmaster + + Copyright (C) 2004 Mathieu Olivier + + 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 "common.h" +#include "servers.h" + + +// ---------- Constants ---------- // + +// Address hash bitmask +#define HASH_BITMASK (hash_table_size - 1) + + +// ---------- Variables ---------- // + +// All server structures are allocated in one block in the "servers" array. +// Each used slot is also part of a linked list in "hash_table". A simple +// hash of the address and port of a server gives its index in the table. +static server_t* servers = NULL; +static size_t max_nb_servers = DEFAULT_MAX_NB_SERVERS; +static size_t nb_servers = 0; +static server_t** hash_table = NULL; +static size_t hash_table_size = (1 << DEFAULT_HASH_SIZE); + +// Last allocated entry in the "servers" array. +// Used as a start index for finding a free slot in "servers" more quickly +static unsigned int last_alloc; + +// Variables for Sv_GetFirst, Sv_GetNext and Sv_RemoveCurrentAndGetNext +static server_t* crt_server = NULL; +static server_t** prev_pointer = NULL; +static int crt_hash_ind = -1; + +// List of address mappings. They are sorted by "from" field (IP, then port) +static addrmap_t* addrmaps = NULL; + + +// ---------- Private functions ---------- // + +/* +==================== +Sv_AddressHash + +Compute the hash of a server address +==================== +*/ +static unsigned int Sv_AddressHash (const struct sockaddr_in* address) +{ + qbyte* addr = (qbyte*)&address->sin_addr.s_addr; + qbyte* port = (qbyte*)&address->sin_port; + qbyte hash; + + hash = addr[0] ^ addr[1] ^ addr[2] ^ addr[3] ^ port[0] ^ port[1]; + return hash & HASH_BITMASK; +} + + +/* +==================== +Sv_RemoveAndGetNextPtr + +Remove a server from the list and returns its "next" pointer +==================== +*/ +static server_t* Sv_RemoveAndGetNextPtr (server_t* sv, server_t** prev) +{ + nb_servers--; + MsgPrint (MSG_NORMAL, + "%s:%hu timed out; %u servers currently registered\n", + inet_ntoa (sv->address.sin_addr), ntohs (sv->address.sin_port), + nb_servers); + + // Mark this structure as "free" + sv->active = qfalse; + + *prev = sv->next; + return sv->next; +} + + +/* +==================== +Sv_ResolveAddr + +Resolve an internet address +name may include a port number, after a ':' +==================== +*/ +static qboolean Sv_ResolveAddr (const char* name, struct sockaddr_in* addr) +{ + char *namecpy, *port; + struct hostent* host; + + // Create a work copy + namecpy = strdup (name); + if (namecpy == NULL) + { + MsgPrint (MSG_ERROR, + "ERROR: can't allocate enough memory to resolve %s\n", + name); + return qfalse; + } + + // Find the port in the address + port = strchr (namecpy, ':'); + if (port != NULL) + *port++ = '\0'; + + // Resolve the address + host = gethostbyname (namecpy); + if (host == NULL) + { + MsgPrint (MSG_ERROR, "ERROR: can't resolve %s\n", namecpy); + free (namecpy); + return qfalse; + } + if (host->h_addrtype != AF_INET) + { + MsgPrint (MSG_ERROR, "ERROR: %s is not an IPv4 address\n", + namecpy); + free (namecpy); + return qfalse; + } + + // Build the structure + memset (addr, 0, sizeof (*addr)); + addr->sin_family = AF_INET; + memcpy (&addr->sin_addr.s_addr, host->h_addr, + sizeof (addr->sin_addr.s_addr)); + if (port != NULL) + addr->sin_port = htons ((unsigned short)atoi (port)); + + MsgPrint (MSG_DEBUG, "\"%s\" resolved to %s:%hu\n", + name, inet_ntoa (addr->sin_addr), ntohs (addr->sin_port)); + + free (namecpy); + return qtrue; +} + + +/* +==================== +Sv_InsertAddrmapIntoList + +Insert an addrmap structure to the addrmaps list +==================== +*/ +static void Sv_InsertAddrmapIntoList (addrmap_t* new_map) +{ + addrmap_t* addrmap = addrmaps; + addrmap_t** prev = &addrmaps; + + // Stop at the end of the list, or if the addresses become too high + while (addrmap != NULL && + addrmap->from.sin_addr.s_addr <= new_map->from.sin_addr.s_addr) + { + // If we found the right place + if (addrmap->from.sin_addr.s_addr == new_map->from.sin_addr.s_addr && + addrmap->from.sin_port >= new_map->from.sin_port) + { + // If a mapping is already recorded for this address + if (addrmap->from.sin_port == new_map->from.sin_port) + { + MsgPrint (MSG_WARNING, + "WARNING: Address %s:%hu has several mappings\n", + inet_ntoa (new_map->from.sin_addr), + ntohs (new_map->from.sin_port)); + + *prev = addrmap->next; + free (addrmap); + } + break; + } + + prev = &addrmap->next; + addrmap = addrmap->next; + } + + // Insert it + new_map->next = *prev; + *prev = new_map; + + MsgPrint (MSG_NORMAL, "Address \"%s\" mapped to \"%s\" (%s:%hu)\n", + new_map->from_string, new_map->to_string, + inet_ntoa (new_map->to.sin_addr), ntohs (new_map->to.sin_port)); +} + + +/* +==================== +Sv_GetAddrmap + +Look for an address mapping corresponding to addr +==================== +*/ +static const addrmap_t* Sv_GetAddrmap (const struct sockaddr_in* addr) +{ + const addrmap_t* addrmap = addrmaps; + const addrmap_t* found = NULL; + + // Stop at the end of the list, or if the addresses become too high + while (addrmap != NULL && + addrmap->from.sin_addr.s_addr <= addr->sin_addr.s_addr) + { + // If it's the right address + if (addrmap->from.sin_addr.s_addr == addr->sin_addr.s_addr) + { + // If the exact mapping isn't there + if (addrmap->from.sin_port > addr->sin_port) + return found; + + // If we found the exact address + if (addrmap->from.sin_port == addr->sin_port) + return addrmap; + + // General mapping + // Store it in case we don't find the exact address mapping + if (addrmap->from.sin_port == 0) + found = addrmap; + } + + addrmap = addrmap->next; + } + + return found; +} + + +/* +==================== +Sv_ResolveAddrmap + +Resolve an addrmap structure and check the parameters validity +==================== +*/ +static qboolean Sv_ResolveAddrmap (addrmap_t* addrmap) +{ + // Resolve the addresses + if (!Sv_ResolveAddr (addrmap->from_string, &addrmap->from) || + !Sv_ResolveAddr (addrmap->to_string, &addrmap->to)) + return qfalse; + + // 0.0.0.0 addresses are forbidden + if (addrmap->from.sin_addr.s_addr == 0 || + addrmap->to.sin_addr.s_addr == 0) + { + MsgPrint (MSG_ERROR, + "ERROR: Mapping from or to 0.0.0.0 is forbidden\n"); + return qfalse; + } + + // Do NOT allow mapping to loopback addresses + if ((ntohl (addrmap->to.sin_addr.s_addr) >> 24) == 127) + { + MsgPrint (MSG_ERROR, + "ERROR: Mapping to a loopback address is forbidden\n"); + return qfalse; + } + + return qtrue; +} + + +/* +==================== +Sv_IsActive + +Return qtrue if a server is active. +Test if the server has timed out and remove it if it's the case. +==================== +*/ +static qboolean Sv_IsActive (server_t* server) +{ + + // If the entry isn't even used + if (! server->active) + return qfalse; + + // If the server has timed out + if (server->timeout < crt_time) + { + unsigned int hash; + server_t **prev, *sv; + + hash = Sv_AddressHash (&server->address); + prev = &hash_table[hash]; + sv = hash_table[hash]; + + while (sv != server) + { + prev = &sv->next; + sv = sv->next; + } + + Sv_RemoveAndGetNextPtr (sv, prev); + return qfalse; + } + + return qtrue; +} + + +// ---------- Public functions (servers) ---------- // + +/* +==================== +Sv_SetHashSize + +Set a new hash size value +==================== +*/ +qboolean Sv_SetHashSize (unsigned int size) +{ + // Too late? + if (hash_table != NULL) + return qfalse; + + // Too big? + if (size > MAX_HASH_SIZE) + return qfalse; + + hash_table_size = 1 << size; + return qtrue; +} + + +/* +==================== +Sv_SetMaxNbServers + +Set a new hash size value +==================== +*/ +qboolean Sv_SetMaxNbServers (unsigned int nb) +{ + // Too late? + if (servers != NULL) + return qfalse; + + max_nb_servers = nb; + return qtrue; +} + + +/* +==================== +Sv_Init + +Initialize the server list and hash table +==================== +*/ +qboolean Sv_Init (void) +{ + size_t array_size; + + // Allocate "servers" and clean it + array_size = max_nb_servers * sizeof (servers[0]); + servers = malloc (array_size); + if (!servers) + { + MsgPrint (MSG_ERROR, + "ERROR: can't allocate the servers array (%s)\n", + strerror (errno)); + return qfalse; + } + last_alloc = max_nb_servers - 1; + memset (servers, 0, array_size); + MsgPrint (MSG_NORMAL, "%u server records allocated\n", max_nb_servers); + + // Allocate "hash_table" and clean it + array_size = hash_table_size * sizeof (hash_table[0]); + hash_table = malloc (array_size); + if (!hash_table) + { + MsgPrint (MSG_ERROR, "ERROR: can't allocate the hash table (%s)\n", + strerror (errno)); + free (servers); + return qfalse; + } + memset (hash_table, 0, array_size); + MsgPrint (MSG_NORMAL, + "Hash table allocated (%u entries)\n", hash_table_size); + + return qtrue; +} + + +/* +==================== +Sv_GetByAddr + +Search for a particular server in the list; add it if necessary +==================== +*/ +server_t* Sv_GetByAddr (const struct sockaddr_in* address, qboolean add_it) +{ + server_t **prev, *sv; + unsigned int hash; + const addrmap_t* addrmap = Sv_GetAddrmap (address); + unsigned int startpt; + + // Allow servers on a loopback address ONLY if a mapping is defined for them + if ((ntohl (address->sin_addr.s_addr) >> 24) == 127 && addrmap == NULL) + { + MsgPrint (MSG_WARNING, + "WARNING: server %s isn't allowed (loopback address)\n", + peer_address); + return NULL; + } + + hash = Sv_AddressHash (address); + prev = &hash_table[hash]; + sv = hash_table[hash]; + + while (sv != NULL) + { + // We check the timeout values while browsing this list + if (sv->timeout < crt_time) + { + sv = Sv_RemoveAndGetNextPtr (sv, prev); + continue; + } + + // Found! + if (sv->address.sin_addr.s_addr == address->sin_addr.s_addr && + sv->address.sin_port == address->sin_port) + { + // Put it on top of the list (it's useful because heartbeats + // are almost always followed by infoResponses) + *prev = sv->next; + sv->next = hash_table[hash]; + hash_table[hash] = sv; + + return sv; + } + + prev = &sv->next; + sv = sv->next; + } + + if (! add_it) + return NULL; + + // Look for the first free entry in "servers" + startpt = last_alloc; + for (;;) + { + last_alloc = (last_alloc + 1) % max_nb_servers; + + // Free entry found? + if (!Sv_IsActive (&servers[last_alloc])) + break; + + // No more room + if (last_alloc == startpt) + return NULL; + } + sv = &servers[last_alloc]; + + // Initialize the structure + memset (sv, 0, sizeof (*sv)); + memcpy (&sv->address, address, sizeof (sv->address)); + sv->addrmap = addrmap; + + // Add it to the list it belongs to + sv->next = hash_table[hash]; + hash_table[hash] = sv; + nb_servers++; + + MsgPrint (MSG_NORMAL, + "New server added: %s; %u servers are currently registered\n", + peer_address, nb_servers); + MsgPrint (MSG_DEBUG, + " - index: %u\n" + " - hash: 0x%02X\n", + last_alloc, hash); + + return sv; +} + + +/* +==================== +Sv_GetFirst + +Get the first server in the list +==================== +*/ +server_t* Sv_GetFirst (void) +{ + crt_server = NULL; + prev_pointer = NULL; + crt_hash_ind = -1; + + return Sv_GetNext (); +} + + +/* +==================== +Sv_GetNext + +Get the next server in the list +==================== +*/ +server_t* Sv_GetNext (void) +{ + for (;;) + { + // If there is a current server, follow the link + if (crt_server != NULL) + { + prev_pointer = &crt_server->next; + crt_server = crt_server->next; + } + + // If we don't have the next server yet + if (crt_server == NULL) + { + // Search the hash table for the next server + while (crt_hash_ind < (int)(hash_table_size - 1)) + { + crt_hash_ind++; + + if (hash_table[crt_hash_ind] != NULL) + { + crt_server = hash_table[crt_hash_ind]; + prev_pointer = &hash_table[crt_hash_ind]; + break; + } + } + } + + // Did we hit the end of the list? + if (crt_server == NULL) + return NULL; + + // If the new current server has timed out, remove it + if (crt_server->timeout < crt_time) + crt_server = Sv_RemoveAndGetNextPtr (crt_server, prev_pointer); + else + return crt_server; + } +} + + +// ---------- Public functions (address mappings) ---------- // + +/* +==================== +Sv_AddAddressMapping + +Add an unresolved address mapping to the list +mapping must be of the form "addr1:port1=addr2:port2", ":portX" are optional +==================== +*/ +qboolean Sv_AddAddressMapping (const char* mapping) +{ + char *map_string, *to_ip; + addrmap_t* addrmap; + + // Get a working copy of the mapping string + map_string = strdup (mapping); + if (map_string == NULL) + { + MsgPrint (MSG_ERROR, + "ERROR: can't allocate address mapping string\n"); + return qfalse; + } + + // Find the '=' + to_ip = strchr (map_string, '='); + if (to_ip == NULL) + { + MsgPrint (MSG_ERROR, + "ERROR: invalid syntax in address mapping string\n"); + free (map_string); + return qfalse; + } + *to_ip++ = '\0'; + + // Allocate the structure + addrmap = malloc (sizeof (*addrmap)); + if (addrmap == NULL) + { + MsgPrint (MSG_ERROR, + "ERROR: can't allocate address mapping structure\n"); + free (map_string); + return qfalse; + } + memset (addrmap, 0, sizeof (*addrmap)); + addrmap->from_string = strdup (map_string); + addrmap->to_string = strdup (to_ip); + if (addrmap->from_string == NULL || addrmap->to_string == NULL) + { + MsgPrint (MSG_ERROR, + "ERROR: can't allocate address mapping strings\n"); + free (addrmap->to_string); + free (addrmap->from_string); + free (map_string); + return qfalse; + } + + // Add it on top of "addrmaps" + addrmap->next = addrmaps; + addrmaps = addrmap; + + return qtrue; +} + + +/* +==================== +Sv_ResolveAddressMappings + +Resolve the address mapping list +==================== +*/ +qboolean Sv_ResolveAddressMappings (void) +{ + addrmap_t* unresolved = addrmaps; + addrmap_t* addrmap; + qboolean succeeded = qtrue; + + addrmaps = NULL; + + while (unresolved != NULL) + { + // Remove it from the unresolved list + addrmap = unresolved; + unresolved = unresolved->next; + + // Continue the resolution, even if there's an error + if (!Sv_ResolveAddrmap (addrmap)) + { + free (addrmap->from_string); + free (addrmap->to_string); + free (addrmap); + succeeded = qfalse; + } + else + Sv_InsertAddrmapIntoList (addrmap); + } + + return succeeded; +} diff --git a/src/master/servers.h b/src/master/servers.h new file mode 100644 index 0000000..5864c03 --- /dev/null +++ b/src/master/servers.h @@ -0,0 +1,106 @@ +/* + servers.h + + Server list and address mapping management for dpmaster + + Copyright (C) 2004-2005 Mathieu Olivier + + 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 _SERVERS_H_ +#define _SERVERS_H_ + + +// ---------- Constants ---------- // + +// Maximum number of servers in all lists by default +#define DEFAULT_MAX_NB_SERVERS 1024 + +// Address hash size in bits (between 0 and MAX_HASH_SIZE) +#define DEFAULT_HASH_SIZE 6 +#define MAX_HASH_SIZE 8 + +// Number of characters in a challenge, including the '\0' +#define CHALLENGE_MIN_LENGTH 9 +#define CHALLENGE_MAX_LENGTH 12 + +// Minimum number of seconds between gamestat messages per server +#define MIN_GAMESTAT_DELAY 120 + +// ---------- Types ---------- // + +// Address mapping +typedef struct addrmap_s +{ + struct addrmap_s* next; + struct sockaddr_in from; + struct sockaddr_in to; + char* from_string; + char* to_string; +} addrmap_t; + +// Server properties +typedef struct server_s +{ + struct server_s* next; + struct sockaddr_in address; + unsigned int protocol; + char challenge [CHALLENGE_MAX_LENGTH]; + unsigned short nbclients; + unsigned short maxclients; + time_t timeout; + time_t challenge_timeout; + const struct addrmap_s* addrmap; + qboolean active; + time_t lastGameStat; +} server_t; + + +// ---------- Public functions (servers) ---------- // + +// Will simply return "false" if called after Sv_Init +qboolean Sv_SetHashSize (unsigned int size); +qboolean Sv_SetMaxNbServers (unsigned int nb); + +// Initialize the server list and hash table +qboolean Sv_Init (void); + +// Search for a particular server in the list; add it if necessary +// NOTE: doesn't change the current position for "Sv_GetNext" +server_t* Sv_GetByAddr (const struct sockaddr_in* address, qboolean add_it); + +// Get the first server in the list +server_t* Sv_GetFirst (void); + +// Get the next server in the list +server_t* Sv_GetNext (void); + + +// ---------- Public functions (address mappings) ---------- // + +// NOTE: this is a 2-step process because resolving address mappings directly +// during the parsing of the command line would cause several problems + +// Add an unresolved address mapping to the list +// mapping must be of the form "addr1:port1=addr2:port2", ":portX" are optional +qboolean Sv_AddAddressMapping (const char* mapping); + +// Resolve the address mapping list +qboolean Sv_ResolveAddressMappings (void); + + +#endif // #ifndef _SERVERS_H_ diff --git a/src/master/stats.c b/src/master/stats.c new file mode 100644 index 0000000..b4ea5f8 --- /dev/null +++ b/src/master/stats.c @@ -0,0 +1,136 @@ +/* +stats.c + +Statistics for tremmaster + +Copyright (C) 2009 Darklegion Development + +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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define MAX_DATA_SIZE 1024 +#define CS_FILENAME "clientStats.tdb" + +/* +==================== +RecordClientStat +==================== +*/ +void RecordClientStat( const char *address, const char *version, const char *renderer ) +{ + TDB_CONTEXT *tctx = NULL; + TDB_DATA key, data; + char ipText[ 22 ]; + char dataText[ MAX_DATA_SIZE ] = { 0 }; + char *p; + int i; + + tctx = tdb_open( CS_FILENAME, 0, 0, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR ); + + if( !tctx ) + { + MsgPrint( MSG_DEBUG, "Couldn't open %s\n", CS_FILENAME ); + return; + } + + strncpy( ipText, address, 22 ); + if( ( p = strrchr( ipText, ':' ) ) ) // Remove port + *p = '\0'; + + key.dptr = ipText; + key.dsize = strlen( ipText ); + + strncat( dataText, "\"", MAX_DATA_SIZE ); + strncat( dataText, version, MAX_DATA_SIZE ); + + // Remove last three tokens (the date) + for( i = 0; i < 3; i++ ) + { + if( ( p = strrchr( dataText, ' ' ) ) ) + *p = '\0'; + } + strncat( dataText, "\"", MAX_DATA_SIZE ); + + strncat( dataText, " \"", MAX_DATA_SIZE ); + strncat( dataText, renderer, MAX_DATA_SIZE ); + strncat( dataText, "\"", MAX_DATA_SIZE ); + + data.dptr = dataText; + data.dsize = strlen( dataText ); + + if( tdb_store( tctx, key, data, 0 ) < 0 ) + MsgPrint( MSG_DEBUG, "tdb_store failed\n" ); + + tdb_close( tctx ); + MsgPrint( MSG_DEBUG, "Recorded client stat for %s\n", address ); +} + +#define GS_FILENAME "gameStats.tdb" + +/* +==================== +RecordGameStat +==================== +*/ +void RecordGameStat( const char *address, const char *dataText ) +{ + TDB_CONTEXT *tctx = NULL; + TDB_DATA key, data; + char keyText[ MAX_DATA_SIZE ] = { 0 }; + char *p; + time_t tm = time( NULL ); + + tctx = tdb_open( GS_FILENAME, 0, 0, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR ); + + if( !tctx ) + { + MsgPrint( MSG_DEBUG, "Couldn't open %s\n", GS_FILENAME ); + return; + } + + strncpy( keyText, address, 22 ); + if( ( p = strrchr( keyText, ':' ) ) ) // Remove port + *p = '\0'; + + strncat( keyText, " ", MAX_DATA_SIZE ); + strncat( keyText, asctime( gmtime( &tm ) ), MAX_DATA_SIZE ); + + key.dptr = keyText; + key.dsize = strlen( keyText ); + + data.dptr = (char *)dataText; + data.dsize = strlen( dataText ); + + if( tdb_store( tctx, key, data, 0 ) < 0 ) + MsgPrint( MSG_DEBUG, "tdb_store failed\n" ); + + tdb_close( tctx ); + MsgPrint( MSG_NORMAL, "Recorded game stat from %s\n", address ); +} + +#endif diff --git a/src/null/mac_net.c b/src/null/mac_net.c new file mode 100644 index 0000000..cca208b --- /dev/null +++ b/src/null/mac_net.c @@ -0,0 +1,55 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +/* +============= +NET_StringToAdr + +localhost +idnewt +idnewt:28000 +192.246.40.70 +192.246.40.70:28000 +============= +*/ +qboolean NET_StringToAdr (char *s, netadr_t *a) +{ + if (!strcmp (s, "localhost")) { + memset (a, 0, sizeof(*a)); + a->type = NA_LOOPBACK; + return true; + } + + return false; +} + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket( int length, void *data, netadr_t to ) { +} diff --git a/src/null/null_client.c b/src/null/null_client.c new file mode 100644 index 0000000..aca1406 --- /dev/null +++ b/src/null/null_client.c @@ -0,0 +1,93 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +cvar_t *cl_shownet; + +void CL_Shutdown( char *finalmsg ) { +} + +void CL_Init( void ) { + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); +} + +void CL_MouseEvent( int dx, int dy, int time ) { +} + +void Key_WriteBindings( fileHandle_t f ) { +} + +void CL_Frame ( int msec ) { +} + +void CL_PacketEvent( netadr_t from, msg_t *msg ) { +} + +void CL_CharEvent( int key ) { +} + +void CL_Disconnect( qboolean showMainMenu ) { +} + +void CL_MapLoading( void ) { +} + +qboolean CL_GameCommand( void ) { + return qfalse; +} + +void CL_KeyEvent (int key, qboolean down, unsigned time) { +} + +qboolean UI_GameCommand( void ) { + return qfalse; +} + +void CL_ForwardCommandToServer( const char *string ) { +} + +void CL_ConsolePrint( char *txt ) { +} + +void CL_JoystickEvent( int axis, int value, int time ) { +} + +void CL_InitKeyCommands( void ) { +} + +void CL_CDDialog( void ) { +} + +void CL_FlushMemory( void ) { +} + +void CL_StartHunkUsers( qboolean rendererOnly ) { +} + +void CL_Snd_Restart(void) +{ +} + +void CL_ShutdownAll(void) {} diff --git a/src/null/null_glimp.c b/src/null/null_glimp.c new file mode 100644 index 0000000..aae34eb --- /dev/null +++ b/src/null/null_glimp.c @@ -0,0 +1,57 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "../renderer/tr_local.h" + + +qboolean ( * qwglSwapIntervalEXT)( int interval ); +void ( * qglMultiTexCoord2fARB )( GLenum texture, float s, float t ); +void ( * qglActiveTextureARB )( GLenum texture ); +void ( * qglClientActiveTextureARB )( GLenum texture ); + + +void ( * qglLockArraysEXT)( int, int); +void ( * qglUnlockArraysEXT) ( void ); + + +void GLimp_EndFrame( void ) { +} + +int GLimp_Init( void ) +{ +} + +void GLimp_Shutdown( void ) { +} + +void GLimp_EnableLogging( qboolean enable ) { +} + +void GLimp_LogComment( char *comment ) { +} + +qboolean QGL_Init( const char *dllname ) { + return qtrue; +} + +void QGL_Shutdown( void ) { +} diff --git a/src/null/null_input.c b/src/null/null_input.c new file mode 100644 index 0000000..d6cf79a --- /dev/null +++ b/src/null/null_input.c @@ -0,0 +1,38 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +void IN_Init( void ) { +} + +void IN_Frame (void) { +} + +void IN_Shutdown( void ) { +} + +void IN_Restart( void ) { +} + +void Sys_SendKeyEvents (void) { +} + diff --git a/src/null/null_main.c b/src/null/null_main.c new file mode 100644 index 0000000..ae77595 --- /dev/null +++ b/src/null/null_main.c @@ -0,0 +1,117 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// sys_null.h -- null system driver to aid porting efforts + +#include +#include +#include "../qcommon/qcommon.h" + +int sys_curtime; + + +//=================================================================== + +void Sys_BeginStreamedFile( FILE *f, int readAhead ) { +} + +void Sys_EndStreamedFile( FILE *f ) { +} + +int Sys_StreamedRead( void *buffer, int size, int count, FILE *f ) { + return fread( buffer, size, count, f ); +} + +void Sys_StreamSeek( FILE *f, int offset, int origin ) { + fseek( f, offset, origin ); +} + + +//=================================================================== + + +void Sys_mkdir ( const char *path ) { +} + +void Sys_Error (char *error, ...) { + va_list argptr; + + printf ("Sys_Error: "); + va_start (argptr,error); + vprintf (error,argptr); + va_end (argptr); + printf ("\n"); + + exit (1); +} + +void Sys_Quit (void) { + exit (0); +} + +void Sys_UnloadGame (void) { +} + +void *Sys_GetGameAPI (void *parms) { + return NULL; +} + +char *Sys_GetClipboardData( void ) { + return NULL; +} + +int Sys_Milliseconds (void) { + return 0; +} + +void Sys_Mkdir (char *path) { +} + +char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave) { + return NULL; +} + +char *Sys_FindNext (unsigned musthave, unsigned canthave) { + return NULL; +} + +void Sys_FindClose (void) { +} + +void Sys_Init (void) { +} + + +void Sys_EarlyOutput( char *string ) { + printf( "%s", string ); +} + + +void main (int argc, char **argv) { + Com_Init (argc, argv); + + while (1) { + Com_Frame( ); + } +} + + diff --git a/src/null/null_net.c b/src/null/null_net.c new file mode 100644 index 0000000..9562eac --- /dev/null +++ b/src/null/null_net.c @@ -0,0 +1,54 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/qcommon.h" + +/* +============= +NET_StringToAdr + +localhost +idnewt +idnewt:28000 +192.246.40.70 +192.246.40.70:28000 +============= +*/ +qboolean NET_StringToAdr (char *s, netadr_t *a) +{ + if (!strcmp (s, "localhost")) { + memset (a, 0, sizeof(*a)); + a->type = NA_LOOPBACK; + return true; + } + + return false; +} + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket( int length, void *data, netadr_t to ) { +} diff --git a/src/null/null_snddma.c b/src/null/null_snddma.c new file mode 100644 index 0000000..2dd5e33 --- /dev/null +++ b/src/null/null_snddma.c @@ -0,0 +1,61 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// snddma_null.c +// all other sound mixing is portable + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +qboolean SNDDMA_Init(void) +{ + return qfalse; +} + +int SNDDMA_GetDMAPos(void) +{ + return 0; +} + +void SNDDMA_Shutdown(void) +{ +} + +void SNDDMA_BeginPainting (void) +{ +} + +void SNDDMA_Submit(void) +{ +} + +sfxHandle_t S_RegisterSound( const char *name, qboolean compressed ) +{ + return 0; +} + +void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { +} + +void S_ClearSoundBuffer( void ) { +} diff --git a/src/qcommon/cm_load.c b/src/qcommon/cm_load.c new file mode 100644 index 0000000..230b18b --- /dev/null +++ b/src/qcommon/cm_load.c @@ -0,0 +1,1024 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cmodel.c -- model loading + +#include "cm_local.h" + +#ifdef BSPC + +#include "../bspc/l_qfiles.h" + +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0) { + bits |= 1<signbits = bits; +} +#endif //BSPC + +// to allow boxes to be treated as brush models, we allocate +// some extra indexes along with those needed by the map +#define BOX_BRUSHES 1 +#define BOX_SIDES 6 +#define BOX_LEAFS 2 +#define BOX_PLANES 12 + +#define LL(x) x=LittleLong(x) + + +clipMap_t cm; +int c_pointcontents; +int c_traces, c_brush_traces, c_patch_traces; + + +byte *cmod_base; + +#ifndef BSPC +cvar_t *cm_noAreas; +cvar_t *cm_noCurves; +cvar_t *cm_playerCurveClip; +#endif + +cmodel_t box_model; +cplane_t *box_planes; +cbrush_t *box_brush; + + + +void CM_InitBoxHull (void); +void CM_FloodAreaConnections (void); + + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ + +/* +================= +CMod_LoadShaders +================= +*/ +void CMod_LoadShaders( lump_t *l ) { + dshader_t *in, *out; + int i, count; + + in = (void *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) { + Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size"); + } + count = l->filelen / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no shaders"); + } + cm.shaders = Hunk_Alloc( count * sizeof( *cm.shaders ), h_high ); + cm.numShaders = count; + + Com_Memcpy( cm.shaders, in, count * sizeof( *cm.shaders ) ); + + out = cm.shaders; + for ( i=0 ; icontentFlags = LittleLong( out->contentFlags ); + out->surfaceFlags = LittleLong( out->surfaceFlags ); + } +} + + +/* +================= +CMod_LoadSubmodels +================= +*/ +void CMod_LoadSubmodels( lump_t *l ) { + dmodel_t *in; + cmodel_t *out; + int i, j, count; + int *indexes; + + in = (void *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no models"); + cm.cmodels = Hunk_Alloc( count * sizeof( *cm.cmodels ), h_high ); + cm.numSubModels = count; + + if ( count > MAX_SUBMODELS ) { + Com_Error( ERR_DROP, "MAX_SUBMODELS exceeded" ); + } + + for ( i=0 ; imins[j] = LittleFloat (in->mins[j]) - 1; + out->maxs[j] = LittleFloat (in->maxs[j]) + 1; + } + + if ( i == 0 ) { + continue; // world model doesn't need other info + } + + // make a "leaf" just to hold the model's brushes and surfaces + out->leaf.numLeafBrushes = LittleLong( in->numBrushes ); + indexes = Hunk_Alloc( out->leaf.numLeafBrushes * 4, h_high ); + out->leaf.firstLeafBrush = indexes - cm.leafbrushes; + for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) { + indexes[j] = LittleLong( in->firstBrush ) + j; + } + + out->leaf.numLeafSurfaces = LittleLong( in->numSurfaces ); + indexes = Hunk_Alloc( out->leaf.numLeafSurfaces * 4, h_high ); + out->leaf.firstLeafSurface = indexes - cm.leafsurfaces; + for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) { + indexes[j] = LittleLong( in->firstSurface ) + j; + } + } +} + + +/* +================= +CMod_LoadNodes + +================= +*/ +void CMod_LoadNodes( lump_t *l ) { + dnode_t *in; + int child; + cNode_t *out; + int i, j, count; + + in = (void *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map has no nodes"); + cm.nodes = Hunk_Alloc( count * sizeof( *cm.nodes ), h_high ); + cm.numNodes = count; + + out = cm.nodes; + + for (i=0 ; iplane = cm.planes + LittleLong( in->planeNum ); + for (j=0 ; j<2 ; j++) + { + child = LittleLong (in->children[j]); + out->children[j] = child; + } + } + +} + +/* +================= +CM_BoundBrush + +================= +*/ +void CM_BoundBrush( cbrush_t *b ) { + b->bounds[0][0] = -b->sides[0].plane->dist; + b->bounds[1][0] = b->sides[1].plane->dist; + + b->bounds[0][1] = -b->sides[2].plane->dist; + b->bounds[1][1] = b->sides[3].plane->dist; + + b->bounds[0][2] = -b->sides[4].plane->dist; + b->bounds[1][2] = b->sides[5].plane->dist; +} + + +/* +================= +CMod_LoadBrushes + +================= +*/ +void CMod_LoadBrushes( lump_t *l ) { + dbrush_t *in; + cbrush_t *out; + int i, count; + + in = (void *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = l->filelen / sizeof(*in); + + cm.brushes = Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), h_high ); + cm.numBrushes = count; + + out = cm.brushes; + + for ( i=0 ; isides = cm.brushsides + LittleLong(in->firstSide); + out->numsides = LittleLong(in->numSides); + + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum ); + } + out->contents = cm.shaders[out->shaderNum].contentFlags; + + CM_BoundBrush( out ); + } + +} + +/* +================= +CMod_LoadLeafs +================= +*/ +void CMod_LoadLeafs (lump_t *l) +{ + int i; + cLeaf_t *out; + dleaf_t *in; + int count; + + in = (void *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no leafs"); + + cm.leafs = Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), h_high ); + cm.numLeafs = count; + + out = cm.leafs; + for ( i=0 ; icluster = LittleLong (in->cluster); + out->area = LittleLong (in->area); + out->firstLeafBrush = LittleLong (in->firstLeafBrush); + out->numLeafBrushes = LittleLong (in->numLeafBrushes); + out->firstLeafSurface = LittleLong (in->firstLeafSurface); + out->numLeafSurfaces = LittleLong (in->numLeafSurfaces); + + if (out->cluster >= cm.numClusters) + cm.numClusters = out->cluster + 1; + if (out->area >= cm.numAreas) + cm.numAreas = out->area + 1; + } + + cm.areas = Hunk_Alloc( cm.numAreas * sizeof( *cm.areas ), h_high ); + cm.areaPortals = Hunk_Alloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), h_high ); +} + +/* +================= +CMod_LoadPlanes +================= +*/ +void CMod_LoadPlanes (lump_t *l) +{ + int i, j; + cplane_t *out; + dplane_t *in; + int count; + int bits; + + in = (void *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no planes"); + cm.planes = Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), h_high ); + cm.numPlanes = count; + + out = cm.planes; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +CMod_LoadLeafBrushes +================= +*/ +void CMod_LoadLeafBrushes (lump_t *l) +{ + int i; + int *out; + int *in; + int count; + + in = (void *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + cm.leafbrushes = Hunk_Alloc( (count + BOX_BRUSHES) * sizeof( *cm.leafbrushes ), h_high ); + cm.numLeafBrushes = count; + + out = cm.leafbrushes; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + cm.leafsurfaces = Hunk_Alloc( count * sizeof( *cm.leafsurfaces ), h_high ); + cm.numLeafSurfaces = count; + + out = cm.leafsurfaces; + + for ( i=0 ; ifileofs); + if ( l->filelen % sizeof(*in) ) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = l->filelen / sizeof(*in); + + cm.brushsides = Hunk_Alloc( ( BOX_SIDES + count ) * sizeof( *cm.brushsides ), h_high ); + cm.numBrushSides = count; + + out = cm.brushsides; + + for ( i=0 ; iplaneNum ); + out->planeNum = num; + out->plane = &cm.planes[num]; + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum ); + } + out->surfaceFlags = cm.shaders[out->shaderNum].surfaceFlags; + } +} + +#define CM_EDGE_VERTEX_EPSILON 0.1f + +/* +================= +CMod_BrushEdgesAreTheSame +================= +*/ +static qboolean CMod_BrushEdgesAreTheSame( const vec3_t p0, const vec3_t p1, + const vec3_t q0, const vec3_t q1 ) +{ + if( VectorCompareEpsilon( p0, q0, CM_EDGE_VERTEX_EPSILON ) && + VectorCompareEpsilon( p1, q1, CM_EDGE_VERTEX_EPSILON ) ) + return qtrue; + + if( VectorCompareEpsilon( p1, q0, CM_EDGE_VERTEX_EPSILON ) && + VectorCompareEpsilon( p0, q1, CM_EDGE_VERTEX_EPSILON ) ) + return qtrue; + + return qfalse; +} + +/* +================= +CMod_AddEdgeToBrush +================= +*/ +static qboolean CMod_AddEdgeToBrush( const vec3_t p0, const vec3_t p1, + cbrushedge_t *edges, int *numEdges ) +{ + int i; + + if( !edges || !numEdges ) + return qfalse; + + for( i = 0; i < *numEdges; i++ ) + { + if( CMod_BrushEdgesAreTheSame( p0, p1, + edges[ i ].p0, edges[ i ].p1 ) ) + return qfalse; + } + + VectorCopy( p0, edges[ *numEdges ].p0 ); + VectorCopy( p1, edges[ *numEdges ].p1 ); + (*numEdges)++; + + return qtrue; +} + +/* +================= +CMod_CreateBrushSideWindings +================= +*/ +static void CMod_CreateBrushSideWindings( void ) +{ + int i, j, k; + winding_t *w; + cbrushside_t *side, *chopSide; + cplane_t *plane; + cbrush_t *brush; + cbrushedge_t *tempEdges; + int numEdges; + int edgesAlloc; + int totalEdgesAlloc = 0; + int totalEdges = 0; + + for( i = 0; i < cm.numBrushes; i++ ) + { + brush = &cm.brushes[ i ]; + numEdges = 0; + + // walk the list of brush sides + for( j = 0; j < brush->numsides; j++ ) + { + // get side and plane + side = &brush->sides[ j ]; + plane = side->plane; + + w = BaseWindingForPlane( plane->normal, plane->dist ); + + // walk the list of brush sides + for( k = 0; k < brush->numsides && w != NULL; k++ ) + { + chopSide = &brush->sides[ k ]; + + if( chopSide == side ) + continue; + + if( chopSide->planeNum == ( side->planeNum ^ 1 ) ) + continue; // back side clipaway + + plane = &cm.planes[ chopSide->planeNum ^ 1 ]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); + } + + if( w ) + numEdges += w->numpoints; + + // set side winding + side->winding = w; + } + + // Allocate a temporary buffer of the maximal size + tempEdges = (cbrushedge_t *)Z_Malloc( sizeof( cbrushedge_t ) * numEdges ); + brush->numEdges = 0; + + // compose the points into edges + for( j = 0; j < brush->numsides; j++ ) + { + side = &brush->sides[ j ]; + + if( side->winding ) + { + for( k = 0; k < side->winding->numpoints - 1; k++ ) + { + if( brush->numEdges == numEdges ) + Com_Error( ERR_FATAL, + "Insufficient memory allocated for collision map edges" ); + + CMod_AddEdgeToBrush( side->winding->p[ k ], + side->winding->p[ k + 1 ], tempEdges, &brush->numEdges ); + } + + FreeWinding( side->winding ); + side->winding = NULL; + } + } + + // Allocate a buffer of the actual size + edgesAlloc = sizeof( cbrushedge_t ) * brush->numEdges; + totalEdgesAlloc += edgesAlloc; + brush->edges = (cbrushedge_t *)Hunk_Alloc( edgesAlloc, h_low ); + + // Copy temporary buffer to permanent buffer + Com_Memcpy( brush->edges, tempEdges, edgesAlloc ); + + // Free temporary buffer + Z_Free( tempEdges ); + + totalEdges += brush->numEdges; + } + + Com_DPrintf( "Allocated %d bytes for %d collision map edges...\n", + totalEdgesAlloc, totalEdges ); +} + +/* +================= +CMod_LoadEntityString +================= +*/ +void CMod_LoadEntityString( lump_t *l ) { + cm.entityString = Hunk_Alloc( l->filelen, h_high ); + cm.numEntityChars = l->filelen; + Com_Memcpy (cm.entityString, cmod_base + l->fileofs, l->filelen); +} + +/* +================= +CMod_LoadVisibility +================= +*/ +#define VIS_HEADER 8 +void CMod_LoadVisibility( lump_t *l ) { + int len; + byte *buf; + + len = l->filelen; + if ( !len ) { + cm.clusterBytes = ( cm.numClusters + 31 ) & ~31; + cm.visibility = Hunk_Alloc( cm.clusterBytes, h_high ); + Com_Memset( cm.visibility, 255, cm.clusterBytes ); + return; + } + buf = cmod_base + l->fileofs; + + cm.vised = qtrue; + cm.visibility = Hunk_Alloc( len, h_high ); + cm.numClusters = LittleLong( ((int *)buf)[0] ); + cm.clusterBytes = LittleLong( ((int *)buf)[1] ); + Com_Memcpy (cm.visibility, buf + VIS_HEADER, len - VIS_HEADER ); +} + +//================================================================== + + +/* +================= +CMod_LoadPatches +================= +*/ +#define MAX_PATCH_VERTS 1024 +void CMod_LoadPatches( lump_t *surfs, lump_t *verts ) { + drawVert_t *dv, *dv_p; + dsurface_t *in; + int count; + int i, j; + int c; + cPatch_t *patch; + vec3_t points[MAX_PATCH_VERTS]; + int width, height; + int shaderNum; + + in = (void *)(cmod_base + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + cm.numSurfaces = count = surfs->filelen / sizeof(*in); + cm.surfaces = Hunk_Alloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), h_high ); + + dv = (void *)(cmod_base + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + + // scan through all the surfaces, but only load patches, + // not planar faces + for ( i = 0 ; i < count ; i++, in++ ) { + if ( LittleLong( in->surfaceType ) != MST_PATCH ) { + continue; // ignore other surfaces + } + // FIXME: check for non-colliding patches + + cm.surfaces[ i ] = patch = Hunk_Alloc( sizeof( *patch ), h_high ); + + // load the full drawverts onto the stack + width = LittleLong( in->patchWidth ); + height = LittleLong( in->patchHeight ); + c = width * height; + if ( c > MAX_PATCH_VERTS ) { + Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" ); + } + + dv_p = dv + LittleLong( in->firstVert ); + for ( j = 0 ; j < c ; j++, dv_p++ ) { + points[j][0] = LittleFloat( dv_p->xyz[0] ); + points[j][1] = LittleFloat( dv_p->xyz[1] ); + points[j][2] = LittleFloat( dv_p->xyz[2] ); + } + + shaderNum = LittleLong( in->shaderNum ); + patch->contents = cm.shaders[shaderNum].contentFlags; + patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags; + + // create the internal facet structure + patch->pc = CM_GeneratePatchCollide( width, height, points ); + } +} + +//================================================================== + +unsigned CM_LumpChecksum(lump_t *lump) { + return LittleLong (Com_BlockChecksum (cmod_base + lump->fileofs, lump->filelen)); +} + +unsigned CM_Checksum(dheader_t *header) { + unsigned checksums[16]; + checksums[0] = CM_LumpChecksum(&header->lumps[LUMP_SHADERS]); + checksums[1] = CM_LumpChecksum(&header->lumps[LUMP_LEAFS]); + checksums[2] = CM_LumpChecksum(&header->lumps[LUMP_LEAFBRUSHES]); + checksums[3] = CM_LumpChecksum(&header->lumps[LUMP_LEAFSURFACES]); + checksums[4] = CM_LumpChecksum(&header->lumps[LUMP_PLANES]); + checksums[5] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHSIDES]); + checksums[6] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHES]); + checksums[7] = CM_LumpChecksum(&header->lumps[LUMP_MODELS]); + checksums[8] = CM_LumpChecksum(&header->lumps[LUMP_NODES]); + checksums[9] = CM_LumpChecksum(&header->lumps[LUMP_SURFACES]); + checksums[10] = CM_LumpChecksum(&header->lumps[LUMP_DRAWVERTS]); + + return LittleLong(Com_BlockChecksum(checksums, 11 * 4)); +} + +/* +================== +CM_LoadMap + +Loads in the map and all submodels +================== +*/ +void CM_LoadMap( const char *name, qboolean clientload, int *checksum ) { + union { + int *i; + void *v; + } buf; + int i; + dheader_t header; + int length; + static unsigned last_checksum; + + if ( !name || !name[0] ) { + Com_Error( ERR_DROP, "CM_LoadMap: NULL name" ); + } + +#ifndef BSPC + cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT); + cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT); + cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT ); +#endif + Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload ); + + if ( !strcmp( cm.name, name ) && clientload ) { + *checksum = last_checksum; + return; + } + + // free old stuff + Com_Memset( &cm, 0, sizeof( cm ) ); + CM_ClearLevelPatches(); + + if ( !name[0] ) { + cm.numLeafs = 1; + cm.numClusters = 1; + cm.numAreas = 1; + cm.cmodels = Hunk_Alloc( sizeof( *cm.cmodels ), h_high ); + *checksum = 0; + return; + } + + // + // load the file + // +#ifndef BSPC + length = FS_ReadFile( name, &buf.v ); +#else + length = LoadQuakeFile((quakefile_t *) name, &buf.v); +#endif + + if ( !buf.i ) { + Com_Error (ERR_DROP, "Couldn't load %s", name); + } + + last_checksum = LittleLong (Com_BlockChecksum (buf.i, length)); + *checksum = last_checksum; + + header = *(dheader_t *)buf.i; + for (i=0 ; i= cm.numSubModels ) { + Com_Error (ERR_DROP, "CM_InlineModel: bad number"); + } + return index; +} + +int CM_NumClusters( void ) { + return cm.numClusters; +} + +int CM_NumInlineModels( void ) { + return cm.numSubModels; +} + +char *CM_EntityString( void ) { + return cm.entityString; +} + +int CM_LeafCluster( int leafnum ) { + if (leafnum < 0 || leafnum >= cm.numLeafs) { + Com_Error (ERR_DROP, "CM_LeafCluster: bad number"); + } + return cm.leafs[leafnum].cluster; +} + +int CM_LeafArea( int leafnum ) { + if ( leafnum < 0 || leafnum >= cm.numLeafs ) { + Com_Error (ERR_DROP, "CM_LeafArea: bad number"); + } + return cm.leafs[leafnum].area; +} + +//======================================================================= + + +/* +=================== +CM_InitBoxHull + +Set up the planes and nodes so that the six floats of a bounding box +can just be stored out and get a proper clipping hull structure. +=================== +*/ +void CM_InitBoxHull (void) +{ + int i; + int side; + cplane_t *p; + cbrushside_t *s; + + box_planes = &cm.planes[cm.numPlanes]; + + box_brush = &cm.brushes[cm.numBrushes]; + box_brush->numsides = 6; + box_brush->sides = cm.brushsides + cm.numBrushSides; + box_brush->contents = CONTENTS_BODY; + box_brush->edges = (cbrushedge_t *)Hunk_Alloc( + sizeof( cbrushedge_t ) * 12, h_low ); + box_brush->numEdges = 12; + + box_model.leaf.numLeafBrushes = 1; +// box_model.leaf.firstLeafBrush = cm.numBrushes; + box_model.leaf.firstLeafBrush = cm.numLeafBrushes; + cm.leafbrushes[cm.numLeafBrushes] = cm.numBrushes; + + for (i=0 ; i<6 ; i++) + { + side = i&1; + + // brush sides + s = &cm.brushsides[cm.numBrushSides+i]; + s->plane = cm.planes + (cm.numPlanes+i*2+side); + s->surfaceFlags = 0; + + // planes + p = &box_planes[i*2]; + p->type = i>>1; + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = 1; + + p = &box_planes[i*2+1]; + p->type = 3 + (i>>1); + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = -1; + + SetPlaneSignbits( p ); + } +} + +/* +=================== +CM_TempBoxModel + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +Capsules are handled differently though. +=================== +*/ +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ) { + + VectorCopy( mins, box_model.mins ); + VectorCopy( maxs, box_model.maxs ); + + if ( capsule ) { + return CAPSULE_MODEL_HANDLE; + } + + box_planes[0].dist = maxs[0]; + box_planes[1].dist = -maxs[0]; + box_planes[2].dist = mins[0]; + box_planes[3].dist = -mins[0]; + box_planes[4].dist = maxs[1]; + box_planes[5].dist = -maxs[1]; + box_planes[6].dist = mins[1]; + box_planes[7].dist = -mins[1]; + box_planes[8].dist = maxs[2]; + box_planes[9].dist = -maxs[2]; + box_planes[10].dist = mins[2]; + box_planes[11].dist = -mins[2]; + + // First side + VectorSet( box_brush->edges[ 0 ].p0, mins[ 0 ], mins[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 0 ].p1, mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 1 ].p0, mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 1 ].p1, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 2 ].p0, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 2 ].p1, mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 3 ].p0, mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 3 ].p1, mins[ 0 ], mins[ 1 ], mins[ 2 ] ); + + // Opposite side + VectorSet( box_brush->edges[ 4 ].p0, maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 4 ].p1, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 5 ].p0, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 5 ].p1, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 6 ].p0, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 6 ].p1, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 7 ].p0, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 7 ].p1, maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); + + // Connecting edges + VectorSet( box_brush->edges[ 8 ].p0, mins[ 0 ], mins[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 8 ].p1, maxs[ 0 ], mins[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 9 ].p0, mins[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 9 ].p1, maxs[ 0 ], maxs[ 1 ], mins[ 2 ] ); + VectorSet( box_brush->edges[ 10 ].p0, mins[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 10 ].p1, maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 11 ].p0, mins[ 0 ], mins[ 1 ], maxs[ 2 ] ); + VectorSet( box_brush->edges[ 11 ].p1, maxs[ 0 ], mins[ 1 ], maxs[ 2 ] ); + + VectorCopy( mins, box_brush->bounds[0] ); + VectorCopy( maxs, box_brush->bounds[1] ); + + return BOX_MODEL_HANDLE; +} + +/* +=================== +CM_ModelBounds +=================== +*/ +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + VectorCopy( cmod->mins, mins ); + VectorCopy( cmod->maxs, maxs ); +} + + diff --git a/src/qcommon/cm_local.h b/src/qcommon/cm_local.h new file mode 100644 index 0000000..8b93b9f --- /dev/null +++ b/src/qcommon/cm_local.h @@ -0,0 +1,216 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "q_shared.h" +#include "qcommon.h" +#include "cm_polylib.h" + +#define MAX_SUBMODELS 256 +#define BOX_MODEL_HANDLE 255 +#define CAPSULE_MODEL_HANDLE 254 + + +typedef struct { + cplane_t *plane; + int children[2]; // negative numbers are leafs +} cNode_t; + +typedef struct { + int cluster; + int area; + + int firstLeafBrush; + int numLeafBrushes; + + int firstLeafSurface; + int numLeafSurfaces; +} cLeaf_t; + +typedef struct cmodel_s { + vec3_t mins, maxs; + cLeaf_t leaf; // submodels don't reference the main tree +} cmodel_t; + +typedef struct cbrushedge_s +{ + vec3_t p0; + vec3_t p1; +} cbrushedge_t; + +typedef struct { + cplane_t *plane; + int planeNum; + int surfaceFlags; + int shaderNum; + winding_t *winding; +} cbrushside_t; + +typedef struct { + int shaderNum; // the shader that determined the contents + int contents; + vec3_t bounds[2]; + int numsides; + cbrushside_t *sides; + int checkcount; // to avoid repeated testings + qboolean collided; // marker for optimisation + cbrushedge_t *edges; + int numEdges; +} cbrush_t; + + +typedef struct { + int checkcount; // to avoid repeated testings + int surfaceFlags; + int contents; + struct patchCollide_s *pc; +} cPatch_t; + + +typedef struct { + int floodnum; + int floodvalid; +} cArea_t; + +typedef struct { + char name[MAX_QPATH]; + + int numShaders; + dshader_t *shaders; + + int numBrushSides; + cbrushside_t *brushsides; + + int numPlanes; + cplane_t *planes; + + int numNodes; + cNode_t *nodes; + + int numLeafs; + cLeaf_t *leafs; + + int numLeafBrushes; + int *leafbrushes; + + int numLeafSurfaces; + int *leafsurfaces; + + int numSubModels; + cmodel_t *cmodels; + + int numBrushes; + cbrush_t *brushes; + + int numClusters; + int clusterBytes; + byte *visibility; + qboolean vised; // if false, visibility is just a single cluster of ffs + + int numEntityChars; + char *entityString; + + int numAreas; + cArea_t *areas; + int *areaPortals; // [ numAreas*numAreas ] reference counts + + int numSurfaces; + cPatch_t **surfaces; // non-patches will be NULL + + int floodvalid; + int checkcount; // incremented on each trace +} clipMap_t; + + +// keep 1/8 unit away to keep the position valid before network snapping +// and to avoid various numeric issues +#define SURFACE_CLIP_EPSILON (0.125) + +extern clipMap_t cm; +extern int c_pointcontents; +extern int c_traces, c_brush_traces, c_patch_traces; +extern cvar_t *cm_noAreas; +extern cvar_t *cm_noCurves; +extern cvar_t *cm_playerCurveClip; + +// cm_test.c + +typedef struct +{ + float startRadius; + float endRadius; +} biSphere_t; + +// Used for oriented capsule collision detection +typedef struct +{ + float radius; + float halfheight; + vec3_t offset; +} sphere_t; + +typedef struct { + traceType_t type; + vec3_t start; + vec3_t end; + vec3_t size[2]; // size of the box being swept through the model + vec3_t offsets[8]; // [signbits][x] = either size[0][x] or size[1][x] + float maxOffset; // longest corner length from origin + vec3_t extents; // greatest of abs(size[0]) and abs(size[1]) + vec3_t bounds[2]; // enclosing box of start and end surrounding by size + vec3_t modelOrigin;// origin of the model tracing through + int contents; // ored contents of the model tracing through + qboolean isPoint; // optimized case + trace_t trace; // returned from trace call + sphere_t sphere; // sphere for oriendted capsule collision + biSphere_t biSphere; + qboolean testLateralCollision; // whether or not to test for lateral collision +} traceWork_t; + +typedef struct leafList_s { + int count; + int maxcount; + qboolean overflowed; + int *list; + vec3_t bounds[2]; + int lastLeaf; // for overflows where each leaf can't be stored individually + void (*storeLeafs)( struct leafList_s *ll, int nodenum ); +} leafList_t; + + +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ); + +void CM_StoreLeafs( leafList_t *ll, int nodenum ); +void CM_StoreBrushes( leafList_t *ll, int nodenum ); + +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ); + +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle ); +qboolean CM_BoundsIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2 ); +qboolean CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point ); + +// cm_patch.c + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_ClearLevelPatches( void ); diff --git a/src/qcommon/cm_patch.c b/src/qcommon/cm_patch.c new file mode 100644 index 0000000..55e2762 --- /dev/null +++ b/src/qcommon/cm_patch.c @@ -0,0 +1,1773 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "cm_local.h" +#include "cm_patch.h" + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + +/* +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + qboolean borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 +*/ + +int c_totalPatchBlocks; +int c_totalPatchSurfaces; +int c_totalPatchEdges; + +static const patchCollide_t *debugPatchCollide; +static const facet_t *debugFacet; +static qboolean debugBlock; +static vec3_t debugBlockPoints[4]; + +/* +================= +CM_ClearLevelPatches +================= +*/ +void CM_ClearLevelPatches( void ) { + debugPatchCollide = NULL; + debugFacet = NULL; +} + +/* +================= +CM_SignbitsForNormal +================= +*/ +static int CM_SignbitsForNormal( vec3_t normal ) { + int bits, j; + + bits = 0; + for (j=0 ; j<3 ; j++) { + if ( normal[j] < 0 ) { + bits |= 1<= SUBDIVIDE_DISTANCE; +} + +/* +=============== +CM_Subdivide + +a, b, and c are control points. +the subdivided sequence will be: a, out1, out2, out3, c +=============== +*/ +static void CM_Subdivide( vec3_t a, vec3_t b, vec3_t c, vec3_t out1, vec3_t out2, vec3_t out3 ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + out1[i] = 0.5 * (a[i] + b[i]); + out3[i] = 0.5 * (b[i] + c[i]); + out2[i] = 0.5 * (out1[i] + out3[i]); + } +} + +/* +================= +CM_TransposeGrid + +Swaps the rows and columns in place +================= +*/ +static void CM_TransposeGrid( cGrid_t *grid ) { + int i, j, l; + vec3_t temp; + qboolean tempWrap; + + if ( grid->width > grid->height ) { + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = i + 1 ; j < grid->width ; j++ ) { + if ( j < grid->height ) { + // swap the value + VectorCopy( grid->points[i][j], temp ); + VectorCopy( grid->points[j][i], grid->points[i][j] ); + VectorCopy( temp, grid->points[j][i] ); + } else { + // just copy + VectorCopy( grid->points[j][i], grid->points[i][j] ); + } + } + } + } else { + for ( i = 0 ; i < grid->width ; i++ ) { + for ( j = i + 1 ; j < grid->height ; j++ ) { + if ( j < grid->width ) { + // swap the value + VectorCopy( grid->points[j][i], temp ); + VectorCopy( grid->points[i][j], grid->points[j][i] ); + VectorCopy( temp, grid->points[i][j] ); + } else { + // just copy + VectorCopy( grid->points[i][j], grid->points[j][i] ); + } + } + } + } + + l = grid->width; + grid->width = grid->height; + grid->height = l; + + tempWrap = grid->wrapWidth; + grid->wrapWidth = grid->wrapHeight; + grid->wrapHeight = tempWrap; +} + +/* +=================== +CM_SetGridWrapWidth + +If the left and right columns are exactly equal, set grid->wrapWidth qtrue +=================== +*/ +static void CM_SetGridWrapWidth( cGrid_t *grid ) { + int i, j; + float d; + + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + d = grid->points[0][i][j] - grid->points[grid->width-1][i][j]; + if ( d < -WRAP_POINT_EPSILON || d > WRAP_POINT_EPSILON ) { + break; + } + } + if ( j != 3 ) { + break; + } + } + if ( i == grid->height ) { + grid->wrapWidth = qtrue; + } else { + grid->wrapWidth = qfalse; + } +} + +/* +================= +CM_SubdivideGridColumns + +Adds columns as necessary to the grid until +all the aproximating points are within SUBDIVIDE_DISTANCE +from the true curve +================= +*/ +static void CM_SubdivideGridColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 2 ; ) { + // grid->points[i][x] is an interpolating control point + // grid->points[i+1][x] is an aproximating control point + // grid->points[i+2][x] is an interpolating control point + + // + // first see if we can collapse the aproximating collumn away + // + for ( j = 0 ; j < grid->height ; j++ ) { + if ( CM_NeedsSubdivision( grid->points[i][j], grid->points[i+1][j], grid->points[i+2][j] ) ) { + break; + } + } + if ( j == grid->height ) { + // all of the points were close enough to the linear midpoints + // that we can collapse the entire column away + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + + grid->width--; + + // go to the next curve segment + i++; + continue; + } + + // + // we need to subdivide the curve + // + for ( j = 0 ; j < grid->height ; j++ ) { + vec3_t prev, mid, next; + + // save the control points now + VectorCopy( grid->points[i][j], prev ); + VectorCopy( grid->points[i+1][j], mid ); + VectorCopy( grid->points[i+2][j], next ); + + // make room for two additional columns in the grid + // columns i+1 will be replaced, column i+2 will become i+4 + // i+1, i+2, and i+3 will be generated + for ( k = grid->width - 1 ; k > i + 1 ; k-- ) { + VectorCopy( grid->points[k][j], grid->points[k+2][j] ); + } + + // generate the subdivided points + CM_Subdivide( prev, mid, next, grid->points[i+1][j], grid->points[i+2][j], grid->points[i+3][j] ); + } + + grid->width += 2; + + // the new aproximating point at i+1 may need to be removed + // or subdivided farther, so don't advance i + } +} + +/* +====================== +CM_ComparePoints +====================== +*/ +#define POINT_EPSILON 0.1 +static qboolean CM_ComparePoints( float *a, float *b ) { + float d; + + d = a[0] - b[0]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[1] - b[1]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[2] - b[2]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + return qtrue; +} + +/* +================= +CM_RemoveDegenerateColumns + +If there are any identical columns, remove them +================= +*/ +static void CM_RemoveDegenerateColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height ; j++ ) { + if ( !CM_ComparePoints( grid->points[i][j], grid->points[i+1][j] ) ) { + break; + } + } + + if ( j != grid->height ) { + continue; // not degenerate + } + + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + grid->width--; + + // check against the next column + i--; + } +} + +/* +================================================================================ + +PATCH COLLIDE GENERATION + +================================================================================ +*/ + +static int numPlanes; +static patchPlane_t planes[MAX_PATCH_PLANES]; + +static int numFacets; +static facet_t facets[MAX_PATCH_PLANES]; //maybe MAX_FACETS ?? + +#define NORMAL_EPSILON 0.0001 +#define DIST_EPSILON 0.02 + +/* +================== +CM_PlaneEqual +================== +*/ +int CM_PlaneEqual(patchPlane_t *p, float plane[4], int *flipped) { + float invplane[4]; + + if ( + fabs(p->plane[0] - plane[0]) < NORMAL_EPSILON + && fabs(p->plane[1] - plane[1]) < NORMAL_EPSILON + && fabs(p->plane[2] - plane[2]) < NORMAL_EPSILON + && fabs(p->plane[3] - plane[3]) < DIST_EPSILON ) + { + *flipped = qfalse; + return qtrue; + } + + VectorNegate(plane, invplane); + invplane[3] = -plane[3]; + + if ( + fabs(p->plane[0] - invplane[0]) < NORMAL_EPSILON + && fabs(p->plane[1] - invplane[1]) < NORMAL_EPSILON + && fabs(p->plane[2] - invplane[2]) < NORMAL_EPSILON + && fabs(p->plane[3] - invplane[3]) < DIST_EPSILON ) + { + *flipped = qtrue; + return qtrue; + } + + return qfalse; +} + +/* +================== +CM_SnapVector +================== +*/ +void CM_SnapVector(vec3_t normal) { + int i; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + break; + } + if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + break; + } + } +} + +/* +================== +CM_FindPlane2 +================== +*/ +int CM_FindPlane2(float plane[4], int *flipped) { + int i; + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if (CM_PlaneEqual(&planes[i], plane, flipped)) return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + *flipped = qfalse; + + return numPlanes-1; +} + +/* +================== +CM_FindPlane +================== +*/ +static int CM_FindPlane( float *p1, float *p2, float *p3 ) { + float plane[4]; + int i; + float d; + + if ( !CM_PlaneFromPoints( plane, p1, p2, p3 ) ) { + return -1; + } + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if ( DotProduct( plane, planes[i].plane ) < 0 ) { + continue; // allow backwards planes? + } + + d = DotProduct( p1, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p2, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p3, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + // found it + return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + return numPlanes-1; +} + +/* +================== +CM_PointOnPlaneSide +================== +*/ +static int CM_PointOnPlaneSide( float *p, int planeNum ) { + float *plane; + float d; + + if ( planeNum == -1 ) { + return SIDE_ON; + } + plane = planes[ planeNum ].plane; + + d = DotProduct( p, plane ) - plane[3]; + + if ( d > PLANE_TRI_EPSILON ) { + return SIDE_FRONT; + } + + if ( d < -PLANE_TRI_EPSILON ) { + return SIDE_BACK; + } + + return SIDE_ON; +} + +/* +================== +CM_GridPlane +================== +*/ +static int CM_GridPlane( int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int tri ) { + int p; + + p = gridPlanes[i][j][tri]; + if ( p != -1 ) { + return p; + } + p = gridPlanes[i][j][!tri]; + if ( p != -1 ) { + return p; + } + + // should never happen + Com_Printf( "WARNING: CM_GridPlane unresolvable\n" ); + return -1; +} + +/* +================== +CM_EdgePlaneNum +================== +*/ +static int CM_EdgePlaneNum( cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int k ) { + float *p1, *p2; + vec3_t up; + int p; + + switch ( k ) { + case 0: // top border + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 2: // bottom border + p1 = grid->points[i][j+1]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 3: // left border + p1 = grid->points[i][j]; + p2 = grid->points[i][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 1: // right border + p1 = grid->points[i+1][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 4: // diagonal out of triangle 0 + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 5: // diagonal out of triangle 1 + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + } + + Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" ); + return -1; +} + +/* +=================== +CM_SetBorderInward +=================== +*/ +static void CM_SetBorderInward( facet_t *facet, cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], + int i, int j, int which ) { + int k, l; + float *points[4]; + int numPoints; + + switch ( which ) { + case -1: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + points[3] = grid->points[i][j+1]; + numPoints = 4; + break; + case 0: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + numPoints = 3; + break; + case 1: + points[0] = grid->points[i+1][j+1]; + points[1] = grid->points[i][j+1]; + points[2] = grid->points[i][j]; + numPoints = 3; + break; + default: + Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" ); + numPoints = 0; + break; + } + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + int front, back; + + front = 0; + back = 0; + + for ( l = 0 ; l < numPoints ; l++ ) { + int side; + + side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] ); + if ( side == SIDE_FRONT ) { + front++; + } if ( side == SIDE_BACK ) { + back++; + } + } + + if ( front && !back ) { + facet->borderInward[k] = qtrue; + } else if ( back && !front ) { + facet->borderInward[k] = qfalse; + } else if ( !front && !back ) { + // flat side border + facet->borderPlanes[k] = -1; + } else { + // bisecting side border + Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" ); + facet->borderInward[k] = qfalse; + if ( !debugBlock ) { + debugBlock = qtrue; + VectorCopy( grid->points[i][j], debugBlockPoints[0] ); + VectorCopy( grid->points[i+1][j], debugBlockPoints[1] ); + VectorCopy( grid->points[i+1][j+1], debugBlockPoints[2] ); + VectorCopy( grid->points[i][j+1], debugBlockPoints[3] ); + } + } + } +} + +/* +================== +CM_ValidateFacet + +If the facet isn't bounded by its borders, we screwed up. +================== +*/ +static qboolean CM_ValidateFacet( facet_t *facet ) { + float plane[4]; + int j; + winding_t *w; + vec3_t bounds[2]; + + if ( facet->surfacePlane == -1 ) { + return qfalse; + } + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if ( facet->borderPlanes[j] == -1 ) { + FreeWinding( w ); + return qfalse; + } + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + + if ( !w ) { + return qfalse; // winding was completely chopped away + } + + // see if the facet is unreasonably large + WindingBounds( w, bounds[0], bounds[1] ); + FreeWinding( w ); + + for ( j = 0 ; j < 3 ; j++ ) { + if ( bounds[1][j] - bounds[0][j] > MAX_MAP_BOUNDS ) { + return qfalse; // we must be missing a plane + } + if ( bounds[0][j] >= MAX_MAP_BOUNDS ) { + return qfalse; + } + if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) { + return qfalse; + } + } + return qtrue; // winding is fine +} + +/* +================== +CM_AddFacetBevels +================== +*/ +void CM_AddFacetBevels( facet_t *facet ) { + + int i, j, k, l; + int axis, dir, order, flipped; + float plane[4], d, newplane[4]; + winding_t *w, *w2; + vec3_t mins, maxs, vec, vec2; + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if (facet->borderPlanes[j] == facet->surfacePlane) continue; + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( !w ) { + return; + } + + WindingBounds(w, mins, maxs); + + // add the axial planes + order = 0; + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) + { + VectorClear(plane); + plane[axis] = dir; + if (dir == 1) { + plane[3] = maxs[axis]; + } + else { + plane[3] = -mins[axis]; + } + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) + break; + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf("ERROR: too many bevels\n"); + facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + facet->numBorders++; + } + } + } + // + // add the edge bevels + // + // test the non-axial plane edges + for ( j = 0 ; j < w->numpoints ; j++ ) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + //if it's a degenerate edge + if (VectorNormalize (vec) < 0.5) + continue; + CM_SnapVector(vec); + for ( k = 0; k < 3 ; k++ ) + if ( vec[k] == -1 || vec[k] == 1 ) + break; // axial + if ( k < 3 ) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, plane); + if (VectorNormalize (plane) < 0.5) + continue; + plane[3] = DotProduct (w->p[j], plane); + + // if all the points of the facet winding are + // behind this plane, it is a proper edge bevel + for ( l = 0 ; l < w->numpoints ; l++ ) + { + d = DotProduct (w->p[l], plane) - plane[3]; + if (d > 0.1) + break; // point in front + } + if ( l < w->numpoints ) + continue; + + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) { + break; + } + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf("ERROR: too many bevels\n"); + facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + if (facet->borderPlanes[facet->numBorders] == + facet->borderPlanes[k]) Com_Printf("WARNING: bevel plane already used\n"); + } + + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + // + w2 = CopyWinding(w); + Vector4Copy(planes[facet->borderPlanes[facet->numBorders]].plane, newplane); + if (!facet->borderInward[facet->numBorders]) + { + VectorNegate(newplane, newplane); + newplane[3] = -newplane[3]; + } //end if + ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f ); + if (!w2) { + Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n"); + continue; + } + else { + FreeWinding(w2); + } + // + facet->numBorders++; + //already got a bevel +// break; + } + } + } + } + FreeWinding( w ); + +#ifndef BSPC + //add opposite plane + facet->borderPlanes[facet->numBorders] = facet->surfacePlane; + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = qtrue; + facet->numBorders++; +#endif //BSPC + +} + +typedef enum { + EN_TOP, + EN_RIGHT, + EN_BOTTOM, + EN_LEFT +} edgeName_t; + +/* +================== +CM_PatchCollideFromGrid +================== +*/ +static void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf ) { + int i, j; + float *p1, *p2, *p3; + int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2]; + facet_t *facet; + int borders[4]; + int noAdjust[4]; + + numPlanes = 0; + numFacets = 0; + + // find the planes for each triangle of the grid + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p3 = grid->points[i+1][j+1]; + gridPlanes[i][j][0] = CM_FindPlane( p1, p2, p3 ); + + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j+1]; + p3 = grid->points[i][j]; + gridPlanes[i][j][1] = CM_FindPlane( p1, p2, p3 ); + } + } + + // create the borders for each facet + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + + borders[EN_TOP] = -1; + if ( j > 0 ) { + borders[EN_TOP] = gridPlanes[i][j-1][1]; + } else if ( grid->wrapHeight ) { + borders[EN_TOP] = gridPlanes[i][grid->height-2][1]; + } + noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i][j][0] ); + if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) { + borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 ); + } + + borders[EN_BOTTOM] = -1; + if ( j < grid->height - 2 ) { + borders[EN_BOTTOM] = gridPlanes[i][j+1][0]; + } else if ( grid->wrapHeight ) { + borders[EN_BOTTOM] = gridPlanes[i][0][0]; + } + noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i][j][1] ); + if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) { + borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 ); + } + + borders[EN_LEFT] = -1; + if ( i > 0 ) { + borders[EN_LEFT] = gridPlanes[i-1][j][0]; + } else if ( grid->wrapWidth ) { + borders[EN_LEFT] = gridPlanes[grid->width-2][j][0]; + } + noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i][j][1] ); + if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) { + borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 ); + } + + borders[EN_RIGHT] = -1; + if ( i < grid->width - 2 ) { + borders[EN_RIGHT] = gridPlanes[i+1][j][1]; + } else if ( grid->wrapWidth ) { + borders[EN_RIGHT] = gridPlanes[0][j][1]; + } + noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i][j][0] ); + if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) { + borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 ); + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + Com_Memset( facet, 0, sizeof( *facet ) ); + + if ( gridPlanes[i][j][0] == gridPlanes[i][j][1] ) { + if ( gridPlanes[i][j][0] == -1 ) { + continue; // degenrate + } + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 4; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = borders[EN_BOTTOM]; + facet->borderNoAdjust[2] = noAdjust[EN_BOTTOM]; + facet->borderPlanes[3] = borders[EN_LEFT]; + facet->borderNoAdjust[3] = noAdjust[EN_LEFT]; + CM_SetBorderInward( facet, grid, gridPlanes, i, j, -1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } else { + // two seperate triangles + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = gridPlanes[i][j][1]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_BOTTOM]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 0 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + Com_Memset( facet, 0, sizeof( *facet ) ); + + facet->surfacePlane = gridPlanes[i][j][1]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_BOTTOM]; + facet->borderNoAdjust[0] = noAdjust[EN_BOTTOM]; + facet->borderPlanes[1] = borders[EN_LEFT]; + facet->borderNoAdjust[1] = noAdjust[EN_LEFT]; + facet->borderPlanes[2] = gridPlanes[i][j][0]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_TOP]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } + } + } + + // copy the results out + pf->numPlanes = numPlanes; + pf->numFacets = numFacets; + pf->facets = Hunk_Alloc( numFacets * sizeof( *pf->facets ), h_high ); + Com_Memcpy( pf->facets, facets, numFacets * sizeof( *pf->facets ) ); + pf->planes = Hunk_Alloc( numPlanes * sizeof( *pf->planes ), h_high ); + Com_Memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) ); +} + + +/* +=================== +CM_GeneratePatchCollide + +Creates an internal structure that will be used to perform +collision detection with a patch mesh. + +Points is packed as concatenated rows. +=================== +*/ +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ) { + patchCollide_t *pf; + cGrid_t grid; + int i, j; + + if ( width <= 2 || height <= 2 || !points ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)", + width, height, (void *)points ); + } + + if ( !(width & 1) || !(height & 1) ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" ); + } + + if ( width > MAX_GRID_SIZE || height > MAX_GRID_SIZE ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > MAX_GRID_SIZE" ); + } + + // build a grid + grid.width = width; + grid.height = height; + grid.wrapWidth = qfalse; + grid.wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + VectorCopy( points[j*width + i], grid.points[i][j] ); + } + } + + // subdivide the grid + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + CM_TransposeGrid( &grid ); + + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + // we now have a grid of points exactly on the curve + // the aproximate surface defined by these points will be + // collided against + pf = Hunk_Alloc( sizeof( *pf ), h_high ); + ClearBounds( pf->bounds[0], pf->bounds[1] ); + for ( i = 0 ; i < grid.width ; i++ ) { + for ( j = 0 ; j < grid.height ; j++ ) { + AddPointToBounds( grid.points[i][j], pf->bounds[0], pf->bounds[1] ); + } + } + + c_totalPatchBlocks += ( grid.width - 1 ) * ( grid.height - 1 ); + + // generate a bsp tree for the surface + CM_PatchCollideFromGrid( &grid, pf ); + + // expand by one unit for epsilon purposes + pf->bounds[0][0] -= 1; + pf->bounds[0][1] -= 1; + pf->bounds[0][2] -= 1; + + pf->bounds[1][0] += 1; + pf->bounds[1][1] += 1; + pf->bounds[1][2] += 1; + + return pf; +} + +/* +================================================================================ + +TRACE TESTING + +================================================================================ +*/ + +/* +==================== +CM_TracePointThroughPatchCollide + + special case for point traces because the patch collide "brushes" have no volume +==================== +*/ +void CM_TracePointThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + qboolean frontFacing[MAX_PATCH_PLANES]; + float intersection[MAX_PATCH_PLANES]; + float intersect; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d1, d2; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef BSPC + if ( !cm_playerCurveClip->integer || !tw->isPoint ) { + return; + } +#endif + + // determine the trace's relationship to all planes + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + if ( d1 <= 0 ) { + frontFacing[i] = qfalse; + } else { + frontFacing[i] = qtrue; + } + if ( d1 == d2 ) { + intersection[i] = 99999; + } else { + intersection[i] = d1 / ( d1 - d2 ); + if ( intersection[i] <= 0 ) { + intersection[i] = 99999; + } + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + if ( !frontFacing[facet->surfacePlane] ) { + continue; + } + intersect = intersection[facet->surfacePlane]; + if ( intersect < 0 ) { + continue; // surface is behind the starting point + } + if ( intersect > tw->trace.fraction ) { + continue; // already hit something closer + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->borderPlanes[j]; + if ( frontFacing[k] ^ facet->borderInward[j] ) { + if ( intersection[k] > intersect ) { + break; + } + } else { + if ( intersection[k] < intersect ) { + break; + } + } + } + if ( j == facet->numBorders ) { + // we hit this facet +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + planes = &pc->planes[facet->surfacePlane]; + + // calculate intersection with a slight pushoff + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + tw->trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if ( tw->trace.fraction < 0 ) { + tw->trace.fraction = 0; + } + + VectorCopy( planes->plane, tw->trace.plane.normal ); + tw->trace.plane.dist = planes->plane[3]; + } + } +} + +/* +==================== +CM_CheckFacetPlane +==================== +*/ +int CM_CheckFacetPlane(float *plane, vec3_t start, vec3_t end, float *enterFrac, float *leaveFrac, int *hit) { + float d1, d2, f; + + *hit = qfalse; + + d1 = DotProduct( start, plane ) - plane[3]; + d2 = DotProduct( end, plane ) - plane[3]; + + // if completely in front of face, no intersection with the entire facet + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return qfalse; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + return qtrue; + } + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + //always favor previous plane hits and thus also the surface plane hit + if (f > *enterFrac) { + *enterFrac = f; + *hit = qtrue; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < *leaveFrac) { + *leaveFrac = f; + } + } + return qtrue; +} + +/* +==================== +CM_TraceThroughPatchCollide +==================== +*/ +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int i, j, hit, hitnum; + float offset, enterFrac, leaveFrac, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4] = {0, 0, 0, 0}, bestplane[4] = {0, 0, 0, 0}; + vec3_t startp, endp; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + + if ( !CM_BoundsIntersect( tw->bounds[0], tw->bounds[1], + pc->bounds[0], pc->bounds[1] ) ) { + return; + } + + if (tw->isPoint) { + CM_TracePointThroughPatchCollide( tw, pc ); + return; + } + + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + enterFrac = -1.0; + leaveFrac = 1.0; + hitnum = -1; + // + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->type == TT_CAPSULE ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + continue; + } + if (hit) { + Vector4Copy(plane, bestplane); + } + + for ( j = 0; j < facet->numBorders; j++ ) { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (facet->borderInward[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->type == TT_CAPSULE ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += fabs(offset); + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + break; + } + if (hit) { + hitnum = j; + Vector4Copy(plane, bestplane); + } + } + if (j < facet->numBorders) continue; + //never clip against the back side + if (hitnum == facet->numBorders - 1) continue; + + if (enterFrac < leaveFrac && enterFrac >= 0) { + if (enterFrac < tw->trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv && cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + + tw->trace.fraction = enterFrac; + VectorCopy( bestplane, tw->trace.plane.normal ); + tw->trace.plane.dist = bestplane[3]; + } + } + } +} + + +/* +======================================================================= + +POSITION TEST + +======================================================================= +*/ + +/* +==================== +CM_PositionTestInPatchCollide +==================== +*/ +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int i, j; + float offset, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4]; + vec3_t startp; + + if (tw->isPoint) { + return qfalse; + } + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->type == TT_CAPSULE ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0 ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + } + + if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) { + continue; + } + + for ( j = 0; j < facet->numBorders; j++ ) { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (facet->borderInward[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->type == TT_CAPSULE ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += fabs(offset); + VectorCopy( tw->start, startp ); + } + + if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) { + break; + } + } + if (j < facet->numBorders) { + continue; + } + // inside this patch facet + return qtrue; + } + return qfalse; +} + +/* +======================================================================= + +DEBUGGING + +======================================================================= +*/ + + +/* +================== +CM_DrawDebugSurface + +Called from the renderer +================== +*/ +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ) { + static cvar_t *cv; +#ifndef BSPC + static cvar_t *cv2; +#endif + const patchCollide_t *pc; + facet_t *facet; + winding_t *w; + int i, j, k, n; + int curplanenum, planenum, curinward, inward; + float plane[4]; + vec3_t mins = {-15, -15, -28}, maxs = {15, 15, 28}; + //vec3_t mins = {0, 0, 0}, maxs = {0, 0, 0}; + vec3_t v1, v2; + +#ifndef BSPC + if ( !cv2 ) + { + cv2 = Cvar_Get( "r_debugSurface", "0", 0 ); + } + + if (cv2->integer != 1) + { + return; + } +#endif + + if ( !debugPatchCollide ) { + return; + } + +#ifndef BSPC + if ( !cv ) { + cv = Cvar_Get( "cm_debugSize", "2", 0 ); + } +#endif + pc = debugPatchCollide; + + for ( i = 0, facet = pc->facets ; i < pc->numFacets ; i++, facet++ ) { + + for ( k = 0 ; k < facet->numBorders + 1; k++ ) { + // + if (k < facet->numBorders) { + planenum = facet->borderPlanes[k]; + inward = facet->borderInward[k]; + } + else { + planenum = facet->surfacePlane; + inward = qfalse; + //continue; + } + + Vector4Copy( pc->planes[ planenum ].plane, plane ); + + //planenum = facet->surfacePlane; + if ( inward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + plane[3] += cv->value; + //* + for (n = 0; n < 3; n++) + { + if (plane[n] > 0) v1[n] = maxs[n]; + else v1[n] = mins[n]; + } //end for + VectorNegate(plane, v2); + plane[3] += fabs(DotProduct(v1, v2)); + //*/ + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders + 1 && w; j++ ) { + // + if (j < facet->numBorders) { + curplanenum = facet->borderPlanes[j]; + curinward = facet->borderInward[j]; + } + else { + curplanenum = facet->surfacePlane; + curinward = qfalse; + //continue; + } + // + if (curplanenum == planenum) continue; + + Vector4Copy( pc->planes[ curplanenum ].plane, plane ); + if ( !curinward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + // if ( !facet->borderNoAdjust[j] ) { + plane[3] -= cv->value; + // } + for (n = 0; n < 3; n++) + { + if (plane[n] > 0) v1[n] = maxs[n]; + else v1[n] = mins[n]; + } //end for + VectorNegate(plane, v2); + plane[3] -= fabs(DotProduct(v1, v2)); + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( w ) { + if ( facet == debugFacet ) { + drawPoly( 4, w->numpoints, w->p[0] ); + //Com_Printf("blue facet has %d border planes\n", facet->numBorders); + } else { + drawPoly( 1, w->numpoints, w->p[0] ); + } + FreeWinding( w ); + } + else + Com_Printf("winding chopped away by border planes\n"); + } + } + + // draw the debug block + { + vec3_t v[3]; + + VectorCopy( debugBlockPoints[0], v[0] ); + VectorCopy( debugBlockPoints[1], v[1] ); + VectorCopy( debugBlockPoints[2], v[2] ); + drawPoly( 2, 3, v[0] ); + + VectorCopy( debugBlockPoints[2], v[0] ); + VectorCopy( debugBlockPoints[3], v[1] ); + VectorCopy( debugBlockPoints[0], v[2] ); + drawPoly( 2, 3, v[0] ); + } + +#if 0 + vec3_t v[4]; + + v[0][0] = pc->bounds[1][0]; + v[0][1] = pc->bounds[1][1]; + v[0][2] = pc->bounds[1][2]; + + v[1][0] = pc->bounds[1][0]; + v[1][1] = pc->bounds[0][1]; + v[1][2] = pc->bounds[1][2]; + + v[2][0] = pc->bounds[0][0]; + v[2][1] = pc->bounds[0][1]; + v[2][2] = pc->bounds[1][2]; + + v[3][0] = pc->bounds[0][0]; + v[3][1] = pc->bounds[1][1]; + v[3][2] = pc->bounds[1][2]; + + drawPoly( 4, v[0] ); +#endif +} diff --git a/src/qcommon/cm_patch.h b/src/qcommon/cm_patch.h new file mode 100644 index 0000000..2470339 --- /dev/null +++ b/src/qcommon/cm_patch.h @@ -0,0 +1,104 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +//#define CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + + +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + qboolean borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 + + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); diff --git a/src/qcommon/cm_polylib.c b/src/qcommon/cm_polylib.c new file mode 100644 index 0000000..03fd47d --- /dev/null +++ b/src/qcommon/cm_polylib.c @@ -0,0 +1,738 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// this is only used for visualization tools in cm_ debug functions + + +#include "cm_local.h" + + +// counters are only bumped when running single threaded, +// because they are an awful coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + + s = sizeof(vec_t)*3*points + sizeof(int); + w = Z_Malloc (s); + Com_Memset (w, 0, s); + return w; +} + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Com_Error (ERR_FATAL, "FreeWinding: freed a freed winding"); + *(unsigned *)w = 0xdeaddead; + + c_active_windings--; + Z_Free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize2(v1,v1); + VectorNormalize2(v2,v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + c_removed += w->numpoints - nump; + w->numpoints = nump; + Com_Memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize2(normal, normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +/* +============= +WindingBounds +============= +*/ +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = MAX_MAP_BOUNDS; + maxs[0] = maxs[1] = maxs[2] = -MAX_MAP_BOUNDS; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -MAX_MAP_BOUNDS; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Com_Error (ERR_DROP, "BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize2(vup, vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, MAX_MAP_BOUNDS, vup); + VectorScale (vright, MAX_MAP_BOUNDS, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + unsigned long size; + winding_t *c; + + c = AllocWinding (w->numpoints); + size = (long)((winding_t *)0)->p[w->numpoints]; + Com_Memcpy (c, w, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Com_Error (ERR_DROP, "CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Com_Error (ERR_DROP, "CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > MAX_MAP_BOUNDS || p1[j] < -MAX_MAP_BOUNDS) + Com_Error (ERR_DROP, "CheckFace: BUGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Com_Error (ERR_DROP, "CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Com_Error (ERR_DROP, "CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize2 (edgenormal, edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Com_Error (ERR_DROP, "CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = qfalse; + back = qfalse; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = qtrue; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = qtrue; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + + +/* +================= +AddWindingToConvexHull + +Both w and *hull are on the same plane +================= +*/ +#define MAX_HULL_POINTS 128 +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) { + int i, j, k; + float *p, *copy; + vec3_t dir; + float d; + int numHullPoints, numNew; + vec3_t hullPoints[MAX_HULL_POINTS]; + vec3_t newHullPoints[MAX_HULL_POINTS]; + vec3_t hullDirs[MAX_HULL_POINTS]; + qboolean hullSide[MAX_HULL_POINTS]; + qboolean outside; + + if ( !*hull ) { + *hull = CopyWinding( w ); + return; + } + + numHullPoints = (*hull)->numpoints; + Com_Memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) ); + + for ( i = 0 ; i < w->numpoints ; i++ ) { + p = w->p[i]; + + // calculate hull side vectors + for ( j = 0 ; j < numHullPoints ; j++ ) { + k = ( j + 1 ) % numHullPoints; + + VectorSubtract( hullPoints[k], hullPoints[j], dir ); + VectorNormalize2( dir, dir ); + CrossProduct( normal, dir, hullDirs[j] ); + } + + outside = qfalse; + for ( j = 0 ; j < numHullPoints ; j++ ) { + VectorSubtract( p, hullPoints[j], dir ); + d = DotProduct( dir, hullDirs[j] ); + if ( d >= ON_EPSILON ) { + outside = qtrue; + } + if ( d >= -ON_EPSILON ) { + hullSide[j] = qtrue; + } else { + hullSide[j] = qfalse; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0 ; j < numHullPoints ; j++ ) { + if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) { + break; + } + } + if ( j == numHullPoints ) { + continue; + } + + // insert the point here + VectorCopy( p, newHullPoints[0] ); + numNew = 1; + + // copy over all points that aren't double fronts + j = (j+1)%numHullPoints; + for ( k = 0 ; k < numHullPoints ; k++ ) { + if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) { + continue; + } + copy = hullPoints[ (j+k+1) % numHullPoints ]; + VectorCopy( copy, newHullPoints[numNew] ); + numNew++; + } + + numHullPoints = numNew; + Com_Memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) ); + } + + FreeWinding( *hull ); + w = AllocWinding( numHullPoints ); + w->numpoints = numHullPoints; + *hull = w; + Com_Memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) ); +} + + diff --git a/src/qcommon/cm_polylib.h b/src/qcommon/cm_polylib.h new file mode 100644 index 0000000..b1b2a48 --- /dev/null +++ b/src/qcommon/cm_polylib.h @@ -0,0 +1,69 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// this is only used for visualization tools in cm_ debug functions + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +#define CLIP_EPSILON 0.1f + +#define MAX_MAP_BOUNDS 65535 + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1f +#endif + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); + +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ); + +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +void pw(winding_t *w); diff --git a/src/qcommon/cm_public.h b/src/qcommon/cm_public.h new file mode 100644 index 0000000..123fee3 --- /dev/null +++ b/src/qcommon/cm_public.h @@ -0,0 +1,84 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qfiles.h" + + +void CM_LoadMap( const char *name, qboolean clientload, int *checksum); +void CM_ClearMap( void ); +clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ); + +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); + +int CM_NumClusters (void); +int CM_NumInlineModels( void ); +char *CM_EntityString (void); + +// returns an ORed contents mask +int CM_PointContents( const vec3_t p, clipHandle_t model ); +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); + +void CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end, + vec3_t mins, vec3_t maxs, + clipHandle_t model, int brushmask, traceType_t type ); +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + vec3_t mins, vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, traceType_t type ); +void CM_BiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask ); +void CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask, + const vec3_t origin ); + +byte *CM_ClusterPVS (int cluster); + +int CM_PointLeafnum( const vec3_t p ); + +// only returns non-solid leafs +// overflow if return listsize and if *lastLeaf != list[listsize-1] +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, + int listsize, int *lastLeaf ); + +int CM_LeafCluster (int leafnum); +int CM_LeafArea (int leafnum); + +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ); +qboolean CM_AreasConnected( int area1, int area2 ); + +int CM_WriteAreaBits( byte *buffer, int area ); + +// cm_tag.c +int CM_LerpTag( orientation_t *tag, clipHandle_t model, int startFrame, int endFrame, + float frac, const char *tagName ); + + +// cm_marks.c +int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + +// cm_patch.c +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ); diff --git a/src/qcommon/cm_test.c b/src/qcommon/cm_test.c new file mode 100644 index 0000000..5e520e4 --- /dev/null +++ b/src/qcommon/cm_test.c @@ -0,0 +1,525 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "cm_local.h" + + +/* +================== +CM_PointLeafnum_r + +================== +*/ +int CM_PointLeafnum_r( const vec3_t p, int num ) { + float d; + cNode_t *node; + cplane_t *plane; + + while (num >= 0) + { + node = cm.nodes + num; + plane = node->plane; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + c_pointcontents++; // optimize counter + + return -1 - num; +} + +int CM_PointLeafnum( const vec3_t p ) { + if ( !cm.numNodes ) { // map not loaded + return 0; + } + return CM_PointLeafnum_r (p, 0); +} + + +/* +====================================================================== + +LEAF LISTING + +====================================================================== +*/ + + +void CM_StoreLeafs( leafList_t *ll, int nodenum ) { + int leafNum; + + leafNum = -1 - nodenum; + + // store the lastLeaf even if the list is overflowed + if ( cm.leafs[ leafNum ].cluster != -1 ) { + ll->lastLeaf = leafNum; + } + + if ( ll->count >= ll->maxcount) { + ll->overflowed = qtrue; + return; + } + ll->list[ ll->count++ ] = leafNum; +} + +void CM_StoreBrushes( leafList_t *ll, int nodenum ) { + int i, k; + int leafnum; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + + leafnum = -1 - nodenum; + + leaf = &cm.leafs[leafnum]; + + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + b = &cm.brushes[brushnum]; + if ( b->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + for ( i = 0 ; i < 3 ; i++ ) { + if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) { + break; + } + } + if ( i != 3 ) { + continue; + } + if ( ll->count >= ll->maxcount) { + ll->overflowed = qtrue; + return; + } + ((cbrush_t **)ll->list)[ ll->count++ ] = b; + } +#if 0 + // store patches? + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstleafsurface + k ] ]; + if ( !patch ) { + continue; + } + } +#endif +} + +/* +============= +CM_BoxLeafnums + +Fills in a list of all the leafs touched +============= +*/ +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) { + cplane_t *plane; + cNode_t *node; + int s; + + while (1) { + if (nodenum < 0) { + ll->storeLeafs( ll, nodenum ); + return; + } + + node = &cm.nodes[nodenum]; + plane = node->plane; + s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane ); + if (s == 1) { + nodenum = node->children[0]; + } else if (s == 2) { + nodenum = node->children[1]; + } else { + // go down both + CM_BoxLeafnums_r( ll, node->children[0] ); + nodenum = node->children[1]; + } + + } +} + +/* +================== +CM_BoxLeafnums +================== +*/ +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *lastLeaf) { + leafList_t ll; + + cm.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = list; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + CM_BoxLeafnums_r( &ll, 0 ); + + *lastLeaf = ll.lastLeaf; + return ll.count; +} + +/* +================== +CM_BoxBrushes +================== +*/ +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ) { + leafList_t ll; + + cm.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = (void *)list; + ll.storeLeafs = CM_StoreBrushes; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + CM_BoxLeafnums_r( &ll, 0 ); + + return ll.count; +} + + +//==================================================================== + + +/* +================== +CM_PointContents + +================== +*/ +int CM_PointContents( const vec3_t p, clipHandle_t model ) { + int leafnum; + int i, k; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + int contents; + float d; + cmodel_t *clipm; + + if (!cm.numNodes) { // map not loaded + return 0; + } + + if ( model ) { + clipm = CM_ClipHandleToModel( model ); + leaf = &clipm->leaf; + } else { + leafnum = CM_PointLeafnum_r (p, 0); + leaf = &cm.leafs[leafnum]; + } + + if(leaf->area == -1) + return CONTENTS_SOLID; + + contents = 0; + for (k=0 ; knumLeafBrushes ; k++) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + b = &cm.brushes[brushnum]; + + if ( !CM_BoundsIntersectPoint( b->bounds[0], b->bounds[1], p ) ) { + continue; + } + + // see if the point is in the brush + for ( i = 0 ; i < b->numsides ; i++ ) { + d = DotProduct( p, b->sides[i].plane->normal ); +// FIXME test for Cash +// if ( d >= b->sides[i].plane->dist ) { + if ( d > b->sides[i].plane->dist ) { + break; + } + } + + if ( i == b->numsides ) { + contents |= b->contents; + } + } + + return contents; +} + +/* +================== +CM_TransformedPointContents + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) { + vec3_t p_l; + vec3_t temp; + vec3_t forward, right, up; + + // subtract origin offset + VectorSubtract (p, origin, p_l); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + (angles[0] || angles[1] || angles[2]) ) + { + AngleVectors (angles, forward, right, up); + + VectorCopy (p_l, temp); + p_l[0] = DotProduct (temp, forward); + p_l[1] = -DotProduct (temp, right); + p_l[2] = DotProduct (temp, up); + } + + return CM_PointContents( p_l, model ); +} + + + +/* +=============================================================================== + +PVS + +=============================================================================== +*/ + +byte *CM_ClusterPVS (int cluster) { + if (cluster < 0 || cluster >= cm.numClusters || !cm.vised ) { + return cm.visibility; + } + + return cm.visibility + cluster * cm.clusterBytes; +} + + + +/* +=============================================================================== + +AREAPORTALS + +=============================================================================== +*/ + +void CM_FloodArea_r( int areaNum, int floodnum) { + int i; + cArea_t *area; + int *con; + + area = &cm.areas[ areaNum ]; + + if ( area->floodvalid == cm.floodvalid ) { + if (area->floodnum == floodnum) + return; + Com_Error (ERR_DROP, "FloodArea_r: reflooded"); + } + + area->floodnum = floodnum; + area->floodvalid = cm.floodvalid; + con = cm.areaPortals + areaNum * cm.numAreas; + for ( i=0 ; i < cm.numAreas ; i++ ) { + if ( con[i] > 0 ) { + CM_FloodArea_r( i, floodnum ); + } + } +} + +/* +==================== +CM_FloodAreaConnections + +==================== +*/ +void CM_FloodAreaConnections( void ) { + int i; + cArea_t *area; + int floodnum; + + // all current floods are now invalid + cm.floodvalid++; + floodnum = 0; + + for (i = 0 ; i < cm.numAreas ; i++) { + area = &cm.areas[i]; + if (area->floodvalid == cm.floodvalid) { + continue; // already flooded into + } + floodnum++; + CM_FloodArea_r (i, floodnum); + } + +} + +/* +==================== +CM_AdjustAreaPortalState + +==================== +*/ +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ) { + if ( area1 < 0 || area2 < 0 ) { + return; + } + + if ( area1 >= cm.numAreas || area2 >= cm.numAreas ) { + Com_Error (ERR_DROP, "CM_ChangeAreaPortalState: bad area number"); + } + + if ( open ) { + cm.areaPortals[ area1 * cm.numAreas + area2 ]++; + cm.areaPortals[ area2 * cm.numAreas + area1 ]++; + } else { + cm.areaPortals[ area1 * cm.numAreas + area2 ]--; + cm.areaPortals[ area2 * cm.numAreas + area1 ]--; + if ( cm.areaPortals[ area2 * cm.numAreas + area1 ] < 0 ) { + Com_Error (ERR_DROP, "CM_AdjustAreaPortalState: negative reference count"); + } + } + + CM_FloodAreaConnections (); +} + +/* +==================== +CM_AreasConnected + +==================== +*/ +qboolean CM_AreasConnected( int area1, int area2 ) { +#ifndef BSPC + if ( cm_noAreas->integer ) { + return qtrue; + } +#endif + + if ( area1 < 0 || area2 < 0 ) { + return qfalse; + } + + if (area1 >= cm.numAreas || area2 >= cm.numAreas) { + Com_Error (ERR_DROP, "area >= cm.numAreas"); + } + + if (cm.areas[area1].floodnum == cm.areas[area2].floodnum) { + return qtrue; + } + return qfalse; +} + + +/* +================= +CM_WriteAreaBits + +Writes a bit vector of all the areas +that are in the same flood as the area parameter +Returns the number of bytes needed to hold all the bits. + +The bits are OR'd in, so you can CM_WriteAreaBits from multiple +viewpoints and get the union of all visible areas. + +This is used to cull non-visible entities from snapshots +================= +*/ +int CM_WriteAreaBits (byte *buffer, int area) +{ + int i; + int floodnum; + int bytes; + + bytes = (cm.numAreas+7)>>3; + +#ifndef BSPC + if (cm_noAreas->integer || area == -1) +#else + if ( area == -1) +#endif + { // for debugging, send everything + Com_Memset (buffer, 255, bytes); + } + else + { + floodnum = cm.areas[area].floodnum; + for (i=0 ; i>3] |= 1<<(i&7); + } + } + + return bytes; +} + +/* +==================== +CM_BoundsIntersect +==================== +*/ +qboolean CM_BoundsIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2 ) +{ + if (maxs[0] < mins2[0] - SURFACE_CLIP_EPSILON || + maxs[1] < mins2[1] - SURFACE_CLIP_EPSILON || + maxs[2] < mins2[2] - SURFACE_CLIP_EPSILON || + mins[0] > maxs2[0] + SURFACE_CLIP_EPSILON || + mins[1] > maxs2[1] + SURFACE_CLIP_EPSILON || + mins[2] > maxs2[2] + SURFACE_CLIP_EPSILON) + { + return qfalse; + } + + return qtrue; +} + +/* +==================== +CM_BoundsIntersectPoint +==================== +*/ +qboolean CM_BoundsIntersectPoint( const vec3_t mins, const vec3_t maxs, const vec3_t point ) +{ + if (maxs[0] < point[0] - SURFACE_CLIP_EPSILON || + maxs[1] < point[1] - SURFACE_CLIP_EPSILON || + maxs[2] < point[2] - SURFACE_CLIP_EPSILON || + mins[0] > point[0] + SURFACE_CLIP_EPSILON || + mins[1] > point[1] + SURFACE_CLIP_EPSILON || + mins[2] > point[2] + SURFACE_CLIP_EPSILON) + { + return qfalse; + } + + return qtrue; +} diff --git a/src/qcommon/cm_trace.c b/src/qcommon/cm_trace.c new file mode 100644 index 0000000..6c4751f --- /dev/null +++ b/src/qcommon/cm_trace.c @@ -0,0 +1,1793 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "cm_local.h" + +// always use bbox vs. bbox collision and never capsule vs. bbox or vice versa +//#define ALWAYS_BBOX_VS_BBOX +// always use capsule vs. capsule collision and never capsule vs. bbox or vice versa +//#define ALWAYS_CAPSULE_VS_CAPSULE + +//#define CAPSULE_DEBUG + +/* +=============================================================================== + +BASIC MATH + +=============================================================================== +*/ + +/* +================ +RotatePoint +================ +*/ +void RotatePoint(vec3_t point, /*const*/ vec3_t matrix[3]) { // FIXME + vec3_t tvec; + + VectorCopy(point, tvec); + point[0] = DotProduct(matrix[0], tvec); + point[1] = DotProduct(matrix[1], tvec); + point[2] = DotProduct(matrix[2], tvec); +} + +/* +================ +TransposeMatrix +================ +*/ +void TransposeMatrix(/*const*/ vec3_t matrix[3], vec3_t transpose[3]) { // FIXME + int i, j; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + transpose[i][j] = matrix[j][i]; + } + } +} + +/* +================ +CreateRotationMatrix +================ +*/ +void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]) { + AngleVectors(angles, matrix[0], matrix[1], matrix[2]); + VectorInverse(matrix[1]); +} + +/* +================ +CM_ProjectPointOntoVector +================ +*/ +void CM_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vDir, vec3_t vProj ) +{ + vec3_t pVec; + + VectorSubtract( point, vStart, pVec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vDir ), vDir, vProj ); +} + +/* +================ +CM_DistanceFromLineSquared +================ +*/ +float CM_DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2, vec3_t dir) { + vec3_t proj, t; + int j; + + CM_ProjectPointOntoVector(p, lp1, dir, proj); + for (j = 0; j < 3; j++) + if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || + (proj[j] < lp1[j] && proj[j] < lp2[j])) + break; + if (j < 3) { + if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) + VectorSubtract(p, lp1, t); + else + VectorSubtract(p, lp2, t); + return VectorLengthSquared(t); + } + VectorSubtract(p, proj, t); + return VectorLengthSquared(t); +} + +/* +================ +CM_VectorDistanceSquared +================ +*/ +float CM_VectorDistanceSquared(vec3_t p1, vec3_t p2) { + vec3_t dir; + + VectorSubtract(p2, p1, dir); + return VectorLengthSquared(dir); +} + +/* +================ +SquareRootFloat +================ +*/ +float SquareRootFloat(float number) { + floatint_t t; + float x, y; + const float f = 1.5F; + + x = number * 0.5F; + t.f = number; + t.i = 0x5f3759df - ( t.i >> 1 ); + y = t.f; + y = y * ( f - ( x * y * y ) ); + y = y * ( f - ( x * y * y ) ); + return number * y; +} + + +/* +=============================================================================== + +POSITION TESTING + +=============================================================================== +*/ + +/* +================ +CM_TestBoxInBrush +================ +*/ +void CM_TestBoxInBrush( traceWork_t *tw, cbrush_t *brush ) { + int i; + cplane_t *plane; + float dist; + float d1; + cbrushside_t *side; + float t; + vec3_t startp; + + if (!brush->numsides) { + return; + } + + // special test for axial + if ( tw->bounds[0][0] > brush->bounds[1][0] + || tw->bounds[0][1] > brush->bounds[1][1] + || tw->bounds[0][2] > brush->bounds[1][2] + || tw->bounds[1][0] < brush->bounds[0][0] + || tw->bounds[1][1] < brush->bounds[0][1] + || tw->bounds[1][2] < brush->bounds[0][2] + ) { + return; + } + + if ( tw->type == TT_CAPSULE ) { + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for radius + dist = plane->dist + tw->sphere.radius; + // find the closest point on the capsule to the plane + t = DotProduct( plane->normal, tw->sphere.offset ); + if ( t > 0 ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + d1 = DotProduct( startp, plane->normal ) - dist; + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + } else { + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + } + + // inside this brush + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = brush->contents; +} + + + +/* +================ +CM_TestInLeaf +================ +*/ +void CM_TestInLeaf( traceWork_t *tw, cLeaf_t *leaf ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // test box position against all brushes in the leaf + for (k=0 ; knumLeafBrushes ; k++) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + b = &cm.brushes[brushnum]; + if (b->checkcount == cm.checkcount) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + + if ( !(b->contents & tw->contents)) { + continue; + } + + CM_TestBoxInBrush( tw, b ); + if ( tw->trace.allsolid ) { + return; + } + } + + // test against all patches +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif //BSPC + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if ( !patch ) { + continue; + } + if ( patch->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + patch->checkcount = cm.checkcount; + + if ( !(patch->contents & tw->contents)) { + continue; + } + + if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = patch->contents; + return; + } + } + } +} + +/* +================== +CM_TestCapsuleInCapsule + +capsule inside capsule check +================== +*/ +void CM_TestCapsuleInCapsule( traceWork_t *tw, clipHandle_t model ) { + int i; + vec3_t mins, maxs; + vec3_t top, bottom; + vec3_t p1, p2, tmp; + vec3_t offset, symetricSize[2]; + float radius, halfwidth, halfheight, offs, r; + + CM_ModelBounds(model, mins, maxs); + + VectorAdd(tw->start, tw->sphere.offset, top); + VectorSubtract(tw->start, tw->sphere.offset, bottom); + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + } + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + offs = halfheight - radius; + + r = Square(tw->sphere.radius + radius); + // check if any of the spheres overlap + VectorCopy(offset, p1); + p1[2] += offs; + VectorSubtract(p1, top, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorSubtract(p1, bottom, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorCopy(offset, p2); + p2[2] -= offs; + VectorSubtract(p2, top, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorSubtract(p2, bottom, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + // if between cylinder up and lower bounds + if ( (top[2] >= p1[2] && top[2] <= p2[2]) || + (bottom[2] >= p1[2] && bottom[2] <= p2[2]) ) { + // 2d coordinates + top[2] = p1[2] = 0; + // if the cylinders overlap + VectorSubtract(top, p1, tmp); + if ( VectorLengthSquared(tmp) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + } +} + +/* +================== +CM_TestBoundingBoxInCapsule + +bounding box inside capsule check +================== +*/ +void CM_TestBoundingBoxInCapsule( traceWork_t *tw, clipHandle_t model ) { + vec3_t mins, maxs, offset, size[2]; + clipHandle_t h; + cmodel_t *cmod; + int i; + + // mins maxs of the capsule + CM_ModelBounds(model, mins, maxs); + + // offset for capsule center + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + size[0][i] = mins[i] - offset[i]; + size[1][i] = maxs[i] - offset[i]; + tw->start[i] -= offset[i]; + tw->end[i] -= offset[i]; + } + + // replace the bounding box with the capsule + tw->type = TT_CAPSULE; + tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0]; + tw->sphere.halfheight = size[1][2]; + VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + + // replace the capsule with the bounding box + h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse); + // calculate collision + cmod = CM_ClipHandleToModel( h ); + CM_TestInLeaf( tw, &cmod->leaf ); +} + +/* +================== +CM_PositionTest +================== +*/ +#define MAX_POSITION_LEAFS 1024 +void CM_PositionTest( traceWork_t *tw ) { + int leafs[MAX_POSITION_LEAFS]; + int i; + leafList_t ll; + + // identify the leafs we are touching + VectorAdd( tw->start, tw->size[0], ll.bounds[0] ); + VectorAdd( tw->start, tw->size[1], ll.bounds[1] ); + + for (i=0 ; i<3 ; i++) { + ll.bounds[0][i] -= 1; + ll.bounds[1][i] += 1; + } + + ll.count = 0; + ll.maxcount = MAX_POSITION_LEAFS; + ll.list = leafs; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + cm.checkcount++; + + CM_BoxLeafnums_r( &ll, 0 ); + + + cm.checkcount++; + + // test the contents of the leafs + for (i=0 ; i < ll.count ; i++) { + CM_TestInLeaf( tw, &cm.leafs[leafs[i]] ); + if ( tw->trace.allsolid ) { + break; + } + } +} + +/* +=============================================================================== + +TRACING + +=============================================================================== +*/ + +/* +================ +CM_TraceThroughPatch +================ +*/ + +void CM_TraceThroughPatch( traceWork_t *tw, cPatch_t *patch ) { + float oldFrac; + + c_patch_traces++; + + oldFrac = tw->trace.fraction; + + CM_TraceThroughPatchCollide( tw, patch->pc ); + + if ( tw->trace.fraction < oldFrac ) { + tw->trace.surfaceFlags = patch->surfaceFlags; + tw->trace.contents = patch->contents; + } +} + +/* +================ +CM_TraceThroughBrush +================ +*/ +void CM_TraceThroughBrush( traceWork_t *tw, cbrush_t *brush ) { + int i; + cplane_t *plane, *clipplane; + float dist; + float enterFrac, leaveFrac; + float d1, d2; + qboolean getout, startout; + float f; + cbrushside_t *side, *leadside; + float t; + vec3_t startp; + vec3_t endp; + + enterFrac = -1.0; + leaveFrac = 1.0; + clipplane = NULL; + + if ( !brush->numsides ) { + return; + } + + c_brush_traces++; + + getout = qfalse; + startout = qfalse; + + leadside = NULL; + + if( tw->type == TT_BISPHERE ) + { + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for( i = 0; i < brush->numsides; i++ ) + { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for radius + d1 = DotProduct( tw->start, plane->normal ) - + ( plane->dist + tw->biSphere.startRadius ); + d2 = DotProduct( tw->end, plane->normal ) - + ( plane->dist + tw->biSphere.endRadius ); + + if( d2 > 0 ) + getout = qtrue; // endpoint is not in solid + + if( d1 > 0 ) + startout = qtrue; + + // if completely in front of face, no intersection with the entire brush + if( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) + return; + + // if it doesn't cross the plane, the plane isn't relevent + if( d1 <= 0 && d2 <= 0 ) + continue; + + brush->collided = qtrue; + + // crosses face + if( d1 > d2 ) + { + // enter + f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if( f < 0 ) + f = 0; + + if( f > enterFrac ) + { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } + else + { + // leave + f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if( f > 1 ) + f = 1; + + if( f < leaveFrac ) + leaveFrac = f; + } + } + } + else if ( tw->type == TT_CAPSULE ) { + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for (i = 0; i < brush->numsides; i++) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for radius + dist = plane->dist + tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane->normal, tw->sphere.offset ); + if ( t > 0 ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + + d1 = DotProduct( startp, plane->normal ) - dist; + d2 = DotProduct( endp, plane->normal ) - dist; + + if (d2 > 0) { + getout = qtrue; // endpoint is not in solid + } + if (d1 > 0) { + startout = qtrue; + } + + // if completely in front of face, no intersection with the entire brush + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + continue; + } + + brush->collided = qtrue; + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + if (f > enterFrac) { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < leaveFrac) { + leaveFrac = f; + } + } + } + } else { + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for (i = 0; i < brush->numsides; i++) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + d2 = DotProduct( tw->end, plane->normal ) - dist; + + if (d2 > 0) { + getout = qtrue; // endpoint is not in solid + } + if (d1 > 0) { + startout = qtrue; + } + + // if completely in front of face, no intersection with the entire brush + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + continue; + } + + brush->collided = qtrue; + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + if (f > enterFrac) { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < leaveFrac) { + leaveFrac = f; + } + } + } + } + + // + // all planes have been checked, and the trace was not + // completely outside the brush + // + if (!startout) { // original point was inside brush + tw->trace.startsolid = qtrue; + if (!getout) { + tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = brush->contents; + } + return; + } + + if (enterFrac < leaveFrac) { + if (enterFrac > -1 && enterFrac < tw->trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } + tw->trace.fraction = enterFrac; + tw->trace.plane = *clipplane; + tw->trace.surfaceFlags = leadside->surfaceFlags; + tw->trace.contents = brush->contents; + } + } +} + +/* +================ +CM_ProximityToBrush +================ +*/ +static void CM_ProximityToBrush( traceWork_t *tw, cbrush_t *brush ) +{ + int i; + cbrushedge_t *edge; + float dist, minDist = 1e+10f; + float s, t; + float sAtMin = 0.0f; + float radius = 0.0f, fraction; + traceWork_t tw2; + + // cheapish purely linear trace to test for intersection + Com_Memset( &tw2, 0, sizeof( tw2 ) ); + tw2.trace.fraction = 1.0f; + tw2.type = TT_CAPSULE; + tw2.sphere.radius = 0.0f; + VectorClear( tw2.sphere.offset ); + VectorCopy( tw->start, tw2.start ); + VectorCopy( tw->end, tw2.end ); + + CM_TraceThroughBrush( &tw2, brush ); + + if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid ) + { + for( i = 0; i < brush->numEdges; i++ ) + { + edge = &brush->edges[ i ]; + + dist = DistanceBetweenLineSegmentsSquared( tw->start, tw->end, + edge->p0, edge->p1, &s, &t ); + + if( dist < minDist ) + { + minDist = dist; + sAtMin = s; + } + } + + if( tw->type == TT_BISPHERE ) + { + radius = tw->biSphere.startRadius + + ( sAtMin * ( tw->biSphere.endRadius - tw->biSphere.startRadius ) ); + } + else if( tw->type == TT_CAPSULE ) + { + radius = tw->sphere.radius; + } + else if( tw->type == TT_AABB ) + { + //FIXME + } + + fraction = minDist / ( radius * radius ); + + if( fraction < tw->trace.lateralFraction ) + tw->trace.lateralFraction = fraction; + } + else + tw->trace.lateralFraction = 0.0f; +} + +/* +================ +CM_ProximityToPatch +================ +*/ +static void CM_ProximityToPatch( traceWork_t *tw, cPatch_t *patch ) +{ + traceWork_t tw2; + + // cheapish purely linear trace to test for intersection + Com_Memset( &tw2, 0, sizeof( tw2 ) ); + tw2.trace.fraction = 1.0f; + tw2.type = TT_CAPSULE; + tw2.sphere.radius = 0.0f; + VectorClear( tw2.sphere.offset ); + VectorCopy( tw->start, tw2.start ); + VectorCopy( tw->end, tw2.end ); + + CM_TraceThroughPatch( &tw2, patch ); + + if( tw2.trace.fraction == 1.0f && !tw2.trace.allsolid && !tw2.trace.startsolid ) + { + //FIXME: implement me + } + else + tw->trace.lateralFraction = 0.0f; +} + +/* +================ +CM_TraceThroughLeaf +================ +*/ +void CM_TraceThroughLeaf( traceWork_t *tw, cLeaf_t *leaf ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // trace line against all brushes in the leaf + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush+k]; + + b = &cm.brushes[brushnum]; + if ( b->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + + if ( !(b->contents & tw->contents) ) { + continue; + } + + b->collided = qfalse; + + if ( !CM_BoundsIntersect( tw->bounds[0], tw->bounds[1], + b->bounds[0], b->bounds[1] ) ) { + continue; + } + + CM_TraceThroughBrush( tw, b ); + if ( !tw->trace.fraction ) { + tw->trace.lateralFraction = 0.0f; + return; + } + } + + // trace line against all patches in the leaf +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if ( !patch ) { + continue; + } + if ( patch->checkcount == cm.checkcount ) { + continue; // already checked this patch in another leaf + } + patch->checkcount = cm.checkcount; + + if ( !(patch->contents & tw->contents) ) { + continue; + } + + CM_TraceThroughPatch( tw, patch ); + + if ( !tw->trace.fraction ) { + tw->trace.lateralFraction = 0.0f; + return; + } + } + } + + if( tw->testLateralCollision && tw->trace.fraction < 1.0f ) + { + for( k = 0; k < leaf->numLeafBrushes; k++ ) + { + brushnum = cm.leafbrushes[ leaf->firstLeafBrush + k ]; + + b = &cm.brushes[ brushnum ]; + + // This brush never collided, so don't bother + if( !b->collided ) + continue; + + if( !( b->contents & tw->contents ) ) + continue; + + CM_ProximityToBrush( tw, b ); + + if( !tw->trace.lateralFraction ) + return; + } + + for( k = 0; k < leaf->numLeafSurfaces; k++ ) + { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if( !patch ) + continue; + + if( !( patch->contents & tw->contents ) ) + continue; + + CM_ProximityToPatch( tw, patch ); + + if( !tw->trace.lateralFraction ) + return; + } + } +} + +#define RADIUS_EPSILON 1.0f + +/* +================ +CM_TraceThroughSphere + +get the first intersection of the ray with the sphere +================ +*/ +void CM_TraceThroughSphere( traceWork_t *tw, vec3_t origin, float radius, vec3_t start, vec3_t end ) { + float l1, l2, length, scale, fraction; + float a, b, c, d, sqrtd; + vec3_t v1, dir, intersection; + + // if inside the sphere + VectorSubtract(start, origin, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + tw->trace.fraction = 0; + tw->trace.startsolid = qtrue; + // test for allsolid + VectorSubtract(end, origin, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + tw->trace.allsolid = qtrue; + } + return; + } + // + VectorSubtract(end, start, dir); + length = VectorNormalize(dir); + // + l1 = CM_DistanceFromLineSquared(origin, start, end, dir); + VectorSubtract(end, origin, v1); + l2 = VectorLengthSquared(v1); + // if no intersection with the sphere and the end point is at least an epsilon away + if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { + return; + } + // + // | origin - (start + t * dir) | = radius + // a = dir[0]^2 + dir[1]^2 + dir[2]^2; + // b = 2 * (dir[0] * (start[0] - origin[0]) + dir[1] * (start[1] - origin[1]) + dir[2] * (start[2] - origin[2])); + // c = (start[0] - origin[0])^2 + (start[1] - origin[1])^2 + (start[2] - origin[2])^2 - radius^2; + // + VectorSubtract(start, origin, v1); + // dir is normalized so a = 1 + a = 1.0f;//dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]; + b = 2.0f * (dir[0] * v1[0] + dir[1] * v1[1] + dir[2] * v1[2]); + c = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); + + d = b * b - 4.0f * c;// * a; + if (d > 0) { + sqrtd = SquareRootFloat(d); + // = (- b + sqrtd) * 0.5f; // / (2.0f * a); + fraction = (- b - sqrtd) * 0.5f; // / (2.0f * a); + // + if (fraction < 0) { + fraction = 0; + } + else { + fraction /= length; + } + if ( fraction < tw->trace.fraction ) { + tw->trace.fraction = fraction; + VectorSubtract(end, start, dir); + VectorMA(start, fraction, dir, intersection); + VectorSubtract(intersection, origin, dir); + #ifdef CAPSULE_DEBUG + l2 = VectorLength(dir); + if (l2 < radius) { + int bah = 1; + } + #endif + scale = 1 / (radius+RADIUS_EPSILON); + VectorScale(dir, scale, dir); + VectorCopy(dir, tw->trace.plane.normal); + VectorAdd( tw->modelOrigin, intersection, intersection); + tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection); + tw->trace.contents = CONTENTS_BODY; + } + } + else if (d == 0) { + //t1 = (- b ) / 2; + // slide along the sphere + } + // no intersection at all +} + +/* +================ +CM_TraceThroughVerticalCylinder + +get the first intersection of the ray with the cylinder +the cylinder extends halfheight above and below the origin +================ +*/ +void CM_TraceThroughVerticalCylinder( traceWork_t *tw, vec3_t origin, float radius, float halfheight, vec3_t start, vec3_t end) { + float length, scale, fraction, l1, l2; + float a, b, c, d, sqrtd; + vec3_t v1, dir, start2d, end2d, org2d, intersection; + + // 2d coordinates + VectorSet(start2d, start[0], start[1], 0); + VectorSet(end2d, end[0], end[1], 0); + VectorSet(org2d, origin[0], origin[1], 0); + // if between lower and upper cylinder bounds + if (start[2] <= origin[2] + halfheight && + start[2] >= origin[2] - halfheight) { + // if inside the cylinder + VectorSubtract(start2d, org2d, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + tw->trace.fraction = 0; + tw->trace.startsolid = qtrue; + VectorSubtract(end2d, org2d, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + tw->trace.allsolid = qtrue; + } + return; + } + } + // + VectorSubtract(end2d, start2d, dir); + length = VectorNormalize(dir); + // + l1 = CM_DistanceFromLineSquared(org2d, start2d, end2d, dir); + VectorSubtract(end2d, org2d, v1); + l2 = VectorLengthSquared(v1); + // if no intersection with the cylinder and the end point is at least an epsilon away + if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { + return; + } + // + // + // (start[0] - origin[0] - t * dir[0]) ^ 2 + (start[1] - origin[1] - t * dir[1]) ^ 2 = radius ^ 2 + // (v1[0] + t * dir[0]) ^ 2 + (v1[1] + t * dir[1]) ^ 2 = radius ^ 2; + // v1[0] ^ 2 + 2 * v1[0] * t * dir[0] + (t * dir[0]) ^ 2 + + // v1[1] ^ 2 + 2 * v1[1] * t * dir[1] + (t * dir[1]) ^ 2 = radius ^ 2 + // t ^ 2 * (dir[0] ^ 2 + dir[1] ^ 2) + t * (2 * v1[0] * dir[0] + 2 * v1[1] * dir[1]) + + // v1[0] ^ 2 + v1[1] ^ 2 - radius ^ 2 = 0 + // + VectorSubtract(start, origin, v1); + // dir is normalized so we can use a = 1 + a = 1.0f;// * (dir[0] * dir[0] + dir[1] * dir[1]); + b = 2.0f * (v1[0] * dir[0] + v1[1] * dir[1]); + c = v1[0] * v1[0] + v1[1] * v1[1] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); + + d = b * b - 4.0f * c;// * a; + if (d > 0) { + sqrtd = SquareRootFloat(d); + // = (- b + sqrtd) * 0.5f;// / (2.0f * a); + fraction = (- b - sqrtd) * 0.5f;// / (2.0f * a); + // + if (fraction < 0) { + fraction = 0; + } + else { + fraction /= length; + } + if ( fraction < tw->trace.fraction ) { + VectorSubtract(end, start, dir); + VectorMA(start, fraction, dir, intersection); + // if the intersection is between the cylinder lower and upper bound + if (intersection[2] <= origin[2] + halfheight && + intersection[2] >= origin[2] - halfheight) { + // + tw->trace.fraction = fraction; + VectorSubtract(intersection, origin, dir); + dir[2] = 0; + #ifdef CAPSULE_DEBUG + l2 = VectorLength(dir); + if (l2 <= radius) { + int bah = 1; + } + #endif + scale = 1 / (radius+RADIUS_EPSILON); + VectorScale(dir, scale, dir); + VectorCopy(dir, tw->trace.plane.normal); + VectorAdd( tw->modelOrigin, intersection, intersection); + tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection); + tw->trace.contents = CONTENTS_BODY; + } + } + } + else if (d == 0) { + //t[0] = (- b ) / 2 * a; + // slide along the cylinder + } + // no intersection at all +} + +/* +================ +CM_TraceCapsuleThroughCapsule + +capsule vs. capsule collision (not rotated) +================ +*/ +void CM_TraceCapsuleThroughCapsule( traceWork_t *tw, clipHandle_t model ) { + int i; + vec3_t mins, maxs; + vec3_t top, bottom, starttop, startbottom, endtop, endbottom; + vec3_t offset, symetricSize[2]; + float radius, halfwidth, halfheight, offs, h; + + CM_ModelBounds(model, mins, maxs); + // test trace bounds vs. capsule bounds + if ( tw->bounds[0][0] > maxs[0] + RADIUS_EPSILON + || tw->bounds[0][1] > maxs[1] + RADIUS_EPSILON + || tw->bounds[0][2] > maxs[2] + RADIUS_EPSILON + || tw->bounds[1][0] < mins[0] - RADIUS_EPSILON + || tw->bounds[1][1] < mins[1] - RADIUS_EPSILON + || tw->bounds[1][2] < mins[2] - RADIUS_EPSILON + ) { + return; + } + // top origin and bottom origin of each sphere at start and end of trace + VectorAdd(tw->start, tw->sphere.offset, starttop); + VectorSubtract(tw->start, tw->sphere.offset, startbottom); + VectorAdd(tw->end, tw->sphere.offset, endtop); + VectorSubtract(tw->end, tw->sphere.offset, endbottom); + + // calculate top and bottom of the capsule spheres to collide with + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + } + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + offs = halfheight - radius; + VectorCopy(offset, top); + top[2] += offs; + VectorCopy(offset, bottom); + bottom[2] -= offs; + // expand radius of spheres + radius += tw->sphere.radius; + // if there is horizontal movement + if ( tw->start[0] != tw->end[0] || tw->start[1] != tw->end[1] ) { + // height of the expanded cylinder is the height of both cylinders minus the radius of both spheres + h = halfheight + tw->sphere.halfheight - radius; + // if the cylinder has a height + if ( h > 0 ) { + // test for collisions between the cylinders + CM_TraceThroughVerticalCylinder(tw, offset, radius, h, tw->start, tw->end); + } + } + // test for collision between the spheres + CM_TraceThroughSphere(tw, top, radius, startbottom, endbottom); + CM_TraceThroughSphere(tw, bottom, radius, starttop, endtop); +} + +/* +================ +CM_TraceBoundingBoxThroughCapsule + +bounding box vs. capsule collision +================ +*/ +void CM_TraceBoundingBoxThroughCapsule( traceWork_t *tw, clipHandle_t model ) { + vec3_t mins, maxs, offset, size[2]; + clipHandle_t h; + cmodel_t *cmod; + int i; + + // mins maxs of the capsule + CM_ModelBounds(model, mins, maxs); + + // offset for capsule center + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + size[0][i] = mins[i] - offset[i]; + size[1][i] = maxs[i] - offset[i]; + tw->start[i] -= offset[i]; + tw->end[i] -= offset[i]; + } + + // replace the bounding box with the capsule + tw->type = TT_CAPSULE; + tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0]; + tw->sphere.halfheight = size[1][2]; + VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + + // replace the capsule with the bounding box + h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse); + // calculate collision + cmod = CM_ClipHandleToModel( h ); + CM_TraceThroughLeaf( tw, &cmod->leaf ); +} + +//========================================================================================= + +/* +================== +CM_TraceThroughTree + +Traverse all the contacted leafs from the start to the end position. +If the trace is a point, they will be exactly in order, but for larger +trace volumes it is possible to hit something in a later leaf with +a smaller intercept fraction. +================== +*/ +void CM_TraceThroughTree( traceWork_t *tw, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) { + cNode_t *node; + cplane_t *plane; + float t1, t2, offset; + float frac, frac2; + float idist; + vec3_t mid; + int side; + float midf; + + if (tw->trace.fraction <= p1f) { + return; // already hit something nearer + } + + // if < 0, we are in a leaf node + if (num < 0) { + CM_TraceThroughLeaf( tw, &cm.leafs[-1-num] ); + return; + } + + // + // find the point distances to the seperating plane + // and the offset for the size of the box + // + node = cm.nodes + num; + plane = node->plane; + + // adjust the plane distance apropriately for mins/maxs + if ( plane->type < 3 ) { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + offset = tw->extents[plane->type]; + } else { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + if ( tw->isPoint ) { + offset = 0; + } else { + // this is silly + offset = 2048; + } + } + + // see which sides we need to consider + if ( t1 >= offset + 1 && t2 >= offset + 1 ) { + CM_TraceThroughTree( tw, node->children[0], p1f, p2f, p1, p2 ); + return; + } + if ( t1 < -offset - 1 && t2 < -offset - 1 ) { + CM_TraceThroughTree( tw, node->children[1], p1f, p2f, p1, p2 ); + return; + } + + // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side + if ( t1 < t2 ) { + idist = 1.0/(t1-t2); + side = 1; + frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; + frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist; + } else if (t1 > t2) { + idist = 1.0/(t1-t2); + side = 0; + frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist; + frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; + } else { + side = 0; + frac = 1; + frac2 = 0; + } + + // move up to the node + if ( frac < 0 ) { + frac = 0; + } + if ( frac > 1 ) { + frac = 1; + } + + midf = p1f + (p2f - p1f)*frac; + + mid[0] = p1[0] + frac*(p2[0] - p1[0]); + mid[1] = p1[1] + frac*(p2[1] - p1[1]); + mid[2] = p1[2] + frac*(p2[2] - p1[2]); + + CM_TraceThroughTree( tw, node->children[side], p1f, midf, p1, mid ); + + + // go past the node + if ( frac2 < 0 ) { + frac2 = 0; + } + if ( frac2 > 1 ) { + frac2 = 1; + } + + midf = p1f + (p2f - p1f)*frac2; + + mid[0] = p1[0] + frac2*(p2[0] - p1[0]); + mid[1] = p1[1] + frac2*(p2[1] - p1[1]); + mid[2] = p1[2] + frac2*(p2[2] - p1[2]); + + CM_TraceThroughTree( tw, node->children[side^1], midf, p2f, mid, p2 ); +} + +//====================================================================== + + +/* +================== +CM_Trace +================== +*/ +void CM_Trace( trace_t *results, const vec3_t start, + const vec3_t end, vec3_t mins, vec3_t maxs, + clipHandle_t model, const vec3_t origin, int brushmask, + traceType_t type, sphere_t *sphere ) { + int i; + traceWork_t tw; + vec3_t offset; + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + + cm.checkcount++; // for multi-check avoidance + + c_traces++; // for statistics, may be zeroed + + // fill in a default trace + Com_Memset( &tw, 0, sizeof(tw) ); + tw.trace.fraction = 1; // assume it goes the entire distance until shown otherwise + VectorCopy(origin, tw.modelOrigin); + tw.type = type; + + if (!cm.numNodes) { + *results = tw.trace; + + return; // map not loaded, shouldn't happen + } + + // allow NULL to be passed in for 0,0,0 + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // set basic parms + tw.contents = brushmask; + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + tw.size[0][i] = mins[i] - offset[i]; + tw.size[1][i] = maxs[i] - offset[i]; + tw.start[i] = start[i] + offset[i]; + tw.end[i] = end[i] + offset[i]; + } + + // if a sphere is already specified + if ( sphere ) { + tw.sphere = *sphere; + } + else { + tw.sphere.radius = ( tw.size[1][0] > tw.size[1][2] ) ? tw.size[1][2]: tw.size[1][0]; + tw.sphere.halfheight = tw.size[1][2]; + VectorSet( tw.sphere.offset, 0, 0, tw.size[1][2] - tw.sphere.radius ); + } + + tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2]; + + // tw.offsets[signbits] = vector to apropriate corner from origin + tw.offsets[0][0] = tw.size[0][0]; + tw.offsets[0][1] = tw.size[0][1]; + tw.offsets[0][2] = tw.size[0][2]; + + tw.offsets[1][0] = tw.size[1][0]; + tw.offsets[1][1] = tw.size[0][1]; + tw.offsets[1][2] = tw.size[0][2]; + + tw.offsets[2][0] = tw.size[0][0]; + tw.offsets[2][1] = tw.size[1][1]; + tw.offsets[2][2] = tw.size[0][2]; + + tw.offsets[3][0] = tw.size[1][0]; + tw.offsets[3][1] = tw.size[1][1]; + tw.offsets[3][2] = tw.size[0][2]; + + tw.offsets[4][0] = tw.size[0][0]; + tw.offsets[4][1] = tw.size[0][1]; + tw.offsets[4][2] = tw.size[1][2]; + + tw.offsets[5][0] = tw.size[1][0]; + tw.offsets[5][1] = tw.size[0][1]; + tw.offsets[5][2] = tw.size[1][2]; + + tw.offsets[6][0] = tw.size[0][0]; + tw.offsets[6][1] = tw.size[1][1]; + tw.offsets[6][2] = tw.size[1][2]; + + tw.offsets[7][0] = tw.size[1][0]; + tw.offsets[7][1] = tw.size[1][1]; + tw.offsets[7][2] = tw.size[1][2]; + + // + // calculate bounds + // + if ( tw.type == TT_CAPSULE ) { + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius; + tw.bounds[1][i] = tw.end[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius; + } else { + tw.bounds[0][i] = tw.end[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius; + tw.bounds[1][i] = tw.start[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius; + } + } + } + else { + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i]; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i]; + } + } + } + + // + // check for position test special case + // + if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2]) { + if ( model ) { +#ifdef ALWAYS_BBOX_VS_BBOX // FIXME - compile time flag? + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { + tw.type = TT_AABB; + CM_TestInLeaf( &tw, &cmod->leaf ); + } + else +#elif defined(ALWAYS_CAPSULE_VS_CAPSULE) + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { + CM_TestCapsuleInCapsule( &tw, model ); + } + else +#endif + if ( model == CAPSULE_MODEL_HANDLE ) { + if ( tw.type == TT_CAPSULE ) { + CM_TestCapsuleInCapsule( &tw, model ); + } + else { + CM_TestBoundingBoxInCapsule( &tw, model ); + } + } + else { + CM_TestInLeaf( &tw, &cmod->leaf ); + } + } else { + CM_PositionTest( &tw ); + } + } else { + // + // check for point special case + // + if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 ) { + tw.isPoint = qtrue; + VectorClear( tw.extents ); + } else { + tw.isPoint = qfalse; + tw.extents[0] = tw.size[1][0]; + tw.extents[1] = tw.size[1][1]; + tw.extents[2] = tw.size[1][2]; + } + + // + // general sweeping through world + // + if ( model ) { +#ifdef ALWAYS_BBOX_VS_BBOX + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { + tw.type = TT_AABB; + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + } + else +#elif defined(ALWAYS_CAPSULE_VS_CAPSULE) + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) { + CM_TraceCapsuleThroughCapsule( &tw, model ); + } + else +#endif + if ( model == CAPSULE_MODEL_HANDLE ) { + if ( tw.type == TT_CAPSULE ) { + CM_TraceCapsuleThroughCapsule( &tw, model ); + } + else { + CM_TraceBoundingBoxThroughCapsule( &tw, model ); + } + } + else { + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + } + } else { + CM_TraceThroughTree( &tw, 0, 0, 1, tw.start, tw.end ); + } + } + + // generate endpos from the original, unmodified start/end + if ( tw.trace.fraction == 1 ) { + VectorCopy (end, tw.trace.endpos); + } else { + for ( i=0 ; i<3 ; i++ ) { + tw.trace.endpos[i] = start[i] + tw.trace.fraction * (end[i] - start[i]); + } + } + + // If allsolid is set (was entirely inside something solid), the plane is not valid. + // If fraction == 1.0, we never hit anything, and thus the plane is not valid. + // Otherwise, the normal on the plane should have unit length + assert(tw.trace.allsolid || + tw.trace.fraction == 1.0 || + VectorLengthSquared(tw.trace.plane.normal) > 0.9999); + *results = tw.trace; +} + +/* +================== +CM_BoxTrace +================== +*/ +void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + vec3_t mins, vec3_t maxs, + clipHandle_t model, int brushmask, traceType_t type ) { + CM_Trace( results, start, end, mins, maxs, model, vec3_origin, brushmask, type, NULL ); +} + +/* +================== +CM_TransformedBoxTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + vec3_t mins, vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, traceType_t type ) { + trace_t trace; + vec3_t start_l, end_l; + qboolean rotated; + vec3_t offset; + vec3_t symetricSize[2]; + vec3_t matrix[3], transpose[3]; + int i; + float halfwidth; + float halfheight; + float t; + sphere_t sphere; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + start_l[i] = start[i] + offset[i]; + end_l[i] = end[i] + offset[i]; + } + + // subtract origin offset + VectorSubtract( start_l, origin, start_l ); + VectorSubtract( end_l, origin, end_l ); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + (angles[0] || angles[1] || angles[2]) ) { + rotated = qtrue; + } else { + rotated = qfalse; + } + + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + + sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + sphere.halfheight = halfheight; + t = halfheight - sphere.radius; + + if (rotated) { + // rotation on trace line (start-end) instead of rotating the bmodel + // NOTE: This is still incorrect for bounding boxes because the actual bounding + // box that is swept through the model is not rotated. We cannot rotate + // the bounding box or the bmodel because that would make all the brush + // bevels invalid. + // However this is correct for capsules since a capsule itself is rotated too. + CreateRotationMatrix(angles, matrix); + RotatePoint(start_l, matrix); + RotatePoint(end_l, matrix); + // rotated sphere offset for capsule + sphere.offset[0] = matrix[0][ 2 ] * t; + sphere.offset[1] = -matrix[1][ 2 ] * t; + sphere.offset[2] = matrix[2][ 2 ] * t; + } + else { + VectorSet( sphere.offset, 0, 0, t ); + } + + // sweep the box through the model + CM_Trace( &trace, start_l, end_l, symetricSize[0], symetricSize[1], + model, origin, brushmask, type, &sphere ); + + // if the bmodel was rotated and there was a collision + if ( rotated && trace.fraction != 1.0 ) { + // rotation of bmodel collision plane + TransposeMatrix(matrix, transpose); + RotatePoint(trace.plane.normal, transpose); + } + + // re-calculate the end position of the trace because the trace.endpos + // calculated by CM_Trace could be rotated and have an offset + trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]); + trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]); + trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]); + + *results = trace; +} + +/* +================== +CM_BiSphereTrace +================== +*/ +void CM_BiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask ) +{ + int i; + traceWork_t tw; + float largestRadius = startRad > endRad ? startRad : endRad; + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + + cm.checkcount++; // for multi-check avoidance + + c_traces++; // for statistics, may be zeroed + + // fill in a default trace + Com_Memset( &tw, 0, sizeof( tw ) ); + tw.trace.fraction = 1.0f; // assume it goes the entire distance until shown otherwise + VectorCopy( vec3_origin, tw.modelOrigin ); + tw.type = TT_BISPHERE; + tw.testLateralCollision = qtrue; + tw.trace.lateralFraction = 1.0f; + + if( !cm.numNodes ) + { + *results = tw.trace; + + return; // map not loaded, shouldn't happen + } + + // set basic parms + tw.contents = mask; + + VectorCopy( start, tw.start ); + VectorCopy( end, tw.end ); + + tw.biSphere.startRadius = startRad; + tw.biSphere.endRadius = endRad; + + // + // calculate bounds + // + for( i = 0 ; i < 3 ; i++ ) + { + if( tw.start[ i ] < tw.end[ i ] ) + { + tw.bounds[ 0 ][ i ] = tw.start[ i ] - tw.biSphere.startRadius; + tw.bounds[ 1 ][ i ] = tw.end[ i ] + tw.biSphere.endRadius; + } + else + { + tw.bounds[ 0 ][ i ] = tw.end[ i ] + tw.biSphere.endRadius; + tw.bounds[ 1 ][ i ] = tw.start[ i ] - tw.biSphere.startRadius; + } + } + + tw.isPoint = qfalse; + tw.extents[ 0 ] = largestRadius; + tw.extents[ 1 ] = largestRadius; + tw.extents[ 2 ] = largestRadius; + + // + // general sweeping through world + // + if( model ) + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + else + CM_TraceThroughTree( &tw, 0, 0.0f, 1.0f, tw.start, tw.end ); + + // generate endpos from the original, unmodified start/end + if( tw.trace.fraction == 1.0f ) + { + VectorCopy( end, tw.trace.endpos ); + } + else + { + for( i = 0; i < 3; i++ ) + tw.trace.endpos[ i ] = start[ i ] + tw.trace.fraction * ( end[ i ] - start[ i ] ); + } + + // If allsolid is set (was entirely inside something solid), the plane is not valid. + // If fraction == 1.0, we never hit anything, and thus the plane is not valid. + // Otherwise, the normal on the plane should have unit length + assert( tw.trace.allsolid || + tw.trace.fraction == 1.0 || + VectorLengthSquared(tw.trace.plane.normal ) > 0.9999 ); + + *results = tw.trace; +} + +/* +================== +CM_TransformedBiSphereTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start, + const vec3_t end, float startRad, float endRad, + clipHandle_t model, int mask, + const vec3_t origin ) +{ + trace_t trace; + vec3_t start_l, end_l; + + // subtract origin offset + VectorSubtract( start, origin, start_l ); + VectorSubtract( end, origin, end_l ); + + CM_BiSphereTrace( &trace, start_l, end_l, startRad, endRad, model, mask ); + + // re-calculate the end position of the trace because the trace.endpos + // calculated by CM_BiSphereTrace could be rotated and have an offset + trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]); + trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]); + trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]); + + *results = trace; +} diff --git a/src/qcommon/cmd.c b/src/qcommon/cmd.c new file mode 100644 index 0000000..8ea761e --- /dev/null +++ b/src/qcommon/cmd.c @@ -0,0 +1,878 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cmd.c -- Quake script command processing module + +#include "q_shared.h" +#include "qcommon.h" + +#define MAX_CMD_BUFFER 16384 +#define MAX_CMD_LINE 1024 + +typedef struct { + byte *data; + int maxsize; + int cursize; +} cmd_t; + +int cmd_wait; +cmd_t cmd_text; +byte cmd_text_buf[MAX_CMD_BUFFER]; + + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster" +============ +*/ +void Cmd_Wait_f( void ) { + if ( Cmd_Argc() == 2 ) { + cmd_wait = atoi( Cmd_Argv( 1 ) ); + if ( cmd_wait < 0 ) + cmd_wait = 1; // ignore the argument + } else { + cmd_wait = 1; + } +} + + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init (void) +{ + cmd_text.data = cmd_text_buf; + cmd_text.maxsize = MAX_CMD_BUFFER; + cmd_text.cursize = 0; +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer, does NOT add a final \n +============ +*/ +void Cbuf_AddText( const char *text ) { + int l; + + l = strlen (text); + + if (cmd_text.cursize + l >= cmd_text.maxsize) + { + Com_Printf ("Cbuf_AddText: overflow\n"); + return; + } + Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l); + cmd_text.cursize += l; +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void Cbuf_InsertText( const char *text ) { + int len; + int i; + + len = strlen( text ) + 1; + if ( len + cmd_text.cursize > cmd_text.maxsize ) { + Com_Printf( "Cbuf_InsertText overflowed\n" ); + return; + } + + // move the existing command text + for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) { + cmd_text.data[ i + len ] = cmd_text.data[ i ]; + } + + // copy the new text in + Com_Memcpy( cmd_text.data, text, len - 1 ); + + // add a \n + cmd_text.data[ len - 1 ] = '\n'; + + cmd_text.cursize += len; +} + + +/* +============ +Cbuf_ExecuteText +============ +*/ +void Cbuf_ExecuteText (int exec_when, const char *text) +{ + switch (exec_when) + { + case EXEC_NOW: + if (text && strlen(text) > 0) { + Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text); + Cmd_ExecuteString (text); + } else { + Cbuf_Execute(); + Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data); + } + break; + case EXEC_INSERT: + Cbuf_InsertText (text); + break; + case EXEC_APPEND: + Cbuf_AddText (text); + break; + default: + Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when"); + } +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute (void) +{ + int i; + char *text; + char line[MAX_CMD_LINE]; + int quotes; + + while (cmd_text.cursize) + { + if ( cmd_wait > 0 ) { + // skip out while text still remains in buffer, leaving it + // for next frame + cmd_wait--; + break; + } + + // find a \n or ; line break + 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' || text[i] == '\r' ) + break; + } + + if( i >= (MAX_CMD_LINE - 1)) { + i = MAX_CMD_LINE - 1; + } + + Com_Memcpy (line, text, i); + line[i] = 0; + +// delete the text from the command buffer and move remaining commands down +// this is necessary because commands (exec) can insert data at the +// beginning of the text buffer + + if (i == cmd_text.cursize) + cmd_text.cursize = 0; + else + { + i++; + cmd_text.cursize -= i; + memmove (text, text+i, cmd_text.cursize); + } + +// execute the command line + + Cmd_ExecuteString (line); + } +} + + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + + +/* +=============== +Cmd_Exec_f +=============== +*/ +void Cmd_Exec_f( void ) { + union { + char *c; + void *v; + } f; + int len; + char filename[MAX_QPATH]; + + if (Cmd_Argc () != 2) { + Com_Printf ("exec : execute a script file\n"); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + len = FS_ReadFile( filename, &f.v); + if (!f.c) { + Com_Printf ("couldn't exec %s\n",Cmd_Argv(1)); + return; + } + Com_Printf ("execing %s\n",Cmd_Argv(1)); + + Cbuf_InsertText (f.c); + + FS_FreeFile (f.v); +} + + +/* +=============== +Cmd_Vstr_f + +Inserts the current value of a variable as command text +=============== +*/ +void Cmd_Vstr_f( void ) { + char *v; + + if (Cmd_Argc () != 2) { + Com_Printf ("vstr : execute a variable command\n"); + return; + } + + v = Cvar_VariableString( Cmd_Argv( 1 ) ); + Cbuf_InsertText( va("%s\n", v ) ); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f (void) +{ + Com_Printf ("%s\n", Cmd_Args()); +} + + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + char *name; + xcommand_t function; + completionFunc_t complete; +} cmd_function_t; + + +typedef struct cmdContext_s +{ + int argc; + char *argv[ MAX_STRING_TOKENS ]; // points into cmd.tokenized + char tokenized[ BIG_INFO_STRING + MAX_STRING_TOKENS ]; // will have 0 bytes inserted + char cmd[ BIG_INFO_STRING ]; // the original command we received (no token processing) +} cmdContext_t; + +static cmdContext_t cmd; +static cmdContext_t savedCmd; +static cmd_function_t *cmd_functions; // possible commands to execute + +/* +============ +Cmd_SaveCmdContext +============ +*/ +void Cmd_SaveCmdContext( void ) +{ + Com_Memcpy( &savedCmd, &cmd, sizeof( cmdContext_t ) ); +} + +/* +============ +Cmd_RestoreCmdContext +============ +*/ +void Cmd_RestoreCmdContext( void ) +{ + Com_Memcpy( &cmd, &savedCmd, sizeof( cmdContext_t ) ); +} + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc( void ) { + return cmd.argc; +} + +/* +============ +Cmd_Argv +============ +*/ +char *Cmd_Argv( int arg ) { + if ( (unsigned)arg >= cmd.argc ) { + return ""; + } + return cmd.argv[arg]; +} + +/* +============ +Cmd_ArgvBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength ); +} + + +/* +============ +Cmd_Args + +Returns a single string containing argv(1) to argv(argc()-1) +============ +*/ +char *Cmd_Args( void ) { + static char cmd_args[MAX_STRING_CHARS]; + int i; + + cmd_args[0] = 0; + for ( i = 1 ; i < cmd.argc ; i++ ) { + strcat( cmd_args, cmd.argv[i] ); + if ( i != cmd.argc-1 ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + +/* +============ +Cmd_Args + +Returns a single string containing argv(arg) to argv(argc()-1) +============ +*/ +char *Cmd_ArgsFrom( int arg ) { + static char cmd_args[BIG_INFO_STRING]; + int i; + + cmd_args[0] = 0; + if (arg < 0) + arg = 0; + for ( i = arg ; i < cmd.argc ; i++ ) { + strcat( cmd_args, cmd.argv[i] ); + if ( i != cmd.argc-1 ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + +/* +============ +Cmd_ArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgsBuffer( char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Args(), bufferLength ); +} + +/* +============ +Cmd_LiteralArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_LiteralArgsBuffer( char *buffer, int bufferLength ) { + Q_strncpyz( buffer, cmd.cmd, bufferLength ); +} + +/* +============ +Cmd_Cmd + +Retrieve the unmodified command string +For rcon use when you want to transmit without altering quoting +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 +============ +*/ +char *Cmd_Cmd(void) +{ + return cmd.cmd; +} + +/* + Replace command separators with space to prevent interpretation + This is a hack to protect buggy qvms + https://bugzilla.icculus.org/show_bug.cgi?id=3593 + https://bugzilla.icculus.org/show_bug.cgi?id=4769 +*/ + +void Cmd_Args_Sanitize(void) +{ + int i; + + for(i = 1; i < cmd.argc; i++) + { + char *c = cmd.argv[i]; + + if(strlen(c) > MAX_CVAR_VALUE_STRING - 1) + c[MAX_CVAR_VALUE_STRING - 1] = '\0'; + + while ((c = strpbrk(c, "\n\r;"))) { + *c = ' '; + ++c; + } + } +} + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +The text is copied to a seperate buffer and 0 characters +are inserted in the apropriate place, The argv array +will point into this temporary buffer. +============ +*/ +// NOTE TTimo define that to track tokenization issues +//#define TKN_DBG +static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) { + const char *text; + char *textOut; + +#ifdef TKN_DBG + // FIXME TTimo blunt hook to try to find the tokenization of userinfo + Com_DPrintf("Cmd_TokenizeString: %s\n", text_in); +#endif + + // clear previous args + cmd.argc = 0; + cmd.cmd[ 0 ] = '\0'; + + if ( !text_in ) { + return; + } + + Q_strncpyz( cmd.cmd, text_in, sizeof(cmd.cmd) ); + + text = text_in; + textOut = cmd.tokenized; + + while ( 1 ) { + if ( cmd.argc == MAX_STRING_TOKENS ) { + return; // this is usually something malicious + } + + while ( 1 ) { + // skip whitespace + while ( *text && *text <= ' ' ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + + // skip // comments + if ( text[0] == '/' && text[1] == '/' ) { + return; // all tokens parsed + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] =='*' ) { + while ( *text && ( text[0] != '*' || text[1] != '/' ) ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + text += 2; + } else { + break; // we are ready to parse a token + } + } + + // handle quoted strings + // NOTE TTimo this doesn't handle \" escaping + if ( !ignoreQuotes && *text == '"' ) { + cmd.argv[cmd.argc] = textOut; + cmd.argc++; + text++; + while ( *text && *text != '"' ) { + *textOut++ = *text++; + } + *textOut++ = 0; + if ( !*text ) { + return; // all tokens parsed + } + text++; + continue; + } + + // regular token + cmd.argv[cmd.argc] = textOut; + cmd.argc++; + + // skip until whitespace, quote, or command + while ( *text > ' ' ) { + if ( !ignoreQuotes && text[0] == '"' ) { + break; + } + + if ( text[0] == '/' && text[1] == '/' ) { + break; + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] =='*' ) { + break; + } + + *textOut++ = *text++; + } + + *textOut++ = 0; + + if ( !*text ) { + return; // all tokens parsed + } + } + +} + +/* +============ +Cmd_TokenizeString +============ +*/ +void Cmd_TokenizeString( const char *text_in ) { + Cmd_TokenizeString2( text_in, qfalse ); +} + +/* +============ +Cmd_TokenizeStringIgnoreQuotes +============ +*/ +void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) { + Cmd_TokenizeString2( text_in, qtrue ); +} + +/* +============ +Cmd_FindCommand +============ +*/ +cmd_function_t *Cmd_FindCommand( const char *cmd_name ) +{ + cmd_function_t *cmd; + for( cmd = cmd_functions; cmd; cmd = cmd->next ) + if( !Q_stricmp( cmd_name, cmd->name ) ) + return cmd; + return NULL; +} + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { + cmd_function_t *cmd; + + // fail if the command already exists + if( Cmd_FindCommand( cmd_name ) ) + { + // allow completion-only commands to be silently doubled + if( function != NULL ) + Com_Printf( "Cmd_AddCommand: %s already defined\n", cmd_name ); + return; + } + + // use a small malloc to avoid zone fragmentation + cmd = S_Malloc (sizeof(cmd_function_t)); + cmd->name = CopyString( cmd_name ); + cmd->function = function; + cmd->complete = NULL; + cmd->next = cmd_functions; + cmd_functions = cmd; +} + +/* +============ +Cmd_SetCommandCompletionFunc +============ +*/ +void Cmd_SetCommandCompletionFunc( const char *command, completionFunc_t complete ) { + cmd_function_t *cmd; + + for( cmd = cmd_functions; cmd; cmd = cmd->next ) { + if( !Q_stricmp( command, cmd->name ) ) { + cmd->complete = complete; + } + } +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *cmd_name ) { + cmd_function_t *cmd, **back; + + back = &cmd_functions; + while( 1 ) { + cmd = *back; + if ( !cmd ) { + // command wasn't active + return; + } + if ( !strcmp( cmd_name, cmd->name ) ) { + *back = cmd->next; + if (cmd->name) { + Z_Free(cmd->name); + } + Z_Free (cmd); + return; + } + back = &cmd->next; + } +} + +/* +============ +Cmd_RemoveCommandSafe + +Only remove commands with no associated function +============ +*/ +void Cmd_RemoveCommandSafe( const char *cmd_name ) +{ + cmd_function_t *cmd = Cmd_FindCommand( cmd_name ); + + if( !cmd ) + return; + if( cmd->function ) + { + Com_Error( ERR_DROP, "Restricted source tried to remove " + "system command \"%s\"\n", cmd_name ); + return; + } + + Cmd_RemoveCommand( cmd_name ); +} + +/* +============ +Cmd_CommandCompletion +============ +*/ +void Cmd_CommandCompletion( void(*callback)(const char *s) ) { + cmd_function_t *cmd; + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { + callback( cmd->name ); + } +} + +/* +============ +Cmd_CompleteArgument +============ +*/ +void Cmd_CompleteArgument( const char *command, char *args, int argNum ) { + cmd_function_t *cmd; + + for( cmd = cmd_functions; cmd; cmd = cmd->next ) { + if( !Q_stricmp( command, cmd->name ) && cmd->complete ) { + cmd->complete( args, argNum ); + } + } +} + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void Cmd_ExecuteString( const char *text ) { + cmd_function_t *cmdFunc, **prev; + + // execute the command line + Cmd_TokenizeString( text ); + if ( !Cmd_Argc() ) { + return; // no tokens + } + + // check registered command functions + for ( prev = &cmd_functions ; *prev ; prev = &cmdFunc->next ) { + cmdFunc = *prev; + if ( !Q_stricmp( cmd.argv[0], cmdFunc->name ) ) { + // rearrange the links so that the command will be + // near the head of the list next time it is used + *prev = cmdFunc->next; + cmdFunc->next = cmd_functions; + cmd_functions = cmdFunc; + + // perform the action + if ( !cmdFunc->function ) { + // let the cgame or game handle it + break; + } else { + cmdFunc->function (); + } + return; + } + } + + // check cvars + if ( Cvar_Command() ) { + return; + } + + // check client game commands + if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) { + return; + } + + // check server game commands + if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) { + return; + } + + // check ui commands + if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) { + return; + } + + // send it as a server command if we are connected + // this will usually result in a chat message + CL_ForwardCommandToServer ( text ); +} + +/* +============ +Cmd_List_f +============ +*/ +void Cmd_List_f (void) +{ + cmd_function_t *cmd; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { + if (match && !Com_Filter(match, cmd->name, qfalse)) continue; + + Com_Printf ("%s\n", cmd->name); + i++; + } + Com_Printf ("%i commands\n", i); +} + +/* +================== +Cmd_CompleteCfgName +================== +*/ +void Cmd_CompleteCfgName( char *args, int argNum ) { + if( argNum == 2 ) { + Field_CompleteFilename( "", "cfg", qfalse, qtrue ); + } +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) { + Cmd_AddCommand ("cmdlist",Cmd_List_f); + Cmd_AddCommand ("exec",Cmd_Exec_f); + Cmd_SetCommandCompletionFunc( "exec", Cmd_CompleteCfgName ); + Cmd_AddCommand ("vstr",Cmd_Vstr_f); + Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName ); + Cmd_AddCommand ("echo",Cmd_Echo_f); + Cmd_AddCommand ("wait", Cmd_Wait_f); +} + diff --git a/src/qcommon/common.c b/src/qcommon/common.c new file mode 100644 index 0000000..761fe72 --- /dev/null +++ b/src/qcommon/common.c @@ -0,0 +1,3326 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// common.c -- misc functions used in client and server + +#include "q_shared.h" +#include "qcommon.h" +#include +#ifndef _WIN32 +#include +#include // umask +#else +#include +#endif + +int demo_protocols[] = +{ PROTOCOL_VERSION, 0 }; + +#define MAX_NUM_ARGVS 50 + +#define MIN_DEDICATED_COMHUNKMEGS 1 +#define MIN_COMHUNKMEGS 128 +#define DEF_COMHUNKMEGS 128 +#define DEF_COMZONEMEGS 24 +#define DEF_COMHUNKMEGS_S XSTRING(DEF_COMHUNKMEGS) +#define DEF_COMZONEMEGS_S XSTRING(DEF_COMZONEMEGS) + +int com_argc; +char *com_argv[MAX_NUM_ARGVS+1]; + +jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame + + +FILE *debuglogfile; +static fileHandle_t pipefile; +static fileHandle_t logfile; +fileHandle_t com_journalFile; // events are written here +fileHandle_t com_journalDataFile; // config files are written here + +cvar_t *com_speeds; +cvar_t *com_developer; +cvar_t *com_dedicated; +cvar_t *com_timescale; +cvar_t *com_fixedtime; +cvar_t *com_journal; +cvar_t *com_maxfps; +cvar_t *com_altivec; +cvar_t *com_timedemo; +cvar_t *com_sv_running; +cvar_t *com_cl_running; +cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print +cvar_t *com_pipefile; +cvar_t *com_showtrace; +cvar_t *com_version; +cvar_t *com_blood; +cvar_t *com_buildScript; // for automated data building scripts +cvar_t *cl_paused; +cvar_t *sv_paused; +cvar_t *cl_packetdelay; +cvar_t *sv_packetdelay; +cvar_t *com_cameraMode; +cvar_t *com_ansiColor; +cvar_t *com_unfocused; +cvar_t *com_maxfpsUnfocused; +cvar_t *com_minimized; +cvar_t *com_maxfpsMinimized; +cvar_t *com_abnormalExit; +cvar_t *com_homepath; +cvar_t *com_busyWait; + +// com_speeds times +int time_game; +int time_frontend; // renderer frontend time +int time_backend; // renderer backend time + +int com_frameTime; +int com_frameNumber; + +qboolean com_errorEntered = qfalse; +qboolean com_fullyInitialized = qfalse; +qboolean com_gameRestarting = qfalse; + +char com_errorMessage[MAXPRINTMSG]; + +void Com_WriteConfig_f( void ); +void CIN_CloseAllVideos( void ); + +//============================================================================ + +static char *rd_buffer; +static int rd_buffersize; +static void (*rd_flush)( char *buffer ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) ) +{ + if (!buffer || !buffersize || !flush) + return; + rd_buffer = buffer; + rd_buffersize = buffersize; + rd_flush = flush; + + *rd_buffer = 0; +} + +void Com_EndRedirect (void) +{ + if ( rd_flush ) { + rd_flush(rd_buffer); + } + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; +} + +/* +============= +Com_Printf + +Both client and server can use this, and it will output +to the apropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +============= +*/ +void QDECL Com_Printf( const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + static qboolean opening_qconsole = qfalse; + + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + if ( rd_buffer ) { + if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) { + rd_flush(rd_buffer); + *rd_buffer = 0; + } + Q_strcat(rd_buffer, rd_buffersize, msg); + // TTimo nooo .. that would defeat the purpose + //rd_flush(rd_buffer); + //*rd_buffer = 0; + return; + } + +#ifndef DEDICATED + CL_ConsolePrint( msg ); +#endif + + Q_StripIndentMarker( msg ); + + // echo to dedicated console and early console + Sys_Print( msg ); + + // logfile + if ( com_logfile && com_logfile->integer ) { + // TTimo: only open the qconsole.log if the filesystem is in an initialized state + // also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on) + if ( !logfile && FS_Initialized() && !opening_qconsole) { + struct tm *newtime; + time_t aclock; + + opening_qconsole = qtrue; + + time( &aclock ); + newtime = localtime( &aclock ); + + logfile = FS_FOpenFileWrite( "qconsole.log" ); + + if(logfile) + { + Com_Printf( "logfile opened on %s\n", asctime( newtime ) ); + + if ( com_logfile->integer > 1 ) + { + // force it to not buffer so we get valid + // data even if we are crashing + FS_ForceFlush(logfile); + } + } + else + { + Com_Printf("Opening qconsole.log failed!\n"); + Cvar_SetValue("logfile", 0); + } + + opening_qconsole = qfalse; + } + if ( logfile && FS_Initialized()) { + FS_Write(msg, strlen(msg), logfile); + } + } +} + + +/* +================ +Com_DPrintf + +A Com_Printf that only shows up if the "developer" cvar is set +================ +*/ +void QDECL Com_DPrintf( const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + if ( !com_developer || !com_developer->integer ) { + return; // don't confuse non-developers with techie stuff... + } + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + Com_Printf ("%s", msg); +} + +/* +============= +Com_Error + +Both client and server can use this, and it will +do the appropriate thing. +============= +*/ +void QDECL Com_Error( int code, const char *fmt, ... ) { + va_list argptr; + static int lastErrorTime; + static int errorCount; + static qboolean calledSysError = qfalse; + int currentTime; + + if(com_errorEntered) + { + if(!calledSysError) + { + calledSysError = qtrue; + Sys_Error("recursive error after: %s", com_errorMessage); + } + + return; + } + + com_errorEntered = qtrue; + + Cvar_Set("com_errorCode", va("%i", code)); + + // when we are running automated scripts, make sure we + // know if anything failed + if ( com_buildScript && com_buildScript->integer ) { + code = ERR_FATAL; + } + + // if we are getting a solid stream of ERR_DROP, do an ERR_FATAL + currentTime = Sys_Milliseconds(); + if ( currentTime - lastErrorTime < 100 ) { + if ( ++errorCount > 3 ) { + code = ERR_FATAL; + } + } else { + errorCount = 0; + } + lastErrorTime = currentTime; + + va_start (argptr,fmt); + Q_vsnprintf (com_errorMessage, sizeof(com_errorMessage),fmt,argptr); + va_end (argptr); + + if (code != ERR_DISCONNECT && code != ERR_NEED_CD) + Cvar_Set("com_errorMessage", com_errorMessage); + + if (code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT) { + VM_Forced_Unload_Start(); + SV_Shutdown( "Server disconnected" ); + CL_Disconnect( qtrue ); + CL_FlushMemory( ); + VM_Forced_Unload_Done(); + // make sure we can get at our local stuff + FS_PureServerSetLoadedPaks("", ""); + com_errorEntered = qfalse; + longjmp (abortframe, -1); + } else if (code == ERR_DROP) { + Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage); + VM_Forced_Unload_Start(); + SV_Shutdown (va("Server crashed: %s", com_errorMessage)); + CL_Disconnect( qtrue ); + CL_FlushMemory( ); + VM_Forced_Unload_Done(); + FS_PureServerSetLoadedPaks("", ""); + com_errorEntered = qfalse; + longjmp (abortframe, -1); + } else if ( code == ERR_NEED_CD ) { + VM_Forced_Unload_Start(); + SV_Shutdown( "Server didn't have CD" ); + if ( com_cl_running && com_cl_running->integer ) { + CL_Disconnect( qtrue ); + CL_FlushMemory( ); + VM_Forced_Unload_Done(); + CL_CDDialog(); + } else { + Com_Printf("Server didn't have CD\n" ); + VM_Forced_Unload_Done(); + } + + FS_PureServerSetLoadedPaks("", ""); + + com_errorEntered = qfalse; + longjmp (abortframe, -1); + } else { + VM_Forced_Unload_Start(); + CL_Shutdown (va("Client fatal crashed: %s", com_errorMessage)); + SV_Shutdown (va("Server fatal crashed: %s", com_errorMessage)); + VM_Forced_Unload_Done(); + } + + Com_Shutdown (); + + calledSysError = qtrue; + Sys_Error ("%s", com_errorMessage); +} + + +/* +============= +Com_Quit_f + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void Com_Quit_f( void ) { + // don't try to shutdown if we are in a recursive error + char *p = Cmd_Args( ); + if ( !com_errorEntered ) { + SV_Shutdown (p[0] ? p : "Server quit"); + CL_Shutdown (p[0] ? p : "Client quit"); + Com_Shutdown (); + FS_Shutdown(qtrue); + } + Sys_Quit (); +} + + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters seperate the commandLine string into multiple console +command lines. + +All of these are valid: + +tremulous +set test blah +map test +tremulous set test blah+map test +tremulous set test blah + map test + +============================================================================ +*/ + +#define MAX_CONSOLE_LINES 32 +int com_numConsoleLines; +char *com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +Com_ParseCommandLine + +Break it up into multiple console lines +================== +*/ +void Com_ParseCommandLine( char *commandLine ) { + int inq = 0; + com_consoleLines[0] = commandLine; + com_numConsoleLines = 1; + + while ( *commandLine ) { + if (*commandLine == '"') { + inq = !inq; + } + // look for a + seperating character + // if commandLine came from a file, we might have real line seperators + if ( (*commandLine == '+' && !inq) || *commandLine == '\n' || *commandLine == '\r' ) { + if ( com_numConsoleLines == MAX_CONSOLE_LINES ) { + return; + } + com_consoleLines[com_numConsoleLines] = commandLine + 1; + com_numConsoleLines++; + *commandLine = 0; + } + commandLine++; + } +} + + +/* +=================== +Com_SafeMode + +Check for "safe" on the command line, which will +skip loading of autogen.cfg +=================== +*/ +qboolean Com_SafeMode( void ) { + int i; + + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( !Q_stricmp( Cmd_Argv(0), "safe" ) + || !Q_stricmp( Cmd_Argv(0), "cvar_restart" ) ) { + com_consoleLines[i][0] = 0; + return qtrue; + } + } + return qfalse; +} + + +/* +=============== +Com_StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets should +be after execing the config and default. +=============== +*/ +void Com_StartupVariable( const char *match ) { + int i; + char *s; + + for (i=0 ; i < com_numConsoleLines ; i++) { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( strcmp( Cmd_Argv(0), "set" ) ) { + continue; + } + + s = Cmd_Argv(1); + + if(!match || !strcmp(s, match)) + { + if(Cvar_Flags(s) == CVAR_NONEXISTENT) + Cvar_Get(s, Cmd_Argv(2), CVAR_USER_CREATED); + else + Cvar_Set(s, Cmd_Argv(2)); + } + } +} + + +/* +================= +Com_AddStartupCommands + +Adds command line parameters as script statements +Commands are seperated by + signs + +Returns qtrue if any late commands were added, which +will keep the demoloop from immediately starting +================= +*/ +qboolean Com_AddStartupCommands( void ) { + int i; + qboolean added; + + added = qfalse; + // quote every token, so args with semicolons can work + for (i=0 ; i < com_numConsoleLines ; i++) { + if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) { + continue; + } + + // set commands already added with Com_StartupVariable + if ( !Q_stricmpn( com_consoleLines[i], "set", 3 ) ) { + continue; + } + + added = qtrue; + Cbuf_AddText( com_consoleLines[i] ); + Cbuf_AddText( "\n" ); + } + + return added; +} + + +//============================================================================ + +void Info_Print( const char *s ) { + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + char *o; + int l; + + if (*s == '\\') + s++; + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + + l = o - key; + if (l < 20) + { + Com_Memset (o, ' ', 20-l); + key[20] = 0; + } + else + *o = 0; + Com_Printf ("%s ", key); + + if (!*s) + { + Com_Printf ("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + Com_Printf ("%s\n", value); + } +} + +/* +============ +Com_StringContains +============ +*/ +char *Com_StringContains(char *str1, char *str2, int casesensitive) { + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) { + for (j = 0; str2[j]; j++) { + if (casesensitive) { + if (str1[j] != str2[j]) { + break; + } + } + else { + if (toupper(str1[j]) != toupper(str2[j])) { + break; + } + } + } + if (!str2[j]) { + return str1; + } + } + return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter(char *filter, char *name, int casesensitive) +{ + char buf[MAX_TOKEN_CHARS]; + char *ptr; + int i, found; + + while(*filter) { + if (*filter == '*') { + filter++; + for (i = 0; *filter; i++) { + if (*filter == '*' || *filter == '?') break; + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if (strlen(buf)) { + ptr = Com_StringContains(name, buf, casesensitive); + if (!ptr) return qfalse; + name = ptr + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else if (*filter == '[' && *(filter+1) == '[') { + filter++; + } + else if (*filter == '[') { + filter++; + found = qfalse; + while(*filter && !found) { + if (*filter == ']' && *(filter+1) != ']') break; + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { + if (casesensitive) { + if (*name >= *filter && *name <= *(filter+2)) found = qtrue; + } + else { + if (toupper(*name) >= toupper(*filter) && + toupper(*name) <= toupper(*(filter+2))) found = qtrue; + } + filter += 3; + } + else { + if (casesensitive) { + if (*filter == *name) found = qtrue; + } + else { + if (toupper(*filter) == toupper(*name)) found = qtrue; + } + filter++; + } + } + if (!found) return qfalse; + while(*filter) { + if (*filter == ']' && *(filter+1) != ']') break; + filter++; + } + filter++; + name++; + } + else { + if (casesensitive) { + if (*filter != *name) return qfalse; + } + else { + if (toupper(*filter) != toupper(*name)) return qfalse; + } + filter++; + name++; + } + } + return qtrue; +} + +/* +============ +Com_FilterPath +============ +*/ +int Com_FilterPath(char *filter, char *name, int casesensitive) +{ + int i; + char new_filter[MAX_QPATH]; + char new_name[MAX_QPATH]; + + for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) { + if ( filter[i] == '\\' || filter[i] == ':' ) { + new_filter[i] = '/'; + } + else { + new_filter[i] = filter[i]; + } + } + new_filter[i] = '\0'; + for (i = 0; i < MAX_QPATH-1 && name[i]; i++) { + if ( name[i] == '\\' || name[i] == ':' ) { + new_name[i] = '/'; + } + else { + new_name[i] = name[i]; + } + } + new_name[i] = '\0'; + return Com_Filter(new_filter, new_name, casesensitive); +} + +/* +================ +Com_RealTime +================ +*/ +int Com_RealTime(qtime_t *qtime) { + time_t t; + struct tm *tms; + + t = time(NULL); + if (!qtime) + return t; + tms = localtime(&t); + if (tms) { + qtime->tm_sec = tms->tm_sec; + qtime->tm_min = tms->tm_min; + qtime->tm_hour = tms->tm_hour; + qtime->tm_mday = tms->tm_mday; + qtime->tm_mon = tms->tm_mon; + qtime->tm_year = tms->tm_year; + qtime->tm_wday = tms->tm_wday; + qtime->tm_yday = tms->tm_yday; + qtime->tm_isdst = tms->tm_isdst; + } + return t; +} + + +/* +============================================================================== + + ZONE MEMORY ALLOCATION + +There is never any space between memblocks, and there will never be two +contiguous free memblocks. + +The rover can be left pointing at a non-empty block + +The zone calls are pretty much only used for small strings and structures, +all big things are allocated on the hunk. +============================================================================== +*/ + +#define ZONEID 0x1d4a11 +#define MINFRAGMENT 64 + +typedef struct zonedebug_s { + char *label; + char *file; + int line; + int allocSize; +} zonedebug_t; + +typedef struct memblock_s { + int size; // including the header and possibly tiny fragments + int tag; // a tag of 0 is a free block + struct memblock_s *next, *prev; + int id; // should be ZONEID +#ifdef ZONE_DEBUG + zonedebug_t d; +#endif +} memblock_t; + +typedef struct { + int size; // total bytes malloced, including header + int used; // total bytes used + memblock_t blocklist; // start / end cap for linked list + memblock_t *rover; +} memzone_t; + +// main zone for all "dynamic" memory allocation +memzone_t *mainzone; +// we also have a small zone for small allocations that would only +// fragment the main zone (think of cvar and cmd strings) +memzone_t *smallzone; + +void Z_CheckHeap( void ); + +/* +======================== +Z_ClearZone +======================== +*/ +void Z_ClearZone( memzone_t *zone, int size ) { + memblock_t *block; + + // set the entire zone to one free block + + zone->blocklist.next = zone->blocklist.prev = block = + (memblock_t *)( (byte *)zone + sizeof(memzone_t) ); + zone->blocklist.tag = 1; // in use block + zone->blocklist.id = 0; + zone->blocklist.size = 0; + zone->rover = block; + zone->size = size; + zone->used = 0; + + block->prev = block->next = &zone->blocklist; + block->tag = 0; // free block + block->id = ZONEID; + block->size = size - sizeof(memzone_t); +} + +/* +======================== +Z_AvailableZoneMemory +======================== +*/ +int Z_AvailableZoneMemory( memzone_t *zone ) { + return zone->size - zone->used; +} + +/* +======================== +Z_AvailableMemory +======================== +*/ +int Z_AvailableMemory( void ) { + return Z_AvailableZoneMemory( mainzone ); +} + +/* +======================== +Z_Free +======================== +*/ +void Z_Free( void *ptr ) { + memblock_t *block, *other; + memzone_t *zone; + + if (!ptr) { + Com_Error( ERR_DROP, "Z_Free: NULL pointer" ); + } + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + if (block->id != ZONEID) { + Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" ); + } + if (block->tag == 0) { + Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" ); + } + // if static memory + if (block->tag == TAG_STATIC) { + return; + } + + // check the memory trash tester + if ( *(int *)((byte *)block + block->size - 4 ) != ZONEID ) { + Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" ); + } + + if (block->tag == TAG_SMALL) { + zone = smallzone; + } + else { + zone = mainzone; + } + + zone->used -= block->size; + // set the block to something that should cause problems + // if it is referenced... + Com_Memset( ptr, 0xaa, block->size - sizeof( *block ) ); + + block->tag = 0; // mark as free + + other = block->prev; + if (!other->tag) { + // merge with previous free block + other->size += block->size; + other->next = block->next; + other->next->prev = other; + if (block == zone->rover) { + zone->rover = other; + } + block = other; + } + + zone->rover = block; + + other = block->next; + if ( !other->tag ) { + // merge the next free block onto the end + block->size += other->size; + block->next = other->next; + block->next->prev = block; + if (other == zone->rover) { + zone->rover = block; + } + } +} + + +/* +================ +Z_FreeTags +================ +*/ +void Z_FreeTags( int tag ) { + int count; + memzone_t *zone; + + if ( tag == TAG_SMALL ) { + zone = smallzone; + } + else { + zone = mainzone; + } + count = 0; + // use the rover as our pointer, because + // Z_Free automatically adjusts it + zone->rover = zone->blocklist.next; + do { + if ( zone->rover->tag == tag ) { + count++; + Z_Free( (void *)(zone->rover + 1) ); + continue; + } + zone->rover = zone->rover->next; + } while ( zone->rover != &zone->blocklist ); +} + + +/* +================ +Z_TagMalloc +================ +*/ +#ifdef ZONE_DEBUG +void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ) { +#else +void *Z_TagMalloc( int size, int tag ) { +#endif + int extra, allocSize; + memblock_t *start, *rover, *new, *base; + memzone_t *zone; + + if (!tag) { + Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use a 0 tag" ); + } + + if ( tag == TAG_SMALL ) { + zone = smallzone; + } + else { + zone = mainzone; + } + + allocSize = size; + // + // scan through the block list looking for the first free block + // of sufficient size + // + size += sizeof(memblock_t); // account for size of block header + size += 4; // space for memory trash tester + size = PAD(size, sizeof(intptr_t)); // align to 32/64 bit boundary + + base = rover = zone->rover; + start = base->prev; + + do { + if (rover == start) { +#ifdef ZONE_DEBUG + Z_LogHeap(); +#endif + // scaned all the way around the list + Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone", + size, zone == smallzone ? "small" : "main"); + return NULL; + } + if (rover->tag) { + base = rover = rover->next; + } else { + rover = rover->next; + } + } while (base->tag || base->size < size); + + // + // found a block big enough + // + extra = base->size - size; + if (extra > MINFRAGMENT) { + // there will be a free fragment after the allocated block + new = (memblock_t *) ((byte *)base + size ); + new->size = extra; + new->tag = 0; // free block + new->prev = base; + new->id = ZONEID; + new->next = base->next; + new->next->prev = new; + base->next = new; + base->size = size; + } + + base->tag = tag; // no longer a free block + + zone->rover = base->next; // next allocation will start looking here + zone->used += base->size; // + + base->id = ZONEID; + +#ifdef ZONE_DEBUG + base->d.label = label; + base->d.file = file; + base->d.line = line; + base->d.allocSize = allocSize; +#endif + + // marker for memory trash testing + *(int *)((byte *)base + base->size - 4) = ZONEID; + + return (void *) ((byte *)base + sizeof(memblock_t)); +} + +/* +======================== +Z_Malloc +======================== +*/ +#ifdef ZONE_DEBUG +void *Z_MallocDebug( int size, char *label, char *file, int line ) { +#else +void *Z_Malloc( int size ) { +#endif + void *buf; + + //Z_CheckHeap (); // DEBUG + +#ifdef ZONE_DEBUG + buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line ); +#else + buf = Z_TagMalloc( size, TAG_GENERAL ); +#endif + Com_Memset( buf, 0, size ); + + return buf; +} + +#ifdef ZONE_DEBUG +void *S_MallocDebug( int size, char *label, char *file, int line ) { + return Z_TagMallocDebug( size, TAG_SMALL, label, file, line ); +} +#else +void *S_Malloc( int size ) { + return Z_TagMalloc( size, TAG_SMALL ); +} +#endif + +/* +======================== +Z_CheckHeap +======================== +*/ +void Z_CheckHeap( void ) { + memblock_t *block; + + for (block = mainzone->blocklist.next ; ; block = block->next) { + if (block->next == &mainzone->blocklist) { + break; // all blocks have been hit + } + if ( (byte *)block + block->size != (byte *)block->next) + Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block\n" ); + if ( block->next->prev != block) { + Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link\n" ); + } + if ( !block->tag && !block->next->tag ) { + Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks\n" ); + } + } +} + +/* +======================== +Z_LogZoneHeap +======================== +*/ +void Z_LogZoneHeap( memzone_t *zone, char *name ) { +#ifdef ZONE_DEBUG + char dump[32], *ptr; + int i, j; +#endif + memblock_t *block; + char buf[4096]; + int size, allocSize, numBlocks; + + if (!logfile || !FS_Initialized()) + return; + size = allocSize = numBlocks = 0; + Com_sprintf(buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name); + FS_Write(buf, strlen(buf), logfile); + for (block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next) { + if (block->tag) { +#ifdef ZONE_DEBUG + ptr = ((char *) block) + sizeof(memblock_t); + j = 0; + for (i = 0; i < 20 && i < block->d.allocSize; i++) { + if (ptr[i] >= 32 && ptr[i] < 127) { + dump[j++] = ptr[i]; + } + else { + dump[j++] = '_'; + } + } + dump[j] = '\0'; + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s) [%s]\r\n", block->d.allocSize, block->d.file, block->d.line, block->d.label, dump); + FS_Write(buf, strlen(buf), logfile); + allocSize += block->d.allocSize; +#endif + size += block->size; + numBlocks++; + } + } +#ifdef ZONE_DEBUG + // subtract debug memory + size -= numBlocks * sizeof(zonedebug_t); +#else + allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment +#endif + Com_sprintf(buf, sizeof(buf), "%d %s memory in %d blocks\r\n", size, name, numBlocks); + FS_Write(buf, strlen(buf), logfile); + Com_sprintf(buf, sizeof(buf), "%d %s memory overhead\r\n", size - allocSize, name); + FS_Write(buf, strlen(buf), logfile); +} + +/* +======================== +Z_LogHeap +======================== +*/ +void Z_LogHeap( void ) { + Z_LogZoneHeap( mainzone, "MAIN" ); + Z_LogZoneHeap( smallzone, "SMALL" ); +} + +// static mem blocks to reduce a lot of small zone overhead +typedef struct memstatic_s { + memblock_t b; + byte mem[2]; +} memstatic_t; + +memstatic_t emptystring = + { {(sizeof(memblock_t)+2 + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'} }; +memstatic_t numberstring[] = { + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} } +}; + +/* +======================== +CopyString + + NOTE: never write over the memory CopyString returns because + memory from a memstatic_t might be returned +======================== +*/ +char *CopyString( const char *in ) { + char *out; + + if (!in[0]) { + return ((char *)&emptystring) + sizeof(memblock_t); + } + else if (!in[1]) { + if (in[0] >= '0' && in[0] <= '9') { + return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t); + } + } + out = S_Malloc (strlen(in)+1); + strcpy (out, in); + return out; +} + +/* +============================================================================== + +Goals: + reproducable without history effects -- no out of memory errors on weird map to map changes + allow restarting of the client without fragmentation + minimize total pages in use at run time + minimize total pages needed during load time + + Single block of memory with stack allocators coming from both ends towards the middle. + + One side is designated the temporary memory allocator. + + Temporary memory can be allocated and freed in any order. + + A highwater mark is kept of the most in use at any time. + + When there is no temporary memory allocated, the permanent and temp sides + can be switched, allowing the already touched temp memory to be used for + permanent storage. + + Temp memory must never be allocated on two ends at once, or fragmentation + could occur. + + If we have any in-use temp memory, additional temp allocations must come from + that side. + + If not, we can choose to make either side the new temp side and push future + permanent allocations to the other side. Permanent allocations should be + kept on the side that has the current greatest wasted highwater mark. + +============================================================================== +*/ + + +#define HUNK_MAGIC 0x89537892 +#define HUNK_FREE_MAGIC 0x89537893 + +typedef struct { + int magic; + int size; +} hunkHeader_t; + +typedef struct { + int mark; + int permanent; + int temp; + int tempHighwater; +} hunkUsed_t; + +typedef struct hunkblock_s { + int size; + byte printed; + struct hunkblock_s *next; + char *label; + char *file; + int line; +} hunkblock_t; + +static hunkblock_t *hunkblocks; + +static hunkUsed_t hunk_low, hunk_high; +static hunkUsed_t *hunk_permanent, *hunk_temp; + +static byte *s_hunkData = NULL; +static int s_hunkTotal; + +static int s_zoneTotal; +static int s_smallZoneTotal; + + +/* +================= +Com_Meminfo_f +================= +*/ +void Com_Meminfo_f( void ) { + memblock_t *block; + int zoneBytes, zoneBlocks; + int smallZoneBytes, smallZoneBlocks; + int botlibBytes, rendererBytes; + int unused; + + zoneBytes = 0; + botlibBytes = 0; + rendererBytes = 0; + zoneBlocks = 0; + for (block = mainzone->blocklist.next ; ; block = block->next) { + if ( Cmd_Argc() != 1 ) { + Com_Printf ("block:%p size:%7i tag:%3i\n", + (void *)block, block->size, block->tag); + } + if ( block->tag ) { + zoneBytes += block->size; + zoneBlocks++; + if ( block->tag == TAG_BOTLIB ) { + botlibBytes += block->size; + } else if ( block->tag == TAG_RENDERER ) { + rendererBytes += block->size; + } + } + + if (block->next == &mainzone->blocklist) { + break; // all blocks have been hit + } + if ( (byte *)block + block->size != (byte *)block->next) { + Com_Printf ("ERROR: block size does not touch the next block\n"); + } + if ( block->next->prev != block) { + Com_Printf ("ERROR: next block doesn't have proper back link\n"); + } + if ( !block->tag && !block->next->tag ) { + Com_Printf ("ERROR: two consecutive free blocks\n"); + } + } + + smallZoneBytes = 0; + smallZoneBlocks = 0; + for (block = smallzone->blocklist.next ; ; block = block->next) { + if ( block->tag ) { + smallZoneBytes += block->size; + smallZoneBlocks++; + } + + if (block->next == &smallzone->blocklist) { + break; // all blocks have been hit + } + } + + Com_Printf( "%8i bytes total hunk\n", s_hunkTotal ); + Com_Printf( "%8i bytes total zone\n", s_zoneTotal ); + Com_Printf( "\n" ); + Com_Printf( "%8i low mark\n", hunk_low.mark ); + Com_Printf( "%8i low permanent\n", hunk_low.permanent ); + if ( hunk_low.temp != hunk_low.permanent ) { + Com_Printf( "%8i low temp\n", hunk_low.temp ); + } + Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater ); + Com_Printf( "\n" ); + Com_Printf( "%8i high mark\n", hunk_high.mark ); + Com_Printf( "%8i high permanent\n", hunk_high.permanent ); + if ( hunk_high.temp != hunk_high.permanent ) { + Com_Printf( "%8i high temp\n", hunk_high.temp ); + } + Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater ); + Com_Printf( "\n" ); + Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent ); + unused = 0; + if ( hunk_low.tempHighwater > hunk_low.permanent ) { + unused += hunk_low.tempHighwater - hunk_low.permanent; + } + if ( hunk_high.tempHighwater > hunk_high.permanent ) { + unused += hunk_high.tempHighwater - hunk_high.permanent; + } + Com_Printf( "%8i unused highwater\n", unused ); + Com_Printf( "\n" ); + Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks ); + Com_Printf( " %8i bytes in dynamic botlib\n", botlibBytes ); + Com_Printf( " %8i bytes in dynamic renderer\n", rendererBytes ); + Com_Printf( " %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) ); + Com_Printf( " %8i bytes in small Zone memory\n", smallZoneBytes ); +} + +/* +=============== +Com_TouchMemory + +Touch all known used data to make sure it is paged in +=============== +*/ +void Com_TouchMemory( void ) { + int start, end; + int i, j; + int sum; + memblock_t *block; + + Z_CheckHeap(); + + start = Sys_Milliseconds(); + + sum = 0; + + j = hunk_low.permanent >> 2; + for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)s_hunkData)[i]; + } + + i = ( s_hunkTotal - hunk_high.permanent ) >> 2; + j = hunk_high.permanent >> 2; + for ( ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)s_hunkData)[i]; + } + + for (block = mainzone->blocklist.next ; ; block = block->next) { + if ( block->tag ) { + j = block->size >> 2; + for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)block)[i]; + } + } + if ( block->next == &mainzone->blocklist ) { + break; // all blocks have been hit + } + } + + end = Sys_Milliseconds(); + + Com_Printf( "Com_TouchMemory: %i msec\n", end - start ); +} + + + +/* +================= +Com_InitZoneMemory +================= +*/ +void Com_InitSmallZoneMemory( void ) { + s_smallZoneTotal = 512 * 1024; + smallzone = calloc( s_smallZoneTotal, 1 ); + if ( !smallzone ) { + Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024) ); + } + Z_ClearZone( smallzone, s_smallZoneTotal ); + + return; +} + +void Com_InitZoneMemory( void ) { + cvar_t *cv; + + // Please note: com_zoneMegs can only be set on the command line, and + // not in q3config.cfg or Com_StartupVariable, as they haven't been + // executed by this point. It's a chicken and egg problem. We need the + // memory manager configured to handle those places where you would + // configure the memory manager. + + // allocate the random block zone + cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS_S, CVAR_LATCH | CVAR_ARCHIVE ); + + if ( cv->integer < DEF_COMZONEMEGS ) { + s_zoneTotal = 1024 * 1024 * DEF_COMZONEMEGS; + } else { + s_zoneTotal = cv->integer * 1024 * 1024; + } + + mainzone = calloc( s_zoneTotal, 1 ); + if ( !mainzone ) { + Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / (1024*1024) ); + } + Z_ClearZone( mainzone, s_zoneTotal ); + +} + +/* +================= +Hunk_Log +================= +*/ +void Hunk_Log( void) { + hunkblock_t *block; + char buf[4096]; + int size, numBlocks; + + if (!logfile || !FS_Initialized()) + return; + size = 0; + numBlocks = 0; + Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n"); + FS_Write(buf, strlen(buf), logfile); + for (block = hunkblocks ; block; block = block->next) { +#ifdef HUNK_DEBUG + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label); + FS_Write(buf, strlen(buf), logfile); +#endif + size += block->size; + numBlocks++; + } + Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size); + FS_Write(buf, strlen(buf), logfile); + Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks); + FS_Write(buf, strlen(buf), logfile); +} + +/* +================= +Hunk_SmallLog +================= +*/ +void Hunk_SmallLog( void) { + hunkblock_t *block, *block2; + char buf[4096]; + int size, locsize, numBlocks; + + if (!logfile || !FS_Initialized()) + return; + for (block = hunkblocks ; block; block = block->next) { + block->printed = qfalse; + } + size = 0; + numBlocks = 0; + Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n"); + FS_Write(buf, strlen(buf), logfile); + for (block = hunkblocks; block; block = block->next) { + if (block->printed) { + continue; + } + locsize = block->size; + for (block2 = block->next; block2; block2 = block2->next) { + if (block->line != block2->line) { + continue; + } + if (Q_stricmp(block->file, block2->file)) { + continue; + } + size += block2->size; + locsize += block2->size; + block2->printed = qtrue; + } +#ifdef HUNK_DEBUG + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label); + FS_Write(buf, strlen(buf), logfile); +#endif + size += block->size; + numBlocks++; + } + Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size); + FS_Write(buf, strlen(buf), logfile); + Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks); + FS_Write(buf, strlen(buf), logfile); +} + +/* +================= +Com_InitZoneMemory +================= +*/ +void Com_InitHunkMemory( void ) { + cvar_t *cv; + int nMinAlloc; + char *pMsg = NULL; + + // make sure the file system has allocated and "not" freed any temp blocks + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if (FS_LoadStack() != 0) { + Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero"); + } + + // allocate the stack based hunk allocator + cv = Cvar_Get( "com_hunkMegs", DEF_COMHUNKMEGS_S, CVAR_LATCH | CVAR_ARCHIVE ); + + // if we are not dedicated min allocation is 56, otherwise min is 1 + if (com_dedicated && com_dedicated->integer) { + nMinAlloc = MIN_DEDICATED_COMHUNKMEGS; + pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n"; + } + else { + nMinAlloc = MIN_COMHUNKMEGS; + pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n"; + } + + if ( cv->integer < nMinAlloc ) { + s_hunkTotal = 1024 * 1024 * nMinAlloc; + Com_Printf(pMsg, nMinAlloc, s_hunkTotal / (1024 * 1024)); + } else { + s_hunkTotal = cv->integer * 1024 * 1024; + } + + s_hunkData = calloc( s_hunkTotal + 31, 1 ); + if ( !s_hunkData ) { + Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / (1024*1024) ); + } + // cacheline align + s_hunkData = (byte *) ( ( (intptr_t)s_hunkData + 31 ) & ~31 ); + Hunk_Clear(); + + Cmd_AddCommand( "meminfo", Com_Meminfo_f ); +#ifdef ZONE_DEBUG + Cmd_AddCommand( "zonelog", Z_LogHeap ); +#endif +#ifdef HUNK_DEBUG + Cmd_AddCommand( "hunklog", Hunk_Log ); + Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog ); +#endif +} + +/* +==================== +Hunk_MemoryRemaining +==================== +*/ +int Hunk_MemoryRemaining( void ) { + int low, high; + + low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp; + high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp; + + return s_hunkTotal - ( low + high ); +} + +/* +=================== +Hunk_SetMark + +The server calls this after the level and game VM have been loaded +=================== +*/ +void Hunk_SetMark( void ) { + hunk_low.mark = hunk_low.permanent; + hunk_high.mark = hunk_high.permanent; +} + +/* +================= +Hunk_ClearToMark + +The client calls this before starting a vid_restart or snd_restart +================= +*/ +void Hunk_ClearToMark( void ) { + hunk_low.permanent = hunk_low.temp = hunk_low.mark; + hunk_high.permanent = hunk_high.temp = hunk_high.mark; +} + +/* +================= +Hunk_CheckMark +================= +*/ +qboolean Hunk_CheckMark( void ) { + if( hunk_low.mark || hunk_high.mark ) { + return qtrue; + } + return qfalse; +} + +void CL_ShutdownCGame( void ); +void CL_ShutdownUI( void ); +void SV_ShutdownGameProgs( void ); + +/* +================= +Hunk_Clear + +The server calls this before shutting down or loading a new map +================= +*/ +void Hunk_Clear( void ) { + +#ifndef DEDICATED + CL_ShutdownCGame(); + CL_ShutdownUI(); +#endif + SV_ShutdownGameProgs(); +#ifndef DEDICATED + CIN_CloseAllVideos(); +#endif + hunk_low.mark = 0; + hunk_low.permanent = 0; + hunk_low.temp = 0; + hunk_low.tempHighwater = 0; + + hunk_high.mark = 0; + hunk_high.permanent = 0; + hunk_high.temp = 0; + hunk_high.tempHighwater = 0; + + hunk_permanent = &hunk_low; + hunk_temp = &hunk_high; + + Com_Printf( "Hunk_Clear: reset the hunk ok\n" ); + VM_Clear(); +#ifdef HUNK_DEBUG + hunkblocks = NULL; +#endif +} + +static void Hunk_SwapBanks( void ) { + hunkUsed_t *swap; + + // can't swap banks if there is any temp already allocated + if ( hunk_temp->temp != hunk_temp->permanent ) { + return; + } + + // if we have a larger highwater mark on this side, start making + // our permanent allocations here and use the other side for temp + if ( hunk_temp->tempHighwater - hunk_temp->permanent > + hunk_permanent->tempHighwater - hunk_permanent->permanent ) { + swap = hunk_temp; + hunk_temp = hunk_permanent; + hunk_permanent = swap; + } +} + +/* +================= +Hunk_Alloc + +Allocate permanent (until the hunk is cleared) memory +================= +*/ +#ifdef HUNK_DEBUG +void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ) { +#else +void *Hunk_Alloc( int size, ha_pref preference ) { +#endif + void *buf; + + if ( s_hunkData == NULL) + { + Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" ); + } + + // can't do preference if there is any temp allocated + if (preference == h_dontcare || hunk_temp->temp != hunk_temp->permanent) { + Hunk_SwapBanks(); + } else { + if (preference == h_low && hunk_permanent != &hunk_low) { + Hunk_SwapBanks(); + } else if (preference == h_high && hunk_permanent != &hunk_high) { + Hunk_SwapBanks(); + } + } + +#ifdef HUNK_DEBUG + size += sizeof(hunkblock_t); +#endif + + // round to cacheline + size = (size+31)&~31; + + if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) { +#ifdef HUNK_DEBUG + Hunk_Log(); + Hunk_SmallLog(); +#endif + Com_Error( ERR_DROP, "Hunk_Alloc failed on %i", size ); + } + + if ( hunk_permanent == &hunk_low ) { + buf = (void *)(s_hunkData + hunk_permanent->permanent); + hunk_permanent->permanent += size; + } else { + hunk_permanent->permanent += size; + buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent ); + } + + hunk_permanent->temp = hunk_permanent->permanent; + + Com_Memset( buf, 0, size ); + +#ifdef HUNK_DEBUG + { + hunkblock_t *block; + + block = (hunkblock_t *) buf; + block->size = size - sizeof(hunkblock_t); + block->file = file; + block->label = label; + block->line = line; + block->next = hunkblocks; + hunkblocks = block; + buf = ((byte *) buf) + sizeof(hunkblock_t); + } +#endif + return buf; +} + +/* +================= +Hunk_AllocateTempMemory + +This is used by the file loading system. +Multiple files can be loaded in temporary memory. +When the files-in-use count reaches zero, all temp memory will be deleted +================= +*/ +void *Hunk_AllocateTempMemory( int size ) { + void *buf; + hunkHeader_t *hdr; + + // return a Z_Malloc'd block if the hunk has not been initialized + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if ( s_hunkData == NULL ) + { + return Z_Malloc(size); + } + + Hunk_SwapBanks(); + + size = PAD(size, sizeof(intptr_t)) + sizeof( hunkHeader_t ); + + if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) { + Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size ); + } + + if ( hunk_temp == &hunk_low ) { + buf = (void *)(s_hunkData + hunk_temp->temp); + hunk_temp->temp += size; + } else { + hunk_temp->temp += size; + buf = (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ); + } + + if ( hunk_temp->temp > hunk_temp->tempHighwater ) { + hunk_temp->tempHighwater = hunk_temp->temp; + } + + hdr = (hunkHeader_t *)buf; + buf = (void *)(hdr+1); + + hdr->magic = HUNK_MAGIC; + hdr->size = size; + + // don't bother clearing, because we are going to load a file over it + return buf; +} + + +/* +================== +Hunk_FreeTempMemory +================== +*/ +void Hunk_FreeTempMemory( void *buf ) { + hunkHeader_t *hdr; + + // free with Z_Free if the hunk has not been initialized + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if ( s_hunkData == NULL ) + { + Z_Free(buf); + return; + } + + + hdr = ( (hunkHeader_t *)buf ) - 1; + if ( hdr->magic != HUNK_MAGIC ) { + Com_Error( ERR_FATAL, "Hunk_FreeTempMemory: bad magic" ); + } + + hdr->magic = HUNK_FREE_MAGIC; + + // this only works if the files are freed in stack order, + // otherwise the memory will stay around until Hunk_ClearTempMemory + if ( hunk_temp == &hunk_low ) { + if ( hdr == (void *)(s_hunkData + hunk_temp->temp - hdr->size ) ) { + hunk_temp->temp -= hdr->size; + } else { + Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); + } + } else { + if ( hdr == (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ) ) { + hunk_temp->temp -= hdr->size; + } else { + Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); + } + } +} + + +/* +================= +Hunk_ClearTempMemory + +The temp space is no longer needed. If we have left more +touched but unused memory on this side, have future +permanent allocs use this side. +================= +*/ +void Hunk_ClearTempMemory( void ) { + if ( s_hunkData != NULL ) { + hunk_temp->temp = hunk_temp->permanent; + } +} + +/* +================= +Hunk_Trash +================= +*/ +void Hunk_Trash( void ) { + int length, i, rnd; + char *buf, value; + + return; + + if ( s_hunkData == NULL ) + return; + +#ifdef _DEBUG + Com_Error(ERR_DROP, "hunk trashed\n"); + return; +#endif + + Cvar_Set("com_jp", "1"); + Hunk_SwapBanks(); + + if ( hunk_permanent == &hunk_low ) { + buf = (void *)(s_hunkData + hunk_permanent->permanent); + } else { + buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent ); + } + length = hunk_permanent->permanent; + + if (length > 0x7FFFF) { + //randomly trash data within buf + rnd = random() * (length - 0x7FFFF); + value = 31; + for (i = 0; i < 0x7FFFF; i++) { + value *= 109; + buf[rnd+i] ^= value; + } + } +} + +/* +=================================================================== + +EVENTS AND JOURNALING + +In addition to these events, .cfg files are also copied to the +journaled file +=================================================================== +*/ + +#define MAX_PUSHED_EVENTS 1024 +static int com_pushedEventsHead = 0; +static int com_pushedEventsTail = 0; +static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; + +/* +================= +Com_InitJournaling +================= +*/ +void Com_InitJournaling( void ) { + Com_StartupVariable( "journal" ); + com_journal = Cvar_Get ("journal", "0", CVAR_INIT); + if ( !com_journal->integer ) { + return; + } + + if ( com_journal->integer == 1 ) { + Com_Printf( "Journaling events\n"); + com_journalFile = FS_FOpenFileWrite( "journal.dat" ); + com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" ); + } else if ( com_journal->integer == 2 ) { + Com_Printf( "Replaying journaled events\n"); + FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue ); + FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, qtrue ); + } + + if ( !com_journalFile || !com_journalDataFile ) { + Cvar_Set( "com_journal", "0" ); + com_journalFile = 0; + com_journalDataFile = 0; + Com_Printf( "Couldn't open journal files\n" ); + } +} + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +#define MAX_QUEUED_EVENTS 256 +#define MASK_QUEUED_EVENTS ( MAX_QUEUED_EVENTS - 1 ) + +static sysEvent_t eventQueue[ MAX_QUEUED_EVENTS ]; +static int eventHead = 0; +static int eventTail = 0; + +/* +================ +Com_QueueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) +{ + sysEvent_t *ev; + + ev = &eventQueue[ eventHead & MASK_QUEUED_EVENTS ]; + + if ( eventHead - eventTail >= MAX_QUEUED_EVENTS ) + { + Com_Printf("Com_QueueEvent: overflow\n"); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) + { + Z_Free( ev->evPtr ); + } + eventTail++; + } + + eventHead++; + + if ( time == 0 ) + { + time = Sys_Milliseconds(); + } + + ev->evTime = time; + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + +/* +================ +Com_GetSystemEvent + +================ +*/ +sysEvent_t Com_GetSystemEvent( void ) +{ + sysEvent_t ev; + char *s; + + // return if we have data + if ( eventHead > eventTail ) + { + eventTail++; + return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ]; + } + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) + { + char *b; + int len; + + len = strlen( s ) + 1; + b = Z_Malloc( len ); + strcpy( b, s ); + Com_QueueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + // return if we have data + if ( eventHead > eventTail ) + { + eventTail++; + return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ]; + } + + // create an empty event to return + memset( &ev, 0, sizeof( ev ) ); + ev.evTime = Sys_Milliseconds(); + + return ev; +} + +/* +================= +Com_GetRealEvent +================= +*/ +sysEvent_t Com_GetRealEvent( void ) { + int r; + sysEvent_t ev; + + // either get an event from the system or the journal file + if ( com_journal->integer == 2 ) { + r = FS_Read( &ev, sizeof(ev), com_journalFile ); + if ( r != sizeof(ev) ) { + Com_Error( ERR_FATAL, "Error reading from journal file" ); + } + if ( ev.evPtrLength ) { + ev.evPtr = Z_Malloc( ev.evPtrLength ); + r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) { + Com_Error( ERR_FATAL, "Error reading from journal file" ); + } + } + } else { + ev = Com_GetSystemEvent(); + + // write the journal value out if needed + if ( com_journal->integer == 1 ) { + r = FS_Write( &ev, sizeof(ev), com_journalFile ); + if ( r != sizeof(ev) ) { + Com_Error( ERR_FATAL, "Error writing to journal file" ); + } + if ( ev.evPtrLength ) { + r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) { + Com_Error( ERR_FATAL, "Error writing to journal file" ); + } + } + } + } + + return ev; +} + + +/* +================= +Com_InitPushEvent +================= +*/ +void Com_InitPushEvent( void ) { + // clear the static buffer array + // this requires SE_NONE to be accepted as a valid but NOP event + memset( com_pushedEvents, 0, sizeof(com_pushedEvents) ); + // reset counters while we are at it + // beware: GetEvent might still return an SE_NONE from the buffer + com_pushedEventsHead = 0; + com_pushedEventsTail = 0; +} + + +/* +================= +Com_PushEvent +================= +*/ +void Com_PushEvent( sysEvent_t *event ) { + sysEvent_t *ev; + static int printedWarning = 0; + + ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ]; + + if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) { + + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) { + printedWarning = qtrue; + Com_Printf( "WARNING: Com_PushEvent overflow\n" ); + } + + if ( ev->evPtr ) { + Z_Free( ev->evPtr ); + } + com_pushedEventsTail++; + } else { + printedWarning = qfalse; + } + + *ev = *event; + com_pushedEventsHead++; +} + +/* +================= +Com_GetEvent +================= +*/ +sysEvent_t Com_GetEvent( void ) { + if ( com_pushedEventsHead > com_pushedEventsTail ) { + com_pushedEventsTail++; + return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ]; + } + return Com_GetRealEvent(); +} + +/* +================= +Com_RunAndTimeServerPacket +================= +*/ +void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) { + int t1, t2, msec; + + t1 = 0; + + if ( com_speeds->integer ) { + t1 = Sys_Milliseconds (); + } + + SV_PacketEvent( *evFrom, buf ); + + if ( com_speeds->integer ) { + t2 = Sys_Milliseconds (); + msec = t2 - t1; + if ( com_speeds->integer == 3 ) { + Com_Printf( "SV_PacketEvent time: %i\n", msec ); + } + } +} + +/* +================= +Com_EventLoop + +Returns last event time +================= +*/ +int Com_EventLoop( void ) { + sysEvent_t ev; + netadr_t evFrom; + byte bufData[MAX_MSGLEN]; + msg_t buf; + + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + while ( 1 ) { + ev = Com_GetEvent(); + + // if no more events are available + if ( ev.evType == SE_NONE ) { + // manually send packet events for the loopback channel + while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) { + CL_PacketEvent( evFrom, &buf ); + } + + while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) { + // if the server just shut down, flush the events + if ( com_sv_running->integer ) { + Com_RunAndTimeServerPacket( &evFrom, &buf ); + } + } + + return ev.evTime; + } + + + switch(ev.evType) + { + case SE_KEY: + CL_KeyEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CHAR: + CL_CharEvent( ev.evValue ); + break; + case SE_MOUSE: + CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_JOYSTICK_AXIS: + CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CONSOLE: + Cbuf_AddText( (char *)ev.evPtr ); + Cbuf_AddText( "\n" ); + break; + default: + Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType ); + break; + } + + // free any block data + if ( ev.evPtr ) { + Z_Free( ev.evPtr ); + } + } + + return 0; // never reached +} + +/* +================ +Com_Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int Com_Milliseconds (void) { + sysEvent_t ev; + + // get events and push them until we get a null event with the current time + do { + + ev = Com_GetRealEvent(); + if ( ev.evType != SE_NONE ) { + Com_PushEvent( &ev ); + } + } while ( ev.evType != SE_NONE ); + + return ev.evTime; +} + +//============================================================================ + +/* +============= +Com_Error_f + +Just throw a fatal error to +test error shutdown procedures +============= +*/ +static void Com_Error_f (void) { + if ( Cmd_Argc() > 1 ) { + Com_Error( ERR_DROP, "Testing drop error" ); + } else { + Com_Error( ERR_FATAL, "Testing fatal error" ); + } +} + + +/* +============= +Com_Freeze_f + +Just freeze in place for a given number of seconds to test +error recovery +============= +*/ +static void Com_Freeze_f (void) { + float s; + int start, now; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "freeze \n" ); + return; + } + s = atof( Cmd_Argv(1) ); + + start = Com_Milliseconds(); + + while ( 1 ) { + now = Com_Milliseconds(); + if ( ( now - start ) * 0.001 > s ) { + break; + } + } +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +static void Com_Crash_f( void ) { + * ( int * ) 0 = 0x12345678; +} + +/* +================== +Com_Setenv_f + +For controlling environment variables +================== +*/ +void Com_Setenv_f(void) +{ + int argc = Cmd_Argc(); + char *arg1 = Cmd_Argv(1); + + if(argc > 2) + { + char *arg2 = Cmd_ArgsFrom(2); + + Sys_SetEnv(arg1, arg2); + } + else if(argc == 2) + { + char *env = getenv(arg1); + + if(env) + Com_Printf("%s=%s\n", arg1, env); + else + Com_Printf("%s undefined\n", arg1); + } +} + +/* +================== +Com_ExecuteCfg + +For controlling environment variables +================== +*/ + +void Com_ExecuteCfg(void) +{ + Cbuf_ExecuteText(EXEC_NOW, "exec default.cfg\n"); + Cbuf_Execute(); // Always execute after exec to prevent text buffer overflowing + + if(!Com_SafeMode()) + { + // skip the q3config.cfg and autoexec.cfg if "safe" is on the command line + Cbuf_ExecuteText(EXEC_NOW, "exec " Q3CONFIG_CFG "\n"); + Cbuf_Execute(); + Cbuf_ExecuteText(EXEC_NOW, "exec autoexec.cfg\n"); + Cbuf_Execute(); + } +} + +/* +================== +Com_GameRestart + +Change to a new mod properly with cleaning up cvars before switching. +================== +*/ + +void Com_GameRestart(int checksumFeed, qboolean clientRestart) +{ + // make sure no recursion can be triggered + if(!com_gameRestarting && com_fullyInitialized) + { + com_gameRestarting = qtrue; + + if(clientRestart) + { + CL_Disconnect(qfalse); + CL_ShutdownAll(); + } + + // Kill server if we have one + if(com_sv_running->integer) + SV_Shutdown("Game directory changed"); + + FS_Restart(checksumFeed); + + // Clean out any user and VM created cvars + Cvar_Restart(qtrue); + Com_ExecuteCfg(); + + // Restart sound subsystem so old handles are flushed + CL_Snd_Restart(); + + if(clientRestart) + CL_StartHunkUsers(qfalse); + + com_gameRestarting = qfalse; + } +} + +/* +================== +Com_GameRestart_f + +Expose possibility to change current running mod to the user +================== +*/ + +void Com_GameRestart_f(void) +{ + Cvar_Set("fs_game", Cmd_Argv(1)); + + Com_GameRestart(0, qtrue); +} + +static void Com_DetectAltivec(void) +{ + // Only detect if user hasn't forcibly disabled it. + if (com_altivec->integer) { + static qboolean altivec = qfalse; + static qboolean detected = qfalse; + if (!detected) { + altivec = ( Sys_GetProcessorFeatures( ) & CF_ALTIVEC ); + detected = qtrue; + } + + if (!altivec) { + Cvar_Set( "com_altivec", "0" ); // we don't have it! Disable support! + } + } +} + +/* +================= +Com_InitRand +Seed the random number generator, if possible with an OS supplied random seed. +================= +*/ +static void Com_InitRand(void) +{ + unsigned int seed; + + if(Sys_RandomBytes((byte *) &seed, sizeof(seed))) + srand(seed); + else + srand(time(NULL)); +} + +/* +================= +Com_Init +================= +*/ +void Com_Init( char *commandLine ) { + char *s; + int qport; + + Com_Printf( "%s %s %s\n", Q3_VERSION, PLATFORM_STRING, __DATE__ ); + + if ( setjmp (abortframe) ) { + Sys_Error ("Error during initialization"); + } + + // Clear queues + Com_Memset( &eventQueue[ 0 ], 0, MAX_QUEUED_EVENTS * sizeof( sysEvent_t ) ); + + // initialize the weak pseudo-random number generator for use later. + Com_InitRand(); + + // do this before anything else decides to push events + Com_InitPushEvent(); + + Com_InitSmallZoneMemory(); + Cvar_Init (); + + // prepare enough of the subsystems to handle + // cvar and command buffer management + Com_ParseCommandLine( commandLine ); + +// Swap_Init (); + Cbuf_Init (); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + Com_InitZoneMemory(); + Cmd_Init (); + + // get the developer cvar set as early as possible + com_developer = Cvar_Get("developer", "0", CVAR_TEMP); + + // done early so bind command exists + CL_InitKeyCommands(); + + com_homepath = Cvar_Get("com_homepath", "", CVAR_INIT); + + // Com_StartupVariable( + FS_InitFilesystem (); + + Com_InitJournaling(); + + // Add some commands here already so users can use them from config files + Cmd_AddCommand ("setenv", Com_Setenv_f); + if (com_developer && com_developer->integer) + { + Cmd_AddCommand ("error", Com_Error_f); + Cmd_AddCommand ("crash", Com_Crash_f); + Cmd_AddCommand ("freeze", Com_Freeze_f); + } + Cmd_AddCommand ("quit", Com_Quit_f); + Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f ); + Cmd_AddCommand ("writeconfig", Com_WriteConfig_f ); + Cmd_SetCommandCompletionFunc( "writeconfig", Cmd_CompleteCfgName ); + Cmd_AddCommand("game_restart", Com_GameRestart_f); + + Com_ExecuteCfg(); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + // get dedicated here for proper hunk megs initialization +#ifdef DEDICATED + com_dedicated = Cvar_Get ("dedicated", "1", CVAR_INIT); + Cvar_CheckRange( com_dedicated, 1, 2, qtrue ); +#else + com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH); + Cvar_CheckRange( com_dedicated, 0, 2, qtrue ); +#endif + // allocate the stack based hunk allocator + Com_InitHunkMemory(); + + // if any archived cvars are modified after this, we will trigger a writing + // of the config file + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + // + // init commands and vars + // + com_altivec = Cvar_Get ("com_altivec", "1", CVAR_ARCHIVE); + com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE); + com_blood = Cvar_Get ("com_blood", "1", CVAR_ARCHIVE); + + com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP ); + + com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO ); + com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT); + com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT); + com_speeds = Cvar_Get ("com_speeds", "0", 0); + com_timedemo = Cvar_Get ("timedemo", "0", CVAR_CHEAT); + com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT); + + cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM); + sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM); + cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT); + sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT); + com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM); + com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM); + com_buildScript = Cvar_Get( "com_buildScript", "0", 0 ); + com_ansiColor = Cvar_Get( "com_ansiColor", "0", CVAR_ARCHIVE ); + + com_unfocused = Cvar_Get( "com_unfocused", "0", CVAR_ROM ); + com_maxfpsUnfocused = Cvar_Get( "com_maxfpsUnfocused", "0", CVAR_ARCHIVE ); + com_minimized = Cvar_Get( "com_minimized", "0", CVAR_ROM ); + com_maxfpsMinimized = Cvar_Get( "com_maxfpsMinimized", "0", CVAR_ARCHIVE ); + com_abnormalExit = Cvar_Get( "com_abnormalExit", "0", CVAR_ROM ); + com_busyWait = Cvar_Get("com_busyWait", "0", CVAR_ARCHIVE); + + s = va("%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ ); + com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO ); + Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); + + Sys_Init(); + + if( Sys_WritePIDFile( ) ) { +#ifndef DEDICATED + const char *message = "The last time " CLIENT_WINDOW_TITLE " ran, " + "it didn't exit properly. This may be due to inappropriate video " + "settings. Would you like to start with \"safe\" video settings?"; + + if( Sys_Dialog( DT_YES_NO, message, "Abnormal Exit" ) == DR_YES ) { + Cvar_Set( "com_abnormalExit", "1" ); + } +#endif + } + + // Pick a random port value + Com_RandomBytes( (byte*)&qport, sizeof(int) ); + Netchan_Init( qport & 0xffff ); + + VM_Init(); + SV_Init(); + + com_dedicated->modified = qfalse; +#ifndef DEDICATED + CL_Init(); +#endif + + // set com_frameTime so that if a map is started on the + // command line it will still be able to count on com_frameTime + // being random enough for a serverid + com_frameTime = Com_Milliseconds(); + + // add + commands from command line + if ( !Com_AddStartupCommands() ) { + // if the user didn't give any commands, run default action + if ( !com_dedicated->integer ) { + Cbuf_AddText ("cinematic splash.RoQ\n"); + } + } + + // start in full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + CL_StartHunkUsers( qfalse ); + + // make sure single player is off by default + Cvar_Set("ui_singlePlayerActive", "0"); + + com_fullyInitialized = qtrue; + + // always set the cvar, but only print the info if it makes sense. + Com_DetectAltivec(); +#if idppc + Com_Printf ("Altivec support is %s\n", com_altivec->integer ? "enabled" : "disabled"); +#endif + + com_pipefile = Cvar_Get( "com_pipefile", "", CVAR_ARCHIVE|CVAR_LATCH ); + if( com_pipefile->string[0] ) + { + pipefile = FS_FCreateOpenPipeFile( com_pipefile->string ); + } + + Com_Printf ("--- Common Initialization Complete ---\n"); +} + +/* +=============== +Com_ReadFromPipe + +Read whatever is in com_pipefile, if anything, and execute it +=============== +*/ +void Com_ReadFromPipe( void ) +{ + char buffer[MAX_STRING_CHARS] = {""}; + qboolean read; + + if( !pipefile ) + return; + + read = FS_Read( buffer, sizeof( buffer ), pipefile ); + if( read ) + Cbuf_ExecuteText( EXEC_APPEND, buffer ); +} + + +//================================================================== + +void Com_WriteConfigToFile( const char *filename ) { + fileHandle_t f; + + f = FS_FOpenFileWrite( filename ); + if ( !f ) { + Com_Printf ("Couldn't write %s.\n", filename ); + return; + } + + FS_Printf (f, "// generated by tremulous, do not modify\n"); + Key_WriteBindings (f); + Cvar_WriteVariables (f); + FS_FCloseFile( f ); +} + + +/* +=============== +Com_WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void Com_WriteConfiguration( void ) { + // if we are quiting without fully initializing, make sure + // we don't write out anything + if ( !com_fullyInitialized ) { + return; + } + + if ( !(cvar_modifiedFlags & CVAR_ARCHIVE ) ) { + return; + } + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + Com_WriteConfigToFile( Q3CONFIG_CFG ); +} + + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +void Com_WriteConfig_f( void ) { + char filename[MAX_QPATH]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: writeconfig \n" ); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + Com_Printf( "Writing %s.\n", filename ); + Com_WriteConfigToFile( filename ); +} + +/* +================ +Com_ModifyMsec +================ +*/ +int Com_ModifyMsec( int msec ) { + int clampTime; + + // + // modify time for debugging values + // + if ( com_fixedtime->integer ) { + msec = com_fixedtime->integer; + } else if ( com_timescale->value ) { + msec *= com_timescale->value; + } else if (com_cameraMode->integer) { + msec *= com_timescale->value; + } + + // don't let it scale below 1 msec + if ( msec < 1 && com_timescale->value) { + msec = 1; + } + + if ( com_dedicated->integer ) { + // dedicated servers don't want to clamp for a much longer + // period, because it would mess up all the client's views + // of time. + if (com_sv_running->integer && msec > 500) + Com_Printf( "Hitch warning: %i msec frame time\n", msec ); + + clampTime = 5000; + } else + if ( !com_sv_running->integer ) { + // clients of remote servers do not want to clamp time, because + // it would skew their view of the server's time temporarily + clampTime = 5000; + } else { + // for local single player gaming + // we may want to clamp the time to prevent players from + // flying off edges when something hitches. + clampTime = 200; + } + + if ( msec > clampTime ) { + msec = clampTime; + } + + return msec; +} + +/* +================= +Com_Frame +================= +*/ +void Com_Frame( void ) { + + int msec, minMsec; + int timeVal; + static int lastTime = 0, bias = 0; + + int timeBeforeFirstEvents; + int timeBeforeServer; + int timeBeforeEvents; + int timeBeforeClient; + int timeAfter; + + + if ( setjmp (abortframe) ) { + return; // an ERR_DROP was thrown + } + + timeBeforeFirstEvents =0; + timeBeforeServer =0; + timeBeforeEvents =0; + timeBeforeClient = 0; + timeAfter = 0; + + // write config file if anything changed + Com_WriteConfiguration(); + + // + // main event loop + // + if ( com_speeds->integer ) { + timeBeforeFirstEvents = Sys_Milliseconds (); + } + + // Figure out how much time we have + if(!com_timedemo->integer) + { + if(com_dedicated->integer) + minMsec = SV_FrameMsec(); + else + { + if(com_minimized->integer && com_maxfpsMinimized->integer > 0) + minMsec = 1000 / com_maxfpsMinimized->integer; + else if(com_unfocused->integer && com_maxfpsUnfocused->integer > 0) + minMsec = 1000 / com_maxfpsUnfocused->integer; + else if(com_maxfps->integer > 0) + minMsec = 1000 / com_maxfps->integer; + else + minMsec = 1; + + timeVal = com_frameTime - lastTime; + bias += timeVal - minMsec; + + if(bias > minMsec) + bias = minMsec; + + // Adjust minMsec if previous frame took too long to render so + // that framerate is stable at the requested value. + minMsec -= bias; + } + } + else + minMsec = 1; + + timeVal = 0; + do + { + // Busy sleep the last millisecond for better timeout precision + if(com_busyWait->integer || timeVal < 2) + NET_Sleep(0); + else + NET_Sleep(timeVal - 1); + + msec = Sys_Milliseconds() - com_frameTime; + + if(msec >= minMsec) + timeVal = 0; + else + timeVal = minMsec - msec; + + } while(timeVal > 0); + + lastTime = com_frameTime; + com_frameTime = Com_EventLoop(); + + msec = com_frameTime - lastTime; + + Cbuf_Execute (); + + if (com_altivec->modified) + { + Com_DetectAltivec(); + com_altivec->modified = qfalse; + } + + // mess with msec if needed + msec = Com_ModifyMsec(msec); + + // + // server side + // + if ( com_speeds->integer ) { + timeBeforeServer = Sys_Milliseconds (); + } + + SV_Frame( msec ); + + // if "dedicated" has been modified, start up + // or shut down the client system. + // Do this after the server may have started, + // but before the client tries to auto-connect + if ( com_dedicated->modified ) { + // get the latched value + Cvar_Get( "dedicated", "0", 0 ); + com_dedicated->modified = qfalse; + if ( !com_dedicated->integer ) { + SV_Shutdown( "dedicated set to 0" ); + CL_FlushMemory(); + } + } + +#ifndef DEDICATED + // + // client system + // + // + // run event loop a second time to get server to client packets + // without a frame of latency + // + if ( com_speeds->integer ) { + timeBeforeEvents = Sys_Milliseconds (); + } + Com_EventLoop(); + Cbuf_Execute (); + + + // + // client side + // + if ( com_speeds->integer ) { + timeBeforeClient = Sys_Milliseconds (); + } + + CL_Frame( msec ); + + if ( com_speeds->integer ) { + timeAfter = Sys_Milliseconds (); + } +#else + if ( com_speeds->integer ) { + timeAfter = Sys_Milliseconds (); + timeBeforeEvents = timeAfter; + timeBeforeClient = timeAfter; + } +#endif + + + NET_FlushPacketQueue(); + + // + // report timing information + // + if ( com_speeds->integer ) { + int all, sv, ev, cl; + + all = timeAfter - timeBeforeServer; + sv = timeBeforeEvents - timeBeforeServer; + ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents; + cl = timeAfter - timeBeforeClient; + sv -= time_game; + cl -= time_frontend + time_backend; + + Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n", + com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend ); + } + + // + // trace optimization tracking + // + if ( com_showtrace->integer ) { + + extern int c_traces, c_brush_traces, c_patch_traces; + extern int c_pointcontents; + + Com_Printf ("%4i traces (%ib %ip) %4i points\n", c_traces, + c_brush_traces, c_patch_traces, c_pointcontents); + c_traces = 0; + c_brush_traces = 0; + c_patch_traces = 0; + c_pointcontents = 0; + } + + Com_ReadFromPipe( ); + + com_frameNumber++; +} + +/* +================= +Com_Shutdown +================= +*/ +void Com_Shutdown (void) { + if (logfile) { + FS_FCloseFile (logfile); + logfile = 0; + } + + if ( com_journalFile ) { + FS_FCloseFile( com_journalFile ); + com_journalFile = 0; + } + + if( pipefile ) { + FS_FCloseFile( pipefile ); + FS_HomeRemove( com_pipefile->string ); + } + +} + +//------------------------------------------------------------------------ + + +/* +===================== +Q_acos + +the msvc acos doesn't always return a value between -PI and PI: + +int i; +i = 1065353246; +acos(*(float*) &i) == -1.#IND0 + + This should go in q_math but it is too late to add new traps + to game and ui +===================== +*/ +float Q_acos(float c) { + float angle; + + angle = acos(c); + + if (angle > M_PI) { + return (float)M_PI; + } + if (angle < -M_PI) { + return (float)M_PI; + } + return angle; +} + +/* +=========================================== +command line completion +=========================================== +*/ + +/* +================== +Field_Clear +================== +*/ +void Field_Clear( field_t *edit ) { + memset(edit->buffer, 0, MAX_EDIT_LINE); + edit->cursor = 0; + edit->scroll = 0; +} + +static const char *completionString; +static char shortestMatch[MAX_TOKEN_CHARS]; +static int matchCount; +// field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance) +static field_t *completionField; + +/* +=============== +FindMatches + +=============== +*/ +static void FindMatches( const char *s ) { + int i; + + if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) { + return; + } + matchCount++; + if ( matchCount == 1 ) { + Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) ); + return; + } + + // cut shortestMatch to the amount common with s + for ( i = 0 ; shortestMatch[i] ; i++ ) { + if ( i >= strlen( s ) ) { + shortestMatch[i] = 0; + break; + } + + if ( tolower(shortestMatch[i]) != tolower(s[i]) ) { + shortestMatch[i] = 0; + } + } +} + +/* +=============== +PrintMatches + +=============== +*/ +static void PrintMatches( const char *s ) { + if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) { + Com_Printf( " %s\n", s ); + } +} + +/* +=============== +PrintCvarMatches + +=============== +*/ +static void PrintCvarMatches( const char *s ) { + char value[ TRUNCATE_LENGTH ]; + + if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) { + Com_TruncateLongString( value, Cvar_VariableString( s ) ); + Com_Printf( " %s = \"%s\"\n", s, value ); + } +} + +/* +=============== +Field_FindFirstSeparator +=============== +*/ +static char *Field_FindFirstSeparator( char *s ) +{ + int i; + + for( i = 0; i < strlen( s ); i++ ) + { + if( s[ i ] == ';' ) + return &s[ i ]; + } + + return NULL; +} + +/* +=============== +Field_Complete +=============== +*/ +static qboolean Field_Complete( void ) +{ + int completionOffset; + + if( matchCount == 0 ) + return qtrue; + + completionOffset = strlen( completionField->buffer ) - strlen( completionString ); + + Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch, + sizeof( completionField->buffer ) - completionOffset ); + + completionField->cursor = strlen( completionField->buffer ); + + if( matchCount == 1 ) + { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); + completionField->cursor++; + return qtrue; + } + + Com_Printf( "]%s\n", completionField->buffer ); + + return qfalse; +} + +#ifndef DEDICATED +/* +=============== +Field_CompleteKeyname +=============== +*/ +void Field_CompleteKeyname( void ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + Key_KeynameCompletion( FindMatches ); + + if( !Field_Complete( ) ) + Key_KeynameCompletion( PrintMatches ); +} +#endif + +/* +=============== +Field_CompleteFilename +=============== +*/ +void Field_CompleteFilename( const char *dir, + const char *ext, qboolean stripExt, qboolean allowNonPureFilesOnDisk ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + FS_FilenameCompletion( dir, ext, stripExt, FindMatches, allowNonPureFilesOnDisk ); + + if( !Field_Complete( ) ) + FS_FilenameCompletion( dir, ext, stripExt, PrintMatches, allowNonPureFilesOnDisk ); +} + +/* +=============== +Field_CompleteCommand +=============== +*/ +void Field_CompleteCommand( char *cmd, + qboolean doCommands, qboolean doCvars ) +{ + int completionArgument = 0; + + // Skip leading whitespace and quotes + cmd = Com_SkipCharset( cmd, " \"" ); + + Cmd_TokenizeStringIgnoreQuotes( cmd ); + completionArgument = Cmd_Argc( ); + + // If there is trailing whitespace on the cmd + if( *( cmd + strlen( cmd ) - 1 ) == ' ' ) + { + completionString = ""; + completionArgument++; + } + else + completionString = Cmd_Argv( completionArgument - 1 ); + +#ifndef DEDICATED + // Unconditionally add a '\' to the start of the buffer + if( completionField->buffer[ 0 ] && + completionField->buffer[ 0 ] != '\\' ) + { + if( completionField->buffer[ 0 ] != '/' ) + { + // Buffer is full, refuse to complete + if( strlen( completionField->buffer ) + 1 >= + sizeof( completionField->buffer ) ) + return; + + memmove( &completionField->buffer[ 1 ], + &completionField->buffer[ 0 ], + strlen( completionField->buffer ) + 1 ); + completionField->cursor++; + } + + completionField->buffer[ 0 ] = '\\'; + } +#endif + + if( completionArgument > 1 ) + { + const char *baseCmd = Cmd_Argv( 0 ); + char *p; + +#ifndef DEDICATED + // This should always be true + if( baseCmd[ 0 ] == '\\' || baseCmd[ 0 ] == '/' ) + baseCmd++; +#endif + + if( ( p = Field_FindFirstSeparator( cmd ) ) ) + Field_CompleteCommand( p + 1, qtrue, qtrue ); // Compound command + else + Cmd_CompleteArgument( baseCmd, cmd, completionArgument ); + } + else + { + if( completionString[0] == '\\' || completionString[0] == '/' ) + completionString++; + + matchCount = 0; + shortestMatch[ 0 ] = 0; + + if( strlen( completionString ) == 0 ) + return; + + if( doCommands ) + Cmd_CommandCompletion( FindMatches ); + + if( doCvars ) + Cvar_CommandCompletion( FindMatches ); + + if( !Field_Complete( ) ) + { + // run through again, printing matches + if( doCommands ) + Cmd_CommandCompletion( PrintMatches ); + + if( doCvars ) + Cvar_CommandCompletion( PrintCvarMatches ); + } + } +} + +/* +=============== +Field_AutoComplete + +Perform Tab expansion +=============== +*/ +void Field_AutoComplete( field_t *field ) +{ + completionField = field; + + Field_CompleteCommand( completionField->buffer, qtrue, qtrue ); +} + +/* +================== +Com_RandomBytes + +fills string array with len radom bytes, peferably from the OS randomizer +================== +*/ +void Com_RandomBytes( byte *string, int len ) +{ + int i; + + if( Sys_RandomBytes( string, len ) ) + return; + + Com_Printf( "Com_RandomBytes: using weak randomization\n" ); + for( i = 0; i < len; i++ ) + string[i] = (unsigned char)( rand() % 255 ); +} + diff --git a/src/qcommon/cvar.c b/src/qcommon/cvar.c new file mode 100644 index 0000000..8db30e6 --- /dev/null +++ b/src/qcommon/cvar.c @@ -0,0 +1,1321 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cvar.c -- dynamic variable tracking + +#include "q_shared.h" +#include "qcommon.h" + +cvar_t *cvar_vars = NULL; +cvar_t *cvar_cheats; +int cvar_modifiedFlags; + +#define MAX_CVARS 1024 +cvar_t cvar_indexes[MAX_CVARS]; +int cvar_numIndexes; + +#define FILE_HASH_SIZE 256 +static cvar_t *hashTable[FILE_HASH_SIZE]; + +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force); + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +============ +Cvar_ValidateString +============ +*/ +static qboolean Cvar_ValidateString( const char *s ) { + if ( !s ) { + return qfalse; + } + if ( strchr( s, '\\' ) ) { + return qfalse; + } + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* +============ +Cvar_FindVar +============ +*/ +static cvar_t *Cvar_FindVar( const char *var_name ) { + cvar_t *var; + long hash; + + hash = generateHashValue(var_name); + + for (var=hashTable[hash] ; var ; var=var->hashNext) { + if (!Q_stricmp(var_name, var->name)) { + return var; + } + } + + return NULL; +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return 0; + return var->value; +} + + +/* +============ +Cvar_VariableIntegerValue +============ +*/ +int Cvar_VariableIntegerValue( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return 0; + return var->integer; +} + + +/* +============ +Cvar_VariableString +============ +*/ +char *Cvar_VariableString( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return ""; + return var->string; +} + + +/* +============ +Cvar_VariableStringBuffer +============ +*/ +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) { + *buffer = 0; + } + else { + Q_strncpyz( buffer, var->string, bufsize ); + } +} + +/* +============ +Cvar_Flags +============ +*/ +int Cvar_Flags(const char *var_name) +{ + cvar_t *var; + + if(! (var = Cvar_FindVar(var_name)) ) + return CVAR_NONEXISTENT; + else + return var->flags; +} + +/* +============ +Cvar_CommandCompletion +============ +*/ +void Cvar_CommandCompletion(void (*callback)(const char *s)) +{ + cvar_t *cvar; + + for(cvar = cvar_vars; cvar; cvar = cvar->next) + { + if(cvar->name) + callback(cvar->name); + } +} + +/* +============ +Cvar_Validate +============ +*/ +static const char *Cvar_Validate( cvar_t *var, + const char *value, qboolean warn ) +{ + static char s[ MAX_CVAR_VALUE_STRING ]; + float valuef; + qboolean changed = qfalse; + + if( !var->validate ) + return value; + + if( !value ) + return value; + + if( Q_isanumber( value ) ) + { + valuef = atof( value ); + + if( var->integral ) + { + if( !Q_isintegral( valuef ) ) + { + if( warn ) + Com_Printf( "WARNING: cvar '%s' must be integral", var->name ); + + valuef = (int)valuef; + changed = qtrue; + } + } + } + else + { + if( warn ) + Com_Printf( "WARNING: cvar '%s' must be numeric", var->name ); + + valuef = atof( var->resetString ); + changed = qtrue; + } + + if( valuef < var->min ) + { + if( warn ) + { + if( changed ) + Com_Printf( " and is" ); + else + Com_Printf( "WARNING: cvar '%s'", var->name ); + + if( Q_isintegral( var->min ) ) + Com_Printf( " out of range (min %d)", (int)var->min ); + else + Com_Printf( " out of range (min %f)", var->min ); + } + + valuef = var->min; + changed = qtrue; + } + else if( valuef > var->max ) + { + if( warn ) + { + if( changed ) + Com_Printf( " and is" ); + else + Com_Printf( "WARNING: cvar '%s'", var->name ); + + if( Q_isintegral( var->max ) ) + Com_Printf( " out of range (max %d)", (int)var->max ); + else + Com_Printf( " out of range (max %f)", var->max ); + } + + valuef = var->max; + changed = qtrue; + } + + if( changed ) + { + if( Q_isintegral( valuef ) ) + { + Com_sprintf( s, sizeof( s ), "%d", (int)valuef ); + + if( warn ) + Com_Printf( ", setting to %d\n", (int)valuef ); + } + else + { + Com_sprintf( s, sizeof( s ), "%f", valuef ); + + if( warn ) + Com_Printf( ", setting to %f\n", valuef ); + } + + return s; + } + else + return value; +} + + +/* +============ +Cvar_Get + +If the variable already exists, the value will not be set unless CVAR_ROM +The flags will be or'ed in if the variable exists. +============ +*/ +cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) { + cvar_t *var; + long hash; + int index; + + if ( !var_name || ! var_value ) { + Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" ); + } + + if ( !Cvar_ValidateString( var_name ) ) { + Com_Printf("invalid cvar name string: %s\n", var_name ); + var_name = "BADNAME"; + } + +#if 0 // FIXME: values with backslash happen + if ( !Cvar_ValidateString( var_value ) ) { + Com_Printf("invalid cvar value string: %s\n", var_value ); + var_value = "BADVALUE"; + } +#endif + + var = Cvar_FindVar (var_name); + + if(var) + { + var_value = Cvar_Validate(var, var_value, qfalse); + + // if the C code is now specifying a variable that the user already + // set a value for, take the new value as the reset value + if(var->flags & CVAR_USER_CREATED) + { + var->flags &= ~CVAR_USER_CREATED; + Z_Free( var->resetString ); + var->resetString = CopyString( var_value ); + + if(flags & CVAR_ROM) + { + // this variable was set by the user, + // so force it to value given by the engine. + + if(var->latchedString) + Z_Free(var->latchedString); + + var->latchedString = CopyString(var_value); + } + } + + // Make sure the game code cannot mark engine-added variables as gamecode vars + if(var->flags & CVAR_VM_CREATED) + { + if(!(flags & CVAR_VM_CREATED)) + var->flags &= ~CVAR_VM_CREATED; + } + else + { + if(flags & CVAR_VM_CREATED) + flags &= ~CVAR_VM_CREATED; + } + + // Make sure servers cannot mark engine-added variables as SERVER_CREATED + if(var->flags & CVAR_SERVER_CREATED) + { + if(!(flags & CVAR_SERVER_CREATED)) + var->flags &= ~CVAR_SERVER_CREATED; + } + else + { + if(flags & CVAR_SERVER_CREATED) + flags &= ~CVAR_SERVER_CREATED; + } + + var->flags |= flags; + + // only allow one non-empty reset string without a warning + if ( !var->resetString[0] ) { + // we don't have a reset string yet + Z_Free( var->resetString ); + var->resetString = CopyString( var_value ); + } else if ( var_value[0] && strcmp( var->resetString, var_value ) ) { + Com_DPrintf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", + var_name, var->resetString, var_value ); + } + // if we have a latched string, take that value now + if ( var->latchedString ) { + char *s; + + s = var->latchedString; + var->latchedString = NULL; // otherwise cvar_set2 would free it + Cvar_Set2( var_name, s, qtrue ); + Z_Free( s ); + } + + // ZOID--needs to be set so that cvars the game sets as + // SERVERINFO get sent to clients + cvar_modifiedFlags |= flags; + + return var; + } + + // + // allocate a new cvar + // + + // find a free cvar + for(index = 0; index < MAX_CVARS; index++) + { + if(!cvar_indexes[index].name) + break; + } + + if(index >= MAX_CVARS) + { + if(!com_errorEntered) + Com_Error(ERR_FATAL, "Error: Too many cvars, cannot create a new one!"); + + return NULL; + } + + var = &cvar_indexes[index]; + + if(index >= cvar_numIndexes) + cvar_numIndexes = index + 1; + + var->name = CopyString (var_name); + var->string = CopyString (var_value); + var->modified = qtrue; + var->modificationCount = 1; + var->value = atof (var->string); + var->integer = atoi(var->string); + var->resetString = CopyString( var_value ); + var->validate = qfalse; + + // link the variable in + var->next = cvar_vars; + if(cvar_vars) + cvar_vars->prev = var; + + var->prev = NULL; + cvar_vars = var; + + var->flags = flags; + // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) + cvar_modifiedFlags |= var->flags; + + hash = generateHashValue(var_name); + var->hashIndex = hash; + + var->hashNext = hashTable[hash]; + if(hashTable[hash]) + hashTable[hash]->hashPrev = var; + + var->hashPrev = NULL; + hashTable[hash] = var; + + return var; +} + +/* +============ +Cvar_Print + +Prints the value, default, and latched string of the given variable +============ +*/ +void Cvar_Print( cvar_t *v ) { + Com_Printf ("\"%s\" is:\"%s" S_COLOR_WHITE "\"", + v->name, v->string ); + + if ( !( v->flags & CVAR_ROM ) ) { + if ( !Q_stricmp( v->string, v->resetString ) ) { + Com_Printf (", the default" ); + } else { + Com_Printf (" default:\"%s" S_COLOR_WHITE "\"", + v->resetString ); + } + } + + Com_Printf ("\n"); + + if ( v->latchedString ) { + Com_Printf( "latched: \"%s\"\n", v->latchedString ); + } +} + +/* +============ +Cvar_Set2 +============ +*/ +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) { + cvar_t *var; + +// Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value ); + + if ( !Cvar_ValidateString( var_name ) ) { + Com_Printf("invalid cvar name string: %s\n", var_name ); + var_name = "BADNAME"; + } + +#if 0 // FIXME + if ( value && !Cvar_ValidateString( value ) ) { + Com_Printf("invalid cvar value string: %s\n", value ); + var_value = "BADVALUE"; + } +#endif + + var = Cvar_FindVar (var_name); + if (!var) { + if ( !value ) { + return NULL; + } + // create it + if ( !force ) { + return Cvar_Get( var_name, value, CVAR_USER_CREATED ); + } else { + return Cvar_Get (var_name, value, 0); + } + } + + if (!value ) { + value = var->resetString; + } + + value = Cvar_Validate(var, value, qtrue); + + if((var->flags & CVAR_LATCH) && var->latchedString) + { + if(!strcmp(value, var->string)) + { + Z_Free(var->latchedString); + var->latchedString = NULL; + return var; + } + + if(!strcmp(value, var->latchedString)) + return var; + } + else if(!strcmp(value, var->string)) + return var; + + // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) + cvar_modifiedFlags |= var->flags; + + if (!force) + { + if (var->flags & CVAR_ROM) + { + Com_Printf ("%s is read only.\n", var_name); + return var; + } + + if (var->flags & CVAR_INIT) + { + Com_Printf ("%s is write protected.\n", var_name); + return var; + } + + if (var->flags & CVAR_LATCH) + { + if (var->latchedString) + { + if (strcmp(value, var->latchedString) == 0) + return var; + Z_Free (var->latchedString); + } + else + { + if (strcmp(value, var->string) == 0) + return var; + } + + Com_Printf ("%s will be changed upon restarting.\n", var_name); + var->latchedString = CopyString(value); + var->modified = qtrue; + var->modificationCount++; + return var; + } + + if ( (var->flags & CVAR_CHEAT) && !cvar_cheats->integer ) + { + Com_Printf ("%s is cheat protected.\n", var_name); + return var; + } + + } + else + { + if (var->latchedString) + { + Z_Free (var->latchedString); + var->latchedString = NULL; + } + } + + if (!strcmp(value, var->string)) + return var; // not changed + + var->modified = qtrue; + var->modificationCount++; + + Z_Free (var->string); // free the old value string + + var->string = CopyString(value); + var->value = atof (var->string); + var->integer = atoi (var->string); + + return var; +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set( const char *var_name, const char *value) { + Cvar_Set2 (var_name, value, qtrue); +} + +/* +============ +Cvar_SetSafe +============ +*/ +void Cvar_SetSafe( const char *var_name, const char *value ) +{ + int flags = Cvar_Flags( var_name ); + + if( flags != CVAR_NONEXISTENT && flags & CVAR_PROTECTED ) + { + if( value ) + Com_Error( ERR_DROP, "Restricted source tried to set " + "\"%s\" to \"%s\"\n", var_name, value ); + else + Com_Error( ERR_DROP, "Restricted source tried to " + "modify \"%s\"\n", var_name ); + return; + } + Cvar_Set( var_name, value ); +} + +/* +============ +Cvar_SetLatched +============ +*/ +void Cvar_SetLatched( const char *var_name, const char *value) { + Cvar_Set2 (var_name, value, qfalse); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue( const char *var_name, float value) { + char val[32]; + + if ( value == (int)value ) { + Com_sprintf (val, sizeof(val), "%i",(int)value); + } else { + Com_sprintf (val, sizeof(val), "%f",value); + } + Cvar_Set (var_name, val); +} + +/* +============ +Cvar_SetValueSafe +============ +*/ +void Cvar_SetValueSafe( const char *var_name, float value ) +{ + char val[32]; + + if( Q_isintegral( value ) ) + Com_sprintf( val, sizeof(val), "%i", (int)value ); + else + Com_sprintf( val, sizeof(val), "%f", value ); + Cvar_SetSafe( var_name, val ); +} + +/* +============ +Cvar_Reset +============ +*/ +void Cvar_Reset( const char *var_name ) { + Cvar_Set2( var_name, NULL, qfalse ); +} + +/* +============ +Cvar_ForceReset +============ +*/ +void Cvar_ForceReset(const char *var_name) +{ + Cvar_Set2(var_name, NULL, qtrue); +} + +/* +============ +Cvar_SetCheatState + +Any testing variables will be reset to the safe values +============ +*/ +void Cvar_SetCheatState(void) +{ + cvar_t *var; + + // set all default vars to the safe value + for(var = cvar_vars; var ; var = var->next) + { + if(var->flags & CVAR_CHEAT) + { + // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here + // because of a different var->latchedString + if (var->latchedString) + { + Z_Free(var->latchedString); + var->latchedString = NULL; + } + if (strcmp(var->resetString,var->string)) + Cvar_Set(var->name, var->resetString); + } + } +} + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +qboolean Cvar_Command( void ) { + cvar_t *v; + + // check variables + v = Cvar_FindVar (Cmd_Argv(0)); + if (!v) { + return qfalse; + } + + // perform a variable print or set + if ( Cmd_Argc() == 1 ) { + Cvar_Print( v ); + return qtrue; + } + + // set the value if forcing isn't required + Cvar_Set2 (v->name, Cmd_Args(), qfalse); + return qtrue; +} + + +/* +============ +Cvar_Print_f + +Prints the contents of a cvar +(preferred over Cvar_Command where cvar names and commands conflict) +============ +*/ +void Cvar_Print_f(void) +{ + char *name; + cvar_t *cv; + + if(Cmd_Argc() != 2) + { + Com_Printf ("usage: print \n"); + return; + } + + name = Cmd_Argv(1); + + cv = Cvar_FindVar(name); + + if(cv) + Cvar_Print(cv); + else + Com_Printf ("Cvar %s does not exist.\n", name); +} + +/* +============ +Cvar_Toggle_f + +Toggles a cvar for easy single key binding, optionally through a list of +given values +============ +*/ +void Cvar_Toggle_f( void ) { + int i, c = Cmd_Argc(); + char *curval; + + if(c < 2) { + Com_Printf("usage: toggle [value1, value2, ...]\n"); + return; + } + + if(c == 2) { + Cvar_Set2(Cmd_Argv(1), va("%d", + !Cvar_VariableValue(Cmd_Argv(1))), + qfalse); + return; + } + + if(c == 3) { + Com_Printf("toggle: nothing to toggle to\n"); + return; + } + + curval = Cvar_VariableString(Cmd_Argv(1)); + + // don't bother checking the last arg for a match since the desired + // behaviour is the same as no match (set to the first argument) + for(i = 2; i + 1 < c; i++) { + if(strcmp(curval, Cmd_Argv(i)) == 0) { + Cvar_Set2(Cmd_Argv(1), Cmd_Argv(i + 1), qfalse); + return; + } + } + + // fallback + Cvar_Set2(Cmd_Argv(1), Cmd_Argv(2), qfalse); +} + +/* +============ +Cvar_Set_f + +Allows setting and defining of arbitrary cvars from console, even if they +weren't declared in C code. +============ +*/ +void Cvar_Set_f( void ) { + int c; + char *cmd; + cvar_t *v; + + c = Cmd_Argc(); + cmd = Cmd_Argv(0); + + if ( c < 2 ) { + Com_Printf ("usage: %s \n", cmd); + return; + } + if ( c == 2 ) { + Cvar_Print_f(); + return; + } + + v = Cvar_Set2 (Cmd_Argv(1), Cmd_ArgsFrom(2), qfalse); + if( !v ) { + return; + } + switch( cmd[3] ) { + case 'a': + if( !( v->flags & CVAR_ARCHIVE ) ) { + v->flags |= CVAR_ARCHIVE; + cvar_modifiedFlags |= CVAR_ARCHIVE; + } + break; + case 'u': + if( !( v->flags & CVAR_USERINFO ) ) { + v->flags |= CVAR_USERINFO; + cvar_modifiedFlags |= CVAR_USERINFO; + } + break; + case 's': + if( !( v->flags & CVAR_SERVERINFO ) ) { + v->flags |= CVAR_SERVERINFO; + cvar_modifiedFlags |= CVAR_SERVERINFO; + } + break; + } +} + +/* +============ +Cvar_Reset_f +============ +*/ +void Cvar_Reset_f( void ) { + if ( Cmd_Argc() != 2 ) { + Com_Printf ("usage: reset \n"); + return; + } + Cvar_Reset( Cmd_Argv( 1 ) ); +} + +/* +============ +Cvar_WriteVariables + +Appends lines containing "set variable value" for all variables +with the archive flag set to qtrue. +============ +*/ +void Cvar_WriteVariables(fileHandle_t f) +{ + cvar_t *var; + char buffer[1024]; + + for (var = cvar_vars; var; var = var->next) + { + if(!var->name) + continue; + + if( var->flags & CVAR_ARCHIVE ) { + // write the latched value, even if it hasn't taken effect yet + if ( var->latchedString ) { + if( strlen( var->name ) + strlen( var->latchedString ) + 10 > sizeof( buffer ) ) { + Com_Printf( S_COLOR_YELLOW "WARNING: value of variable " + "\"%s\" too long to write to file\n", var->name ); + continue; + } + Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString); + } else { + if( strlen( var->name ) + strlen( var->string ) + 10 > sizeof( buffer ) ) { + Com_Printf( S_COLOR_YELLOW "WARNING: value of variable " + "\"%s\" too long to write to file\n", var->name ); + continue; + } + Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string); + } + FS_Write( buffer, strlen( buffer ), f ); + } + } +} + +/* +============ +Cvar_List_f +============ +*/ +void Cvar_List_f( void ) { + cvar_t *var; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for (var = cvar_vars ; var ; var = var->next, i++) + { + if(!var->name || (match && !Com_Filter(match, var->name, qfalse))) + continue; + + if (var->flags & CVAR_SERVERINFO) { + Com_Printf("S"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_SYSTEMINFO) { + Com_Printf("s"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_USERINFO) { + Com_Printf("U"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_ROM) { + Com_Printf("R"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_INIT) { + Com_Printf("I"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_ARCHIVE) { + Com_Printf("A"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_LATCH) { + Com_Printf("L"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_CHEAT) { + Com_Printf("C"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_USER_CREATED) { + Com_Printf("?"); + } else { + Com_Printf(" "); + } + + Com_Printf (" %s \"%s\"\n", var->name, var->string); + } + + Com_Printf ("\n%i total cvars\n", i); + Com_Printf ("%i cvar indexes\n", cvar_numIndexes); +} + +/* +============ +Cvar_Unset + +Unsets a cvar +============ +*/ + +cvar_t *Cvar_Unset(cvar_t *cv) +{ + cvar_t *next = cv->next; + + if(cv->name) + Z_Free(cv->name); + if(cv->string) + Z_Free(cv->string); + if(cv->latchedString) + Z_Free(cv->latchedString); + if(cv->resetString) + Z_Free(cv->resetString); + + if(cv->prev) + cv->prev->next = cv->next; + else + cvar_vars = cv->next; + if(cv->next) + cv->next->prev = cv->prev; + + if(cv->hashPrev) + cv->hashPrev->hashNext = cv->hashNext; + else + hashTable[cv->hashIndex] = cv->hashNext; + if(cv->hashNext) + cv->hashNext->hashPrev = cv->hashPrev; + + Com_Memset(cv, '\0', sizeof(*cv)); + + return next; +} + +/* +============ +Cvar_Unset_f + +Unsets a userdefined cvar +============ +*/ + +void Cvar_Unset_f(void) +{ + cvar_t *cv; + + if(Cmd_Argc() != 2) + { + Com_Printf("Usage: %s \n", Cmd_Argv(0)); + return; + } + + cv = Cvar_FindVar(Cmd_Argv(1)); + + if(!cv) + return; + + if(cv->flags & CVAR_USER_CREATED) + Cvar_Unset(cv); + else + Com_Printf("Error: %s: Variable %s is not user created.\n", Cmd_Argv(0), cv->name); +} + + + +/* +============ +Cvar_Restart + +Resets all cvars to their hardcoded values and removes userdefined variables +and variables added via the VMs if requested. +============ +*/ + +void Cvar_Restart(qboolean unsetVM) +{ + cvar_t *curvar; + + curvar = cvar_vars; + + while(curvar) + { + if((curvar->flags & CVAR_USER_CREATED) || + (unsetVM && (curvar->flags & CVAR_VM_CREATED))) + { + // throw out any variables the user/vm created + curvar = Cvar_Unset(curvar); + continue; + } + + if(!(curvar->flags & (CVAR_ROM | CVAR_INIT | CVAR_NORESTART))) + { + // Just reset the rest to their default values. + Cvar_Set2(curvar->name, curvar->resetString, qfalse); + } + + curvar = curvar->next; + } +} + + +/* +============ +Cvar_Restart_f + +Resets all cvars to their hardcoded values +============ +*/ +void Cvar_Restart_f(void) +{ + Cvar_Restart(qfalse); +} + +/* +===================== +Cvar_InfoString +===================== +*/ +char *Cvar_InfoString(int bit) +{ + static char info[MAX_INFO_STRING]; + cvar_t *var; + + info[0] = 0; + + for(var = cvar_vars; var; var = var->next) + { + if(var->name && (var->flags & bit)) + Info_SetValueForKey (info, var->name, var->string); + } + + return info; +} + +/* +===================== +Cvar_InfoString_Big + + handles large info strings ( CS_SYSTEMINFO ) +===================== +*/ +char *Cvar_InfoString_Big(int bit) +{ + static char info[BIG_INFO_STRING]; + cvar_t *var; + + info[0] = 0; + + for (var = cvar_vars; var; var = var->next) + { + if(var->name && (var->flags & bit)) + Info_SetValueForKey_Big (info, var->name, var->string); + } + return info; +} + + + +/* +===================== +Cvar_InfoStringBuffer +===================== +*/ +void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) { + Q_strncpyz(buff,Cvar_InfoString(bit),buffsize); +} + +/* +===================== +Cvar_CheckRange +===================== +*/ +void Cvar_CheckRange( cvar_t *var, float min, float max, qboolean integral ) +{ + var->validate = qtrue; + var->min = min; + var->max = max; + var->integral = integral; + + // Force an initial range check + Cvar_Set( var->name, var->string ); +} + +/* +===================== +Cvar_Register + +basically a slightly modified Cvar_Get for the interpreted modules +===================== +*/ +void Cvar_Register(vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags) +{ + cvar_t *cv; + + // There is code in Cvar_Get to prevent CVAR_ROM cvars being changed by the + // user. In other words CVAR_ARCHIVE and CVAR_ROM are mutually exclusive + // flags. Unfortunately some historical game code (including single player + // baseq3) sets both flags. We unset CVAR_ROM for such cvars. + if ((flags & (CVAR_ARCHIVE | CVAR_ROM)) == (CVAR_ARCHIVE | CVAR_ROM)) { + Com_DPrintf( S_COLOR_YELLOW "WARNING: Unsetting CVAR_ROM cvar '%s', " + "since it is also CVAR_ARCHIVE\n", varName ); + flags &= ~CVAR_ROM; + } + + cv = Cvar_Get(varName, defaultValue, flags | CVAR_VM_CREATED); + + if (!vmCvar) + return; + + vmCvar->handle = cv - cvar_indexes; + vmCvar->modificationCount = -1; + Cvar_Update( vmCvar ); +} + + +/* +===================== +Cvar_Update + +updates an interpreted modules' version of a cvar +===================== +*/ +void Cvar_Update( vmCvar_t *vmCvar ) { + cvar_t *cv = NULL; + assert(vmCvar); + + if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) { + Com_Error( ERR_DROP, "Cvar_Update: handle out of range" ); + } + + cv = cvar_indexes + vmCvar->handle; + + if ( cv->modificationCount == vmCvar->modificationCount ) { + return; + } + if ( !cv->string ) { + return; // variable might have been cleared by a cvar_restart + } + vmCvar->modificationCount = cv->modificationCount; + if ( strlen(cv->string)+1 > MAX_CVAR_VALUE_STRING ) + Com_Error( ERR_DROP, "Cvar_Update: src %s length %u exceeds MAX_CVAR_VALUE_STRING", + cv->string, + (unsigned int) strlen(cv->string)); + Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING ); + + vmCvar->value = cv->value; + vmCvar->integer = cv->integer; +} + +/* +================== +Cvar_CompleteCvarName +================== +*/ +void Cvar_CompleteCvarName( char *args, int argNum ) +{ + if( argNum == 2 ) + { + // Skip " " + char *p = Com_SkipTokens( args, 1, " " ); + + if( p > args ) + Field_CompleteCommand( p, qfalse, qtrue ); + } +} + +/* +============ +Cvar_Init + +Reads in all archived cvars +============ +*/ +void Cvar_Init (void) +{ + Com_Memset(cvar_indexes, '\0', sizeof(cvar_indexes)); + Com_Memset(hashTable, '\0', sizeof(hashTable)); + + cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO ); + + Cmd_AddCommand ("print", Cvar_Print_f); + Cmd_AddCommand ("toggle", Cvar_Toggle_f); + Cmd_SetCommandCompletionFunc( "toggle", Cvar_CompleteCvarName ); + Cmd_AddCommand ("set", Cvar_Set_f); + Cmd_SetCommandCompletionFunc( "set", Cvar_CompleteCvarName ); + Cmd_AddCommand ("sets", Cvar_Set_f); + Cmd_SetCommandCompletionFunc( "sets", Cvar_CompleteCvarName ); + Cmd_AddCommand ("setu", Cvar_Set_f); + Cmd_SetCommandCompletionFunc( "setu", Cvar_CompleteCvarName ); + Cmd_AddCommand ("seta", Cvar_Set_f); + Cmd_SetCommandCompletionFunc( "seta", Cvar_CompleteCvarName ); + Cmd_AddCommand ("reset", Cvar_Reset_f); + Cmd_SetCommandCompletionFunc( "reset", Cvar_CompleteCvarName ); + Cmd_AddCommand ("unset", Cvar_Unset_f); + Cmd_SetCommandCompletionFunc("unset", Cvar_CompleteCvarName); + + Cmd_AddCommand ("cvarlist", Cvar_List_f); + Cmd_AddCommand ("cvar_restart", Cvar_Restart_f); +} diff --git a/src/qcommon/files.c b/src/qcommon/files.c new file mode 100644 index 0000000..b85a1af --- /dev/null +++ b/src/qcommon/files.c @@ -0,0 +1,3525 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +/***************************************************************************** + * name: files.c + * + * desc: handle based filesystem for Quake III Arena + * + * $Archive: /MissionPack/code/qcommon/files.c $ + * + *****************************************************************************/ + + +#include "q_shared.h" +#include "qcommon.h" +#include "unzip.h" + +/* +============================================================================= + +QUAKE3 FILESYSTEM + +All of Quake's data access is through a hierarchical file system, but the contents of +the file system can be transparently merged from several sources. + +A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include +a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any +references outside the quake directory system. + +The "base path" is the path to the directory holding all the game directories and usually +the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3" +command line to allow code debugging in a different directory. Basepath cannot +be modified at all after startup. Any files that are created (demos, screenshots, +etc) will be created reletive to the base path, so base path should usually be writable. + +The "home path" is the path used for all write access. On win32 systems we have "base path" +== "home path", but on *nix systems the base installation is usually readonly, and +"home path" points to ~/.q3a or similar + +The user can also install custom mods and content in "home path", so it should be searched +along with "home path" and "cd path" for game content. + + +The "base game" is the directory under the paths where data comes from by default, and +can be "base". + +The "current game" may be the same as the base game, or it may be the name of another +directory under the paths that should be searched for files before looking in the base game. +This is the basis for addons. + +Clients automatically set the game directory after receiving a gamestate from a server, +so only servers need to worry about +set fs_game. + +No other directories outside of the base game and current game will ever be referenced by +filesystem functions. + +To save disk space and speed loading, directory trees can be collapsed into zip files. +The files use a ".pk3" extension to prevent users from unzipping them accidentally, but +otherwise the are simply normal uncompressed zip files. A game directory can have multiple +zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order +from the highest number to the lowest, and will always take precedence over the filesystem. +This allows a pk3 distributed as a patch to override all existing data. + +Because we will have updated executables freely available online, there is no point to +trying to restrict demo / oem versions of the game with code changes. Demo / oem versions +should be exactly the same executables as release versions, but with different data that +automatically restricts where game media can come from to prevent add-ons from working. + +File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths +structure and stop on the first successful hit. fs_searchpaths is built with successive +calls to FS_AddGameDirectory + +Additionaly, we search in several subdirectories: +current game is the current mode +base game is a variable to allow mods based on other mods +(such as base + missionpack content combination in a mod for instance) +BASEGAME is the hardcoded base game ("base") + +e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places: + +home path + current game's zip files +home path + current game's directory +base path + current game's zip files +base path + current game's directory +cd path + current game's zip files +cd path + current game's directory + +home path + base game's zip file +home path + base game's directory +base path + base game's zip file +base path + base game's directory +cd path + base game's zip file +cd path + base game's directory + +home path + BASEGAME's zip file +home path + BASEGAME's directory +base path + BASEGAME's zip file +base path + BASEGAME's directory +cd path + BASEGAME's zip file +cd path + BASEGAME's directory + +server download, to be written to home path + current game's directory + + +The filesystem can be safely shutdown and reinitialized with different +basedir / cddir / game combinations, but all other subsystems that rely on it +(sound, video) must also be forced to restart. + +Because the same files are loaded by both the clip model (CM_) and renderer (TR_) +subsystems, a simple single-file caching scheme is used. The CM_ subsystems will +load the file with a request to cache. Only one file will be kept cached at a time, +so any models that are going to be referenced by both subsystems should alternate +between the CM_ load function and the ref load function. + +TODO: A qpath that starts with a leading slash will always refer to the base game, even if another +game is currently active. This allows character models, skins, and sounds to be downloaded +to a common directory no matter which game is active. + +How to prevent downloading zip files? +Pass pk3 file names in systeminfo, and download before FS_Restart()? + +Aborting a download disconnects the client from the server. + +How to mark files as downloadable? Commercial add-ons won't be downloadable. + +Non-commercial downloads will want to download the entire zip file. +the game would have to be reset to actually read the zip in + +Auto-update information + +Path separators + +Casing + + separate server gamedir and client gamedir, so if the user starts + a local game after having connected to a network game, it won't stick + with the network game. + + allow menu options for game selection? + +Read / write config to floppy option. + +Different version coexistance? + +When building a pak file, make sure a autogen.cfg isn't present in it, +or configs will never get loaded from disk! + + todo: + + downloading (outside fs?) + game directory passing and restarting + +============================================================================= + +*/ + +// if this is defined, the executable positively won't work with any paks other +// than the demo pak, even if productid is present. This is only used for our +// last demo release to prevent the mac and linux users from using the demo +// executable with the production windows pak before the mac/linux products +// hit the shelves a little later +// NOW defined in build files +//#define PRE_RELEASE_TADEMO + +#define MAX_ZPATH 256 +#define MAX_SEARCH_PATHS 4096 +#define MAX_FILEHASH_SIZE 1024 + +typedef struct fileInPack_s { + char *name; // name of the file + unsigned long pos; // file info position in zip + unsigned long len; // uncompress file size + struct fileInPack_s* next; // next file in the hash +} fileInPack_t; + +typedef struct { + char pakFilename[MAX_OSPATH]; // /tremulous/base/pak0.pk3 + char pakBasename[MAX_OSPATH]; // pak0 + char pakGamename[MAX_OSPATH]; // base + unzFile handle; // handle to zip file + int checksum; // regular checksum + int pure_checksum; // checksum for pure + int numfiles; // number of files in pk3 + int referenced; // referenced file flags + int hashSize; // hash table size (power of 2) + fileInPack_t* *hashTable; // hash table + fileInPack_t* buildBuffer; // buffer with the filenames etc. +} pack_t; + +typedef struct { + char path[MAX_OSPATH]; + char gamedir[MAX_OSPATH]; // base +} directory_t; + +typedef struct searchpath_s { + struct searchpath_s *next; + + pack_t *pack; // only one of pack / dir will be non NULL + directory_t *dir; +} searchpath_t; + +static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators +static cvar_t *fs_debug; +static cvar_t *fs_homepath; + +#ifdef MACOS_X +// Also search the .app bundle for .pk3 files +static cvar_t *fs_apppath; +#endif + +static cvar_t *fs_basepath; +static cvar_t *fs_basegame; +static cvar_t *fs_gamedirvar; +static searchpath_t *fs_searchpaths; +static int fs_readCount; // total bytes read +static int fs_loadCount; // total files read +static int fs_loadStack; // total files in memory +static int fs_packFiles = 0; // total number of files in packs + +static int fs_checksumFeed; + +typedef union qfile_gus { + FILE* o; + unzFile z; +} qfile_gut; + +typedef struct qfile_us { + qfile_gut file; + qboolean unique; +} qfile_ut; + +typedef struct { + qfile_ut handleFiles; + qboolean handleSync; + int baseOffset; + int fileSize; + int zipFilePos; + qboolean zipFile; + qboolean streamed; + char name[MAX_ZPATH]; +} fileHandleData_t; + +static fileHandleData_t fsh[MAX_FILE_HANDLES]; + +// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +// wether we did a reorder on the current search path when joining the server +static qboolean fs_reordered; + +// never load anything from pk3 files that are not present at the server when pure +static int fs_numServerPaks = 0; +static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums +static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// only used for autodownload, to make sure the client has at least +// all the pk3 files that are referenced at the server side +static int fs_numServerReferencedPaks; +static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums +static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// last valid game folder used +char lastValidBase[MAX_OSPATH]; +char lastValidGame[MAX_OSPATH]; + +#ifdef FS_MISSING +FILE* missingFiles = NULL; +#endif + +/* C99 defines __func__ */ +#ifndef __func__ +#define __func__ "(unknown)" +#endif + +/* +============== +FS_Initialized +============== +*/ + +qboolean FS_Initialized( void ) { + return (fs_searchpaths != NULL); +} + +/* +================= +FS_PakIsPure +================= +*/ +qboolean FS_PakIsPure( pack_t *pack ) { + int i; + + if ( fs_numServerPaks ) { + for ( i = 0 ; i < fs_numServerPaks ; i++ ) { + // FIXME: also use hashed file names + // NOTE TTimo: a pk3 with same checksum but different name would be validated too + // I don't see this as allowing for any exploit, it would only happen if the client does manips of its file names 'not a bug' + if ( pack->checksum == fs_serverPaks[i] ) { + return qtrue; // on the aproved list + } + } + return qfalse; // not on the pure server pak list + } + return qtrue; +} + + +/* +================= +FS_LoadStack +return load stack +================= +*/ +int FS_LoadStack( void ) +{ + return fs_loadStack; +} + +/* +================ +return a hash value for the filename +================ +*/ +static long FS_HashFileName( const char *fname, int hashSize ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (hashSize-1); + return hash; +} + +static fileHandle_t FS_HandleForFile(void) { + int i; + + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { + if ( fsh[i].handleFiles.file.o == NULL ) { + return i; + } + } + Com_Error( ERR_DROP, "FS_HandleForFile: none free" ); + return 0; +} + +static FILE *FS_FileForHandle( fileHandle_t f ) { + if ( f < 0 || f > MAX_FILE_HANDLES ) { + Com_Error( ERR_DROP, "FS_FileForHandle: out of range" ); + } + if (fsh[f].zipFile == qtrue) { + Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" ); + } + if ( ! fsh[f].handleFiles.file.o ) { + Com_Error( ERR_DROP, "FS_FileForHandle: NULL" ); + } + + return fsh[f].handleFiles.file.o; +} + +void FS_ForceFlush( fileHandle_t f ) { + FILE *file; + + file = FS_FileForHandle(f); + setvbuf( file, NULL, _IONBF, 0 ); +} + +/* +================ +FS_filelength + +If this is called on a non-unique FILE (from a pak file), +it will return the size of the pak file, not the expected +size of the file. +================ +*/ +int FS_filelength( fileHandle_t f ) { + int pos; + int end; + FILE* h; + + h = FS_FileForHandle(f); + pos = ftell (h); + fseek (h, 0, SEEK_END); + end = ftell (h); + fseek (h, pos, SEEK_SET); + + return end; +} + +/* +==================== +FS_ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +static void FS_ReplaceSeparators( char *path ) { + char *s; + qboolean lastCharWasSep = qfalse; + + for ( s = path ; *s ; s++ ) { + if ( *s == '/' || *s == '\\' ) { + if ( !lastCharWasSep ) { + *s = PATH_SEP; + lastCharWasSep = qtrue; + } else { + memmove (s, s + 1, strlen (s)); + } + } else { + lastCharWasSep = qfalse; + } + } +} + +/* +=================== +FS_BuildOSPath + +Qpath may have either forward or backwards slashes +=================== +*/ +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) { + char temp[MAX_OSPATH]; + static char ospath[2][MAX_OSPATH]; + static int toggle; + + toggle ^= 1; // flip-flop to allow two returns without clash + + if( !game || !game[0] ) { + game = fs_gamedir; + } + + Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath ); + FS_ReplaceSeparators( temp ); + Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp ); + + return ospath[toggle]; +} + + +/* +============ +FS_CreatePath + +Creates any directories needed to store the given filename +============ +*/ +qboolean FS_CreatePath (char *OSPath) { + char *ofs; + char path[MAX_OSPATH]; + + // make absolutely sure that it can't back up the path + // FIXME: is c: allowed??? + if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { + Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath ); + return qtrue; + } + + Q_strncpyz( path, OSPath, sizeof( path ) ); + FS_ReplaceSeparators( path ); + + // Skip creation of the root directory as it will always be there + ofs = strchr( path, PATH_SEP ); + ofs++; + + for (; ofs != NULL && *ofs ; ofs++) { + if (*ofs == PATH_SEP) { + // create the directory + *ofs = 0; + if (!Sys_Mkdir (path)) { + Com_Error( ERR_FATAL, "FS_CreatePath: failed to create path \"%s\"\n", + path ); + } + *ofs = PATH_SEP; + } + } + + return qfalse; +} + +/* +================= +FS_CheckFilenameIsNotExecutable + +ERR_FATAL if trying to maniuplate a file with the platform library extension +================= + */ +static void FS_CheckFilenameIsNotExecutable( const char *filename, + const char *function ) +{ + // Check if the filename ends with the library extension + if( !Q_stricmp( COM_GetExtension( filename ), DLL_EXT ) ) + { + Com_Error( ERR_FATAL, "%s: Not allowed to manipulate '%s' due " + "to %s extension\n", function, filename, DLL_EXT ); + } +} + +/* +=========== +FS_Remove + +=========== +*/ +void FS_Remove( const char *osPath ) { + FS_CheckFilenameIsNotExecutable( osPath, __func__ ); + + remove( osPath ); +} + +/* +=========== +FS_HomeRemove + +=========== +*/ +void FS_HomeRemove( const char *homePath ) { + FS_CheckFilenameIsNotExecutable( homePath, __func__ ); + + remove( FS_BuildOSPath( fs_homepath->string, + fs_gamedir, homePath ) ); +} + +/* +================ +FS_FileExists + +Tests if the file exists in the current gamedir, this DOES NOT +search the paths. This is to determine if opening a file to write +(which always goes into the current gamedir) will cause any overwrites. +NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards +================ +*/ +qboolean FS_FileExists( const char *file ) +{ + FILE *f; + char *testpath; + + testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file ); + + f = fopen( testpath, "rb" ); + if (f) { + fclose( f ); + return qtrue; + } + return qfalse; +} + +/* +================ +FS_SV_FileExists + +Tests if the file exists +================ +*/ +qboolean FS_SV_FileExists( const char *file ) +{ + FILE *f; + char *testpath; + + testpath = FS_BuildOSPath( fs_homepath->string, file, ""); + testpath[strlen(testpath)-1] = '\0'; + + f = fopen( testpath, "rb" ); + if (f) { + fclose( f ); + return qtrue; + } + return qfalse; +} + + +/* +=========== +FS_SV_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); + ospath[strlen(ospath)-1] = '\0'; + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath ); + } + + FS_CheckFilenameIsNotExecutable( ospath, __func__ ); + + if( FS_CreatePath( ospath ) ) { + return 0; + } + + Com_DPrintf( "writing to: %s\n", ospath ); + fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + return f; +} + +/* +=========== +FS_SV_FOpenFileRead + +Search for a file somewhere below the home path then base path +in that order +=========== +*/ +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) { + char *ospath; + fileHandle_t f = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + + // search homepath + ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); + // remove trailing slash + ospath[strlen(ospath)-1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) + { + // If fs_homepath == fs_basepath, don't bother + if (Q_stricmp(fs_homepath->string,fs_basepath->string)) + { + // search basepath + ospath = FS_BuildOSPath( fs_basepath->string, filename, "" ); + ospath[strlen(ospath)-1] = '\0'; + + if ( fs_debug->integer ) + { + Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + } + + if ( !fsh[f].handleFiles.file.o ) + { + f = 0; + } + } + + *fp = f; + if (f) { + return FS_filelength(f); + } + + return -1; +} + + +/* +=========== +FS_SV_Rename + +=========== +*/ +void FS_SV_Rename( const char *from, const char *to ) { + char *from_ospath, *to_ospath; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + // don't let sound stutter + S_ClearSoundBuffer(); + + from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" ); + to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" ); + from_ospath[strlen(from_ospath)-1] = '\0'; + to_ospath[strlen(to_ospath)-1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath ); + } + + FS_CheckFilenameIsNotExecutable( to_ospath, __func__ ); + + rename(from_ospath, to_ospath); +} + + + +/* +=========== +FS_Rename + +=========== +*/ +void FS_Rename( const char *from, const char *to ) { + char *from_ospath, *to_ospath; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + // don't let sound stutter + S_ClearSoundBuffer(); + + from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from ); + to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath ); + } + + FS_CheckFilenameIsNotExecutable( to_ospath, __func__ ); + + rename(from_ospath, to_ospath); +} + +/* +============== +FS_FCloseFile + +If the FILE pointer is an open pak file, leave it open. + +For some reason, other dll's can't just cal fclose() +on files returned by FS_FOpenFile... +============== +*/ +void FS_FCloseFile( fileHandle_t f ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if (fsh[f].zipFile == qtrue) { + unzCloseCurrentFile( fsh[f].handleFiles.file.z ); + if ( fsh[f].handleFiles.unique ) { + unzClose( fsh[f].handleFiles.file.z ); + } + Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); + return; + } + + // we didn't find it as a pak, so close it as a unique file + if (fsh[f].handleFiles.file.o) { + fclose (fsh[f].handleFiles.file.o); + } + Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); +} + +/* +=========== +FS_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_FOpenFileWrite( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileWrite: %s\n", ospath ); + } + + FS_CheckFilenameIsNotExecutable( ospath, __func__ ); + + if( FS_CreatePath( ospath ) ) { + return 0; + } + + // enabling the following line causes a recursive function call loop + // when running with +set logfile 1 +set developer 1 + //Com_DPrintf( "writing to: %s\n", ospath ); + fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + return f; +} + +/* +=========== +FS_FOpenFileAppend + +=========== +*/ +fileHandle_t FS_FOpenFileAppend( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + + ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileAppend: %s\n", ospath ); + } + + FS_CheckFilenameIsNotExecutable( ospath, __func__ ); + + if( FS_CreatePath( ospath ) ) { + return 0; + } + + fsh[f].handleFiles.file.o = fopen( ospath, "ab" ); + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + return f; +} + +/* +=========== +FS_FCreateOpenPipeFile + +=========== +*/ +fileHandle_t FS_FCreateOpenPipeFile( const char *filename ) { + char *ospath; + FILE *fifo; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + + ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_FCreateOpenPipeFile: %s\n", ospath ); + } + + FS_CheckFilenameIsNotExecutable( ospath, __func__ ); + + fifo = Sys_Mkfifo( ospath ); + if( fifo ) { + fsh[f].handleFiles.file.o = fifo; + fsh[f].handleSync = qfalse; + } + else + { + Com_Printf( S_COLOR_YELLOW "WARNING: Could not create new com_pipefile at %s. " + "com_pipefile will not be used.\n", ospath ); + f = 0; + } + + return f; +} + +/* +=========== +FS_FilenameCompare + +Ignore case and seprator char distinctions +=========== +*/ +qboolean FS_FilenameCompare( const char *s1, const char *s2 ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if (c1 >= 'a' && c1 <= 'z') { + c1 -= ('a' - 'A'); + } + if (c2 >= 'a' && c2 <= 'z') { + c2 -= ('a' - 'A'); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if (c1 != c2) { + return qtrue; // strings not equal + } + } while (c1); + + return qfalse; // strings are equal +} + +/* +=========== +FS_IsExt + +Return qtrue if ext matches file extension filename +=========== +*/ + +qboolean FS_IsExt(const char *filename, const char *ext, int namelen) +{ + int extlen; + + extlen = strlen(ext); + + if(extlen > namelen) + return qfalse; + + filename += namelen - extlen; + + return !Q_stricmp(filename, ext); +} + +/* +=========== +FS_IsDemoExt + +Return qtrue if filename has a demo extension +=========== +*/ + +qboolean FS_IsDemoExt(const char *filename, int namelen) +{ + char *ext_test; + int index, protocol; + + ext_test = Q_strrchr(filename, '.'); + if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1)) + { + protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT)); + + if(protocol == PROTOCOL_VERSION) + return qtrue; + + for(index = 0; demo_protocols[index]; index++) + { + if(demo_protocols[index] == protocol) + return qtrue; + } + } + + return qfalse; +} + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +extern qboolean com_fullyInitialized; + +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) { + searchpath_t *search; + char *netpath; + pack_t *pak; + fileInPack_t *pakFile; + directory_t *dir; + long hash; + FILE *temp; + int l; + + hash = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( file == NULL ) { + // just wants to see if file is there + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if ( search->pack ) { + hash = FS_HashFileName(filename, search->pack->hashSize); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + // found it! + return qtrue; + } + pakFile = pakFile->next; + } while(pakFile != NULL); + } else if ( search->dir ) { + dir = search->dir; + + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); + temp = fopen (netpath, "rb"); + if ( !temp ) { + continue; + } + fclose(temp); + return qtrue; + } + } + return qfalse; + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { + *file = 0; + return -1; + } + + // + // search through the path, one element at a time + // + + *file = FS_HandleForFile(); + fsh[*file].handleFiles.unique = uniqueFILE; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if ( search->pack ) { + hash = FS_HashFileName(filename, search->pack->hashSize); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // disregard if it doesn't match one of the allowed pure pak files + if ( !FS_PakIsPure(search->pack) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + // found it! + + // mark the pak as having been referenced and mark specifics on cgame and ui + // shaders, txt, arena files by themselves do not count as a reference as + // these are loaded from all pk3s + // from every pk3 file.. + l = strlen(filename); + + if (!(pak->referenced & FS_GENERAL_REF)) + { + if(!FS_IsExt(filename, ".shader", l) && + !FS_IsExt(filename, ".txt", l) && + !FS_IsExt(filename, ".cfg", l) && + !FS_IsExt(filename, ".config", l) && + !FS_IsExt(filename, ".bot", l) && + !FS_IsExt(filename, ".arena", l) && + !FS_IsExt(filename, ".menu", l) && + !strstr(filename, "levelshots")) + { + pak->referenced |= FS_GENERAL_REF; + } + } + + if (!(pak->referenced & FS_QAGAME_REF) && strstr(filename, "game.qvm")) { + pak->referenced |= FS_QAGAME_REF; + } + if (!(pak->referenced & FS_CGAME_REF) && strstr(filename, "cgame.qvm")) { + pak->referenced |= FS_CGAME_REF; + } + if (!(pak->referenced & FS_UI_REF) && strstr(filename, "ui.qvm")) { + pak->referenced |= FS_UI_REF; + } + + if ( uniqueFILE ) { + // open a new file on the pakfile + fsh[*file].handleFiles.file.z = unzOpen (pak->pakFilename); + if (fsh[*file].handleFiles.file.z == NULL) { + Com_Error (ERR_FATAL, "Couldn't open %s", pak->pakFilename); + } + } else { + fsh[*file].handleFiles.file.z = pak->handle; + } + Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); + fsh[*file].zipFile = qtrue; + // set the file position in the zip file (also sets the current file info) + unzSetOffset(fsh[*file].handleFiles.file.z, pakFile->pos); + // open the file in the zip + unzOpenCurrentFile( fsh[*file].handleFiles.file.z ); + fsh[*file].zipFilePos = pakFile->pos; + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", + filename, pak->pakFilename ); + } + return pakFile->len; + } + pakFile = pakFile->next; + } while(pakFile != NULL); + } else if ( search->dir ) { + // check a file in the directory tree + + // if we are running restricted, the only files we + // will allow to come from the directory are .cfg files + l = strlen( filename ); + // FIXME TTimo I'm not sure about the fs_numServerPaks test + // if you are using FS_ReadFile to find out if a file exists, + // this test can make the search fail although the file is in the directory + // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8 + // turned out I used FS_FileExists instead + if(fs_numServerPaks) + { + if(!FS_IsExt(filename, ".cfg", l) && // for config files + !FS_IsExt(filename, ".menu", l) && // menu files + !FS_IsExt(filename, ".game", l) && // menu files + !FS_IsExt(filename, ".cfg", l) && // for journal files + !FS_IsDemoExt(filename, l)) // demos + { + continue; + } + } + + dir = search->dir; + + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); + fsh[*file].handleFiles.file.o = fopen (netpath, "rb"); + if ( !fsh[*file].handleFiles.file.o ) { + continue; + } + + Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); + fsh[*file].zipFile = qfalse; + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename, + dir->path, dir->gamedir ); + } + + return FS_filelength (*file); + } + } + +#ifdef FS_MISSING + if (missingFiles) { + fprintf(missingFiles, "%s\n", filename); + } +#endif + *file = 0; + return -1; +} + + +char *FS_FindDll( const char *filename ) { + searchpath_t *search; + directory_t *dir; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + for ( search = fs_searchpaths ; search ; search = search->next ) { + if ( search->dir ) { + FILE *f; + char *netpath; + + dir = search->dir; + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); + f = fopen( netpath, "rb" ); + if (f) { + fclose( f ); + return netpath; + } + } + } + + return NULL; +} + +/* +================= +FS_Read + +Properly handles partial reads +================= +*/ +int FS_Read2( void *buffer, int len, fileHandle_t f ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !f ) { + return 0; + } + if (fsh[f].streamed) { + int r; + fsh[f].streamed = qfalse; + r = FS_Read( buffer, len, f ); + fsh[f].streamed = qtrue; + return r; + } else { + return FS_Read( buffer, len, f); + } +} + +int FS_Read( void *buffer, int len, fileHandle_t f ) { + int block, remaining; + int read; + byte *buf; + int tries; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !f ) { + return 0; + } + + buf = (byte *)buffer; + fs_readCount += len; + + if (fsh[f].zipFile == qfalse) { + remaining = len; + tries = 0; + while (remaining) { + block = remaining; + read = fread (buf, 1, block, fsh[f].handleFiles.file.o); + if (read == 0) { + // we might have been trying to read from a CD, which + // sometimes returns a 0 read on windows + if (!tries) { + tries = 1; + } else { + return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read"); + } + } + + if (read == -1) { + Com_Error (ERR_FATAL, "FS_Read: -1 bytes read"); + } + + remaining -= read; + buf += read; + } + return len; + } else { + return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len); + } +} + +/* +================= +FS_Write + +Properly handles partial writes +================= +*/ +int FS_Write( const void *buffer, int len, fileHandle_t h ) { + int block, remaining; + int written; + byte *buf; + int tries; + FILE *f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !h ) { + return 0; + } + + f = FS_FileForHandle(h); + buf = (byte *)buffer; + + remaining = len; + tries = 0; + while (remaining) { + block = remaining; + written = fwrite (buf, 1, block, f); + if (written == 0) { + if (!tries) { + tries = 1; + } else { + Com_Printf( "FS_Write: 0 bytes written\n" ); + return 0; + } + } + + if (written == -1) { + Com_Printf( "FS_Write: -1 bytes written\n" ); + return 0; + } + + remaining -= written; + buf += written; + } + if ( fsh[h].handleSync ) { + fflush( f ); + } + return len; +} + +void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + FS_Write(msg, strlen(msg), h); +} + +#define PK3_SEEK_BUFFER_SIZE 65536 + +/* +================= +FS_Seek + +================= +*/ +int FS_Seek( fileHandle_t f, long offset, int origin ) { + int _origin; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + return -1; + } + + if (fsh[f].streamed) { + fsh[f].streamed = qfalse; + FS_Seek( f, offset, origin ); + fsh[f].streamed = qtrue; + } + + if (fsh[f].zipFile == qtrue) { + //FIXME: this is incomplete and really, really + //crappy (but better than what was here before) + byte buffer[PK3_SEEK_BUFFER_SIZE]; + int remainder = offset; + + if( offset < 0 || origin == FS_SEEK_END ) { + Com_Error( ERR_FATAL, "Negative offsets and FS_SEEK_END not implemented " + "for FS_Seek on pk3 file contents\n" ); + return -1; + } + + switch( origin ) { + case FS_SEEK_SET: + unzSetOffset(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); + unzOpenCurrentFile(fsh[f].handleFiles.file.z); + //fallthrough + + case FS_SEEK_CUR: + while( remainder > PK3_SEEK_BUFFER_SIZE ) { + FS_Read( buffer, PK3_SEEK_BUFFER_SIZE, f ); + remainder -= PK3_SEEK_BUFFER_SIZE; + } + FS_Read( buffer, remainder, f ); + return offset; + break; + + default: + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + return -1; + break; + } + } else { + FILE *file; + file = FS_FileForHandle(f); + switch( origin ) { + case FS_SEEK_CUR: + _origin = SEEK_CUR; + break; + case FS_SEEK_END: + _origin = SEEK_END; + break; + case FS_SEEK_SET: + _origin = SEEK_SET; + break; + default: + _origin = SEEK_CUR; + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + break; + } + + return fseek( file, offset, _origin ); + } +} + + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +int FS_FileIsInPAK(const char *filename, int *pChecksum ) { + searchpath_t *search; + pack_t *pak; + fileInPack_t *pakFile; + long hash = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { + return -1; + } + + // + // search through the path, one element at a time + // + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if (search->pack) { + hash = FS_HashFileName(filename, search->pack->hashSize); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // disregard if it doesn't match one of the allowed pure pak files + if ( !FS_PakIsPure(search->pack) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + if (pChecksum) { + *pChecksum = pak->pure_checksum; + } + return 1; + } + pakFile = pakFile->next; + } while(pakFile != NULL); + } + } + return -1; +} + +/* +============ +FS_ReadFile + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +int FS_ReadFile( const char *qpath, void **buffer ) { + fileHandle_t h; + byte* buf; + qboolean isConfig; + int len; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !qpath || !qpath[0] ) { + Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" ); + } + + buf = NULL; // quiet compiler warning + + // if this is a .cfg file and we are playing back a journal, read + // it from the journal file + if ( strstr( qpath, ".cfg" ) ) { + isConfig = qtrue; + if ( com_journal && com_journal->integer == 2 ) { + int r; + + Com_DPrintf( "Loading %s from journal file.\n", qpath ); + r = FS_Read( &len, sizeof( len ), com_journalDataFile ); + if ( r != sizeof( len ) ) { + if (buffer != NULL) *buffer = NULL; + return -1; + } + // if the file didn't exist when the journal was created + if (!len) { + if (buffer == NULL) { + return 1; // hack for old journal files + } + *buffer = NULL; + return -1; + } + if (buffer == NULL) { + return len; + } + + buf = Hunk_AllocateTempMemory(len+1); + *buffer = buf; + + r = FS_Read( buf, len, com_journalDataFile ); + if ( r != len ) { + Com_Error( ERR_FATAL, "Read from journalDataFile failed" ); + } + + fs_loadCount++; + fs_loadStack++; + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + + return len; + } + } else { + isConfig = qfalse; + } + + // look for it in the filesystem or pack files + len = FS_FOpenFileRead( qpath, &h, qfalse ); + if ( h == 0 ) { + if ( buffer ) { + *buffer = NULL; + } + // if we are journalling and it is a config file, write a zero to the journal file + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing zero for %s to journal file.\n", qpath ); + len = 0; + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + return -1; + } + + if ( !buffer ) { + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing len for %s to journal file.\n", qpath ); + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + FS_FCloseFile( h); + return len; + } + + fs_loadCount++; + fs_loadStack++; + + buf = Hunk_AllocateTempMemory(len+1); + *buffer = buf; + + FS_Read (buf, len, h); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + FS_FCloseFile( h ); + + // if we are journalling and it is a config file, write it to the journal file + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing %s to journal file.\n", qpath ); + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Write( buf, len, com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + return len; +} + +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile( void *buffer ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + if ( !buffer ) { + Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); + } + fs_loadStack--; + + Hunk_FreeTempMemory( buffer ); + + // if all of our temp files are free, clear all of our space + if ( fs_loadStack == 0 ) { + Hunk_ClearTempMemory(); + } +} + +/* +============ +FS_WriteFile + +Filename are reletive to the quake search path +============ +*/ +void FS_WriteFile( const char *qpath, const void *buffer, int size ) { + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !qpath || !buffer ) { + Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" ); + } + + f = FS_FOpenFileWrite( qpath ); + if ( !f ) { + Com_Printf( "Failed to open %s\n", qpath ); + return; + } + + FS_Write( buffer, size, f ); + + FS_FCloseFile( f ); +} + + + +/* +========================================================================== + +ZIP FILE LOADING + +========================================================================== +*/ + +/* +================= +FS_LoadZipFile + +Creates a new pak_t in the search chain for the contents +of a zip file. +================= +*/ +static pack_t *FS_LoadZipFile(const char *zipfile, const char *basename) +{ + fileInPack_t *buildBuffer; + pack_t *pack; + unzFile uf; + int err; + unz_global_info gi; + char filename_inzip[MAX_ZPATH]; + unz_file_info file_info; + int i, len; + long hash; + int fs_numHeaderLongs; + int *fs_headerLongs; + char *namePtr; + + fs_numHeaderLongs = 0; + + uf = unzOpen(zipfile); + err = unzGetGlobalInfo (uf,&gi); + + if (err != UNZ_OK) + return NULL; + + len = 0; + unzGoToFirstFile(uf); + for (i = 0; i < gi.number_entry; i++) + { + err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); + if (err != UNZ_OK) { + break; + } + len += strlen(filename_inzip) + 1; + unzGoToNextFile(uf); + } + + buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len ); + namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t ); + fs_headerLongs = Z_Malloc( ( gi.number_entry + 1 ) * sizeof(int) ); + fs_headerLongs[ fs_numHeaderLongs++ ] = LittleLong( fs_checksumFeed ); + + // get the hash table size from the number of files in the zip + // because lots of custom pk3 files have less than 32 or 64 files + for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) { + if (i > gi.number_entry) { + break; + } + } + + pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) ); + pack->hashSize = i; + pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t )); + for(i = 0; i < pack->hashSize; i++) { + pack->hashTable[i] = NULL; + } + + Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) ); + Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) ); + + // strip .pk3 if needed + if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) { + pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0; + } + + pack->handle = uf; + pack->numfiles = gi.number_entry; + unzGoToFirstFile(uf); + + for (i = 0; i < gi.number_entry; i++) + { + err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); + if (err != UNZ_OK) { + break; + } + if (file_info.uncompressed_size > 0) { + fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc); + } + Q_strlwr( filename_inzip ); + hash = FS_HashFileName(filename_inzip, pack->hashSize); + buildBuffer[i].name = namePtr; + strcpy( buildBuffer[i].name, filename_inzip ); + namePtr += strlen(filename_inzip) + 1; + // store the file position in the zip + buildBuffer[i].pos = unzGetOffset(uf); + buildBuffer[i].len = file_info.uncompressed_size; + buildBuffer[i].next = pack->hashTable[hash]; + pack->hashTable[hash] = &buildBuffer[i]; + unzGoToNextFile(uf); + } + + pack->checksum = Com_BlockChecksum( &fs_headerLongs[ 1 ], sizeof(*fs_headerLongs) * ( fs_numHeaderLongs - 1 ) ); + pack->pure_checksum = Com_BlockChecksum( fs_headerLongs, sizeof(*fs_headerLongs) * fs_numHeaderLongs ); + pack->checksum = LittleLong( pack->checksum ); + pack->pure_checksum = LittleLong( pack->pure_checksum ); + + Z_Free(fs_headerLongs); + + pack->buildBuffer = buildBuffer; + return pack; +} + +/* +================= +FS_FreePak + +Frees a pak structure and releases all associated resources +================= +*/ + +static void FS_FreePak(pack_t *thepak) +{ + unzClose(thepak->handle); + Z_Free(thepak->buildBuffer); + Z_Free(thepak); +} + +/* +================= +FS_GetZipChecksum + +Compares whether the given pak file matches a referenced checksum +================= +*/ +qboolean FS_CompareZipChecksum(const char *zipfile) +{ + pack_t *thepak; + int index, checksum; + + thepak = FS_LoadZipFile(zipfile, ""); + + if(!thepak) + return qfalse; + + checksum = thepak->checksum; + FS_FreePak(thepak); + + for(index = 0; index < fs_numServerReferencedPaks; index++) + { + if(checksum == fs_serverReferencedPaks[index]) + return qtrue; + } + + return qfalse; +} + +/* +================================================================================= + +DIRECTORY SCANNING FUNCTIONS + +================================================================================= +*/ + +#define MAX_FOUND_FILES 0x1000 + +static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) { + int len, at, newdep; + + newdep = 0; + zpath[0] = 0; + len = 0; + at = 0; + + while(zname[at] != 0) + { + if (zname[at]=='/' || zname[at]=='\\') { + len = at; + newdep++; + } + at++; + } + strcpy(zpath, zname); + zpath[len] = 0; + *depth = newdep; + + return len; +} + +/* +================== +FS_AddFileToList +================== +*/ +static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) { + int i; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + for ( i = 0 ; i < nfiles ; i++ ) { + if ( !Q_stricmp( name, list[i] ) ) { + return nfiles; // allready in list + } + } + list[nfiles] = CopyString( name ); + nfiles++; + + return nfiles; +} + +/* +=============== +FS_ListFilteredFiles + +Returns a uniqued list of files that match the given criteria +from all search paths +=============== +*/ +char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles, qboolean allowNonPureFilesOnDisk ) { + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + searchpath_t *search; + int i; + int pathLength; + int extensionLength; + int length, pathDepth, temp; + pack_t *pak; + fileInPack_t *buildBuffer; + char zpath[MAX_ZPATH]; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !path ) { + *numfiles = 0; + return NULL; + } + if ( !extension ) { + extension = ""; + } + + pathLength = strlen( path ); + if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) { + pathLength--; + } + extensionLength = strlen( extension ); + nfiles = 0; + FS_ReturnPath(path, zpath, &pathDepth); + + // + // search through the path, one element at a time, adding to list + // + for (search = fs_searchpaths ; search ; search = search->next) { + // is the element a pak file? + if (search->pack) { + + //ZOID: If we are pure, don't search for files on paks that + // aren't on the pure list + if ( !FS_PakIsPure(search->pack) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + buildBuffer = pak->buildBuffer; + for (i = 0; i < pak->numfiles; i++) { + char *name; + int zpathLen, depth; + + // check for directory match + name = buildBuffer[i].name; + // + if (filter) { + // case insensitive + if (!Com_FilterPath( filter, name, qfalse )) + continue; + // unique the match + nfiles = FS_AddFileToList( name, list, nfiles ); + } + else { + + zpathLen = FS_ReturnPath(name, zpath, &depth); + + if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) { + continue; + } + + // check for extension match + length = strlen( name ); + if ( length < extensionLength ) { + continue; + } + + if ( Q_stricmp( name + length - extensionLength, extension ) ) { + continue; + } + // unique the match + + temp = pathLength; + if (pathLength) { + temp++; // include the '/' + } + nfiles = FS_AddFileToList( name + temp, list, nfiles ); + } + } + } else if (search->dir) { // scan for files in the filesystem + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + + // don't scan directories for files if we are pure or restricted + if ( fs_numServerPaks && !allowNonPureFilesOnDisk ) { + continue; + } else { + netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path ); + sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse ); + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToList( name, list, nfiles ); + } + Sys_FreeFileList( sysFiles ); + } + } + } + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + +/* +================= +FS_ListFiles +================= +*/ +char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) { + return FS_ListFilteredFiles( path, extension, NULL, numfiles, qfalse ); +} + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList( char **list ) { + int i; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !list ) { + return; + } + + for ( i = 0 ; list[i] ; i++ ) { + Z_Free( list[i] ); + } + + Z_Free( list ); +} + + +/* +================ +FS_GetFileList +================ +*/ +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + int nFiles, i, nTotal, nLen; + char **pFiles = NULL; + + *listbuf = 0; + nFiles = 0; + nTotal = 0; + + if (Q_stricmp(path, "$modlist") == 0) { + return FS_GetModList(listbuf, bufsize); + } + + pFiles = FS_ListFiles(path, extension, &nFiles); + + for (i =0; i < nFiles; i++) { + nLen = strlen(pFiles[i]) + 1; + if (nTotal + nLen + 1 < bufsize) { + strcpy(listbuf, pFiles[i]); + listbuf += nLen; + nTotal += nLen; + } + else { + nFiles = i; + break; + } + } + + FS_FreeFileList(pFiles); + + return nFiles; +} + +/* +======================= +Sys_ConcatenateFileLists + +mkv: Naive implementation. Concatenates three lists into a + new list, and frees the old lists from the heap. +bk001129 - from cvs1.17 (mkv) + +FIXME TTimo those two should move to common.c next to Sys_ListFiles +======================= + */ +static unsigned int Sys_CountFileList(char **list) +{ + int i = 0; + + if (list) + { + while (*list) + { + list++; + i++; + } + } + return i; +} + +static char** Sys_ConcatenateFileLists( char **list0, char **list1 ) +{ + int totalLength = 0; + char** cat = NULL, **dst, **src; + + totalLength += Sys_CountFileList(list0); + totalLength += Sys_CountFileList(list1); + + /* Create new list. */ + dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) ); + + /* Copy over lists. */ + if (list0) + { + for (src = list0; *src; src++, dst++) + *dst = *src; + } + if (list1) + { + for (src = list1; *src; src++, dst++) + *dst = *src; + } + + // Terminate the list + *dst = NULL; + + // Free our old lists. + // NOTE: not freeing their content, it's been merged in dst and still being used + if (list0) Z_Free( list0 ); + if (list1) Z_Free( list1 ); + + return cat; +} + +/* +================ +FS_GetModList + +Returns a list of mod directory names +A mod directory is a peer to base with a pk3 in it +The directories are searched in base path, cd path and home path +================ +*/ +int FS_GetModList( char *listbuf, int bufsize ) { + int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen; + char **pFiles = NULL; + char **pPaks = NULL; + char *name, *path; + char descPath[MAX_OSPATH]; + fileHandle_t descHandle; + + int dummy; + char **pFiles0 = NULL; + char **pFiles1 = NULL; + qboolean bDrop = qfalse; + + *listbuf = 0; + nMods = nPotential = nTotal = 0; + + pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue ); + pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue ); + // we searched for mods in the three paths + // it is likely that we have duplicate names now, which we will cleanup below + pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1 ); + nPotential = Sys_CountFileList(pFiles); + + for ( i = 0 ; i < nPotential ; i++ ) { + name = pFiles[i]; + // NOTE: cleaner would involve more changes + // ignore duplicate mod directories + if (i!=0) { + bDrop = qfalse; + for(j=0; jstring, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); + Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present + + /* try on home path */ + if ( nPaks <= 0 ) + { + path = FS_BuildOSPath( fs_homepath->string, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); + Sys_FreeFileList( pPaks ); + } + + if (nPaks > 0) { + nLen = strlen(name) + 1; + // nLen is the length of the mod path + // we need to see if there is a description available + descPath[0] = '\0'; + strcpy(descPath, name); + strcat(descPath, "/description.txt"); + nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle ); + if ( nDescLen > 0 && descHandle) { + FILE *file; + file = FS_FileForHandle(descHandle); + Com_Memset( descPath, 0, sizeof( descPath ) ); + nDescLen = fread(descPath, 1, 48, file); + if (nDescLen >= 0) { + descPath[nDescLen] = '\0'; + } + FS_FCloseFile(descHandle); + } else { + strcpy(descPath, name); + } + nDescLen = strlen(descPath) + 1; + + if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) { + strcpy(listbuf, name); + listbuf += nLen; + strcpy(listbuf, descPath); + listbuf += nDescLen; + nTotal += nLen + nDescLen; + nMods++; + } + else { + break; + } + } + } + } + Sys_FreeFileList( pFiles ); + + return nMods; +} + + + + +//============================================================================ + +/* +================ +FS_Dir_f +================ +*/ +void FS_Dir_f( void ) { + char *path; + char *extension; + char **dirnames; + int ndirs; + int i; + + if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) { + Com_Printf( "usage: dir [extension]\n" ); + return; + } + + if ( Cmd_Argc() == 2 ) { + path = Cmd_Argv( 1 ); + extension = ""; + } else { + path = Cmd_Argv( 1 ); + extension = Cmd_Argv( 2 ); + } + + Com_Printf( "Directory of %s %s\n", path, extension ); + Com_Printf( "---------------\n" ); + + dirnames = FS_ListFiles( path, extension, &ndirs ); + + for ( i = 0; i < ndirs; i++ ) { + Com_Printf( "%s\n", dirnames[i] ); + } + FS_FreeFileList( dirnames ); +} + +/* +=========== +FS_ConvertPath +=========== +*/ +void FS_ConvertPath( char *s ) { + while (*s) { + if ( *s == '\\' || *s == ':' ) { + *s = '/'; + } + s++; + } +} + +/* +=========== +FS_PathCmp + +Ignore case and seprator char distinctions +=========== +*/ +int FS_PathCmp( const char *s1, const char *s2 ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if (c1 >= 'a' && c1 <= 'z') { + c1 -= ('a' - 'A'); + } + if (c2 >= 'a' && c2 <= 'z') { + c2 -= ('a' - 'A'); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if (c1 < c2) { + return -1; // strings not equal + } + if (c1 > c2) { + return 1; + } + } while (c1); + + return 0; // strings are equal +} + +/* +================ +FS_SortFileList +================ +*/ +void FS_SortFileList(char **filelist, int numfiles) { + int i, j, k, numsortedfiles; + char **sortedlist; + + sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) ); + sortedlist[0] = NULL; + numsortedfiles = 0; + for (i = 0; i < numfiles; i++) { + for (j = 0; j < numsortedfiles; j++) { + if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) { + break; + } + } + for (k = numsortedfiles; k > j; k--) { + sortedlist[k] = sortedlist[k-1]; + } + sortedlist[j] = filelist[i]; + numsortedfiles++; + } + Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) ); + Z_Free(sortedlist); +} + +/* +================ +FS_NewDir_f +================ +*/ +void FS_NewDir_f( void ) { + char *filter; + char **dirnames; + int ndirs; + int i; + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "usage: fdir \n" ); + Com_Printf( "example: fdir *q3dm*.bsp\n"); + return; + } + + filter = Cmd_Argv( 1 ); + + Com_Printf( "---------------\n" ); + + dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs, qfalse ); + + FS_SortFileList(dirnames, ndirs); + + for ( i = 0; i < ndirs; i++ ) { + FS_ConvertPath(dirnames[i]); + Com_Printf( "%s\n", dirnames[i] ); + } + Com_Printf( "%d files listed\n", ndirs ); + FS_FreeFileList( dirnames ); +} + +/* +============ +FS_Path_f + +============ +*/ +void FS_Path_f( void ) { + searchpath_t *s; + int i; + + Com_Printf ("Current search path:\n"); + for (s = fs_searchpaths; s; s = s->next) { + if (s->pack) { + Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles); + if ( fs_numServerPaks ) { + if ( !FS_PakIsPure(s->pack) ) { + Com_Printf( " not on the pure list\n" ); + } else { + Com_Printf( " on the pure list\n" ); + } + } + } else { + Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir ); + } + } + + + Com_Printf( "\n" ); + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { + if ( fsh[i].handleFiles.file.o ) { + Com_Printf( "handle %i: %s\n", i, fsh[i].name ); + } + } +} + +/* +============ +FS_TouchFile_f +============ +*/ +void FS_TouchFile_f( void ) { + fileHandle_t f; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: touchFile \n" ); + return; + } + + FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } +} + +/* +============ +FS_Which_f +============ +*/ +void FS_Which_f( void ) { + searchpath_t *search; + char *netpath; + pack_t *pak; + fileInPack_t *pakFile; + directory_t *dir; + long hash; + FILE *temp; + char *filename; + char buf[ MAX_OSPATH ]; + + hash = 0; + filename = Cmd_Argv(1); + + if ( !filename[0] ) { + Com_Printf( "Usage: which \n" ); + return; + } + + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // just wants to see if file is there + for ( search = fs_searchpaths ; search ; search = search->next ) { + if ( search->pack ) { + hash = FS_HashFileName(filename, search->pack->hashSize); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + // found it! + Com_Printf( "File \"%s\" found in \"%s\"\n", filename, pak->pakFilename ); + return; + } + pakFile = pakFile->next; + } while(pakFile != NULL); + } else if ( search->dir ) { + dir = search->dir; + + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); + temp = fopen (netpath, "rb"); + if ( !temp ) { + continue; + } + fclose(temp); + Com_sprintf( buf, sizeof( buf ), "%s/%s", dir->path, dir->gamedir ); + FS_ReplaceSeparators( buf ); + Com_Printf( "File \"%s\" found at \"%s\"\n", filename, buf ); + return; + } + } + Com_Printf( "File not found: \"%s\"\n", filename ); + return; +} + + +//=========================================================================== + + +static int QDECL paksort( const void *a, const void *b ) { + char *aa, *bb; + + aa = *(char **)a; + bb = *(char **)b; + + return FS_PathCmp( aa, bb ); +} + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads the zip headers +================ +*/ +void FS_AddGameDirectory( const char *path, const char *dir ) { + searchpath_t *sp; + int i; + searchpath_t *search; + pack_t *pak; + char *pakfile; + int numfiles; + char **pakfiles; + + // Unique + for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { + if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) { + return; // we've already got this one + } + } + + Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) ); + + // + // add the directory to the search path + // + search = Z_Malloc (sizeof(searchpath_t)); + search->dir = Z_Malloc( sizeof( *search->dir ) ); + + Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) ); + Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) ); + search->next = fs_searchpaths; + fs_searchpaths = search; + + // find all pak files in this directory + pakfile = FS_BuildOSPath( path, dir, "" ); + pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash + + pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse ); + + qsort( pakfiles, numfiles, sizeof(char*), paksort ); + + for ( i = 0 ; i < numfiles ; i++ ) { + pakfile = FS_BuildOSPath( path, dir, pakfiles[i] ); + if ( ( pak = FS_LoadZipFile( pakfile, pakfiles[i] ) ) == 0 ) + continue; + // store the game name for downloading + strcpy(pak->pakGamename, dir); + + fs_packFiles += pak->numfiles; + + search = Z_Malloc (sizeof(searchpath_t)); + search->pack = pak; + search->next = fs_searchpaths; + fs_searchpaths = search; + } + + // done + Sys_FreeFileList( pakfiles ); +} + +/* +================ +FS_CheckDirTraversal + +Check whether the string contains stuff like "../" to prevent directory traversal bugs +and return qtrue if it does. +================ +*/ + +qboolean FS_CheckDirTraversal(const char *checkdir) +{ + if(strstr(checkdir, "../") || strstr(checkdir, "..\\")) + return qtrue; + + return qfalse; +} + +/* +================ +FS_ComparePaks + +---------------- +dlstring == qtrue + +Returns a list of pak files that we should download from the server. They all get stored +in the current gamedir and an FS_Restart will be fired up after we download them all. + +The string is the format: + +@remotename@localname [repeat] + +static int fs_numServerReferencedPaks; +static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; +static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; + +---------------- +dlstring == qfalse + +we are not interested in a download string format, we want something human-readable +(this is used for diagnostics while connecting to a pure server) + +================ +*/ +qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { + searchpath_t *sp; + qboolean havepak, badchecksum; + char *origpos = neededpaks; + int i; + + if (!fs_numServerReferencedPaks) + return qfalse; // Server didn't send any pack information along + + *neededpaks = 0; + + for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) + { + // Ok, see if we have this pak file + badchecksum = qfalse; + havepak = qfalse; + + // Make sure the server cannot make us write to non-quake3 directories. + if(FS_CheckDirTraversal(fs_serverReferencedPakNames[i])) + { + Com_Printf("WARNING: Invalid download name %s\n", fs_serverReferencedPakNames[i]); + continue; + } + + for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { + if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) { + havepak = qtrue; // This is it! + break; + } + } + + if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { + // Don't got it + + if (dlstring) + { + // We need this to make sure we won't hit the end of the buffer or the server could + // overwrite non-pk3 files on clients by writing so much crap into neededpaks that + // Q_strcat cuts off the .pk3 extension. + + origpos += strlen(origpos); + + // Remote name + Q_strcat( neededpaks, len, "@"); + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + + // Local name + Q_strcat( neededpaks, len, "@"); + // Do we have one with the same name? + if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) + { + char st[MAX_ZPATH]; + // We already have one called this, we need to download it to another name + // Make something up with the checksum in it + Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] ); + Q_strcat( neededpaks, len, st ); + } + else + { + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + } + + // Find out whether it might have overflowed the buffer and don't add this file to the + // list if that is the case. + if(strlen(origpos) + (origpos - neededpaks) >= len - 1) + { + *origpos = '\0'; + break; + } + } + else + { + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + // Do we have one with the same name? + if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) + { + Q_strcat( neededpaks, len, " (local file exists with wrong checksum)"); + } + Q_strcat( neededpaks, len, "\n"); + } + } + } + + if ( *neededpaks ) { + return qtrue; + } + + return qfalse; // We have them all +} + +/* +================ +FS_Shutdown + +Frees all resources. +================ +*/ +void FS_Shutdown( qboolean closemfp ) { + searchpath_t *p, *next; + int i; + + for(i = 0; i < MAX_FILE_HANDLES; i++) { + if (fsh[i].fileSize) { + FS_FCloseFile(i); + } + } + + // free everything + for(p = fs_searchpaths; p; p = next) + { + next = p->next; + + if(p->pack) + FS_FreePak(p->pack); + if (p->dir) + Z_Free(p->dir); + + Z_Free(p); + } + + // any FS_ calls will now be an error until reinitialized + fs_searchpaths = NULL; + + Cmd_RemoveCommand( "path" ); + Cmd_RemoveCommand( "dir" ); + Cmd_RemoveCommand( "fdir" ); + Cmd_RemoveCommand( "touchFile" ); + Cmd_RemoveCommand( "which" ); + +#ifdef FS_MISSING + if (closemfp) { + fclose(missingFiles); + } +#endif +} + +/* +================ +FS_ReorderPurePaks +NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*) + this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +================ +*/ +static void FS_ReorderPurePaks( void ) +{ + searchpath_t *s; + int i; + searchpath_t **p_insert_index, // for linked list reordering + **p_previous; // when doing the scan + + // only relevant when connected to pure server + if ( !fs_numServerPaks ) + return; + + fs_reordered = qfalse; + + p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list + for ( i = 0 ; i < fs_numServerPaks ; i++ ) { + p_previous = p_insert_index; // track the pointer-to-current-item + for (s = *p_insert_index; s; s = s->next) { + // the part of the list before p_insert_index has been sorted already + if (s->pack && fs_serverPaks[i] == s->pack->checksum) { + fs_reordered = qtrue; + // move this element to the insert list + *p_previous = s->next; + s->next = *p_insert_index; + *p_insert_index = s; + // increment insert list + p_insert_index = &s->next; + break; // iterate to next server pack + } + p_previous = &s->next; + } + } +} + +/* +================ +FS_Startup +================ +*/ +static void FS_Startup( const char *gameName ) +{ + const char *homePath; + + Com_Printf( "----- FS_Startup -----\n" ); + + fs_packFiles = 0; + + fs_debug = Cvar_Get( "fs_debug", "0", 0 ); + fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT ); + fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT ); + homePath = Sys_DefaultHomePath(); + if (!homePath || !homePath[0]) { + homePath = fs_basepath->string; + } + fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT ); + fs_gamedirvar = Cvar_Get ("fs_game", "gpp", CVAR_INIT|CVAR_SYSTEMINFO ); + + // add search path elements in reverse priority order + if (fs_basepath->string[0]) { + FS_AddGameDirectory( fs_basepath->string, gameName ); + } + // fs_homepath is somewhat particular to *nix systems, only add if relevant + + #ifdef MACOS_X + fs_apppath = Cvar_Get ("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT|CVAR_PROTECTED ); + // Make MacOSX also include the base path included with the .app bundle + if (fs_apppath->string[0]) + FS_AddGameDirectory(fs_apppath->string, gameName); + #endif + + // NOTE: same filtering below for mods and basegame + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { + FS_CreatePath ( fs_homepath->string ); + FS_AddGameDirectory ( fs_homepath->string, gameName ); + } + + // check for additional base game so mods can be based upon other mods + if ( fs_basegame->string[0] && Q_stricmp( fs_basegame->string, gameName ) ) { + if (fs_basepath->string[0]) { + FS_AddGameDirectory(fs_basepath->string, fs_basegame->string); + } + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { + FS_AddGameDirectory(fs_homepath->string, fs_basegame->string); + } + } + + // check for additional game folder for mods + if ( fs_gamedirvar->string[0] && Q_stricmp( fs_gamedirvar->string, gameName ) ) { + if (fs_basepath->string[0]) { + FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string); + } + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { + FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string); + } + } + + // add our commands + Cmd_AddCommand ("path", FS_Path_f); + Cmd_AddCommand ("dir", FS_Dir_f ); + Cmd_AddCommand ("fdir", FS_NewDir_f ); + Cmd_AddCommand ("touchFile", FS_TouchFile_f ); + Cmd_AddCommand ("which", FS_Which_f ); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506 + // reorder the pure pk3 files according to server order + FS_ReorderPurePaks(); + + // print the current search paths + FS_Path_f(); + + fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified + + Com_Printf( "----------------------\n" ); + +#ifdef FS_MISSING + if (missingFiles == NULL) { + missingFiles = fopen( "\\missing.txt", "ab" ); + } +#endif + Com_Printf( "%d files in pk3 files\n", fs_packFiles ); +} + +/* +===================== +FS_GamePureChecksum + +Returns the checksum of the pk3 from which the server loaded the game.qvm +===================== +*/ +const char *FS_GamePureChecksum( void ) { + static char info[MAX_STRING_TOKENS]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if (search->pack->referenced & FS_QAGAME_REF) { + Com_sprintf(info, sizeof(info), "%d", search->pack->checksum); + } + } + } + + return info; +} + +/* +===================== +FS_LoadedPakChecksums + +Returns a space separated string containing the checksums of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); + } + + return info; +} + +/* +===================== +FS_LoadedPakNames + +Returns a space separated string containing the names of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakNames( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + if (*info) { + Q_strcat(info, sizeof( info ), " " ); + } + Q_strcat( info, sizeof( info ), search->pack->pakBasename ); + } + + return info; +} + +/* +===================== +FS_LoadedPakPureChecksums + +Returns a space separated string containing the pure checksums of all loaded pk3 files. +Servers with sv_pure use these checksums to compare with the checksums the clients send +back to the server. +===================== +*/ +const char *FS_LoadedPakPureChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) ); + } + + return info; +} + +/* +===================== +FS_ReferencedPakChecksums + +Returns a space separated string containing the checksums of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. +===================== +*/ +const char *FS_ReferencedPakChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { + Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); + } + } + } + + return info; +} + +/* +===================== +FS_ReferencedPakPureChecksums + +Returns a space separated string containing the pure checksums of all referenced pk3 files. +Servers with sv_pure set will get this string back from clients for pure validation + +The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..." +===================== +*/ +const char *FS_ReferencedPakPureChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + int nFlags, numPaks, checksum; + + info[0] = 0; + + checksum = fs_checksumFeed; + numPaks = 0; + for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) { + if (nFlags & FS_GENERAL_REF) { + // add a delimter between must haves and general refs + //Q_strcat(info, sizeof(info), "@ "); + info[strlen(info)+1] = '\0'; + info[strlen(info)+2] = '\0'; + info[strlen(info)] = '@'; + info[strlen(info)] = ' '; + } + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file and has it been referenced based on flag? + if ( search->pack && (search->pack->referenced & nFlags)) { + Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) ); + if (nFlags & (FS_CGAME_REF | FS_UI_REF)) { + break; + } + checksum ^= search->pack->pure_checksum; + numPaks++; + } + } + } + // last checksum is the encoded number of referenced pk3s + checksum ^= numPaks; + Q_strcat( info, sizeof( info ), va("%i ", checksum ) ); + + return info; +} + +/* +===================== +FS_ReferencedPakNames + +Returns a space separated string containing the names of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. +===================== +*/ +const char *FS_ReferencedPakNames( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + // we want to return ALL pk3's from the fs_game path + // and referenced one's from base + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { + if (*info) { + Q_strcat(info, sizeof( info ), " " ); + } + Q_strcat( info, sizeof( info ), search->pack->pakGamename ); + Q_strcat( info, sizeof( info ), "/" ); + Q_strcat( info, sizeof( info ), search->pack->pakBasename ); + } + } + } + + return info; +} + +/* +===================== +FS_ClearPakReferences +===================== +*/ +void FS_ClearPakReferences( int flags ) { + searchpath_t *search; + + if ( !flags ) { + flags = -1; + } + for ( search = fs_searchpaths; search; search = search->next ) { + // is the element a pak file and has it been referenced? + if ( search->pack ) { + search->pack->referenced &= ~flags; + } + } +} + + +/* +===================== +FS_PureServerSetLoadedPaks + +If the string is empty, all data sources will be allowed. +If not empty, only pk3 files that match one of the space +separated checksums will be checked for files, with the +exception of .cfg and .dat files. +===================== +*/ +void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) { + int i, c, d; + + Cmd_TokenizeString( pakSums ); + + c = Cmd_Argc(); + if ( c > MAX_SEARCH_PATHS ) { + c = MAX_SEARCH_PATHS; + } + + fs_numServerPaks = c; + + for ( i = 0 ; i < c ; i++ ) { + fs_serverPaks[i] = atoi( Cmd_Argv( i ) ); + } + + if (fs_numServerPaks) { + Com_DPrintf( "Connected to a pure server.\n" ); + } + else + { + if (fs_reordered) + { + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 + // force a restart to make sure the search order will be correct + Com_DPrintf( "FS search reorder is required\n" ); + FS_Restart(fs_checksumFeed); + return; + } + } + + for ( i = 0 ; i < c ; i++ ) { + if (fs_serverPakNames[i]) { + Z_Free(fs_serverPakNames[i]); + } + fs_serverPakNames[i] = NULL; + } + if ( pakNames && *pakNames ) { + Cmd_TokenizeString( pakNames ); + + d = Cmd_Argc(); + if ( d > MAX_SEARCH_PATHS ) { + d = MAX_SEARCH_PATHS; + } + + for ( i = 0 ; i < d ; i++ ) { + fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) ); + } + } +} + +/* +===================== +FS_PureServerSetReferencedPaks + +The checksums and names of the pk3 files referenced at the server +are sent to the client and stored here. The client will use these +checksums to see if any pk3 files need to be auto-downloaded. +===================== +*/ +void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) { + int i, c, d = 0; + + Cmd_TokenizeString( pakSums ); + + c = Cmd_Argc(); + if ( c > MAX_SEARCH_PATHS ) { + c = MAX_SEARCH_PATHS; + } + + for ( i = 0 ; i < c ; i++ ) { + fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) ); + } + + for (i = 0 ; i < ARRAY_LEN(fs_serverReferencedPakNames); i++) + { + if(fs_serverReferencedPakNames[i]) + Z_Free(fs_serverReferencedPakNames[i]); + + fs_serverReferencedPakNames[i] = NULL; + } + + if ( pakNames && *pakNames ) { + Cmd_TokenizeString( pakNames ); + + d = Cmd_Argc(); + + if(d > c) + d = c; + + for ( i = 0 ; i < d ; i++ ) { + fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) ); + } + } + + // ensure that there are as many checksums as there are pak names. + if(d < c) + c = d; + + fs_numServerReferencedPaks = c; +} + +/* +================ +FS_InitFilesystem + +Called only at inital startup, not when the filesystem +is resetting due to a game change +================ +*/ +void FS_InitFilesystem( void ) { + // allow command line parms to override our defaults + // we have to specially handle this, because normal command + // line variable sets don't happen until after the filesystem + // has already been initialized + Com_StartupVariable( "fs_basepath" ); + Com_StartupVariable( "fs_homepath" ); + Com_StartupVariable( "fs_game" ); + + // try to start up normally + FS_Startup( BASEGAME ); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { + Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); + } + + Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); + Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); +} + + +/* +================ +FS_Restart +================ +*/ +void FS_Restart( int checksumFeed ) { + + // free anything we currently have loaded + FS_Shutdown(qfalse); + + // set the checksum feed + fs_checksumFeed = checksumFeed; + + // clear pak references + FS_ClearPakReferences(0); + + // try to start up normally + FS_Startup( BASEGAME ); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { + // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3 + // (for instance a TA demo server) + if (lastValidBase[0]) { + FS_PureServerSetLoadedPaks("", ""); + Cvar_Set("fs_basepath", lastValidBase); + Cvar_Set("fs_gamedirvar", lastValidGame); + lastValidBase[0] = '\0'; + lastValidGame[0] = '\0'; + FS_Restart(checksumFeed); + Com_Error( ERR_DROP, "Invalid game folder\n" ); + return; + } + Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); + } + + if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) { + // skip the autogen.cfg if "safe" is on the command line + if ( !Com_SafeMode() ) { + Cbuf_AddText ("exec " Q3CONFIG_CFG "\n"); + } + } + + Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); + Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); + +} + +/* +================= +FS_ConditionalRestart +restart if necessary +================= +*/ +qboolean FS_ConditionalRestart(int checksumFeed) +{ + if(fs_gamedirvar->modified) + { + Com_GameRestart(checksumFeed, qfalse); + return qtrue; + } + + else if(checksumFeed != fs_checksumFeed) + { + FS_Restart(checksumFeed); + return qtrue; + } + + return qfalse; +} + +/* +======================================================================================== + +Handle based file calls for virtual machines + +======================================================================================== +*/ + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + int r; + qboolean sync; + + sync = qfalse; + + switch( mode ) { + case FS_READ: + r = FS_FOpenFileRead( qpath, f, qtrue ); + break; + case FS_WRITE: + *f = FS_FOpenFileWrite( qpath ); + r = 0; + if (*f == 0) { + r = -1; + } + break; + case FS_APPEND_SYNC: + sync = qtrue; + case FS_APPEND: + *f = FS_FOpenFileAppend( qpath ); + r = 0; + if (*f == 0) { + r = -1; + } + break; + default: + Com_Error( ERR_FATAL, "FS_FOpenFileByMode: bad mode" ); + return -1; + } + + if (!f) { + return r; + } + + if ( *f ) { + if (fsh[*f].zipFile == qtrue) { + fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z); + } else { + fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o); + } + fsh[*f].fileSize = r; + fsh[*f].streamed = qfalse; + + if (mode == FS_READ) { + fsh[*f].streamed = qtrue; + } + } + fsh[*f].handleSync = sync; + + return r; +} + +int FS_FTell( fileHandle_t f ) { + int pos; + if (fsh[f].zipFile == qtrue) { + pos = unztell(fsh[f].handleFiles.file.z); + } else { + pos = ftell(fsh[f].handleFiles.file.o); + } + return pos; +} + +void FS_Flush( fileHandle_t f ) { + fflush(fsh[f].handleFiles.file.o); +} + +void FS_FilenameCompletion( const char *dir, const char *ext, + qboolean stripExt, void(*callback)(const char *s), qboolean allowNonPureFilesOnDisk ) { + char **filenames; + int nfiles; + int i; + char filename[ MAX_STRING_CHARS ]; + + filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles, allowNonPureFilesOnDisk ); + + FS_SortFileList( filenames, nfiles ); + + for( i = 0; i < nfiles; i++ ) { + FS_ConvertPath( filenames[ i ] ); + Q_strncpyz( filename, filenames[ i ], MAX_STRING_CHARS ); + + if( stripExt ) { + COM_StripExtension(filename, filename, sizeof(filename)); + } + + callback( filename ); + } + FS_FreeFileList( filenames ); +} + +const char *FS_GetCurrentGameDir(void) +{ + if(fs_gamedirvar->string[0]) + return fs_gamedirvar->string; + + return BASEGAME; +} diff --git a/src/qcommon/huffman.c b/src/qcommon/huffman.c new file mode 100644 index 0000000..8ef702a --- /dev/null +++ b/src/qcommon/huffman.c @@ -0,0 +1,448 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book. The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +#include "q_shared.h" +#include "qcommon.h" + +static int bloc = 0; + +void Huff_putBit( int bit, byte *fout, int *offset) { + bloc = *offset; + if ((bloc&7) == 0) { + fout[(bloc>>3)] = 0; + } + fout[(bloc>>3)] |= bit << (bloc&7); + bloc++; + *offset = bloc; +} + +int Huff_getBloc(void) +{ + return bloc; +} + +void Huff_setBloc(int _bloc) +{ + bloc = _bloc; +} + +int Huff_getBit( byte *fin, int *offset) { + int t; + bloc = *offset; + t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1; + bloc++; + *offset = bloc; + return t; +} + +/* Add a bit to the output file (buffered) */ +static void add_bit (char bit, byte *fout) { + if ((bloc&7) == 0) { + fout[(bloc>>3)] = 0; + } + fout[(bloc>>3)] |= bit << (bloc&7); + bloc++; +} + +/* Receive one bit from the input file (buffered) */ +static int get_bit (byte *fin) { + int t; + t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1; + bloc++; + return t; +} + +static node_t **get_ppnode(huff_t* huff) { + node_t **tppnode; + if (!huff->freelist) { + return &(huff->nodePtrs[huff->blocPtrs++]); + } else { + tppnode = huff->freelist; + huff->freelist = (node_t **)*tppnode; + return tppnode; + } +} + +static void free_ppnode(huff_t* huff, node_t **ppnode) { + *ppnode = (node_t *)huff->freelist; + huff->freelist = ppnode; +} + +/* Swap the location of these two nodes in the tree */ +static void swap (huff_t* huff, node_t *node1, node_t *node2) { + node_t *par1, *par2; + + par1 = node1->parent; + par2 = node2->parent; + + if (par1) { + if (par1->left == node1) { + par1->left = node2; + } else { + par1->right = node2; + } + } else { + huff->tree = node2; + } + + if (par2) { + if (par2->left == node2) { + par2->left = node1; + } else { + par2->right = node1; + } + } else { + huff->tree = node1; + } + + node1->parent = par2; + node2->parent = par1; +} + +/* Swap these two nodes in the linked list (update ranks) */ +static void swaplist(node_t *node1, node_t *node2) { + node_t *par1; + + par1 = node1->next; + node1->next = node2->next; + node2->next = par1; + + par1 = node1->prev; + node1->prev = node2->prev; + node2->prev = par1; + + if (node1->next == node1) { + node1->next = node2; + } + if (node2->next == node2) { + node2->next = node1; + } + if (node1->next) { + node1->next->prev = node1; + } + if (node2->next) { + node2->next->prev = node2; + } + if (node1->prev) { + node1->prev->next = node1; + } + if (node2->prev) { + node2->prev->next = node2; + } +} + +/* Do the increments */ +static void increment(huff_t* huff, node_t *node) { + node_t *lnode; + + if (!node) { + return; + } + + if (node->next != NULL && node->next->weight == node->weight) { + lnode = *node->head; + if (lnode != node->parent) { + swap(huff, lnode, node); + } + swaplist(lnode, node); + } + if (node->prev && node->prev->weight == node->weight) { + *node->head = node->prev; + } else { + *node->head = NULL; + free_ppnode(huff, node->head); + } + node->weight++; + if (node->next && node->next->weight == node->weight) { + node->head = node->next->head; + } else { + node->head = get_ppnode(huff); + *node->head = node; + } + if (node->parent) { + increment(huff, node->parent); + if (node->prev == node->parent) { + swaplist(node, node->parent); + if (*node->head == node) { + *node->head = node->parent; + } + } + } +} + +void Huff_addRef(huff_t* huff, byte ch) { + node_t *tnode, *tnode2; + if (huff->loc[ch] == NULL) { /* if this is the first transmission of this node */ + tnode = &(huff->nodeList[huff->blocNode++]); + tnode2 = &(huff->nodeList[huff->blocNode++]); + + tnode2->symbol = INTERNAL_NODE; + tnode2->weight = 1; + tnode2->next = huff->lhead->next; + if (huff->lhead->next) { + huff->lhead->next->prev = tnode2; + if (huff->lhead->next->weight == 1) { + tnode2->head = huff->lhead->next->head; + } else { + tnode2->head = get_ppnode(huff); + *tnode2->head = tnode2; + } + } else { + tnode2->head = get_ppnode(huff); + *tnode2->head = tnode2; + } + huff->lhead->next = tnode2; + tnode2->prev = huff->lhead; + + tnode->symbol = ch; + tnode->weight = 1; + tnode->next = huff->lhead->next; + if (huff->lhead->next) { + huff->lhead->next->prev = tnode; + if (huff->lhead->next->weight == 1) { + tnode->head = huff->lhead->next->head; + } else { + /* this should never happen */ + tnode->head = get_ppnode(huff); + *tnode->head = tnode2; + } + } else { + /* this should never happen */ + tnode->head = get_ppnode(huff); + *tnode->head = tnode; + } + huff->lhead->next = tnode; + tnode->prev = huff->lhead; + tnode->left = tnode->right = NULL; + + if (huff->lhead->parent) { + if (huff->lhead->parent->left == huff->lhead) { /* lhead is guaranteed to by the NYT */ + huff->lhead->parent->left = tnode2; + } else { + huff->lhead->parent->right = tnode2; + } + } else { + huff->tree = tnode2; + } + + tnode2->right = tnode; + tnode2->left = huff->lhead; + + tnode2->parent = huff->lhead->parent; + huff->lhead->parent = tnode->parent = tnode2; + + huff->loc[ch] = tnode; + + increment(huff, tnode2->parent); + } else { + increment(huff, huff->loc[ch]); + } +} + +/* Get a symbol */ +int Huff_Receive (node_t *node, int *ch, byte *fin) { + while (node && node->symbol == INTERNAL_NODE) { + if (get_bit(fin)) { + node = node->right; + } else { + node = node->left; + } + } + if (!node) { + return 0; +// Com_Error(ERR_DROP, "Illegal tree!\n"); + } + return (*ch = node->symbol); +} + +/* Get a symbol */ +void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset) { + bloc = *offset; + while (node && node->symbol == INTERNAL_NODE) { + if (get_bit(fin)) { + node = node->right; + } else { + node = node->left; + } + } + if (!node) { + *ch = 0; + return; +// Com_Error(ERR_DROP, "Illegal tree!\n"); + } + *ch = node->symbol; + *offset = bloc; +} + +/* Send the prefix code for this node */ +static void send(node_t *node, node_t *child, byte *fout) { + if (node->parent) { + send(node->parent, node, fout); + } + if (child) { + if (node->right == child) { + add_bit(1, fout); + } else { + add_bit(0, fout); + } + } +} + +/* Send a symbol */ +void Huff_transmit (huff_t *huff, int ch, byte *fout) { + int i; + if (huff->loc[ch] == NULL) { + /* node_t hasn't been transmitted, send a NYT, then the symbol */ + Huff_transmit(huff, NYT, fout); + for (i = 7; i >= 0; i--) { + add_bit((char)((ch >> i) & 0x1), fout); + } + } else { + send(huff->loc[ch], NULL, fout); + } +} + +void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset) { + bloc = *offset; + send(huff->loc[ch], NULL, fout); + *offset = bloc; +} + +void Huff_Decompress(msg_t *mbuf, int offset) { + int ch, cch, i, j, size; + byte seq[65536]; + byte* buffer; + huff_t huff; + + size = mbuf->cursize - offset; + buffer = mbuf->data + offset; + + if ( size <= 0 ) { + return; + } + + Com_Memset(&huff, 0, sizeof(huff_t)); + // Initialize the tree & list with the NYT node + huff.tree = huff.lhead = huff.ltail = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]); + huff.tree->symbol = NYT; + huff.tree->weight = 0; + huff.lhead->next = huff.lhead->prev = NULL; + huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + + cch = buffer[0]*256 + buffer[1]; + // don't overflow with bad messages + if ( cch > mbuf->maxsize - offset ) { + cch = mbuf->maxsize - offset; + } + bloc = 16; + + for ( j = 0; j < cch; j++ ) { + ch = 0; + // don't overflow reading from the messages + // FIXME: would it be better to have a overflow check in get_bit ? + if ( (bloc >> 3) > size ) { + seq[j] = 0; + break; + } + Huff_Receive(huff.tree, &ch, buffer); /* Get a character */ + if ( ch == NYT ) { /* We got a NYT, get the symbol associated with it */ + ch = 0; + for ( i = 0; i < 8; i++ ) { + ch = (ch<<1) + get_bit(buffer); + } + } + + seq[j] = ch; /* Write symbol */ + + Huff_addRef(&huff, (byte)ch); /* Increment node */ + } + mbuf->cursize = cch + offset; + Com_Memcpy(mbuf->data + offset, seq, cch); +} + +extern int oldsize; + +void Huff_Compress(msg_t *mbuf, int offset) { + int i, ch, size; + byte seq[65536]; + byte* buffer; + huff_t huff; + + size = mbuf->cursize - offset; + buffer = mbuf->data+ + offset; + + if (size<=0) { + return; + } + + Com_Memset(&huff, 0, sizeof(huff_t)); + // Add the NYT (not yet transmitted) node into the tree/list */ + huff.tree = huff.lhead = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]); + huff.tree->symbol = NYT; + huff.tree->weight = 0; + huff.lhead->next = huff.lhead->prev = NULL; + huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + huff.loc[NYT] = huff.tree; + + seq[0] = (size>>8); + seq[1] = size&0xff; + + bloc = 16; + + for (i=0; icursize = (bloc>>3) + offset; + Com_Memcpy(mbuf->data+offset, seq, (bloc>>3)); +} + +void Huff_Init(huffman_t *huff) { + + Com_Memset(&huff->compressor, 0, sizeof(huff_t)); + Com_Memset(&huff->decompressor, 0, sizeof(huff_t)); + + // Initialize the tree & list with the NYT node + huff->decompressor.tree = huff->decompressor.lhead = huff->decompressor.ltail = huff->decompressor.loc[NYT] = &(huff->decompressor.nodeList[huff->decompressor.blocNode++]); + huff->decompressor.tree->symbol = NYT; + huff->decompressor.tree->weight = 0; + huff->decompressor.lhead->next = huff->decompressor.lhead->prev = NULL; + huff->decompressor.tree->parent = huff->decompressor.tree->left = huff->decompressor.tree->right = NULL; + + // Add the NYT (not yet transmitted) node into the tree/list */ + huff->compressor.tree = huff->compressor.lhead = huff->compressor.loc[NYT] = &(huff->compressor.nodeList[huff->compressor.blocNode++]); + huff->compressor.tree->symbol = NYT; + huff->compressor.tree->weight = 0; + huff->compressor.lhead->next = huff->compressor.lhead->prev = NULL; + huff->compressor.tree->parent = huff->compressor.tree->left = huff->compressor.tree->right = NULL; + huff->compressor.loc[NYT] = huff->compressor.tree; +} + diff --git a/src/qcommon/ioapi.c b/src/qcommon/ioapi.c new file mode 100644 index 0000000..aab3c00 --- /dev/null +++ b/src/qcommon/ioapi.c @@ -0,0 +1,182 @@ +/* ioapi.c -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant +*/ + +#include +#include +#include + +#ifdef USE_LOCAL_HEADERS +#include "../zlib/zlib.h" +#else +#include +#endif + +#include "ioapi.h" + + + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +voidpf ZCALLBACK fopen_file_func OF(( + voidpf opaque, + const char* filename, + int mode)); + +uLong ZCALLBACK fread_file_func OF(( + voidpf opaque, + voidpf stream, + void* buf, + uLong size)); + +uLong ZCALLBACK fwrite_file_func OF(( + voidpf opaque, + voidpf stream, + const void* buf, + uLong size)); + +long ZCALLBACK ftell_file_func OF(( + voidpf opaque, + voidpf stream)); + +long ZCALLBACK fseek_file_func OF(( + voidpf opaque, + voidpf stream, + uLong offset, + int origin)); + +int ZCALLBACK fclose_file_func OF(( + voidpf opaque, + voidpf stream)); + +int ZCALLBACK ferror_file_func OF(( + voidpf opaque, + voidpf stream)); + + +voidpf ZCALLBACK fopen_file_func (opaque, filename, mode) + voidpf opaque; + const char* filename; + int mode; +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = fopen(filename, mode_fopen); + return file; +} + + +uLong ZCALLBACK fread_file_func (opaque, stream, buf, size) + voidpf opaque; + voidpf stream; + void* buf; + uLong size; +{ + uLong ret; + ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + + +uLong ZCALLBACK fwrite_file_func (opaque, stream, buf, size) + voidpf opaque; + voidpf stream; + const void* buf; + uLong size; +{ + uLong ret; + ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +long ZCALLBACK ftell_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + long ret; + ret = ftell((FILE *)stream); + return ret; +} + +long ZCALLBACK fseek_file_func (opaque, stream, offset, origin) + voidpf opaque; + voidpf stream; + uLong offset; + int origin; +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + fseek((FILE *)stream, offset, fseek_origin); + return ret; +} + +int ZCALLBACK fclose_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + int ret; + ret = fclose((FILE *)stream); + return ret; +} + +int ZCALLBACK ferror_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + int ret; + ret = ferror((FILE *)stream); + return ret; +} + +void fill_fopen_filefunc (pzlib_filefunc_def) + zlib_filefunc_def* pzlib_filefunc_def; +{ + pzlib_filefunc_def->zopen_file = fopen_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell_file = ftell_file_func; + pzlib_filefunc_def->zseek_file = fseek_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} diff --git a/src/qcommon/ioapi.h b/src/qcommon/ioapi.h new file mode 100644 index 0000000..7d457ba --- /dev/null +++ b/src/qcommon/ioapi.h @@ -0,0 +1,75 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant +*/ + +#ifndef _ZLIBIOAPI_H +#define _ZLIBIOAPI_H + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + + +#ifndef ZCALLBACK + +#if (defined(WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) +#define ZCALLBACK CALLBACK +#else +#define ZCALLBACK +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + + + +void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +#define ZREAD(filefunc,filestream,buf,size) ((*((filefunc).zread_file))((filefunc).opaque,filestream,buf,size)) +#define ZWRITE(filefunc,filestream,buf,size) ((*((filefunc).zwrite_file))((filefunc).opaque,filestream,buf,size)) +#define ZTELL(filefunc,filestream) ((*((filefunc).ztell_file))((filefunc).opaque,filestream)) +#define ZSEEK(filefunc,filestream,pos,mode) ((*((filefunc).zseek_file))((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE(filefunc,filestream) ((*((filefunc).zclose_file))((filefunc).opaque,filestream)) +#define ZERROR(filefunc,filestream) ((*((filefunc).zerror_file))((filefunc).opaque,filestream)) + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/qcommon/md4.c b/src/qcommon/md4.c new file mode 100644 index 0000000..838b062 --- /dev/null +++ b/src/qcommon/md4.c @@ -0,0 +1,208 @@ +/* + mdfour.c + + An implementation of MD4 designed for use in the samba SMB + authentication protocol + + Copyright (C) 1997-1998 Andrew Tridgell + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + + $Id: mdfour.c,v 1.1 2002/08/23 22:03:27 abster Exp $ +*/ + +#include "q_shared.h" +#include "qcommon.h" + +struct mdfour { + uint32_t A, B, C, D; + uint32_t totalN; +}; + + +/* NOTE: This code makes no attempt to be fast! + + It assumes that a int is at least 32 bits long +*/ + +static struct mdfour *m; + +#define F(X,Y,Z) (((X)&(Y)) | ((~(X))&(Z))) +#define G(X,Y,Z) (((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z))) +#define H(X,Y,Z) ((X)^(Y)^(Z)) +#define lshift(x,s) (((x)<<(s)) | ((x)>>(32-(s)))) + +#define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s) +#define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + 0x5A827999,s) +#define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + 0x6ED9EBA1,s) + +/* this applies md4 to 64 byte chunks */ +static void mdfour64(uint32_t *M) +{ + int j; + uint32_t AA, BB, CC, DD; + uint32_t X[16]; + uint32_t A,B,C,D; + + for (j=0;j<16;j++) + X[j] = M[j]; + + A = m->A; B = m->B; C = m->C; D = m->D; + AA = A; BB = B; CC = C; DD = D; + + ROUND1(A,B,C,D, 0, 3); ROUND1(D,A,B,C, 1, 7); + ROUND1(C,D,A,B, 2, 11); ROUND1(B,C,D,A, 3, 19); + ROUND1(A,B,C,D, 4, 3); ROUND1(D,A,B,C, 5, 7); + ROUND1(C,D,A,B, 6, 11); ROUND1(B,C,D,A, 7, 19); + ROUND1(A,B,C,D, 8, 3); ROUND1(D,A,B,C, 9, 7); + ROUND1(C,D,A,B, 10, 11); ROUND1(B,C,D,A, 11, 19); + ROUND1(A,B,C,D, 12, 3); ROUND1(D,A,B,C, 13, 7); + ROUND1(C,D,A,B, 14, 11); ROUND1(B,C,D,A, 15, 19); + + ROUND2(A,B,C,D, 0, 3); ROUND2(D,A,B,C, 4, 5); + ROUND2(C,D,A,B, 8, 9); ROUND2(B,C,D,A, 12, 13); + ROUND2(A,B,C,D, 1, 3); ROUND2(D,A,B,C, 5, 5); + ROUND2(C,D,A,B, 9, 9); ROUND2(B,C,D,A, 13, 13); + ROUND2(A,B,C,D, 2, 3); ROUND2(D,A,B,C, 6, 5); + ROUND2(C,D,A,B, 10, 9); ROUND2(B,C,D,A, 14, 13); + ROUND2(A,B,C,D, 3, 3); ROUND2(D,A,B,C, 7, 5); + ROUND2(C,D,A,B, 11, 9); ROUND2(B,C,D,A, 15, 13); + + ROUND3(A,B,C,D, 0, 3); ROUND3(D,A,B,C, 8, 9); + ROUND3(C,D,A,B, 4, 11); ROUND3(B,C,D,A, 12, 15); + ROUND3(A,B,C,D, 2, 3); ROUND3(D,A,B,C, 10, 9); + ROUND3(C,D,A,B, 6, 11); ROUND3(B,C,D,A, 14, 15); + ROUND3(A,B,C,D, 1, 3); ROUND3(D,A,B,C, 9, 9); + ROUND3(C,D,A,B, 5, 11); ROUND3(B,C,D,A, 13, 15); + ROUND3(A,B,C,D, 3, 3); ROUND3(D,A,B,C, 11, 9); + ROUND3(C,D,A,B, 7, 11); ROUND3(B,C,D,A, 15, 15); + + A += AA; B += BB; C += CC; D += DD; + + for (j=0;j<16;j++) + X[j] = 0; + + m->A = A; m->B = B; m->C = C; m->D = D; +} + +static void copy64(uint32_t *M, byte *in) +{ + int i; + + for (i=0;i<16;i++) + M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) | + (in[i*4+1]<<8) | (in[i*4+0]<<0); +} + +static void copy4(byte *out,uint32_t x) +{ + out[0] = x&0xFF; + out[1] = (x>>8)&0xFF; + out[2] = (x>>16)&0xFF; + out[3] = (x>>24)&0xFF; +} + +void mdfour_begin(struct mdfour *md) +{ + md->A = 0x67452301; + md->B = 0xefcdab89; + md->C = 0x98badcfe; + md->D = 0x10325476; + md->totalN = 0; +} + + +static void mdfour_tail(byte *in, int n) +{ + byte buf[128]; + uint32_t M[16]; + uint32_t b; + + m->totalN += n; + + b = m->totalN * 8; + + Com_Memset(buf, 0, 128); + if (n) Com_Memcpy(buf, in, n); + buf[n] = 0x80; + + if (n <= 55) { + copy4(buf+56, b); + copy64(M, buf); + mdfour64(M); + } else { + copy4(buf+120, b); + copy64(M, buf); + mdfour64(M); + copy64(M, buf+64); + mdfour64(M); + } +} + +static void mdfour_update(struct mdfour *md, byte *in, int n) +{ + uint32_t M[16]; + + m = md; + + if (n == 0) mdfour_tail(in, n); + + while (n >= 64) { + copy64(M, in); + mdfour64(M); + in += 64; + n -= 64; + m->totalN += 64; + } + + mdfour_tail(in, n); +} + + +static void mdfour_result(struct mdfour *md, byte *out) +{ + m = md; + + copy4(out, m->A); + copy4(out+4, m->B); + copy4(out+8, m->C); + copy4(out+12, m->D); +} + +static void mdfour(byte *out, byte *in, int n) +{ + struct mdfour md; + mdfour_begin(&md); + mdfour_update(&md, in, n); + mdfour_result(&md, out); +} + +//=================================================================== + +unsigned Com_BlockChecksum (const void *buffer, int length) +{ + int digest[4]; + unsigned val; + + mdfour( (byte *)digest, (byte *)buffer, length ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/src/qcommon/md5.c b/src/qcommon/md5.c new file mode 100644 index 0000000..5cf12bb --- /dev/null +++ b/src/qcommon/md5.c @@ -0,0 +1,310 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ +#include "q_shared.h" +#include "qcommon.h" + +typedef struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +} MD5_CTX; + +#ifndef Q3_BIG_ENDIAN + #define byteReverse(buf, len) /* Nothing */ +#else + static void byteReverse(unsigned char *buf, unsigned longs); + + /* + * Note: this code is harmless on little-endian machines. + */ + static void byteReverse(unsigned char *buf, unsigned longs) + { + uint32_t t; + do { + t = (uint32_t) + ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); + } +#endif // Q3_BIG_ENDIAN + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +static void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(uint32_t buf[4], + uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +static void MD5Update(struct MD5Context *ctx, unsigned char const *buf, + unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +static void MD5Final(struct MD5Context *ctx, unsigned char *digest) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + + if (digest!=NULL) + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + + +char *Com_MD5File( const char *fn, int length, const char *prefix, int prefix_len ) +{ + static char final[33] = {""}; + unsigned char digest[16] = {""}; + fileHandle_t f; + MD5_CTX md5; + byte buffer[2048]; + int i; + int filelen = 0; + int r = 0; + int total = 0; + + Q_strncpyz( final, "", sizeof( final ) ); + + filelen = FS_SV_FOpenFileRead( fn, &f ); + + if( !f ) { + return final; + } + if( filelen < 1 ) { + FS_FCloseFile( f ); + return final; + } + if(filelen < length || !length) { + length = filelen; + } + + MD5Init(&md5); + + if( prefix_len && *prefix ) + MD5Update(&md5 , (unsigned char *)prefix, prefix_len); + + for(;;) { + r = FS_Read2(buffer, sizeof(buffer), f); + if(r < 1) + break; + if(r + total > length) + r = length - total; + total += r; + MD5Update(&md5 , buffer, r); + if(r < sizeof(buffer) || total >= length) + break; + } + FS_FCloseFile(f); + MD5Final(&md5, digest); + final[0] = '\0'; + for(i = 0; i < 16; i++) { + Q_strcat(final, sizeof(final), va("%02X", digest[i])); + } + return final; +} diff --git a/src/qcommon/msg.c b/src/qcommon/msg.c new file mode 100644 index 0000000..05a7aa9 --- /dev/null +++ b/src/qcommon/msg.c @@ -0,0 +1,1759 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "q_shared.h" +#include "qcommon.h" + +static huffman_t msgHuff; + +static qboolean msgInit = qfalse; + +int pcount[256]; + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles byte ordering and avoids alignment errors +============================================================================== +*/ + +int oldsize = 0; + +void MSG_initHuffman( void ); + +void MSG_Init( msg_t *buf, byte *data, int length ) { + if (!msgInit) { + MSG_initHuffman(); + } + Com_Memset (buf, 0, sizeof(*buf)); + buf->data = data; + buf->maxsize = length; +} + +void MSG_InitOOB( msg_t *buf, byte *data, int length ) { + if (!msgInit) { + MSG_initHuffman(); + } + Com_Memset (buf, 0, sizeof(*buf)); + buf->data = data; + buf->maxsize = length; + buf->oob = qtrue; +} + +void MSG_Clear( msg_t *buf ) { + buf->cursize = 0; + buf->overflowed = qfalse; + buf->bit = 0; //<- in bits +} + + +void MSG_Bitstream( msg_t *buf ) { + buf->oob = qfalse; +} + +void MSG_BeginReading( msg_t *msg ) { + msg->readcount = 0; + msg->bit = 0; + msg->oob = qfalse; +} + +void MSG_BeginReadingOOB( msg_t *msg ) { + msg->readcount = 0; + msg->bit = 0; + msg->oob = qtrue; +} + +void MSG_Copy(msg_t *buf, byte *data, int length, msg_t *src) +{ + if (lengthcursize) { + Com_Error( ERR_DROP, "MSG_Copy: can't copy into a smaller msg_t buffer"); + } + Com_Memcpy(buf, src, sizeof(msg_t)); + buf->data = data; + Com_Memcpy(buf->data, src->data, src->cursize); +} + +/* +============================================================================= + +bit functions + +============================================================================= +*/ + +int overflows; + +// negative bit values include signs +void MSG_WriteBits( msg_t *msg, int value, int bits ) { + int i; +// FILE* fp; + + oldsize += bits; + + // this isn't an exact overflow check, but close enough + if ( msg->maxsize - msg->cursize < 4 ) { + msg->overflowed = qtrue; + return; + } + + if ( bits == 0 || bits < -31 || bits > 32 ) { + Com_Error( ERR_DROP, "MSG_WriteBits: bad bits %i", bits ); + } + + // check for overflows + if ( bits != 32 ) { + if ( bits > 0 ) { + if ( value > ( ( 1 << bits ) - 1 ) || value < 0 ) { + overflows++; + } + } else { + int r; + + r = 1 << (bits-1); + + if ( value > r - 1 || value < -r ) { + overflows++; + } + } + } + if ( bits < 0 ) { + bits = -bits; + } + if (msg->oob) { + if (bits==8) { + msg->data[msg->cursize] = value; + msg->cursize += 1; + msg->bit += 8; + } else if (bits==16) { + unsigned short *sp = (unsigned short *)&msg->data[msg->cursize]; + *sp = LittleShort(value); + msg->cursize += 2; + msg->bit += 16; + } else if (bits==32) { + unsigned int *ip = (unsigned int *)&msg->data[msg->cursize]; + *ip = LittleLong(value); + msg->cursize += 4; + msg->bit += 32; + } else { + Com_Error(ERR_DROP, "can't read %d bits\n", bits); + } + } else { +// fp = fopen("c:\\netchan.bin", "a"); + value &= (0xffffffff>>(32-bits)); + if (bits&7) { + int nbits; + nbits = bits&7; + for(i=0;idata, &msg->bit); + value = (value>>1); + } + bits = bits - nbits; + } + if (bits) { + for(i=0;idata, &msg->bit); + value = (value>>8); + } + } + msg->cursize = (msg->bit>>3)+1; +// fclose(fp); + } +} + +int MSG_ReadBits( msg_t *msg, int bits ) { + int value; + int get; + qboolean sgn; + int i, nbits; +// FILE* fp; + + value = 0; + + if ( bits < 0 ) { + bits = -bits; + sgn = qtrue; + } else { + sgn = qfalse; + } + + if (msg->oob) { + if (bits==8) { + value = msg->data[msg->readcount]; + msg->readcount += 1; + msg->bit += 8; + } else if (bits==16) { + unsigned short *sp = (unsigned short *)&msg->data[msg->readcount]; + value = LittleShort(*sp); + msg->readcount += 2; + msg->bit += 16; + } else if (bits==32) { + unsigned int *ip = (unsigned int *)&msg->data[msg->readcount]; + value = LittleLong(*ip); + msg->readcount += 4; + msg->bit += 32; + } else { + Com_Error(ERR_DROP, "can't read %d bits\n", bits); + } + } else { + nbits = 0; + if (bits&7) { + nbits = bits&7; + for(i=0;idata, &msg->bit)<data, &msg->bit); +// fwrite(&get, 1, 1, fp); + value |= (get<<(i+nbits)); + } +// fclose(fp); + } + msg->readcount = (msg->bit>>3)+1; + } + if ( sgn ) { + if ( value & ( 1 << ( bits - 1 ) ) ) { + value |= -1 ^ ( ( 1 << bits ) - 1 ); + } + } + + return value; +} + + + +//================================================================================ + +// +// writing functions +// + +void MSG_WriteChar( msg_t *sb, int c ) { +#ifdef PARANOID + if (c < -128 || c > 127) + Com_Error (ERR_FATAL, "MSG_WriteChar: range error"); +#endif + + MSG_WriteBits( sb, c, 8 ); +} + +void MSG_WriteByte( msg_t *sb, int c ) { +#ifdef PARANOID + if (c < 0 || c > 255) + Com_Error (ERR_FATAL, "MSG_WriteByte: range error"); +#endif + + MSG_WriteBits( sb, c, 8 ); +} + +void MSG_WriteData( msg_t *buf, const void *data, int length ) { + int i; + for(i=0;i (short)0x7fff) + Com_Error (ERR_FATAL, "MSG_WriteShort: range error"); +#endif + + MSG_WriteBits( sb, c, 16 ); +} + +void MSG_WriteLong( msg_t *sb, int c ) { + MSG_WriteBits( sb, c, 32 ); +} + +void MSG_WriteFloat( msg_t *sb, float f ) { + floatint_t dat; + dat.f = f; + MSG_WriteBits( sb, dat.i, 32 ); +} + +void MSG_WriteString( msg_t *sb, const char *s ) { + if ( !s ) { + MSG_WriteData (sb, "", 1); + } else { + int l,i; + char string[MAX_STRING_CHARS]; + + l = strlen( s ); + if ( l >= MAX_STRING_CHARS ) { + Com_Printf( "MSG_WriteString: MAX_STRING_CHARS" ); + MSG_WriteData (sb, "", 1); + return; + } + Q_strncpyz( string, s, sizeof( string ) ); + + // get rid of 0x80+ chars, because old clients don't like them + for ( i = 0 ; i < l ; i++ ) { + if ( ((byte *)string)[i] > 127 ) { + string[i] = '.'; + } + } + + MSG_WriteData (sb, string, l+1); + } +} + +void MSG_WriteBigString( msg_t *sb, const char *s ) { + if ( !s ) { + MSG_WriteData (sb, "", 1); + } else { + int l,i; + char string[BIG_INFO_STRING]; + + l = strlen( s ); + if ( l >= BIG_INFO_STRING ) { + Com_Printf( "MSG_WriteString: BIG_INFO_STRING" ); + MSG_WriteData (sb, "", 1); + return; + } + Q_strncpyz( string, s, sizeof( string ) ); + + // get rid of 0x80+ chars, because old clients don't like them + for ( i = 0 ; i < l ; i++ ) { + if ( ((byte *)string)[i] > 127 ) { + string[i] = '.'; + } + } + + MSG_WriteData (sb, string, l+1); + } +} + +void MSG_WriteAngle( msg_t *sb, float f ) { + MSG_WriteByte (sb, (int)(f*256/360) & 255); +} + +void MSG_WriteAngle16( msg_t *sb, float f ) { + MSG_WriteShort (sb, ANGLE2SHORT(f)); +} + + +//============================================================ + +// +// reading functions +// + +// returns -1 if no more characters are available +int MSG_ReadChar (msg_t *msg ) { + int c; + + c = (signed char)MSG_ReadBits( msg, 8 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +int MSG_ReadByte( msg_t *msg ) { + int c; + + c = (unsigned char)MSG_ReadBits( msg, 8 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + return c; +} + +int MSG_LookaheadByte( msg_t *msg ) { + const int bloc = Huff_getBloc(); + const int readcount = msg->readcount; + const int bit = msg->bit; + int c = MSG_ReadByte(msg); + Huff_setBloc(bloc); + msg->readcount = readcount; + msg->bit = bit; + return c; +} + +int MSG_ReadShort( msg_t *msg ) { + int c; + + c = (short)MSG_ReadBits( msg, 16 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +int MSG_ReadLong( msg_t *msg ) { + int c; + + c = MSG_ReadBits( msg, 32 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +float MSG_ReadFloat( msg_t *msg ) { + floatint_t dat; + + dat.i = MSG_ReadBits( msg, 32 ); + if ( msg->readcount > msg->cursize ) { + dat.f = -1; + } + + return dat.f; +} + +char *MSG_ReadString( msg_t *msg ) { + static char string[MAX_STRING_CHARS]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if ( c == -1 || c == 0 ) { + break; + } + // don't allow higher ascii values + if ( c > 127 ) { + c = '.'; + } + + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +char *MSG_ReadBigString( msg_t *msg ) { + static char string[BIG_INFO_STRING]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if ( c == -1 || c == 0 ) { + break; + } + // don't allow higher ascii values + if ( c > 127 ) { + c = '.'; + } + + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +char *MSG_ReadStringLine( msg_t *msg ) { + static char string[MAX_STRING_CHARS]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if (c == -1 || c == 0 || c == '\n') { + break; + } + // don't allow higher ascii values + if ( c > 127 ) { + c = '.'; + } + + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +float MSG_ReadAngle16( msg_t *msg ) { + return SHORT2ANGLE(MSG_ReadShort(msg)); +} + +void MSG_ReadData( msg_t *msg, void *data, int len ) { + int i; + + for (i=0 ; i> 10) ^ (hash >> 20)); + return hash; +} + +/* +============================================================================= + +delta functions + +============================================================================= +*/ + +extern cvar_t *cl_shownet; + +#define LOG(x) if( cl_shownet->integer == 4 ) { Com_Printf("%s ", x ); }; + +void MSG_WriteDelta( msg_t *msg, int oldV, int newV, int bits ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, newV, bits ); +} + +int MSG_ReadDelta( msg_t *msg, int oldV, int bits ) { + if ( MSG_ReadBits( msg, 1 ) ) { + return MSG_ReadBits( msg, bits ); + } + return oldV; +} + +void MSG_WriteDeltaFloat( msg_t *msg, float oldV, float newV ) { + floatint_t fi; + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + fi.f = newV; + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, fi.i, 32 ); +} + +float MSG_ReadDeltaFloat( msg_t *msg, float oldV ) { + if ( MSG_ReadBits( msg, 1 ) ) { + floatint_t fi; + + fi.i = MSG_ReadBits( msg, 32 ); + return fi.f; + } + return oldV; +} + +/* +============================================================================= + +delta functions with keys + +============================================================================= +*/ + +int kbitmask[32] = { + 0x00000001, 0x00000003, 0x00000007, 0x0000000F, + 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, + 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, + 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, + 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, + 0x001FFFFf, 0x003FFFFF, 0x007FFFFF, 0x00FFFFFF, + 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, + 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, +}; + +void MSG_WriteDeltaKey( msg_t *msg, int key, int oldV, int newV, int bits ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, newV ^ key, bits ); +} + +int MSG_ReadDeltaKey( msg_t *msg, int key, int oldV, int bits ) { + if ( MSG_ReadBits( msg, 1 ) ) { + return MSG_ReadBits( msg, bits ) ^ (key & kbitmask[bits]); + } + return oldV; +} + +void MSG_WriteDeltaKeyFloat( msg_t *msg, int key, float oldV, float newV ) { + floatint_t fi; + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + fi.f = newV; + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, fi.i ^ key, 32 ); +} + +float MSG_ReadDeltaKeyFloat( msg_t *msg, int key, float oldV ) { + if ( MSG_ReadBits( msg, 1 ) ) { + floatint_t fi; + + fi.i = MSG_ReadBits( msg, 32 ) ^ key; + return fi.f; + } + return oldV; +} + + +/* +============================================================================ + +usercmd_t communication + +============================================================================ +*/ + +// ms is allways sent, the others are optional +#define CM_ANGLE1 (1<<0) +#define CM_ANGLE2 (1<<1) +#define CM_ANGLE3 (1<<2) +#define CM_FORWARD (1<<3) +#define CM_SIDE (1<<4) +#define CM_UP (1<<5) +#define CM_BUTTONS (1<<6) +#define CM_WEAPON (1<<7) + +/* +===================== +MSG_WriteDeltaUsercmd +===================== +*/ +void MSG_WriteDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { + if ( to->serverTime - from->serverTime < 256 ) { + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 ); + } else { + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, to->serverTime, 32 ); + } + MSG_WriteDelta( msg, from->angles[0], to->angles[0], 16 ); + MSG_WriteDelta( msg, from->angles[1], to->angles[1], 16 ); + MSG_WriteDelta( msg, from->angles[2], to->angles[2], 16 ); + MSG_WriteDelta( msg, from->forwardmove, to->forwardmove, 8 ); + MSG_WriteDelta( msg, from->rightmove, to->rightmove, 8 ); + MSG_WriteDelta( msg, from->upmove, to->upmove, 8 ); + MSG_WriteDelta( msg, from->buttons, to->buttons, 16 ); + MSG_WriteDelta( msg, from->weapon, to->weapon, 8 ); +} + + +/* +===================== +MSG_ReadDeltaUsercmd +===================== +*/ +void MSG_ReadDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { + if ( MSG_ReadBits( msg, 1 ) ) { + to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 ); + } else { + to->serverTime = MSG_ReadBits( msg, 32 ); + } + to->angles[0] = MSG_ReadDelta( msg, from->angles[0], 16); + to->angles[1] = MSG_ReadDelta( msg, from->angles[1], 16); + to->angles[2] = MSG_ReadDelta( msg, from->angles[2], 16); + to->forwardmove = MSG_ReadDelta( msg, from->forwardmove, 8); + to->rightmove = MSG_ReadDelta( msg, from->rightmove, 8); + to->upmove = MSG_ReadDelta( msg, from->upmove, 8); + to->buttons = MSG_ReadDelta( msg, from->buttons, 16); + to->weapon = MSG_ReadDelta( msg, from->weapon, 8); +} + +/* +===================== +MSG_WriteDeltaUsercmd +===================== +*/ +void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) { + if ( to->serverTime - from->serverTime < 256 ) { + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 ); + } else { + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, to->serverTime, 32 ); + } + if (from->angles[0] == to->angles[0] && + from->angles[1] == to->angles[1] && + from->angles[2] == to->angles[2] && + from->forwardmove == to->forwardmove && + from->rightmove == to->rightmove && + from->upmove == to->upmove && + from->buttons == to->buttons && + from->weapon == to->weapon) { + MSG_WriteBits( msg, 0, 1 ); // no change + oldsize += 7; + return; + } + key ^= to->serverTime; + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 ); + MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 ); + MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 ); + MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 ); + MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 ); +} + + +/* +===================== +MSG_ReadDeltaUsercmd +===================== +*/ +void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) { + if ( MSG_ReadBits( msg, 1 ) ) { + to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 ); + } else { + to->serverTime = MSG_ReadBits( msg, 32 ); + } + if ( MSG_ReadBits( msg, 1 ) ) { + key ^= to->serverTime; + to->angles[0] = MSG_ReadDeltaKey( msg, key, from->angles[0], 16); + to->angles[1] = MSG_ReadDeltaKey( msg, key, from->angles[1], 16); + to->angles[2] = MSG_ReadDeltaKey( msg, key, from->angles[2], 16); + to->forwardmove = MSG_ReadDeltaKey( msg, key, from->forwardmove, 8); + to->rightmove = MSG_ReadDeltaKey( msg, key, from->rightmove, 8); + to->upmove = MSG_ReadDeltaKey( msg, key, from->upmove, 8); + to->buttons = MSG_ReadDeltaKey( msg, key, from->buttons, 16); + to->weapon = MSG_ReadDeltaKey( msg, key, from->weapon, 8); + } else { + to->angles[0] = from->angles[0]; + to->angles[1] = from->angles[1]; + to->angles[2] = from->angles[2]; + to->forwardmove = from->forwardmove; + to->rightmove = from->rightmove; + to->upmove = from->upmove; + to->buttons = from->buttons; + to->weapon = from->weapon; + } +} + +/* +============================================================================= + +entityState_t communication + +============================================================================= +*/ + +/* +================= +MSG_ReportChangeVectors_f + +Prints out a table from the current statistics for copying to code +================= +*/ +void MSG_ReportChangeVectors_f( void ) { + int i; + for(i=0;i<256;i++) { + if (pcount[i]) { + Com_Printf("%d used %d\n", i, pcount[i]); + } + } +} + +typedef struct { + char *name; + int offset; + int bits; // 0 = float +} netField_t; + +// using the stringizing operator to save typing... +#define NETF(x) #x,(size_t)&((entityState_t*)0)->x + +netField_t entityStateFields[] = +{ +{ NETF(pos.trTime), 32 }, +{ NETF(pos.trBase[0]), 0 }, +{ NETF(pos.trBase[1]), 0 }, +{ NETF(pos.trDelta[0]), 0 }, +{ NETF(pos.trDelta[1]), 0 }, +{ NETF(pos.trBase[2]), 0 }, +{ NETF(apos.trBase[1]), 0 }, +{ NETF(pos.trDelta[2]), 0 }, +{ NETF(apos.trBase[0]), 0 }, +{ NETF(event), 10 }, +{ NETF(angles2[1]), 0 }, +{ NETF(eType), 8 }, +{ NETF(torsoAnim), 8 }, +{ NETF(weaponAnim), 8 }, +{ NETF(eventParm), 8 }, +{ NETF(legsAnim), 8 }, +{ NETF(groundEntityNum), GENTITYNUM_BITS }, +{ NETF(pos.trType), 8 }, +{ NETF(eFlags), 19 }, +{ NETF(otherEntityNum), GENTITYNUM_BITS }, +{ NETF(weapon), 8 }, +{ NETF(clientNum), 8 }, +{ NETF(angles[1]), 0 }, +{ NETF(pos.trDuration), 32 }, +{ NETF(apos.trType), 8 }, +{ NETF(origin[0]), 0 }, +{ NETF(origin[1]), 0 }, +{ NETF(origin[2]), 0 }, +{ NETF(solid), 24 }, +{ NETF(misc), MAX_MISC }, +{ NETF(modelindex), 8 }, +{ NETF(otherEntityNum2), GENTITYNUM_BITS }, +{ NETF(loopSound), 8 }, +{ NETF(generic1), 10 }, +{ NETF(origin2[2]), 0 }, +{ NETF(origin2[0]), 0 }, +{ NETF(origin2[1]), 0 }, +{ NETF(modelindex2), 8 }, +{ NETF(angles[0]), 0 }, +{ NETF(time), 32 }, +{ NETF(apos.trTime), 32 }, +{ NETF(apos.trDuration), 32 }, +{ NETF(apos.trBase[2]), 0 }, +{ NETF(apos.trDelta[0]), 0 }, +{ NETF(apos.trDelta[1]), 0 }, +{ NETF(apos.trDelta[2]), 0 }, +{ NETF(time2), 32 }, +{ NETF(angles[2]), 0 }, +{ NETF(angles2[0]), 0 }, +{ NETF(angles2[2]), 0 }, +{ NETF(constantLight), 32 }, +{ NETF(frame), 16 } +}; + + +// if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS ) +// the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent +#define FLOAT_INT_BITS 13 +#define FLOAT_INT_BIAS (1<<(FLOAT_INT_BITS-1)) + +/* +================== +MSG_WriteDeltaEntity + +Writes part of a packetentities message, including the entity number. +Can delta from either a baseline or a previous packet_entity +If to is NULL, a remove entity update will be sent +If force is not set, then nothing at all will be generated if the entity is +identical, under the assumption that the in-order delta code will catch it. +================== +*/ +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to, + qboolean force ) { + int i, lc; + int numFields; + netField_t *field; + int trunc; + float fullFloat; + int *fromF, *toF; + + numFields = ARRAY_LEN( entityStateFields ); + + // all fields should be 32 bits to avoid any compiler packing issues + // the "number" field is not part of the field list + // if this assert fails, someone added a field to the entityState_t + // struct without updating the message fields + assert( numFields + 1 == sizeof( *from )/4 ); + + // a NULL to is a delta remove message + if ( to == NULL ) { + if ( from == NULL ) { + return; + } + MSG_WriteBits( msg, from->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 1, 1 ); + return; + } + + if ( to->number < 0 || to->number >= MAX_GENTITIES ) { + Com_Error (ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number ); + } + + lc = 0; + // build the change vector as bytes so it is endien independent + for ( i = 0, field = entityStateFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + if ( *fromF != *toF ) { + lc = i+1; + } + } + + if ( lc == 0 ) { + // nothing at all changed + if ( !force ) { + return; // nothing at all + } + // write two bits for no change + MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // not removed + MSG_WriteBits( msg, 0, 1 ); // no delta + return; + } + + MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // not removed + MSG_WriteBits( msg, 1, 1 ); // we have a delta + + MSG_WriteByte( msg, lc ); // # of changes + + oldsize += numFields; + + for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( *fromF == *toF ) { + MSG_WriteBits( msg, 0, 1 ); // no change + continue; + } + + MSG_WriteBits( msg, 1, 1 ); // changed + + if ( field->bits == 0 ) { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if (fullFloat == 0.0f) { + MSG_WriteBits( msg, 0, 1 ); + oldsize += FLOAT_INT_BITS; + } else { + MSG_WriteBits( msg, 1, 1 ); + if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && + trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { + // send as small integer + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); + } else { + // send as full floating point value + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *toF, 32 ); + } + } + } else { + if (*toF == 0) { + MSG_WriteBits( msg, 0, 1 ); + } else { + MSG_WriteBits( msg, 1, 1 ); + // integer + MSG_WriteBits( msg, *toF, field->bits ); + } + } + } +} + +/* +================== +MSG_ReadDeltaEntity + +The entity number has already been read from the message, which +is how the from state is identified. + +If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1 + +Can go from either a baseline or a previous packet_entity +================== +*/ +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, + int number) { + int i, lc; + int numFields; + netField_t *field; + int *fromF, *toF; + int print; + int trunc; + int startBit, endBit; + + if ( number < 0 || number >= MAX_GENTITIES) { + Com_Error( ERR_DROP, "Bad delta entity number: %i", number ); + } + + if ( msg->bit == 0 ) { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // check for a remove + if ( MSG_ReadBits( msg, 1 ) == 1 ) { + Com_Memset( to, 0, sizeof( *to ) ); + to->number = MAX_GENTITIES - 1; + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { + Com_Printf( "%3i: #%-3i remove\n", msg->readcount, number ); + } + return; + } + + // check for no delta + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *to = *from; + to->number = number; + return; + } + + numFields = ARRAY_LEN( entityStateFields ); + lc = MSG_ReadByte(msg); + + if ( lc > numFields || lc < 0 ) { + Com_Error( ERR_DROP, "invalid entityState field count" ); + } + + // shownet 2/3 will interleave with other printed info, -1 will + // just print the delta records` + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { + print = 1; + Com_Printf( "%3i: #%-3i ", msg->readcount, to->number ); + } else { + print = 0; + } + + to->number = number; + + for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( ! MSG_ReadBits( msg, 1 ) ) { + // no change + *toF = *fromF; + } else { + if ( field->bits == 0 ) { + // float + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *(float *)toF = 0.0f; + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + // integral float + trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + } + } else { + // full floating point value + *toF = MSG_ReadBits( msg, 32 ); + if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + } + } + } + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *toF = 0; + } else { + // integer + *toF = MSG_ReadBits( msg, field->bits ); + if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + } + } + } +// pcount[i]++; + } + } + for ( i = lc, field = &entityStateFields[lc] ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + // no change + *toF = *fromF; + } + + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } +} + + +/* +============================================================================ + +plyer_state_t communication + +============================================================================ +*/ + +// using the stringizing operator to save typing... +#define PSF(x) #x,(size_t)&((playerState_t*)0)->x + +netField_t playerStateFields[] = +{ +{ PSF(commandTime), 32 }, +{ PSF(origin[0]), 0 }, +{ PSF(origin[1]), 0 }, +{ PSF(bobCycle), 8 }, +{ PSF(velocity[0]), 0 }, +{ PSF(velocity[1]), 0 }, +{ PSF(viewangles[1]), 0 }, +{ PSF(viewangles[0]), 0 }, +{ PSF(weaponTime), -16 }, +{ PSF(origin[2]), 0 }, +{ PSF(velocity[2]), 0 }, +{ PSF(legsTimer), 8 }, +{ PSF(pm_time), -16 }, +{ PSF(eventSequence), 16 }, +{ PSF(torsoAnim), 8 }, +{ PSF(weaponAnim), 8 }, +{ PSF(movementDir), 4 }, +{ PSF(events[0]), 8 }, +{ PSF(legsAnim), 8 }, +{ PSF(events[1]), 8 }, +{ PSF(pm_flags), 24 }, +{ PSF(groundEntityNum), GENTITYNUM_BITS }, +{ PSF(weaponstate), 4 }, +{ PSF(eFlags), 16 }, +{ PSF(externalEvent), 10 }, +{ PSF(gravity), -16 }, +{ PSF(speed), -16 }, +{ PSF(delta_angles[1]), 16 }, +{ PSF(externalEventParm), 8 }, +{ PSF(viewheight), -8 }, +{ PSF(damageEvent), 8 }, +{ PSF(damageYaw), 8 }, +{ PSF(damagePitch), 8 }, +{ PSF(damageCount), 8 }, +{ PSF(ammo), 12 }, +{ PSF(clips), 4 }, +{ PSF(generic1), 10 }, +{ PSF(pm_type), 8 }, +{ PSF(delta_angles[0]), 16 }, +{ PSF(delta_angles[2]), 16 }, +{ PSF(torsoTimer), 12 }, +{ PSF(tauntTimer), 12 }, +{ PSF(eventParms[0]), 8 }, +{ PSF(eventParms[1]), 8 }, +{ PSF(clientNum), 8 }, +{ PSF(weapon), 5 }, +{ PSF(viewangles[2]), 0 }, +{ PSF(grapplePoint[0]), 0 }, +{ PSF(grapplePoint[1]), 0 }, +{ PSF(grapplePoint[2]), 0 }, +{ PSF(otherEntityNum), 10 }, +{ PSF(loopSound), 16 } +}; + +/* +============= +MSG_WriteDeltaPlayerstate + +============= +*/ +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ) { + int i; + playerState_t dummy; + int statsbits; + int persistantbits; + int miscbits; + int numFields; + int c; + netField_t *field; + int *fromF, *toF; + float fullFloat; + int trunc, lc; + + if (!from) { + from = &dummy; + Com_Memset (&dummy, 0, sizeof(dummy)); + } + + c = msg->cursize; + + numFields = ARRAY_LEN( playerStateFields ); + + lc = 0; + for ( i = 0, field = playerStateFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + if ( *fromF != *toF ) { + lc = i+1; + } + } + + MSG_WriteByte( msg, lc ); // # of changes + + oldsize += numFields - lc; + + for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( *fromF == *toF ) { + MSG_WriteBits( msg, 0, 1 ); // no change + continue; + } + + MSG_WriteBits( msg, 1, 1 ); // changed +// pcount[i]++; + + if ( field->bits == 0 ) { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && + trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { + // send as small integer + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); + } else { + // send as full floating point value + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *toF, 32 ); + } + } else { + // integer + MSG_WriteBits( msg, *toF, field->bits ); + } + } + c = msg->cursize - c; + + + // + // send the arrays + // + statsbits = 0; + for (i=0 ; istats[i] != from->stats[i]) { + statsbits |= 1<persistant[i] != from->persistant[i]) { + persistantbits |= 1<misc[i] != from->misc[i]) { + miscbits |= 1<stats[i]); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + + if ( persistantbits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteBits( msg, persistantbits, MAX_PERSISTANT ); + for (i=0 ; ipersistant[i]); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + + if ( miscbits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteBits( msg, miscbits, MAX_MISC ); + for (i=0 ; imisc[i] ); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } +} + + +/* +=================== +MSG_ReadDeltaPlayerstate +=================== +*/ +void MSG_ReadDeltaPlayerstate (msg_t *msg, playerState_t *from, playerState_t *to ) { + int i, lc; + int bits; + netField_t *field; + int numFields; + int startBit, endBit; + int print; + int *fromF, *toF; + int trunc; + playerState_t dummy; + + if ( !from ) { + from = &dummy; + Com_Memset( &dummy, 0, sizeof( dummy ) ); + } + *to = *from; + + if ( msg->bit == 0 ) { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // shownet 2/3 will interleave with other printed info, -2 will + // just print the delta records + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) { + print = 1; + Com_Printf( "%3i: playerstate ", msg->readcount ); + } else { + print = 0; + } + + numFields = ARRAY_LEN( playerStateFields ); + lc = MSG_ReadByte(msg); + + if ( lc > numFields || lc < 0 ) { + Com_Error( ERR_DROP, "invalid playerState field count" ); + } + + for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( ! MSG_ReadBits( msg, 1 ) ) { + // no change + *toF = *fromF; + } else { + if ( field->bits == 0 ) { + // float + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + // integral float + trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + } + } else { + // full floating point value + *toF = MSG_ReadBits( msg, 32 ); + if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + } + } + } else { + // integer + *toF = MSG_ReadBits( msg, field->bits ); + if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + } + } + } + } + for ( i=lc,field = &playerStateFields[lc];ioffset ); + toF = (int *)( (byte *)to + field->offset ); + // no change + *toF = *fromF; + } + + + // read the arrays + if (MSG_ReadBits( msg, 1 ) ) { + // parse stats + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_STATS"); + bits = MSG_ReadBits (msg, MAX_STATS); + for (i=0 ; istats[i] = MSG_ReadShort(msg); + } + } + } + + // parse persistant stats + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_PERSISTANT"); + bits = MSG_ReadBits (msg, MAX_PERSISTANT); + for (i=0 ; ipersistant[i] = MSG_ReadShort(msg); + } + } + } + + // parse misc data + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_MISC"); + bits = MSG_ReadBits (msg, MAX_MISC); + for (i=0 ; imisc[i] = MSG_ReadLong(msg); + } + } + } + } + + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } +} + +int msg_hData[256] = { +250315, // 0 +41193, // 1 +6292, // 2 +7106, // 3 +3730, // 4 +3750, // 5 +6110, // 6 +23283, // 7 +33317, // 8 +6950, // 9 +7838, // 10 +9714, // 11 +9257, // 12 +17259, // 13 +3949, // 14 +1778, // 15 +8288, // 16 +1604, // 17 +1590, // 18 +1663, // 19 +1100, // 20 +1213, // 21 +1238, // 22 +1134, // 23 +1749, // 24 +1059, // 25 +1246, // 26 +1149, // 27 +1273, // 28 +4486, // 29 +2805, // 30 +3472, // 31 +21819, // 32 +1159, // 33 +1670, // 34 +1066, // 35 +1043, // 36 +1012, // 37 +1053, // 38 +1070, // 39 +1726, // 40 +888, // 41 +1180, // 42 +850, // 43 +960, // 44 +780, // 45 +1752, // 46 +3296, // 47 +10630, // 48 +4514, // 49 +5881, // 50 +2685, // 51 +4650, // 52 +3837, // 53 +2093, // 54 +1867, // 55 +2584, // 56 +1949, // 57 +1972, // 58 +940, // 59 +1134, // 60 +1788, // 61 +1670, // 62 +1206, // 63 +5719, // 64 +6128, // 65 +7222, // 66 +6654, // 67 +3710, // 68 +3795, // 69 +1492, // 70 +1524, // 71 +2215, // 72 +1140, // 73 +1355, // 74 +971, // 75 +2180, // 76 +1248, // 77 +1328, // 78 +1195, // 79 +1770, // 80 +1078, // 81 +1264, // 82 +1266, // 83 +1168, // 84 +965, // 85 +1155, // 86 +1186, // 87 +1347, // 88 +1228, // 89 +1529, // 90 +1600, // 91 +2617, // 92 +2048, // 93 +2546, // 94 +3275, // 95 +2410, // 96 +3585, // 97 +2504, // 98 +2800, // 99 +2675, // 100 +6146, // 101 +3663, // 102 +2840, // 103 +14253, // 104 +3164, // 105 +2221, // 106 +1687, // 107 +3208, // 108 +2739, // 109 +3512, // 110 +4796, // 111 +4091, // 112 +3515, // 113 +5288, // 114 +4016, // 115 +7937, // 116 +6031, // 117 +5360, // 118 +3924, // 119 +4892, // 120 +3743, // 121 +4566, // 122 +4807, // 123 +5852, // 124 +6400, // 125 +6225, // 126 +8291, // 127 +23243, // 128 +7838, // 129 +7073, // 130 +8935, // 131 +5437, // 132 +4483, // 133 +3641, // 134 +5256, // 135 +5312, // 136 +5328, // 137 +5370, // 138 +3492, // 139 +2458, // 140 +1694, // 141 +1821, // 142 +2121, // 143 +1916, // 144 +1149, // 145 +1516, // 146 +1367, // 147 +1236, // 148 +1029, // 149 +1258, // 150 +1104, // 151 +1245, // 152 +1006, // 153 +1149, // 154 +1025, // 155 +1241, // 156 +952, // 157 +1287, // 158 +997, // 159 +1713, // 160 +1009, // 161 +1187, // 162 +879, // 163 +1099, // 164 +929, // 165 +1078, // 166 +951, // 167 +1656, // 168 +930, // 169 +1153, // 170 +1030, // 171 +1262, // 172 +1062, // 173 +1214, // 174 +1060, // 175 +1621, // 176 +930, // 177 +1106, // 178 +912, // 179 +1034, // 180 +892, // 181 +1158, // 182 +990, // 183 +1175, // 184 +850, // 185 +1121, // 186 +903, // 187 +1087, // 188 +920, // 189 +1144, // 190 +1056, // 191 +3462, // 192 +2240, // 193 +4397, // 194 +12136, // 195 +7758, // 196 +1345, // 197 +1307, // 198 +3278, // 199 +1950, // 200 +886, // 201 +1023, // 202 +1112, // 203 +1077, // 204 +1042, // 205 +1061, // 206 +1071, // 207 +1484, // 208 +1001, // 209 +1096, // 210 +915, // 211 +1052, // 212 +995, // 213 +1070, // 214 +876, // 215 +1111, // 216 +851, // 217 +1059, // 218 +805, // 219 +1112, // 220 +923, // 221 +1103, // 222 +817, // 223 +1899, // 224 +1872, // 225 +976, // 226 +841, // 227 +1127, // 228 +956, // 229 +1159, // 230 +950, // 231 +7791, // 232 +954, // 233 +1289, // 234 +933, // 235 +1127, // 236 +3207, // 237 +1020, // 238 +927, // 239 +1355, // 240 +768, // 241 +1040, // 242 +745, // 243 +952, // 244 +805, // 245 +1073, // 246 +740, // 247 +1013, // 248 +805, // 249 +1008, // 250 +796, // 251 +996, // 252 +1057, // 253 +11457, // 254 +13504, // 255 +}; + +void MSG_initHuffman( void ) { + int i,j; + + msgInit = qtrue; + Huff_Init(&msgHuff); + for(i=0;i<256;i++) { + for (j=0;jsock = sock; + chan->remoteAddress = adr; + chan->qport = qport; + chan->incomingSequence = 0; + chan->outgoingSequence = 1; +} + +// TTimo: unused, commenting out to make gcc happy +#if 0 +/* +============== +Netchan_ScramblePacket + +A probably futile attempt to make proxy hacking somewhat +more difficult. +============== +*/ +#define SCRAMBLE_START 6 +static void Netchan_ScramblePacket( msg_t *buf ) { + unsigned seed; + int i, j, c, mask, temp; + int seq[MAX_PACKETLEN]; + + seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 ); + c = buf->cursize; + if ( c <= SCRAMBLE_START ) { + return; + } + if ( c > MAX_PACKETLEN ) { + Com_Error( ERR_DROP, "MAX_PACKETLEN" ); + } + + // generate a sequence of "random" numbers + for (i = 0 ; i < c ; i++) { + seed = (119 * seed + 1); + seq[i] = seed; + } + + // transpose each character + for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) { + } + mask >>= 1; + for (i = SCRAMBLE_START ; i < c ; i++) { + j = SCRAMBLE_START + ( seq[i] & mask ); + temp = buf->data[j]; + buf->data[j] = buf->data[i]; + buf->data[i] = temp; + } + + // byte xor the data after the header + for (i = SCRAMBLE_START ; i < c ; i++) { + buf->data[i] ^= seq[i]; + } +} + +static void Netchan_UnScramblePacket( msg_t *buf ) { + unsigned seed; + int i, j, c, mask, temp; + int seq[MAX_PACKETLEN]; + + seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 ); + c = buf->cursize; + if ( c <= SCRAMBLE_START ) { + return; + } + if ( c > MAX_PACKETLEN ) { + Com_Error( ERR_DROP, "MAX_PACKETLEN" ); + } + + // generate a sequence of "random" numbers + for (i = 0 ; i < c ; i++) { + seed = (119 * seed + 1); + seq[i] = seed; + } + + // byte xor the data after the header + for (i = SCRAMBLE_START ; i < c ; i++) { + buf->data[i] ^= seq[i]; + } + + // transpose each character in reverse order + for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) { + } + mask >>= 1; + for (i = c-1 ; i >= SCRAMBLE_START ; i--) { + j = SCRAMBLE_START + ( seq[i] & mask ); + temp = buf->data[j]; + buf->data[j] = buf->data[i]; + buf->data[i] = temp; + } +} +#endif + +/* +================= +Netchan_TransmitNextFragment + +Send one fragment of the current message +================= +*/ +void Netchan_TransmitNextFragment( netchan_t *chan ) { + msg_t send; + byte send_buf[MAX_PACKETLEN]; + int fragmentLength; + + // write the packet header + MSG_InitOOB (&send, send_buf, sizeof(send_buf)); // <-- only do the oob here + + MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { + MSG_WriteShort( &send, qport->integer ); + } + + // copy the reliable message to the packet first + fragmentLength = FRAGMENT_SIZE; + if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) { + fragmentLength = chan->unsentLength - chan->unsentFragmentStart; + } + + MSG_WriteShort( &send, chan->unsentFragmentStart ); + MSG_WriteShort( &send, fragmentLength ); + MSG_WriteData( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength ); + + // send the datagram + NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + + if ( showpackets->integer ) { + Com_Printf ("%s send %4i : s=%i fragment=%i,%i\n" + , netsrcString[ chan->sock ] + , send.cursize + , chan->outgoingSequence + , chan->unsentFragmentStart, fragmentLength); + } + + chan->unsentFragmentStart += fragmentLength; + + // this exit condition is a little tricky, because a packet + // that is exactly the fragment length still needs to send + // a second packet of zero length so that the other side + // can tell there aren't more to follow + if ( chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE ) { + chan->outgoingSequence++; + chan->unsentFragments = qfalse; + } +} + + +/* +=============== +Netchan_Transmit + +Sends a message to a connection, fragmenting if necessary +A 0 length will still generate a packet. +================ +*/ +void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { + msg_t send; + byte send_buf[MAX_PACKETLEN]; + + if ( length > MAX_MSGLEN ) { + Com_Error( ERR_DROP, "Netchan_Transmit: length = %i", length ); + } + chan->unsentFragmentStart = 0; + + // fragment large reliable messages + if ( length >= FRAGMENT_SIZE ) { + chan->unsentFragments = qtrue; + chan->unsentLength = length; + Com_Memcpy( chan->unsentBuffer, data, length ); + + // only send the first fragment now + Netchan_TransmitNextFragment( chan ); + + return; + } + + // write the packet header + MSG_InitOOB (&send, send_buf, sizeof(send_buf)); + + MSG_WriteLong( &send, chan->outgoingSequence ); + chan->outgoingSequence++; + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { + MSG_WriteShort( &send, qport->integer ); + } + + MSG_WriteData( &send, data, length ); + + // send the datagram + NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + + if ( showpackets->integer ) { + Com_Printf( "%s send %4i : s=%i ack=%i\n" + , netsrcString[ chan->sock ] + , send.cursize + , chan->outgoingSequence - 1 + , chan->incomingSequence ); + } +} + +/* +================= +Netchan_Process + +Returns qfalse if the message should not be processed due to being +out of order or a fragment. + +Msg must be large enough to hold MAX_MSGLEN, because if this is the +final fragment of a multi-part message, the entire thing will be +copied out. +================= +*/ +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { + int sequence; + int qport; + int fragmentStart, fragmentLength; + qboolean fragmented; + + // XOR unscramble all data in the packet after the header +// Netchan_UnScramblePacket( msg ); + + // get sequence numbers + MSG_BeginReadingOOB( msg ); + sequence = MSG_ReadLong( msg ); + + // check for fragment information + if ( sequence & FRAGMENT_BIT ) { + sequence &= ~FRAGMENT_BIT; + fragmented = qtrue; + } else { + fragmented = qfalse; + } + + // read the qport if we are a server + if ( chan->sock == NS_SERVER ) { + qport = MSG_ReadShort( msg ); + } + + // read the fragment information + if ( fragmented ) { + fragmentStart = MSG_ReadShort( msg ); + fragmentLength = MSG_ReadShort( msg ); + } else { + fragmentStart = 0; // stop warning message + fragmentLength = 0; + } + + if ( showpackets->integer ) { + if ( fragmented ) { + Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n" + , netsrcString[ chan->sock ] + , msg->cursize + , sequence + , fragmentStart, fragmentLength ); + } else { + Com_Printf( "%s recv %4i : s=%i\n" + , netsrcString[ chan->sock ] + , msg->cursize + , sequence ); + } + } + + // + // discard out of order or duplicated packets + // + if ( sequence <= chan->incomingSequence ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Out of order packet %i at %i\n" + , NET_AdrToString( chan->remoteAddress ) + , sequence + , chan->incomingSequence ); + } + return qfalse; + } + + // + // dropped packets don't keep the message from being used + // + chan->dropped = sequence - (chan->incomingSequence+1); + if ( chan->dropped > 0 ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Dropped %i packets at %i\n" + , NET_AdrToString( chan->remoteAddress ) + , chan->dropped + , sequence ); + } + } + + + // + // if this is the final framgent of a reliable message, + // bump incoming_reliable_sequence + // + if ( fragmented ) { + // TTimo + // make sure we add the fragments in correct order + // either a packet was dropped, or we received this one too soon + // we don't reconstruct the fragments. we will wait till this fragment gets to us again + // (NOTE: we could probably try to rebuild by out of order chunks if needed) + if ( sequence != chan->fragmentSequence ) { + chan->fragmentSequence = sequence; + chan->fragmentLength = 0; + } + + // if we missed a fragment, dump the message + if ( fragmentStart != chan->fragmentLength ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Dropped a message fragment\n" + , NET_AdrToString( chan->remoteAddress )); + } + // we can still keep the part that we have so far, + // so we don't need to clear chan->fragmentLength + return qfalse; + } + + // copy the fragment to the fragment buffer + if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize || + chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf ("%s:illegal fragment length\n" + , NET_AdrToString (chan->remoteAddress ) ); + } + return qfalse; + } + + Com_Memcpy( chan->fragmentBuffer + chan->fragmentLength, + msg->data + msg->readcount, fragmentLength ); + + chan->fragmentLength += fragmentLength; + + // if this wasn't the last fragment, don't process anything + if ( fragmentLength == FRAGMENT_SIZE ) { + return qfalse; + } + + if ( chan->fragmentLength > msg->maxsize ) { + Com_Printf( "%s:fragmentLength %i > msg->maxsize\n" + , NET_AdrToString (chan->remoteAddress ), + chan->fragmentLength ); + return qfalse; + } + + // copy the full message over the partial fragment + + // make sure the sequence number is still there + *(int *)msg->data = LittleLong( sequence ); + + Com_Memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength ); + msg->cursize = chan->fragmentLength + 4; + chan->fragmentLength = 0; + msg->readcount = 4; // past the sequence number + msg->bit = 32; // past the sequence number + + // TTimo + // clients were not acking fragmented messages + chan->incomingSequence = sequence; + + return qtrue; + } + + // + // the message can now be read from the current message pointer + // + chan->incomingSequence = sequence; + + return qtrue; +} + + +//============================================================================== + + + +/* +============================================================================= + +LOOPBACK BUFFERS FOR LOCAL PLAYER + +============================================================================= +*/ + +// there needs to be enough loopback messages to hold a complete +// gamestate of maximum size +#define MAX_LOOPBACK 16 + +typedef struct { + byte data[MAX_PACKETLEN]; + int datalen; +} loopmsg_t; + +typedef struct { + loopmsg_t msgs[MAX_LOOPBACK]; + int get, send; +} loopback_t; + +loopback_t loopbacks[2]; + + +qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message) +{ + int i; + loopback_t *loop; + + loop = &loopbacks[sock]; + + if (loop->send - loop->get > MAX_LOOPBACK) + loop->get = loop->send - MAX_LOOPBACK; + + if (loop->get >= loop->send) + return qfalse; + + i = loop->get & (MAX_LOOPBACK-1); + loop->get++; + + Com_Memcpy (net_message->data, loop->msgs[i].data, loop->msgs[i].datalen); + net_message->cursize = loop->msgs[i].datalen; + Com_Memset (net_from, 0, sizeof(*net_from)); + net_from->type = NA_LOOPBACK; + return qtrue; + +} + + +void NET_SendLoopPacket (netsrc_t sock, int length, const void *data, netadr_t to) +{ + int i; + loopback_t *loop; + + loop = &loopbacks[sock^1]; + + i = loop->send & (MAX_LOOPBACK-1); + loop->send++; + + Com_Memcpy (loop->msgs[i].data, data, length); + loop->msgs[i].datalen = length; +} + +//============================================================================= + +typedef struct packetQueue_s { + struct packetQueue_s *next; + int length; + byte *data; + netadr_t to; + int release; +} packetQueue_t; + +packetQueue_t *packetQueue = NULL; + +static void NET_QueuePacket( int length, const void *data, netadr_t to, + int offset ) +{ + packetQueue_t *new, *next = packetQueue; + + if(offset > 999) + offset = 999; + + new = S_Malloc(sizeof(packetQueue_t)); + new->data = S_Malloc(length); + Com_Memcpy(new->data, data, length); + new->length = length; + new->to = to; + new->release = Sys_Milliseconds() + (int)((float)offset / com_timescale->value); + new->next = NULL; + + if(!packetQueue) { + packetQueue = new; + return; + } + while(next) { + if(!next->next) { + next->next = new; + return; + } + next = next->next; + } +} + +void NET_FlushPacketQueue(void) +{ + packetQueue_t *last; + int now; + + while(packetQueue) { + now = Sys_Milliseconds(); + if(packetQueue->release >= now) + break; + Sys_SendPacket(packetQueue->length, packetQueue->data, + packetQueue->to); + last = packetQueue; + packetQueue = packetQueue->next; + Z_Free(last->data); + Z_Free(last); + } +} + +void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ) { + + // sequenced packets are shown in netchan, so just show oob + if ( showpackets->integer && *(int *)data == -1 ) { + Com_Printf ("send packet %4i\n", length); + } + + if ( to.type == NA_LOOPBACK ) { + NET_SendLoopPacket (sock, length, data, to); + return; + } + if ( to.type == NA_BAD ) { + return; + } + + if ( sock == NS_CLIENT && cl_packetdelay->integer > 0 ) { + NET_QueuePacket( length, data, to, cl_packetdelay->integer ); + } + else if ( sock == NS_SERVER && sv_packetdelay->integer > 0 ) { + NET_QueuePacket( length, data, to, sv_packetdelay->integer ); + } + else { + Sys_SendPacket( length, data, to ); + } +} + +/* +=============== +NET_OutOfBandPrint + +Sends a text message in an out-of-band datagram +================ +*/ +void QDECL NET_OutOfBandPrint( netsrc_t sock, netadr_t adr, const char *format, ... ) { + va_list argptr; + char string[MAX_MSGLEN]; + + + // set the header + string[0] = -1; + string[1] = -1; + string[2] = -1; + string[3] = -1; + + va_start( argptr, format ); + Q_vsnprintf( string+4, sizeof(string)-4, format, argptr ); + va_end( argptr ); + + // send the datagram + NET_SendPacket( sock, strlen( string ), string, adr ); +} + +/* +=============== +NET_OutOfBandPrint + +Sends a data message in an out-of-band datagram (only used for "connect") +================ +*/ +void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ) { + byte string[MAX_MSGLEN*2]; + int i; + msg_t mbuf; + + // set the header + string[0] = 0xff; + string[1] = 0xff; + string[2] = 0xff; + string[3] = 0xff; + + for(i=0;itype = NA_LOOPBACK; +// as NA_LOOPBACK doesn't require ports report port was given. + return 1; + } + + Q_strncpyz( base, s, sizeof( base ) ); + + if(*base == '[' || Q_CountChar(base, ':') > 1) + { + // This is an ipv6 address, handle it specially. + search = strchr(base, ']'); + if(search) + { + *search = '\0'; + search++; + + if(*search == ':') + port = search + 1; + } + + if(*base == '[') + search = base + 1; + else + search = base; + } + else + { + // look for a port number + port = strchr( base, ':' ); + + if ( port ) { + *port = '\0'; + port++; + } + + search = base; + } + + if(!Sys_StringToAdr(search, a, family)) + { + a->type = NA_BAD; + return 0; + } + + if(port) + { + a->port = BigShort((short) atoi(port)); + return 1; + } + else + { + a->port = BigShort(PORT_SERVER); + return 2; + } +} diff --git a/src/qcommon/net_ip.c b/src/qcommon/net_ip.c new file mode 100644 index 0000000..e3a95e4 --- /dev/null +++ b/src/qcommon/net_ip.c @@ -0,0 +1,1727 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +#ifdef _WIN32 +# include +# include +# if WINVER < 0x501 +# ifdef __MINGW32__ + // wspiapi.h isn't available on MinGW, so if it's + // present it's because the end user has added it + // and we should look for it in our tree +# include "wspiapi.h" +# else +# include +# endif +# else +# include +# endif + +typedef int socklen_t; +# ifdef ADDRESS_FAMILY +# define sa_family_t ADDRESS_FAMILY +# else +typedef unsigned short sa_family_t; +# endif + +# define EAGAIN WSAEWOULDBLOCK +# define EADDRNOTAVAIL WSAEADDRNOTAVAIL +# define EAFNOSUPPORT WSAEAFNOSUPPORT +# define ECONNRESET WSAECONNRESET +typedef u_long ioctlarg_t; +# define socketError WSAGetLastError( ) + +static WSADATA winsockdata; +static qboolean winsockInitialized = qfalse; + +#else + +# if MAC_OS_X_VERSION_MIN_REQUIRED == 1020 + // needed for socklen_t on OSX 10.2 +# define _BSD_SOCKLEN_T_ +# endif + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# if !defined(__sun) && !defined(__sgi) +# include +# endif + +# ifdef __sun +# include +# endif + +typedef int SOCKET; +# define INVALID_SOCKET -1 +# define SOCKET_ERROR -1 +# define closesocket close +# define ioctlsocket ioctl +typedef int ioctlarg_t; +# define socketError errno + +#endif + +static qboolean usingSocks = qfalse; +static int networkingEnabled = 0; + +static cvar_t *net_enabled; + +static cvar_t *net_socksEnabled; +static cvar_t *net_socksServer; +static cvar_t *net_socksPort; +static cvar_t *net_socksUsername; +static cvar_t *net_socksPassword; + +static cvar_t *net_ip; +static cvar_t *net_ip6; +static cvar_t *net_port; +static cvar_t *net_port6; +static cvar_t *net_mcast6addr; +static cvar_t *net_mcast6iface; + +static cvar_t *net_dropsim; + +static struct sockaddr socksRelayAddr; + +static SOCKET ip_socket = INVALID_SOCKET; +static SOCKET ip6_socket = INVALID_SOCKET; +static SOCKET socks_socket = INVALID_SOCKET; +static SOCKET multicast6_socket = INVALID_SOCKET; + +// Keep track of currently joined multicast group. +static struct ipv6_mreq curgroup; +// And the currently bound address. +static struct sockaddr_in6 boundto; + +#ifndef IF_NAMESIZE + #define IF_NAMESIZE 16 +#endif + +// use an admin local address per default so that network admins can decide on how to handle quake3 traffic. +#define NET_MULTICAST_IP6 "ff04::696f:7175:616b:6533" + +#define MAX_IPS 32 + +typedef struct +{ + char ifname[IF_NAMESIZE]; + + netadrtype_t type; + sa_family_t family; + struct sockaddr_storage addr; + struct sockaddr_storage netmask; +} nip_localaddr_t; + +static nip_localaddr_t localIP[MAX_IPS]; +static int numIP; + + +//============================================================================= + + +/* +==================== +NET_ErrorString +==================== +*/ +char *NET_ErrorString( void ) { +#ifdef _WIN32 + //FIXME: replace with FormatMessage? + switch( socketError ) { + case WSAEINTR: return "WSAEINTR"; + case WSAEBADF: return "WSAEBADF"; + case WSAEACCES: return "WSAEACCES"; + case WSAEDISCON: return "WSAEDISCON"; + case WSAEFAULT: return "WSAEFAULT"; + case WSAEINVAL: return "WSAEINVAL"; + case WSAEMFILE: return "WSAEMFILE"; + case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: return "WSAEINPROGRESS"; + case WSAEALREADY: return "WSAEALREADY"; + case WSAENOTSOCK: return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: return "WSAENETDOWN"; + case WSAENETUNREACH: return "WSAENETUNREACH"; + case WSAENETRESET: return "WSAENETRESET"; + case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR"; + case WSAECONNRESET: return "WSAECONNRESET"; + case WSAENOBUFS: return "WSAENOBUFS"; + case WSAEISCONN: return "WSAEISCONN"; + case WSAENOTCONN: return "WSAENOTCONN"; + case WSAESHUTDOWN: return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: return "WSAETIMEDOUT"; + case WSAECONNREFUSED: return "WSAECONNREFUSED"; + case WSAELOOP: return "WSAELOOP"; + case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; + case WSASYSNOTREADY: return "WSASYSNOTREADY"; + case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; + case WSANOTINITIALISED: return "WSANOTINITIALISED"; + case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; + case WSATRY_AGAIN: return "WSATRY_AGAIN"; + case WSANO_RECOVERY: return "WSANO_RECOVERY"; + case WSANO_DATA: return "WSANO_DATA"; + default: return "NO ERROR"; + } +#else + return strerror(socketError); +#endif +} + +static void NetadrToSockadr( netadr_t *a, struct sockaddr *s ) { + if( a->type == NA_BROADCAST ) { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_port = a->port; + ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST; + } + else if( a->type == NA_IP ) { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip; + ((struct sockaddr_in *)s)->sin_port = a->port; + } + else if( a->type == NA_IP6 ) { + ((struct sockaddr_in6 *)s)->sin6_family = AF_INET6; + ((struct sockaddr_in6 *)s)->sin6_addr = * ((struct in6_addr *) &a->ip6); + ((struct sockaddr_in6 *)s)->sin6_port = a->port; + ((struct sockaddr_in6 *)s)->sin6_scope_id = a->scope_id; + } + else if(a->type == NA_MULTICAST6) + { + ((struct sockaddr_in6 *)s)->sin6_family = AF_INET6; + ((struct sockaddr_in6 *)s)->sin6_addr = curgroup.ipv6mr_multiaddr; + ((struct sockaddr_in6 *)s)->sin6_port = a->port; + } +} + + +static void SockadrToNetadr( struct sockaddr *s, netadr_t *a ) { + if (s->sa_family == AF_INET) { + a->type = NA_IP; + *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + a->port = ((struct sockaddr_in *)s)->sin_port; + } + else if(s->sa_family == AF_INET6) + { + a->type = NA_IP6; + memcpy(a->ip6, &((struct sockaddr_in6 *)s)->sin6_addr, sizeof(a->ip6)); + a->port = ((struct sockaddr_in6 *)s)->sin6_port; + a->scope_id = ((struct sockaddr_in6 *)s)->sin6_scope_id; + } +} + + +static struct addrinfo *SearchAddrInfo(struct addrinfo *hints, sa_family_t family) +{ + while(hints) + { + if(hints->ai_family == family) + return hints; + + hints = hints->ai_next; + } + + return NULL; +} + +/* +============= +Sys_StringToSockaddr +============= +*/ +static qboolean Sys_StringToSockaddr(const char *s, struct sockaddr *sadr, int sadr_len, sa_family_t family) +{ + struct addrinfo hints; + struct addrinfo *res = NULL; + struct addrinfo *search = NULL; + struct addrinfo *hintsp; + int retval; + + memset(sadr, '\0', sizeof(*sadr)); + memset(&hints, '\0', sizeof(hints)); + + hintsp = &hints; + hintsp->ai_family = family; + hintsp->ai_socktype = SOCK_DGRAM; + + retval = getaddrinfo(s, NULL, hintsp, &res); + + if(!retval) + { + if(family == AF_UNSPEC) + { + // Decide here and now which protocol family to use + if(net_enabled->integer & NET_PRIOV6) + { + if(net_enabled->integer & NET_ENABLEV6) + search = SearchAddrInfo(res, AF_INET6); + + if(!search && (net_enabled->integer & NET_ENABLEV4)) + search = SearchAddrInfo(res, AF_INET); + } + else + { + if(net_enabled->integer & NET_ENABLEV4) + search = SearchAddrInfo(res, AF_INET); + + if(!search && (net_enabled->integer & NET_ENABLEV6)) + search = SearchAddrInfo(res, AF_INET6); + } + } + else + search = SearchAddrInfo(res, family); + + if(search) + { + if(search->ai_addrlen > sadr_len) + search->ai_addrlen = sadr_len; + + memcpy(sadr, search->ai_addr, search->ai_addrlen); + freeaddrinfo(search); + + return qtrue; + } + else + Com_Printf("Sys_StringToSockaddr: Error resolving %s: No address of required type found.\n", s); + } + else + Com_Printf("Sys_StringToSockaddr: Error resolving %s: %s\n", s, gai_strerror(retval)); + + if(res) + freeaddrinfo(res); + + return qfalse; +} + +/* +============= +Sys_SockaddrToString +============= +*/ +static void Sys_SockaddrToString(char *dest, int destlen, struct sockaddr *input) +{ + socklen_t inputlen; + + if (input->sa_family == AF_INET6) + inputlen = sizeof(struct sockaddr_in6); + else + inputlen = sizeof(struct sockaddr_in); + + if(getnameinfo(input, inputlen, dest, destlen, NULL, 0, NI_NUMERICHOST) && destlen > 0) + *dest = '\0'; +} + +/* +============= +Sys_StringToAdr +============= +*/ +qboolean Sys_StringToAdr( const char *s, netadr_t *a, netadrtype_t family ) { + struct sockaddr_storage sadr; + sa_family_t fam; + + switch(family) + { + case NA_IP: + fam = AF_INET; + break; + case NA_IP6: + fam = AF_INET6; + break; + default: + fam = AF_UNSPEC; + break; + } + if( !Sys_StringToSockaddr(s, (struct sockaddr *) &sadr, sizeof(sadr), fam ) ) { + return qfalse; + } + + SockadrToNetadr( (struct sockaddr *) &sadr, a ); + return qtrue; +} + +/* +=================== +NET_CompareBaseAdrMask + +Compare without port, and up to the bit number given in netmask. +=================== +*/ +qboolean NET_CompareBaseAdrMask(netadr_t a, netadr_t b, int netmask) +{ + qboolean differed; + byte cmpmask, *addra, *addrb; + int curbyte; + + if (a.type != b.type) + return qfalse; + + if (a.type == NA_LOOPBACK) + return qtrue; + + if(a.type == NA_IP) + { + addra = (byte *) &a.ip; + addrb = (byte *) &b.ip; + + if(netmask < 0 || netmask > 32) + netmask = 32; + } + else if(a.type == NA_IP6) + { + addra = (byte *) &a.ip6; + addrb = (byte *) &b.ip6; + + if(netmask < 0 || netmask > 128) + netmask = 128; + } + else + { + Com_Printf ("NET_CompareBaseAdr: bad address type\n"); + return qfalse; + } + + differed = qfalse; + curbyte = 0; + + while(netmask > 7) + { + if(addra[curbyte] != addrb[curbyte]) + { + differed = qtrue; + break; + } + + curbyte++; + netmask -= 8; + } + + if(differed) + return qfalse; + + if(netmask) + { + cmpmask = (1 << netmask) - 1; + cmpmask <<= 8 - netmask; + + if((addra[curbyte] & cmpmask) == (addrb[curbyte] & cmpmask)) + return qtrue; + } + else + return qtrue; + + return qfalse; +} + + +/* +=================== +NET_CompareBaseAdr + +Compares without the port +=================== +*/ +qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b) +{ + return NET_CompareBaseAdrMask(a, b, -1); +} + +const char *NET_AdrToString (netadr_t a) +{ + static char s[NET_ADDRSTRMAXLEN]; + + if (a.type == NA_LOOPBACK) + Com_sprintf (s, sizeof(s), "loopback"); + else if (a.type == NA_IP || a.type == NA_IP6) + { + struct sockaddr_storage sadr; + + memset(&sadr, 0, sizeof(sadr)); + NetadrToSockadr(&a, (struct sockaddr *) &sadr); + Sys_SockaddrToString(s, sizeof(s), (struct sockaddr *) &sadr); + } + + return s; +} + +const char *NET_AdrToStringwPort (netadr_t a) +{ + static char s[NET_ADDRSTRMAXLEN]; + + if (a.type == NA_LOOPBACK) + Com_sprintf (s, sizeof(s), "loopback"); + else if(a.type == NA_IP) + Com_sprintf(s, sizeof(s), "%s:%hu", NET_AdrToString(a), ntohs(a.port)); + else if(a.type == NA_IP6) + Com_sprintf(s, sizeof(s), "[%s]:%hu", NET_AdrToString(a), ntohs(a.port)); + + return s; +} + + +qboolean NET_CompareAdr (netadr_t a, netadr_t b) +{ + if(!NET_CompareBaseAdr(a, b)) + return qfalse; + + if (a.type == NA_IP || a.type == NA_IP6) + { + if (a.port == b.port) + return qtrue; + } + else + return qtrue; + + return qfalse; +} + + +qboolean NET_IsLocalAddress( netadr_t adr ) { + return adr.type == NA_LOOPBACK; +} + +//============================================================================= + +/* +================== +NET_GetPacket + +Receive one packet +================== +*/ +#ifdef _DEBUG +int recvfromCount; +#endif + +qboolean NET_GetPacket(netadr_t *net_from, msg_t *net_message, fd_set *fdr) +{ + int ret; + struct sockaddr_storage from; + socklen_t fromlen; + int err; + +#ifdef _DEBUG + recvfromCount++; // performance check +#endif + + if(ip_socket != INVALID_SOCKET && FD_ISSET(ip_socket, fdr)) + { + fromlen = sizeof(from); + ret = recvfrom( ip_socket, (void *)net_message->data, net_message->maxsize, 0, (struct sockaddr *) &from, &fromlen ); + + if (ret == SOCKET_ERROR) + { + err = socketError; + + if( err != EAGAIN && err != ECONNRESET ) + Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() ); + } + else + { + + memset( ((struct sockaddr_in *)&from)->sin_zero, 0, 8 ); + + if ( usingSocks && memcmp( &from, &socksRelayAddr, fromlen ) == 0 ) { + if ( ret < 10 || net_message->data[0] != 0 || net_message->data[1] != 0 || net_message->data[2] != 0 || net_message->data[3] != 1 ) { + return qfalse; + } + net_from->type = NA_IP; + net_from->ip[0] = net_message->data[4]; + net_from->ip[1] = net_message->data[5]; + net_from->ip[2] = net_message->data[6]; + net_from->ip[3] = net_message->data[7]; + net_from->port = *(short *)&net_message->data[8]; + net_message->readcount = 10; + } + else { + SockadrToNetadr( (struct sockaddr *) &from, net_from ); + net_message->readcount = 0; + } + + if( ret >= net_message->maxsize ) { + Com_Printf( "Oversize packet from %s\n", NET_AdrToString (*net_from) ); + return qfalse; + } + + net_message->cursize = ret; + return qtrue; + } + } + + if(ip6_socket != INVALID_SOCKET && FD_ISSET(ip6_socket, fdr)) + { + fromlen = sizeof(from); + ret = recvfrom(ip6_socket, (void *)net_message->data, net_message->maxsize, 0, (struct sockaddr *) &from, &fromlen); + + if (ret == SOCKET_ERROR) + { + err = socketError; + + if( err != EAGAIN && err != ECONNRESET ) + Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() ); + } + else + { + SockadrToNetadr((struct sockaddr *) &from, net_from); + net_message->readcount = 0; + + if(ret >= net_message->maxsize) + { + Com_Printf( "Oversize packet from %s\n", NET_AdrToString (*net_from) ); + return qfalse; + } + + net_message->cursize = ret; + return qtrue; + } + } + + if(multicast6_socket != INVALID_SOCKET && multicast6_socket != ip6_socket && FD_ISSET(multicast6_socket, fdr)) + { + fromlen = sizeof(from); + ret = recvfrom(multicast6_socket, (void *)net_message->data, net_message->maxsize, 0, (struct sockaddr *) &from, &fromlen); + + if (ret == SOCKET_ERROR) + { + err = socketError; + + if( err != EAGAIN && err != ECONNRESET ) + Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() ); + } + else + { + SockadrToNetadr((struct sockaddr *) &from, net_from); + net_message->readcount = 0; + + if(ret >= net_message->maxsize) + { + Com_Printf( "Oversize packet from %s\n", NET_AdrToString (*net_from) ); + return qfalse; + } + + net_message->cursize = ret; + return qtrue; + } + } + + + return qfalse; +} + +//============================================================================= + +static char socksBuf[4096]; + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket( int length, const void *data, netadr_t to ) { + int ret = SOCKET_ERROR; + struct sockaddr_storage addr; + + if( to.type != NA_BROADCAST && to.type != NA_IP && to.type != NA_IP6 && to.type != NA_MULTICAST6) + { + Com_Error( ERR_FATAL, "Sys_SendPacket: bad address type" ); + return; + } + + if( (ip_socket == INVALID_SOCKET && to.type == NA_IP) || + (ip6_socket == INVALID_SOCKET && to.type == NA_IP6) || + (ip6_socket == INVALID_SOCKET && to.type == NA_MULTICAST6) ) + return; + + if(to.type == NA_MULTICAST6 && (net_enabled->integer & NET_DISABLEMCAST)) + return; + + memset(&addr, 0, sizeof(addr)); + NetadrToSockadr( &to, (struct sockaddr *) &addr ); + + if( usingSocks && to.type == NA_IP ) { + socksBuf[0] = 0; // reserved + socksBuf[1] = 0; + socksBuf[2] = 0; // fragment (not fragmented) + socksBuf[3] = 1; // address type: IPV4 + *(int *)&socksBuf[4] = ((struct sockaddr_in *)&addr)->sin_addr.s_addr; + *(short *)&socksBuf[8] = ((struct sockaddr_in *)&addr)->sin_port; + memcpy( &socksBuf[10], data, length ); + ret = sendto( ip_socket, socksBuf, length+10, 0, &socksRelayAddr, sizeof(socksRelayAddr) ); + } + else { + if(addr.ss_family == AF_INET) + ret = sendto( ip_socket, data, length, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_in) ); + else if(addr.ss_family == AF_INET6) + ret = sendto( ip6_socket, data, length, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_in6) ); + } + if( ret == SOCKET_ERROR ) { + int err = socketError; + + // wouldblock is silent + if( err == EAGAIN ) { + return; + } + + // some PPP links do not allow broadcasts and return an error + if( ( err == EADDRNOTAVAIL ) && ( ( to.type == NA_BROADCAST ) ) ) { + return; + } + + Com_Printf( "NET_SendPacket: %s\n", NET_ErrorString() ); + } +} + + +//============================================================================= + +/* +================== +Sys_IsLANAddress + +LAN clients will have their rate var ignored +================== +*/ +qboolean Sys_IsLANAddress( netadr_t adr ) { + int index, run, addrsize; + qboolean differed; + byte *compareadr, *comparemask, *compareip; + + if( adr.type == NA_LOOPBACK ) { + return qtrue; + } + + if( adr.type == NA_IP ) + { + // RFC1918: + // 10.0.0.0 - 10.255.255.255 (10/8 prefix) + // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) + // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) + if(adr.ip[0] == 10) + return qtrue; + if(adr.ip[0] == 172 && (adr.ip[1]&0xf0) == 16) + return qtrue; + if(adr.ip[0] == 192 && adr.ip[1] == 168) + return qtrue; + + if(adr.ip[0] == 127) + return qtrue; + } + else if(adr.type == NA_IP6) + { + if(adr.ip6[0] == 0xfe && (adr.ip6[1] & 0xc0) == 0x80) + return qtrue; + if((adr.ip6[0] & 0xfe) == 0xfc) + return qtrue; + } + + // Now compare against the networks this computer is member of. + for(index = 0; index < numIP; index++) + { + if(localIP[index].type == adr.type) + { + if(adr.type == NA_IP) + { + compareip = (byte *) &((struct sockaddr_in *) &localIP[index].addr)->sin_addr.s_addr; + comparemask = (byte *) &((struct sockaddr_in *) &localIP[index].netmask)->sin_addr.s_addr; + compareadr = adr.ip; + + addrsize = sizeof(adr.ip); + } + else + { + // TODO? should we check the scope_id here? + + compareip = (byte *) &((struct sockaddr_in6 *) &localIP[index].addr)->sin6_addr; + comparemask = (byte *) &((struct sockaddr_in6 *) &localIP[index].netmask)->sin6_addr; + compareadr = adr.ip6; + + addrsize = sizeof(adr.ip6); + } + + differed = qfalse; + for(run = 0; run < addrsize; run++) + { + if((compareip[run] & comparemask[run]) != (compareadr[run] & comparemask[run])) + { + differed = qtrue; + break; + } + } + + if(!differed) + return qtrue; + + } + } + + return qfalse; +} + +/* +================== +Sys_ShowIP +================== +*/ +void Sys_ShowIP(void) { + int i; + char addrbuf[NET_ADDRSTRMAXLEN]; + + for(i = 0; i < numIP; i++) + { + Sys_SockaddrToString(addrbuf, sizeof(addrbuf), (struct sockaddr *) &localIP[i].addr); + + if(localIP[i].type == NA_IP) + Com_Printf( "IP: %s\n", addrbuf); + else if(localIP[i].type == NA_IP6) + Com_Printf( "IP6: %s\n", addrbuf); + } +} + + +//============================================================================= + + +/* +==================== +NET_IPSocket +==================== +*/ +int NET_IPSocket( char *net_interface, int port, int *err ) { + SOCKET newsocket; + struct sockaddr_in address; + ioctlarg_t _true = 1; + int i = 1; + + *err = 0; + + if( net_interface ) { + Com_Printf( "Opening IP socket: %s:%i\n", net_interface, port ); + } + else { + Com_Printf( "Opening IP socket: 0.0.0.0:%i\n", port ); + } + + if( ( newsocket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) { + *err = socketError; + Com_Printf( "WARNING: NET_IPSocket: socket: %s\n", NET_ErrorString() ); + return newsocket; + } + // make it non-blocking + if( ioctlsocket( newsocket, FIONBIO, &_true ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: NET_IPSocket: ioctl FIONBIO: %s\n", NET_ErrorString() ); + *err = socketError; + closesocket(newsocket); + return INVALID_SOCKET; + } + + // make it broadcast capable + if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *) &i, sizeof(i) ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: NET_IPSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString() ); + } + + if( !net_interface || !net_interface[0]) { + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + } + else + { + if(!Sys_StringToSockaddr( net_interface, (struct sockaddr *)&address, sizeof(address), AF_INET)) + { + closesocket(newsocket); + return INVALID_SOCKET; + } + } + + if( port == PORT_ANY ) { + address.sin_port = 0; + } + else { + address.sin_port = htons( (short)port ); + } + + if( bind( newsocket, (void *)&address, sizeof(address) ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: NET_IPSocket: bind: %s\n", NET_ErrorString() ); + *err = socketError; + closesocket( newsocket ); + return INVALID_SOCKET; + } + + return newsocket; +} + +/* +==================== +NET_IP6Socket +==================== +*/ +int NET_IP6Socket( char *net_interface, int port, struct sockaddr_in6 *bindto, int *err ) { + SOCKET newsocket; + struct sockaddr_in6 address; + ioctlarg_t _true = 1; + + *err = 0; + + if( net_interface ) + { + // Print the name in brackets if there is a colon: + if(Q_CountChar(net_interface, ':')) + Com_Printf( "Opening IP6 socket: [%s]:%i\n", net_interface, port ); + else + Com_Printf( "Opening IP6 socket: %s:%i\n", net_interface, port ); + } + else + Com_Printf( "Opening IP6 socket: [::]:%i\n", port ); + + if( ( newsocket = socket( PF_INET6, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) { + *err = socketError; + Com_Printf( "WARNING: NET_IP6Socket: socket: %s\n", NET_ErrorString() ); + return newsocket; + } + + // make it non-blocking + if( ioctlsocket( newsocket, FIONBIO, &_true ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: NET_IP6Socket: ioctl FIONBIO: %s\n", NET_ErrorString() ); + *err = socketError; + closesocket(newsocket); + return INVALID_SOCKET; + } + +#ifdef IPV6_V6ONLY + { + int i = 1; + + // ipv4 addresses should not be allowed to connect via this socket. + if(setsockopt(newsocket, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &i, sizeof(i)) == SOCKET_ERROR) + { + // win32 systems don't seem to support this anyways. + Com_DPrintf("WARNING: NET_IP6Socket: setsockopt IPV6_V6ONLY: %s\n", NET_ErrorString()); + } + } +#endif + + if( !net_interface || !net_interface[0]) { + address.sin6_family = AF_INET6; + address.sin6_addr = in6addr_any; + } + else + { + if(!Sys_StringToSockaddr( net_interface, (struct sockaddr *)&address, sizeof(address), AF_INET6)) + { + closesocket(newsocket); + return INVALID_SOCKET; + } + } + + if( port == PORT_ANY ) { + address.sin6_port = 0; + } + else { + address.sin6_port = htons( (short)port ); + } + + if( bind( newsocket, (void *)&address, sizeof(address) ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: NET_IP6Socket: bind: %s\n", NET_ErrorString() ); + *err = socketError; + closesocket( newsocket ); + return INVALID_SOCKET; + } + + if(bindto) + *bindto = address; + + return newsocket; +} + +/* +==================== +NET_SetMulticast +Set the current multicast group +==================== +*/ +void NET_SetMulticast6(void) +{ + struct sockaddr_in6 addr; + + if(!*net_mcast6addr->string || !Sys_StringToSockaddr(net_mcast6addr->string, (struct sockaddr *) &addr, sizeof(addr), AF_INET6)) + { + Com_Printf("WARNING: NET_JoinMulticast6: Incorrect multicast address given, " + "please set cvar %s to a sane value.\n", net_mcast6addr->name); + + Cvar_SetValue(net_enabled->name, net_enabled->integer | NET_DISABLEMCAST); + + return; + } + + memcpy(&curgroup.ipv6mr_multiaddr, &addr.sin6_addr, sizeof(curgroup.ipv6mr_multiaddr)); + + if(*net_mcast6iface->string) + { +#ifdef _WIN32 + curgroup.ipv6mr_interface = net_mcast6iface->integer; +#else + curgroup.ipv6mr_interface = if_nametoindex(net_mcast6iface->string); +#endif + } + else + curgroup.ipv6mr_interface = 0; +} + +/* +==================== +NET_JoinMulticast +Join an ipv6 multicast group +==================== +*/ +void NET_JoinMulticast6(void) +{ + int err; + + if(ip6_socket == INVALID_SOCKET || multicast6_socket != INVALID_SOCKET || (net_enabled->integer & NET_DISABLEMCAST)) + return; + + if(IN6_IS_ADDR_MULTICAST(&boundto.sin6_addr) || IN6_IS_ADDR_UNSPECIFIED(&boundto.sin6_addr)) + { + // The way the socket was bound does not prohibit receiving multi-cast packets. So we don't need to open a new one. + multicast6_socket = ip6_socket; + } + else + { + if((multicast6_socket = NET_IP6Socket(net_mcast6addr->string, ntohs(boundto.sin6_port), NULL, &err)) == INVALID_SOCKET) + { + // If the OS does not support binding to multicast addresses, like WinXP, at least try with the normal file descriptor. + multicast6_socket = ip6_socket; + } + } + + if(curgroup.ipv6mr_interface) + { + if (setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, + (char *) &curgroup.ipv6mr_interface, sizeof(curgroup.ipv6mr_interface)) < 0) + { + Com_Printf("NET_JoinMulticast6: Couldn't set scope on multicast socket: %s\n", NET_ErrorString()); + + if(multicast6_socket != ip6_socket) + { + closesocket(multicast6_socket); + multicast6_socket = INVALID_SOCKET; + return; + } + } + } + + if (setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *) &curgroup, sizeof(curgroup))) + { + Com_Printf("NET_JoinMulticast6: Couldn't join multicast group: %s\n", NET_ErrorString()); + + if(multicast6_socket != ip6_socket) + { + closesocket(multicast6_socket); + multicast6_socket = INVALID_SOCKET; + return; + } + } +} + +void NET_LeaveMulticast6() +{ + if(multicast6_socket != INVALID_SOCKET) + { + if(multicast6_socket != ip6_socket) + closesocket(multicast6_socket); + else + setsockopt(multicast6_socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *) &curgroup, sizeof(curgroup)); + + multicast6_socket = INVALID_SOCKET; + } +} + +/* +==================== +NET_OpenSocks +==================== +*/ +void NET_OpenSocks( int port ) { + struct sockaddr_in address; + int err; + struct hostent *h; + int len; + qboolean rfc1929; + unsigned char buf[64]; + + usingSocks = qfalse; + + Com_Printf( "Opening connection to SOCKS server.\n" ); + + if ( ( socks_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) { + err = socketError; + Com_Printf( "WARNING: NET_OpenSocks: socket: %s\n", NET_ErrorString() ); + return; + } + + h = gethostbyname( net_socksServer->string ); + if ( h == NULL ) { + err = socketError; + Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: %s\n", NET_ErrorString() ); + return; + } + if ( h->h_addrtype != AF_INET ) { + Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: address type was not AF_INET\n" ); + return; + } + address.sin_family = AF_INET; + address.sin_addr.s_addr = *(int *)h->h_addr_list[0]; + address.sin_port = htons( (short)net_socksPort->integer ); + + if ( connect( socks_socket, (struct sockaddr *)&address, sizeof( address ) ) == SOCKET_ERROR ) { + err = socketError; + Com_Printf( "NET_OpenSocks: connect: %s\n", NET_ErrorString() ); + return; + } + + // send socks authentication handshake + if ( *net_socksUsername->string || *net_socksPassword->string ) { + rfc1929 = qtrue; + } + else { + rfc1929 = qfalse; + } + + buf[0] = 5; // SOCKS version + // method count + if ( rfc1929 ) { + buf[1] = 2; + len = 4; + } + else { + buf[1] = 1; + len = 3; + } + buf[2] = 0; // method #1 - method id #00: no authentication + if ( rfc1929 ) { + buf[2] = 2; // method #2 - method id #02: username/password + } + if ( send( socks_socket, (void *)buf, len, 0 ) == SOCKET_ERROR ) { + err = socketError; + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (void *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + err = socketError; + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 5 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + switch( buf[1] ) { + case 0: // no authentication + break; + case 2: // username/password authentication + break; + default: + Com_Printf( "NET_OpenSocks: request denied\n" ); + return; + } + + // do username/password authentication if needed + if ( buf[1] == 2 ) { + int ulen; + int plen; + + // build the request + ulen = strlen( net_socksUsername->string ); + plen = strlen( net_socksPassword->string ); + + buf[0] = 1; // username/password authentication version + buf[1] = ulen; + if ( ulen ) { + memcpy( &buf[2], net_socksUsername->string, ulen ); + } + buf[2 + ulen] = plen; + if ( plen ) { + memcpy( &buf[3 + ulen], net_socksPassword->string, plen ); + } + + // send it + if ( send( socks_socket, (void *)buf, 3 + ulen + plen, 0 ) == SOCKET_ERROR ) { + err = socketError; + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (void *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + err = socketError; + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 1 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + if ( buf[1] != 0 ) { + Com_Printf( "NET_OpenSocks: authentication failed\n" ); + return; + } + } + + // send the UDP associate request + buf[0] = 5; // SOCKS version + buf[1] = 3; // command: UDP associate + buf[2] = 0; // reserved + buf[3] = 1; // address type: IPV4 + *(int *)&buf[4] = INADDR_ANY; + *(short *)&buf[8] = htons( (short)port ); // port + if ( send( socks_socket, (void *)buf, 10, 0 ) == SOCKET_ERROR ) { + err = socketError; + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (void *)buf, 64, 0 ); + if( len == SOCKET_ERROR ) { + err = socketError; + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if( len < 2 || buf[0] != 5 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + // check completion code + if( buf[1] != 0 ) { + Com_Printf( "NET_OpenSocks: request denied: %i\n", buf[1] ); + return; + } + if( buf[3] != 1 ) { + Com_Printf( "NET_OpenSocks: relay address is not IPV4: %i\n", buf[3] ); + return; + } + ((struct sockaddr_in *)&socksRelayAddr)->sin_family = AF_INET; + ((struct sockaddr_in *)&socksRelayAddr)->sin_addr.s_addr = *(int *)&buf[4]; + ((struct sockaddr_in *)&socksRelayAddr)->sin_port = *(short *)&buf[8]; + memset( ((struct sockaddr_in *)&socksRelayAddr)->sin_zero, 0, 8 ); + + usingSocks = qtrue; +} + + +/* +===================== +NET_AddLocalAddress +===================== +*/ +static void NET_AddLocalAddress(char *ifname, struct sockaddr *addr, struct sockaddr *netmask) +{ + int addrlen; + sa_family_t family; + + // only add addresses that have all required info. + if(!addr || !netmask || !ifname) + return; + + family = addr->sa_family; + + if(numIP < MAX_IPS) + { + if(family == AF_INET) + { + addrlen = sizeof(struct sockaddr_in); + localIP[numIP].type = NA_IP; + } + else if(family == AF_INET6) + { + addrlen = sizeof(struct sockaddr_in6); + localIP[numIP].type = NA_IP6; + } + else + return; + + Q_strncpyz(localIP[numIP].ifname, ifname, sizeof(localIP[numIP].ifname)); + + localIP[numIP].family = family; + + memcpy(&localIP[numIP].addr, addr, addrlen); + memcpy(&localIP[numIP].netmask, netmask, addrlen); + + numIP++; + } +} + +#if defined(__linux__) || defined(MACOSX) || defined(__BSD__) +static void NET_GetLocalAddress(void) +{ + struct ifaddrs *ifap, *search; + + if(getifaddrs(&ifap)) + Com_Printf("NET_GetLocalAddress: Unable to get list of network interfaces: %s\n", NET_ErrorString()); + else + { + for(search = ifap; search; search = search->ifa_next) + { + // Only add interfaces that are up. + if(ifap->ifa_flags & IFF_UP) + NET_AddLocalAddress(search->ifa_name, search->ifa_addr, search->ifa_netmask); + } + + freeifaddrs(ifap); + + Sys_ShowIP(); + } +} +#else +static void NET_GetLocalAddress( void ) { + char hostname[256]; + struct addrinfo hint; + struct addrinfo *res = NULL; + + if(gethostname( hostname, 256 ) == SOCKET_ERROR) + return; + + Com_Printf( "Hostname: %s\n", hostname ); + + memset(&hint, 0, sizeof(hint)); + + hint.ai_family = AF_UNSPEC; + hint.ai_socktype = SOCK_DGRAM; + + if(!getaddrinfo(hostname, NULL, &hint, &res)) + { + struct sockaddr_in mask4; + struct sockaddr_in6 mask6; + struct addrinfo *search; + + /* On operating systems where it's more difficult to find out the configured interfaces, we'll just assume a + * netmask with all bits set. */ + + memset(&mask4, 0, sizeof(mask4)); + memset(&mask6, 0, sizeof(mask6)); + mask4.sin_family = AF_INET; + memset(&mask4.sin_addr.s_addr, 0xFF, sizeof(mask4.sin_addr.s_addr)); + mask6.sin6_family = AF_INET6; + memset(&mask6.sin6_addr, 0xFF, sizeof(mask6.sin6_addr)); + + // add all IPs from returned list. + for(search = res; search; search = search->ai_next) + { + if(search->ai_family == AF_INET) + NET_AddLocalAddress("", search->ai_addr, (struct sockaddr *) &mask4); + else if(search->ai_family == AF_INET6) + NET_AddLocalAddress("", search->ai_addr, (struct sockaddr *) &mask6); + } + + Sys_ShowIP(); + } + + if(res) + freeaddrinfo(res); +} +#endif + +/* +==================== +NET_OpenIP +==================== +*/ +void NET_OpenIP( void ) { + int i; + int err; + int port; + int port6; + + port = net_port->integer; + port6 = net_port6->integer; + + NET_GetLocalAddress(); + + // automatically scan for a valid port, so multiple + // dedicated servers can be started without requiring + // a different net_port for each one + + if(net_enabled->integer & NET_ENABLEV6) + { + for( i = 0 ; i < 10 ; i++ ) + { + ip6_socket = NET_IP6Socket(net_ip6->string, port6 + i, &boundto, &err); + if (ip6_socket != INVALID_SOCKET) + { + Cvar_SetValue( "net_port6", port6 + i ); + break; + } + else + { + if(err == EAFNOSUPPORT) + break; + } + } + if(ip6_socket == INVALID_SOCKET) + Com_Printf( "WARNING: Couldn't bind to a v6 ip address.\n"); + } + + if(net_enabled->integer & NET_ENABLEV4) + { + for( i = 0 ; i < 10 ; i++ ) { + ip_socket = NET_IPSocket( net_ip->string, port + i, &err ); + if (ip_socket != INVALID_SOCKET) { + Cvar_SetValue( "net_port", port + i ); + + if (net_socksEnabled->integer) + NET_OpenSocks( port + i ); + + break; + } + else + { + if(err == EAFNOSUPPORT) + break; + } + } + + if(ip_socket == INVALID_SOCKET) + Com_Printf( "WARNING: Couldn't bind to a v4 ip address.\n"); + } +} + + +//=================================================================== + + +/* +==================== +NET_GetCvars +==================== +*/ +static qboolean NET_GetCvars( void ) { + int modified; + +#ifdef DEDICATED + // I want server owners to explicitly turn on ipv6 support. + net_enabled = Cvar_Get( "net_enabled", "1", CVAR_LATCH | CVAR_ARCHIVE ); +#else + /* End users have it enabled so they can connect to ipv6-only hosts, but ipv4 will be + * used if available due to ping */ + net_enabled = Cvar_Get( "net_enabled", "3", CVAR_LATCH | CVAR_ARCHIVE ); +#endif + modified = net_enabled->modified; + net_enabled->modified = qfalse; + + net_ip = Cvar_Get( "net_ip", "0.0.0.0", CVAR_LATCH ); + modified += net_ip->modified; + net_ip->modified = qfalse; + + net_ip6 = Cvar_Get( "net_ip6", "::", CVAR_LATCH ); + modified += net_ip6->modified; + net_ip6->modified = qfalse; + + net_port = Cvar_Get( "net_port", va( "%i", PORT_SERVER ), CVAR_LATCH ); + modified += net_port->modified; + net_port->modified = qfalse; + + net_port6 = Cvar_Get( "net_port6", va( "%i", PORT_SERVER ), CVAR_LATCH ); + modified += net_port6->modified; + net_port6->modified = qfalse; + + // Some cvars for configuring multicast options which facilitates scanning for servers on local subnets. + net_mcast6addr = Cvar_Get( "net_mcast6addr", NET_MULTICAST_IP6, CVAR_LATCH | CVAR_ARCHIVE ); + modified += net_mcast6addr->modified; + net_mcast6addr->modified = qfalse; + +#ifdef _WIN32 + net_mcast6iface = Cvar_Get( "net_mcast6iface", "0", CVAR_LATCH | CVAR_ARCHIVE ); +#else + net_mcast6iface = Cvar_Get( "net_mcast6iface", "", CVAR_LATCH | CVAR_ARCHIVE ); +#endif + modified += net_mcast6iface->modified; + net_mcast6iface->modified = qfalse; + + net_socksEnabled = Cvar_Get( "net_socksEnabled", "0", CVAR_LATCH | CVAR_ARCHIVE ); + modified += net_socksEnabled->modified; + net_socksEnabled->modified = qfalse; + + net_socksServer = Cvar_Get( "net_socksServer", "", CVAR_LATCH | CVAR_ARCHIVE ); + modified += net_socksServer->modified; + net_socksServer->modified = qfalse; + + net_socksPort = Cvar_Get( "net_socksPort", "1080", CVAR_LATCH | CVAR_ARCHIVE ); + modified += net_socksPort->modified; + net_socksPort->modified = qfalse; + + net_socksUsername = Cvar_Get( "net_socksUsername", "", CVAR_LATCH | CVAR_ARCHIVE ); + modified += net_socksUsername->modified; + net_socksUsername->modified = qfalse; + + net_socksPassword = Cvar_Get( "net_socksPassword", "", CVAR_LATCH | CVAR_ARCHIVE ); + modified += net_socksPassword->modified; + net_socksPassword->modified = qfalse; + + net_dropsim = Cvar_Get("net_dropsim", "", CVAR_TEMP); + + return modified ? qtrue : qfalse; +} + + +/* +==================== +NET_Config +==================== +*/ +void NET_Config( qboolean enableNetworking ) { + qboolean modified; + qboolean stop; + qboolean start; + + // get any latched changes to cvars + modified = NET_GetCvars(); + + if( !net_enabled->integer ) { + enableNetworking = 0; + } + + // if enable state is the same and no cvars were modified, we have nothing to do + if( enableNetworking == networkingEnabled && !modified ) { + return; + } + + if( enableNetworking == networkingEnabled ) { + if( enableNetworking ) { + stop = qtrue; + start = qtrue; + } + else { + stop = qfalse; + start = qfalse; + } + } + else { + if( enableNetworking ) { + stop = qfalse; + start = qtrue; + } + else { + stop = qtrue; + start = qfalse; + } + networkingEnabled = enableNetworking; + } + + if( stop ) { + if ( ip_socket != INVALID_SOCKET ) { + closesocket( ip_socket ); + ip_socket = INVALID_SOCKET; + } + + if(multicast6_socket) + { + if(multicast6_socket != ip6_socket) + closesocket(multicast6_socket); + + multicast6_socket = INVALID_SOCKET; + } + + if ( ip6_socket != INVALID_SOCKET ) { + closesocket( ip6_socket ); + ip6_socket = INVALID_SOCKET; + } + + if ( socks_socket != INVALID_SOCKET ) { + closesocket( socks_socket ); + socks_socket = INVALID_SOCKET; + } + + } + + if( start ) + { + if (net_enabled->integer) + { + NET_OpenIP(); + NET_SetMulticast6(); + } + } +} + + +/* +==================== +NET_Init +==================== +*/ +void NET_Init( void ) { +#ifdef _WIN32 + int r; + + r = WSAStartup( MAKEWORD( 1, 1 ), &winsockdata ); + if( r ) { + Com_Printf( "WARNING: Winsock initialization failed, returned %d\n", r ); + return; + } + + winsockInitialized = qtrue; + Com_Printf( "Winsock Initialized\n" ); +#endif + + NET_Config( qtrue ); + + Cmd_AddCommand ("net_restart", NET_Restart_f); +} + + +/* +==================== +NET_Shutdown +==================== +*/ +void NET_Shutdown( void ) { + if ( !networkingEnabled ) { + return; + } + + NET_Config( qfalse ); + +#ifdef _WIN32 + WSACleanup(); + winsockInitialized = qfalse; +#endif +} + +/* +==================== +NET_Event + +Called from NET_Sleep which uses select() to determine which sockets have seen action. +==================== +*/ + +void NET_Event(fd_set *fdr) +{ + byte bufData[MAX_MSGLEN + 1]; + netadr_t from; + msg_t netmsg; + + while(1) + { + MSG_Init(&netmsg, bufData, sizeof(bufData)); + + if(NET_GetPacket(&from, &netmsg, fdr)) + { + if(net_dropsim->value > 0.0f && net_dropsim->value <= 100.0f) + { + // com_dropsim->value percent of incoming packets get dropped. + if(rand() < (int) (((double) RAND_MAX) / 100.0 * (double) net_dropsim->value)) + continue; // drop this packet + } + + if(com_sv_running->integer) + Com_RunAndTimeServerPacket(&from, &netmsg); + else + CL_PacketEvent(from, &netmsg); + } + else + break; + } +} + +/* +==================== +NET_Sleep + +Sleeps msec or until something happens on the network +==================== +*/ +void NET_Sleep(int msec) +{ + struct timeval timeout; + fd_set fdr; + int highestfd = -1, retval; + + FD_ZERO(&fdr); + + if(ip_socket != INVALID_SOCKET) + { + FD_SET(ip_socket, &fdr); + + highestfd = ip_socket; + } + if(ip6_socket != INVALID_SOCKET) + { + FD_SET(ip6_socket, &fdr); + + if(ip6_socket > highestfd) + highestfd = ip6_socket; + } + + timeout.tv_sec = msec/1000; + timeout.tv_usec = (msec%1000)*1000; + +#ifdef _WIN32 + if(highestfd < 0) + { + // windows ain't happy when select is called without valid FDs + SleepEx(msec, 0); + return; + } +#endif + + retval = select(highestfd + 1, &fdr, NULL, NULL, &timeout); + + if(retval < 0) + Com_Printf("Warning: select() syscall failed: %s\n", NET_ErrorString()); + else if(retval > 0) + NET_Event(&fdr); +} + +/* +==================== +NET_Restart_f +==================== +*/ +void NET_Restart_f( void ) { + NET_Config( networkingEnabled ); +} diff --git a/src/qcommon/parse.c b/src/qcommon/parse.c new file mode 100644 index 0000000..b1f6efd --- /dev/null +++ b/src/qcommon/parse.c @@ -0,0 +1,3727 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "q_shared.h" +#include "qcommon.h" + +//script flags +#define SCFL_NOERRORS 0x0001 +#define SCFL_NOWARNINGS 0x0002 +#define SCFL_NOSTRINGWHITESPACES 0x0004 +#define SCFL_NOSTRINGESCAPECHARS 0x0008 +#define SCFL_PRIMITIVE 0x0010 +#define SCFL_NOBINARYNUMBERS 0x0020 +#define SCFL_NONUMBERVALUES 0x0040 + +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +//string sub type +//--------------- +// the length of the string +//literal sub type +//---------------- +// the ASCII code of the literal +//number sub type +//--------------- +#define TT_DECIMAL 0x0008 // decimal number +#define TT_HEX 0x0100 // hexadecimal number +#define TT_OCTAL 0x0200 // octal number +#define TT_BINARY 0x0400 // binary number +#define TT_FLOAT 0x0800 // floating point number +#define TT_INTEGER 0x1000 // integer number +#define TT_LONG 0x2000 // long number +#define TT_UNSIGNED 0x4000 // unsigned number +//punctuation sub type +//-------------------- +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 + +//name sub type +//------------- +// the length of the name + +//punctuation +typedef struct punctuation_s +{ + char *p; //punctuation character(s) + int n; //punctuation indication + struct punctuation_s *next; //next punctuation +} punctuation_t; + +//token +typedef struct token_s +{ + char string[MAX_TOKEN_CHARS]; //available token + int type; //last read token type + int subtype; //last read token sub type + unsigned long int intvalue; //integer value + double floatvalue; //floating point value + char *whitespace_p; //start of white space before token + char *endwhitespace_p; //start of white space before token + int line; //line the token was on + int linescrossed; //lines crossed in white space + struct token_s *next; //next token in chain +} token_t; + +//script file +typedef struct script_s +{ + char filename[1024]; //file name of the script + char *buffer; //buffer containing the script + char *script_p; //current pointer in the script + char *end_p; //pointer to the end of the script + char *lastscript_p; //script pointer before reading token + char *whitespace_p; //begin of the white space + char *endwhitespace_p; + int length; //length of the script in bytes + int line; //current line in script + int lastline; //line before reading token + int tokenavailable; //set by UnreadLastToken + int flags; //several script flags + punctuation_t *punctuations; //the punctuations used in the script + punctuation_t **punctuationtable; + token_t token; //available token + struct script_s *next; //next script in a chain +} script_t; + + +#define DEFINE_FIXED 0x0001 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +//macro definitions +typedef struct define_s +{ + char *name; //define name + int flags; //define flags + int builtin; // > 0 if builtin define + int numparms; //number of define parameters + token_t *parms; //define parameters + token_t *tokens; //macro tokens (possibly containing parm tokens) + struct define_s *next; //next defined macro in a list + struct define_s *hashnext; //next define in the hash chain +} define_t; + +//indents +//used for conditional compilation directives: +//#if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s +{ + int type; //indent type + int skip; //true if skipping current indent + script_t *script; //script the indent was in + struct indent_s *next; //next indent on the indent stack +} indent_t; + +//source file +typedef struct source_s +{ + char filename[MAX_QPATH]; //file name of the script + char includepath[MAX_QPATH]; //path to include files + punctuation_t *punctuations; //punctuations to use + script_t *scriptstack; //stack with scripts of the source + token_t *tokens; //tokens to read first + define_t *defines; //list with macro definitions + define_t **definehash; //hash chain with defines + indent_t *indentstack; //stack with indents + int skip; // > 0 if skipping conditional code + token_t token; //last read token +} source_t; + +#define MAX_DEFINEPARMS 128 + +//directive name with parse function +typedef struct directive_s +{ + char *name; + int (*func)(source_t *source); +} directive_t; + +#define DEFINEHASHSIZE 1024 + +static int Parse_ReadToken(source_t *source, token_t *token); +static qboolean Parse_AddDefineToSourceFromString( source_t *source, + char *string ); + +int numtokens; + +//list with global defines added to every source loaded +define_t *globaldefines; + +//longer punctuations first +punctuation_t default_punctuations[] = +{ + //binary operators + {">>=",P_RSHIFT_ASSIGN, NULL}, + {"<<=",P_LSHIFT_ASSIGN, NULL}, + // + {"...",P_PARMS, NULL}, + //define merge operator + {"##",P_PRECOMPMERGE, NULL}, + //logic operators + {"&&",P_LOGIC_AND, NULL}, + {"||",P_LOGIC_OR, NULL}, + {">=",P_LOGIC_GEQ, NULL}, + {"<=",P_LOGIC_LEQ, NULL}, + {"==",P_LOGIC_EQ, NULL}, + {"!=",P_LOGIC_UNEQ, NULL}, + //arithmatic operators + {"*=",P_MUL_ASSIGN, NULL}, + {"/=",P_DIV_ASSIGN, NULL}, + {"%=",P_MOD_ASSIGN, NULL}, + {"+=",P_ADD_ASSIGN, NULL}, + {"-=",P_SUB_ASSIGN, NULL}, + {"++",P_INC, NULL}, + {"--",P_DEC, NULL}, + //binary operators + {"&=",P_BIN_AND_ASSIGN, NULL}, + {"|=",P_BIN_OR_ASSIGN, NULL}, + {"^=",P_BIN_XOR_ASSIGN, NULL}, + {">>",P_RSHIFT, NULL}, + {"<<",P_LSHIFT, NULL}, + //reference operators + {"->",P_POINTERREF, NULL}, + //C++ + {"::",P_CPP1, NULL}, + {".*",P_CPP2, NULL}, + //arithmatic operators + {"*",P_MUL, NULL}, + {"/",P_DIV, NULL}, + {"%",P_MOD, NULL}, + {"+",P_ADD, NULL}, + {"-",P_SUB, NULL}, + {"=",P_ASSIGN, NULL}, + //binary operators + {"&",P_BIN_AND, NULL}, + {"|",P_BIN_OR, NULL}, + {"^",P_BIN_XOR, NULL}, + {"~",P_BIN_NOT, NULL}, + //logic operators + {"!",P_LOGIC_NOT, NULL}, + {">",P_LOGIC_GREATER, NULL}, + {"<",P_LOGIC_LESS, NULL}, + //reference operator + {".",P_REF, NULL}, + //seperators + {",",P_COMMA, NULL}, + {";",P_SEMICOLON, NULL}, + //label indication + {":",P_COLON, NULL}, + //if statement + {"?",P_QUESTIONMARK, NULL}, + //embracements + {"(",P_PARENTHESESOPEN, NULL}, + {")",P_PARENTHESESCLOSE, NULL}, + {"{",P_BRACEOPEN, NULL}, + {"}",P_BRACECLOSE, NULL}, + {"[",P_SQBRACKETOPEN, NULL}, + {"]",P_SQBRACKETCLOSE, NULL}, + // + {"\\",P_BACKSLASH, NULL}, + //precompiler operator + {"#",P_PRECOMP, NULL}, + {"$",P_DOLLAR, NULL}, + {NULL, 0} +}; + +/* +=============== +Parse_CreatePunctuationTable +=============== +*/ +static void Parse_CreatePunctuationTable(script_t *script, punctuation_t *punctuations) +{ + int i; + punctuation_t *p, *lastp, *newp; + + //get memory for the table + if (!script->punctuationtable) script->punctuationtable = (punctuation_t **) + Z_Malloc(256 * sizeof(punctuation_t *)); + Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *)); + //add the punctuations in the list to the punctuation table + for (i = 0; punctuations[i].p; i++) + { + newp = &punctuations[i]; + lastp = NULL; + //sort the punctuations in this table entry on length (longer punctuations first) + for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next) + { + if (strlen(p->p) < strlen(newp->p)) + { + newp->next = p; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + break; + } + lastp = p; + } + if (!p) + { + newp->next = NULL; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + } + } +} + +/* +=============== +Parse_ScriptError +=============== +*/ +static void QDECL Parse_ScriptError(script_t *script, char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOERRORS) return; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + Com_Printf( "file %s, line %d: %s\n", script->filename, script->line, text); +} + +/* +=============== +Parse_ScriptWarning +=============== +*/ +static void QDECL Parse_ScriptWarning(script_t *script, char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOWARNINGS) return; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + Com_Printf( "file %s, line %d: %s\n", script->filename, script->line, text); +} + +/* +=============== +Parse_SetScriptPunctuations +=============== +*/ +static void Parse_SetScriptPunctuations(script_t *script, punctuation_t *p) +{ + if (p) Parse_CreatePunctuationTable(script, p); + else Parse_CreatePunctuationTable(script, default_punctuations); + if (p) script->punctuations = p; + else script->punctuations = default_punctuations; +} + +/* +=============== +Parse_ReadWhiteSpace +=============== +*/ +static int Parse_ReadWhiteSpace(script_t *script) +{ + while(1) + { + //skip white space + while(*script->script_p <= ' ') + { + if (!*script->script_p) return 0; + if (*script->script_p == '\n') script->line++; + script->script_p++; + } + //skip comments + if (*script->script_p == '/') + { + //comments // + if (*(script->script_p+1) == '/') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return 0; + } + while(*script->script_p != '\n'); + script->line++; + script->script_p++; + if (!*script->script_p) return 0; + continue; + } + //comments /* */ + else if (*(script->script_p+1) == '*') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return 0; + if (*script->script_p == '\n') script->line++; + } + while(!(*script->script_p == '*' && *(script->script_p+1) == '/')); + script->script_p++; + if (!*script->script_p) return 0; + script->script_p++; + if (!*script->script_p) return 0; + continue; + } + } + break; + } + return 1; +} + +/* +=============== +Parse_ReadEscapeCharacter +=============== +*/ +static int Parse_ReadEscapeCharacter(script_t *script, char *ch) +{ + int c, val, i; + + //step over the leading '\\' + script->script_p++; + //determine the escape character + switch(*script->script_p) + { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + script->script_p++; + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') c = c - 'a' + 10; + else break; + val = (val << 4) + c; + } + script->script_p--; + if (val > 0xFF) + { + Parse_ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } + c = val; + break; + } + default: //NOTE: decimal ASCII code, NOT octal + { + if (*script->script_p < '0' || *script->script_p > '9') Parse_ScriptError(script, "unknown escape char"); + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else break; + val = val * 10 + c; + } + script->script_p--; + if (val > 0xFF) + { + Parse_ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } + c = val; + break; + } + } + //step over the escape character or the last digit of the number + script->script_p++; + //store the escape character + *ch = c; + //succesfully read escape character + return 1; +} + +/* +=============== +Parse_ReadString + +Reads C-like string. Escape characters are interpretted. +Quotes are included with the string. +Reads two strings with a white space between them as one string. +=============== +*/ +static int Parse_ReadString(script_t *script, token_t *token, int quote) +{ + int len, tmpline; + char *tmpscript_p; + + if (quote == '\"') token->type = TT_STRING; + else token->type = TT_LITERAL; + + len = 0; + //leading quote + token->string[len++] = *script->script_p++; + // + while(1) + { + //minus 2 because trailing double quote and zero have to be appended + if (len >= MAX_TOKEN_CHARS - 2) + { + Parse_ScriptError(script, "string longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return 0; + } + //if there is an escape character and + //if escape characters inside a string are allowed + if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS)) + { + if (!Parse_ReadEscapeCharacter(script, &token->string[len])) + { + token->string[len] = 0; + return 0; + } + len++; + } + //if a trailing quote + else if (*script->script_p == quote) + { + //step over the double quote + script->script_p++; + //if white spaces in a string are not allowed + if (script->flags & SCFL_NOSTRINGWHITESPACES) break; + // + tmpscript_p = script->script_p; + tmpline = script->line; + //read unusefull stuff between possible two following strings + if (!Parse_ReadWhiteSpace(script)) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } + //if there's no leading double qoute + if (*script->script_p != quote) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } + //step over the new leading double quote + script->script_p++; + } + else + { + if (*script->script_p == '\0') + { + token->string[len] = 0; + Parse_ScriptError(script, "missing trailing quote"); + return 0; + } + if (*script->script_p == '\n') + { + token->string[len] = 0; + Parse_ScriptError(script, "newline inside string %s", token->string); + return 0; + } + token->string[len++] = *script->script_p++; + } + } + //trailing quote + token->string[len++] = quote; + //end string with a zero + token->string[len] = '\0'; + //the sub type is the length of the string + token->subtype = len; + return 1; +} + +/* +=============== +Parse_ReadName +=============== +*/ +static int Parse_ReadName(script_t *script, token_t *token) +{ + int len = 0; + char c; + + token->type = TT_NAME; + do + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN_CHARS) + { + Parse_ScriptError(script, "name longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return 0; + } + c = *script->script_p; + } while ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_'); + token->string[len] = '\0'; + //the sub type is the length of the name + token->subtype = len; + return 1; +} + +/* +=============== +Parse_NumberValue +=============== +*/ +static void Parse_NumberValue(char *string, int subtype, unsigned long int *intvalue, + double *floatvalue) +{ + unsigned long int dotfound = 0; + + *intvalue = 0; + *floatvalue = 0; + //floating point number + if (subtype & TT_FLOAT) + { + while(*string) + { + if (*string == '.') + { + if (dotfound) return; + dotfound = 10; + string++; + } + if (dotfound) + { + *floatvalue = *floatvalue + (double) (*string - '0') / + (double) dotfound; + dotfound *= 10; + } + else + { + *floatvalue = *floatvalue * 10.0 + (double) (*string - '0'); + } + string++; + } + *intvalue = (unsigned long) *floatvalue; + } + else if (subtype & TT_DECIMAL) + { + while(*string) *intvalue = *intvalue * 10 + (*string++ - '0'); + *floatvalue = *intvalue; + } + else if (subtype & TT_HEX) + { + //step over the leading 0x or 0X + string += 2; + while(*string) + { + *intvalue <<= 4; + if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10; + else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10; + else *intvalue += *string - '0'; + string++; + } + *floatvalue = *intvalue; + } + else if (subtype & TT_OCTAL) + { + //step over the first zero + string += 1; + while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0'); + *floatvalue = *intvalue; + } + else if (subtype & TT_BINARY) + { + //step over the leading 0b or 0B + string += 2; + while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0'); + *floatvalue = *intvalue; + } +} + +/* +=============== +Parse_ReadNumber +=============== +*/ +static int Parse_ReadNumber(script_t *script, token_t *token) +{ + int len = 0, i; + int octal, dot; + char c; +// unsigned long int intvalue = 0; +// double floatvalue = 0; + + token->type = TT_NUMBER; + //check for a hexadecimal number + if (*script->script_p == '0' && + (*(script->script_p + 1) == 'x' || + *(script->script_p + 1) == 'X')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'A')) + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN_CHARS) + { + Parse_ScriptError(script, "hexadecimal number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return 0; + } + c = *script->script_p; + } + token->subtype |= TT_HEX; + } +#ifdef BINARYNUMBERS + //check for a binary number + else if (*script->script_p == '0' && + (*(script->script_p + 1) == 'b' || + *(script->script_p + 1) == 'B')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //binary + while(c == '0' || c == '1') + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN_CHARS) + { + Parse_ScriptError(script, "binary number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return 0; + } + c = *script->script_p; + } + token->subtype |= TT_BINARY; + } +#endif //BINARYNUMBERS + else //decimal or octal integer or floating point number + { + octal = qfalse; + dot = qfalse; + if (*script->script_p == '0') octal = qtrue; + while(1) + { + c = *script->script_p; + if (c == '.') dot = qtrue; + else if (c == '8' || c == '9') octal = qfalse; + else if (c < '0' || c > '9') break; + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN_CHARS - 1) + { + Parse_ScriptError(script, "number longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return 0; + } + } + if (octal) token->subtype |= TT_OCTAL; + else token->subtype |= TT_DECIMAL; + if (dot) token->subtype |= TT_FLOAT; + } + for (i = 0; i < 2; i++) + { + c = *script->script_p; + //check for a LONG number + if ( (c == 'l' || c == 'L') + && !(token->subtype & TT_LONG)) + { + script->script_p++; + token->subtype |= TT_LONG; + } + //check for an UNSIGNED number + else if ( (c == 'u' || c == 'U') + && !(token->subtype & (TT_UNSIGNED | TT_FLOAT))) + { + script->script_p++; + token->subtype |= TT_UNSIGNED; + } + } + token->string[len] = '\0'; + Parse_NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue); + if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER; + return 1; +} + +/* +=============== +Parse_ReadPunctuation +=============== +*/ +static int Parse_ReadPunctuation(script_t *script, token_t *token) +{ + int len; + char *p; + punctuation_t *punc; + + for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next) + { + p = punc->p; + len = strlen(p); + //if the script contains at least as much characters as the punctuation + if (script->script_p + len <= script->end_p) + { + //if the script contains the punctuation + if (!strncmp(script->script_p, p, len)) + { + strncpy(token->string, p, MAX_TOKEN_CHARS); + script->script_p += len; + token->type = TT_PUNCTUATION; + //sub type is the number of the punctuation + token->subtype = punc->n; + return 1; + } + } + } + return 0; +} + +/* +=============== +Parse_ReadPrimitive +=============== +*/ +static int Parse_ReadPrimitive(script_t *script, token_t *token) +{ + int len; + + len = 0; + while(*script->script_p > ' ' && *script->script_p != ';') + { + if (len >= MAX_TOKEN_CHARS) + { + Parse_ScriptError(script, "primitive token longer than MAX_TOKEN_CHARS = %d", MAX_TOKEN_CHARS); + return 0; + } + token->string[len++] = *script->script_p++; + } + token->string[len] = 0; + //copy the token into the script structure + Com_Memcpy(&script->token, token, sizeof(token_t)); + //primitive reading successfull + return 1; +} + +/* +=============== +Parse_ReadScriptToken +=============== +*/ +static int Parse_ReadScriptToken(script_t *script, token_t *token) +{ + //if there is a token available (from UnreadToken) + if (script->tokenavailable) + { + script->tokenavailable = 0; + Com_Memcpy(token, &script->token, sizeof(token_t)); + return 1; + } + //save script pointer + script->lastscript_p = script->script_p; + //save line counter + script->lastline = script->line; + //clear the token stuff + Com_Memset(token, 0, sizeof(token_t)); + //start of the white space + script->whitespace_p = script->script_p; + token->whitespace_p = script->script_p; + //read unusefull stuff + if (!Parse_ReadWhiteSpace(script)) return 0; + + script->endwhitespace_p = script->script_p; + token->endwhitespace_p = script->script_p; + //line the token is on + token->line = script->line; + //number of lines crossed before token + token->linescrossed = script->line - script->lastline; + //if there is a leading double quote + if (*script->script_p == '\"') + { + if (!Parse_ReadString(script, token, '\"')) return 0; + } + //if an literal + else if (*script->script_p == '\'') + { + //if (!Parse_ReadLiteral(script, token)) return 0; + if (!Parse_ReadString(script, token, '\'')) return 0; + } + //if there is a number + else if ((*script->script_p >= '0' && *script->script_p <= '9') || + (*script->script_p == '.' && + (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9'))) + { + if (!Parse_ReadNumber(script, token)) return 0; + } + //if this is a primitive script + else if (script->flags & SCFL_PRIMITIVE) + { + return Parse_ReadPrimitive(script, token); + } + //if there is a name + else if ((*script->script_p >= 'a' && *script->script_p <= 'z') || + (*script->script_p >= 'A' && *script->script_p <= 'Z') || + *script->script_p == '_') + { + if (!Parse_ReadName(script, token)) return 0; + } + //check for punctuations + else if (!Parse_ReadPunctuation(script, token)) + { + Parse_ScriptError(script, "can't read token"); + return 0; + } + //copy the token into the script structure + Com_Memcpy(&script->token, token, sizeof(token_t)); + //succesfully read a token + return 1; +} + +/* +=============== +Parse_StripDoubleQuotes +=============== +*/ +static void Parse_StripDoubleQuotes(char *string) +{ + if (*string == '\"') + { + memmove( string, string + 1, strlen( string ) + 1 ); + } + if (string[strlen(string)-1] == '\"') + { + string[strlen(string)-1] = '\0'; + } +} + +/* +=============== +Parse_EndOfScript +=============== +*/ +static int Parse_EndOfScript(script_t *script) +{ + return script->script_p >= script->end_p; +} + +/* +=============== +Parse_LoadScriptFile +=============== +*/ +static script_t *Parse_LoadScriptFile(const char *filename) +{ + fileHandle_t fp; + int length; + void *buffer; + script_t *script; + + length = FS_FOpenFileRead( filename, &fp, qfalse ); + if (!fp) return NULL; + + buffer = Z_Malloc(sizeof(script_t) + length + 1); + Com_Memset( buffer, 0, sizeof(script_t) + length + 1 ); + + script = (script_t *) buffer; + Com_Memset(script, 0, sizeof(script_t)); + strcpy(script->filename, filename); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + Parse_SetScriptPunctuations(script, NULL); + // + FS_Read(script->buffer, length, fp); + FS_FCloseFile(fp); + // + + return script; +} + +/* +=============== +Parse_LoadScriptMemory +=============== +*/ +static script_t *Parse_LoadScriptMemory(char *ptr, int length, char *name) +{ + void *buffer; + script_t *script; + + buffer = Z_Malloc(sizeof(script_t) + length + 1); + Com_Memset( buffer, 0, sizeof(script_t) + length + 1 ); + + script = (script_t *) buffer; + Com_Memset(script, 0, sizeof(script_t)); + strcpy(script->filename, name); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + Parse_SetScriptPunctuations(script, NULL); + // + Com_Memcpy(script->buffer, ptr, length); + // + return script; +} + +/* +=============== +Parse_FreeScript +=============== +*/ +static void Parse_FreeScript(script_t *script) +{ + if (script->punctuationtable) Z_Free(script->punctuationtable); + Z_Free(script); +} + +/* +=============== +Parse_SourceError +=============== +*/ +static void QDECL Parse_SourceError(source_t *source, char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + Com_Printf( "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +} + +/* +=============== +Parse_SourceWarning +=============== +*/ +static void QDECL Parse_SourceWarning(source_t *source, char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + Com_Printf( "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +} + +/* +=============== +Parse_PushIndent +=============== +*/ +static void Parse_PushIndent(source_t *source, int type, int skip) +{ + indent_t *indent; + + indent = (indent_t *) Z_Malloc(sizeof(indent_t)); + indent->type = type; + indent->script = source->scriptstack; + indent->skip = (skip != 0); + source->skip += indent->skip; + indent->next = source->indentstack; + source->indentstack = indent; +} + +/* +=============== +Parse_PopIndent +=============== +*/ +static void Parse_PopIndent(source_t *source, int *type, int *skip) +{ + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = source->indentstack; + if (!indent) return; + + //must be an indent from the current script + if (source->indentstack->script != source->scriptstack) return; + + *type = indent->type; + *skip = indent->skip; + source->indentstack = source->indentstack->next; + source->skip -= indent->skip; + Z_Free(indent); +} + +/* +=============== +Parse_PushScript +=============== +*/ +static void Parse_PushScript(source_t *source, script_t *script) +{ + script_t *s; + + for (s = source->scriptstack; s; s = s->next) + { + if (!Q_stricmp(s->filename, script->filename)) + { + Parse_SourceError(source, "%s recursively included", script->filename); + return; + } + } + //push the script on the script stack + script->next = source->scriptstack; + source->scriptstack = script; +} + +/* +=============== +Parse_CopyToken +=============== +*/ +static token_t *Parse_CopyToken(token_t *token) +{ + token_t *t; + +// t = (token_t *) malloc(sizeof(token_t)); + t = (token_t *) Z_Malloc(sizeof(token_t)); +// t = freetokens; + if (!t) + { + Com_Error(ERR_FATAL, "out of token space\n"); + return NULL; + } +// freetokens = freetokens->next; + Com_Memcpy(t, token, sizeof(token_t)); + t->next = NULL; + numtokens++; + return t; +} + +/* +=============== +Parse_FreeToken +=============== +*/ +static void Parse_FreeToken(token_t *token) +{ + //free(token); + Z_Free(token); +// token->next = freetokens; +// freetokens = token; + numtokens--; +} + +/* +=============== +Parse_ReadSourceToken +=============== +*/ +static int Parse_ReadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + script_t *script; + int type, skip, lines; + + lines = 0; + //if there's no token already available + while(!source->tokens) + { + //if there's a token to read from the script + if( Parse_ReadScriptToken( source->scriptstack, token ) ) + { + token->linescrossed += lines; + return qtrue; + } + + // if lines were crossed before the end of the script, count them + lines += source->scriptstack->line - source->scriptstack->lastline; + + //if at the end of the script + if (Parse_EndOfScript(source->scriptstack)) + { + //remove all indents of the script + while(source->indentstack && + source->indentstack->script == source->scriptstack) + { + Parse_SourceWarning(source, "missing #endif"); + Parse_PopIndent(source, &type, &skip); + } + } + //if this was the initial script + if (!source->scriptstack->next) return qfalse; + //remove the script and return to the last one + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + Parse_FreeScript(script); + } + //copy the already available token + Com_Memcpy(token, source->tokens, sizeof(token_t)); + //free the read token + t = source->tokens; + source->tokens = source->tokens->next; + Parse_FreeToken(t); + return qtrue; +} + +/* +=============== +Parse_UnreadSourceToken +=============== +*/ +static int Parse_UnreadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + + t = Parse_CopyToken(token); + t->next = source->tokens; + source->tokens = t; + return qtrue; +} + +/* +=============== +Parse_ReadDefineParms +=============== +*/ +static int Parse_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms) +{ + token_t token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "define %s missing parms", define->name); + return qfalse; + } + // + if (define->numparms > maxparms) + { + Parse_SourceError(source, "define with more than %d parameters", maxparms); + return qfalse; + } + // + for (i = 0; i < define->numparms; i++) parms[i] = NULL; + //if no leading "(" + if (strcmp(token.string, "(")) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "define %s missing parms", define->name); + return qfalse; + } + //read the define parameters + for (done = 0, numparms = 0, indent = 0; !done;) + { + if (numparms >= maxparms) + { + Parse_SourceError(source, "define %s with too many parms", define->name); + return qfalse; + } + if (numparms >= define->numparms) + { + Parse_SourceWarning(source, "define %s has too many parms", define->name); + return qfalse; + } + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while(!done) + { + // + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "define %s incomplete", define->name); + return qfalse; + } + // + if (!strcmp(token.string, ",")) + { + if (indent <= 0) + { + if (lastcomma) Parse_SourceWarning(source, "too many comma's"); + lastcomma = 1; + break; + } + } + lastcomma = 0; + // + if (!strcmp(token.string, "(")) + { + indent++; + continue; + } + else if (!strcmp(token.string, ")")) + { + if (--indent <= 0) + { + if (!parms[define->numparms-1]) + { + Parse_SourceWarning(source, "too few define parms"); + } + done = 1; + break; + } + } + // + if (numparms < define->numparms) + { + // + t = Parse_CopyToken(&token); + t->next = NULL; + if (last) last->next = t; + else parms[numparms] = t; + last = t; + } + } + numparms++; + } + return qtrue; +} + +/* +=============== +Parse_StringizeTokens +=============== +*/ +static int Parse_StringizeTokens(token_t *tokens, token_t *token) +{ + token_t *t; + + token->type = TT_STRING; + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->string[0] = '\0'; + strcat(token->string, "\""); + for (t = tokens; t; t = t->next) + { + strncat(token->string, t->string, MAX_TOKEN_CHARS - strlen(token->string)); + } + strncat(token->string, "\"", MAX_TOKEN_CHARS - strlen(token->string)); + return qtrue; +} + +/* +=============== +Parse_MergeTokens +=============== +*/ +static int Parse_MergeTokens(token_t *t1, token_t *t2) +{ + //merging of a name with a name or number + if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER)) + { + strcat(t1->string, t2->string); + return qtrue; + } + //merging of two strings + if (t1->type == TT_STRING && t2->type == TT_STRING) + { + //remove trailing double quote + t1->string[strlen(t1->string)-1] = '\0'; + //concat without leading double quote + strcat(t1->string, &t2->string[1]); + return qtrue; + } + //FIXME: merging of two number of the same sub type + return qfalse; +} + +/* +=============== +Parse_NameHash +=============== +*/ +//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; +static int Parse_NameHash(char *name) +{ + int register hash, i; + + hash = 0; + for (i = 0; name[i] != '\0'; i++) + { + hash += name[i] * (119 + i); + //hash += (name[i] << 7) + i; + //hash += (name[i] << (i&15)); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); + return hash; +} + +/* +=============== +Parse_AddDefineToHash +=============== +*/ +static void Parse_AddDefineToHash(define_t *define, define_t **definehash) +{ + int hash; + + hash = Parse_NameHash(define->name); + define->hashnext = definehash[hash]; + definehash[hash] = define; +} + +/* +=============== +Parse_FindHashedDefine +=============== +*/ +static define_t *Parse_FindHashedDefine(define_t **definehash, char *name) +{ + define_t *d; + int hash; + + hash = Parse_NameHash(name); + for (d = definehash[hash]; d; d = d->hashnext) + { + if (!strcmp(d->name, name)) return d; + } + return NULL; +} + +/* +=============== +Parse_FindDefineParm +=============== +*/ +static int Parse_FindDefineParm(define_t *define, char *name) +{ + token_t *p; + int i; + + i = 0; + for (p = define->parms; p; p = p->next) + { + if (!strcmp(p->string, name)) return i; + i++; + } + return -1; +} + +/* +=============== +Parse_FreeDefine +=============== +*/ +static void Parse_FreeDefine(define_t *define) +{ + token_t *t, *next; + + //free the define parameters + for (t = define->parms; t; t = next) + { + next = t->next; + Parse_FreeToken(t); + } + //free the define tokens + for (t = define->tokens; t; t = next) + { + next = t->next; + Parse_FreeToken(t); + } + //free the define + Z_Free(define); +} + +/* +=============== +Parse_ExpandBuiltinDefine +=============== +*/ +static int Parse_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *token; + time_t t; + + char *curtime; + + token = Parse_CopyToken(deftoken); + switch(define->builtin) + { + case BUILTIN_LINE: + { + sprintf(token->string, "%d", deftoken->line); + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_FILE: + { + strcpy(token->string, source->scriptstack->filename); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_DATE: + { + t = time(NULL); + curtime = ctime(&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+4, 7); + strncat(token->string+7, curtime+20, 4); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_TIME: + { + t = time(NULL); + curtime = ctime(&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+11, 8); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_STDC: + default: + { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } + } + return qtrue; +} + +/* +=============== +Parse_ExpandDefine +=============== +*/ +static int Parse_ExpandDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + token_t *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + //if it is a builtin define + if (define->builtin) + { + return Parse_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken); + } + //if the define has parameters + if (define->numparms) + { + if (!Parse_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse; + } + //empty list at first + first = NULL; + last = NULL; + //create a list with tokens of the expanded define + for (dt = define->tokens; dt; dt = dt->next) + { + parmnum = -1; + //if the token is a name, it could be a define parameter + if (dt->type == TT_NAME) + { + parmnum = Parse_FindDefineParm(define, dt->string); + } + //if it is a define parameter + if (parmnum >= 0) + { + for (pt = parms[parmnum]; pt; pt = pt->next) + { + t = Parse_CopyToken(pt); + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } + } + else + { + //if stringizing operator + if (dt->string[0] == '#' && dt->string[1] == '\0') + { + //the stringizing operator must be followed by a define parameter + if (dt->next) parmnum = Parse_FindDefineParm(define, dt->next->string); + else parmnum = -1; + // + if (parmnum >= 0) + { + //step over the stringizing operator + dt = dt->next; + //stringize the define parameter tokens + if (!Parse_StringizeTokens(parms[parmnum], &token)) + { + Parse_SourceError(source, "can't stringize tokens"); + return qfalse; + } + t = Parse_CopyToken(&token); + } + else + { + Parse_SourceWarning(source, "stringizing operator without define parameter"); + continue; + } + } + else + { + t = Parse_CopyToken(dt); + } + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } + } + //check for the merging operator + for (t = first; t; ) + { + if (t->next) + { + //if the merging operator + if (t->next->string[0] == '#' && t->next->string[1] == '#') + { + t1 = t; + t2 = t->next->next; + if (t2) + { + if (!Parse_MergeTokens(t1, t2)) + { + Parse_SourceError(source, "can't merge %s with %s", t1->string, t2->string); + return qfalse; + } + Parse_FreeToken(t1->next); + t1->next = t2->next; + if (t2 == last) last = t1; + Parse_FreeToken(t2); + continue; + } + } + } + t = t->next; + } + //store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + //free all the parameter tokens + for (i = 0; i < define->numparms; i++) + { + for (pt = parms[i]; pt; pt = nextpt) + { + nextpt = pt->next; + Parse_FreeToken(pt); + } + } + // + return qtrue; +} + +/* +=============== +Parse_ExpandDefineIntoSource +=============== +*/ +static int Parse_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define) +{ + token_t *firsttoken, *lasttoken; + + if (!Parse_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse; + + if (firsttoken && lasttoken) + { + lasttoken->next = source->tokens; + source->tokens = firsttoken; + return qtrue; + } + return qfalse; +} + +/* +=============== +Parse_ConvertPath +=============== +*/ +static void Parse_ConvertPath(char *path) +{ + char *ptr; + + //remove double path seperators + for (ptr = path; *ptr;) + { + if ((*ptr == '\\' || *ptr == '/') && + (*(ptr+1) == '\\' || *(ptr+1) == '/')) + { + memmove(ptr, ptr+1, strlen(ptr)); + } + else + { + ptr++; + } + } + //set OS dependent path seperators + for (ptr = path; *ptr;) + { + if (*ptr == '/' || *ptr == '\\') *ptr = PATH_SEP; + ptr++; + } +} + +/* +=============== +Parse_ReadLine + +reads a token from the current line, continues reading on the next +line only if a backslash '\' is encountered. +=============== +*/ +static int Parse_ReadLine(source_t *source, token_t *token) +{ + int crossline; + + crossline = 0; + do + { + if (!Parse_ReadSourceToken(source, token)) return qfalse; + + if (token->linescrossed > crossline) + { + Parse_UnreadSourceToken(source, token); + return qfalse; + } + crossline = 1; + } while(!strcmp(token->string, "\\")); + return qtrue; +} + +/* +=============== +Parse_OperatorPriority +=============== +*/ +typedef struct operator_s +{ + int operator; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +static int Parse_OperatorPriority(int op) +{ + switch(op) + { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } + return qfalse; +} + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 +#define AllocValue(val) \ + if (numvalues >= MAX_VALUES) { \ + Parse_SourceError(source, "out of value space\n"); \ + error = 1; \ + break; \ + } \ + else \ + val = &value_heap[numvalues++]; +#define FreeValue(val) +// +#define AllocOperator(op) \ + if (numoperators >= MAX_OPERATORS) { \ + Parse_SourceError(source, "out of operator space\n"); \ + error = 1; \ + break; \ + } \ + else \ + op = &operator_heap[numoperators++]; +#define FreeOperator(op) + +/* +=============== +Parse_EvaluateTokens +=============== +*/ +static int Parse_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue, + double *floatvalue, int integer) +{ + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + token_t *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = qfalse; + int lastoperatortype = 0; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + for (t = tokens; t; t = t->next) + { + switch(t->type) + { + case TT_NAME: + { + if (lastwasvalue || negativevalue) + { + Parse_SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } + if (strcmp(t->string, "defined")) + { + Parse_SourceError(source, "undefined name %s in #if/#elif", t->string); + error = 1; + break; + } + t = t->next; + if (!strcmp(t->string, "(")) + { + brace = qtrue; + t = t->next; + } + if (!t || t->type != TT_NAME) + { + Parse_SourceError(source, "defined without name in #if/#elif"); + error = 1; + break; + } + //v = (value_t *) Z_Malloc(sizeof(value_t)); + AllocValue(v); + if (Parse_FindHashedDefine(source->definehash, t->string)) + { + v->intvalue = 1; + v->floatvalue = 1; + } + else + { + v->intvalue = 0; + v->floatvalue = 0; + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + if (brace) + { + t = t->next; + if (!t || strcmp(t->string, ")")) + { + Parse_SourceError(source, "defined without ) in #if/#elif"); + error = 1; + break; + } + } + brace = qfalse; + // defined() creates a value + lastwasvalue = 1; + break; + } + case TT_NUMBER: + { + if (lastwasvalue) + { + Parse_SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } + //v = (value_t *) Z_Malloc(sizeof(value_t)); + AllocValue(v); + if (negativevalue) + { + v->intvalue = - (signed int) t->intvalue; + v->floatvalue = - t->floatvalue; + } + else + { + v->intvalue = t->intvalue; + v->floatvalue = t->floatvalue; + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } + case TT_PUNCTUATION: + { + if (negativevalue) + { + Parse_SourceError(source, "misplaced minus sign in #if/#elif"); + error = 1; + break; + } + if (t->subtype == P_PARENTHESESOPEN) + { + parentheses++; + break; + } + else if (t->subtype == P_PARENTHESESCLOSE) + { + parentheses--; + if (parentheses < 0) + { + Parse_SourceError(source, "too many ) in #if/#elsif"); + error = 1; + } + break; + } + //check for invalid operators on floating point values + if (!integer) + { + if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR) + { + Parse_SourceError(source, "illigal operator %s on floating point operands\n", t->string); + error = 1; + break; + } + } + switch(t->subtype) + { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if (lastwasvalue) + { + Parse_SourceError(source, "! or ~ after value in #if/#elif"); + error = 1; + break; + } + break; + } + case P_INC: + case P_DEC: + { + Parse_SourceError(source, "++ or -- used in #if/#elif"); + break; + } + case P_SUB: + { + if (!lastwasvalue) + { + negativevalue = 1; + break; + } + } + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if (!lastwasvalue) + { + Parse_SourceError(source, "operator %s after operator in #if/#elif", t->string); + error = 1; + break; + } + break; + } + default: + { + Parse_SourceError(source, "invalid operator %s in #if/#elif", t->string); + error = 1; + break; + } + } + if (!error && !negativevalue) + { + //o = (operator_t *) Z_Malloc(sizeof(operator_t)); + AllocOperator(o); + o->operator = t->subtype; + o->priority = Parse_OperatorPriority(t->subtype); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if (lastoperator) lastoperator->next = o; + else firstoperator = o; + lastoperator = o; + lastwasvalue = 0; + } + break; + } + default: + { + Parse_SourceError(source, "unknown %s in #if/#elif", t->string); + error = 1; + break; + } + } + if (error) break; + } + if (!error) + { + if (!lastwasvalue) + { + Parse_SourceError(source, "trailing operator in #if/#elif"); + error = 1; + } + else if (parentheses) + { + Parse_SourceError(source, "too many ( in #if/#elif"); + error = 1; + } + } + // + gotquestmarkvalue = qfalse; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while(!error && firstoperator) + { + v = firstvalue; + for (o = firstoperator; o->next; o = o->next) + { + //if the current operator is nested deeper in parentheses + //than the next operator + if (o->parentheses > o->next->parentheses) break; + //if the current and next operator are nested equally deep in parentheses + if (o->parentheses == o->next->parentheses) + { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if (o->priority >= o->next->priority) break; + } + //if the arity of the operator isn't equal to 1 + if (o->operator != P_LOGIC_NOT + && o->operator != P_BIN_NOT) v = v->next; + //if there's no value or no next value + if (!v) + { + Parse_SourceError(source, "mising values in #if/#elif"); + error = 1; + break; + } + } + if (error) break; + v1 = v; + v2 = v->next; + switch(o->operator) + { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if (!v2->intvalue || !v2->floatvalue) + { + Parse_SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if (!v2->intvalue) + { + Parse_SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if (!gotquestmarkvalue) + { + Parse_SourceError(source, ": without ? in #if/#elif"); + error = 1; + break; + } + if (integer) + { + if (!questmarkintvalue) v1->intvalue = v2->intvalue; + } + else + { + if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; + } + gotquestmarkvalue = qfalse; + break; + } + case P_QUESTIONMARK: + { + if (gotquestmarkvalue) + { + Parse_SourceError(source, "? after ? in #if/#elif"); + error = 1; + break; + } + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = qtrue; + break; + } + } + if (error) break; + lastoperatortype = o->operator; + //if not an operator with arity 1 + if (o->operator != P_LOGIC_NOT + && o->operator != P_BIN_NOT) + { + //remove the second value if not question mark operator + if (o->operator != P_QUESTIONMARK) v = v->next; + // + if (v->prev) v->prev->next = v->next; + else firstvalue = v->next; + if (v->next) v->next->prev = v->prev; + else lastvalue = v->prev; + //Z_Free(v); + FreeValue(v); + } + //remove the operator + if (o->prev) o->prev->next = o->next; + else firstoperator = o->next; + if (o->next) o->next->prev = o->prev; + else lastoperator = o->prev; + //Z_Free(o); + FreeOperator(o); + } + if (firstvalue) + { + if (intvalue) *intvalue = firstvalue->intvalue; + if (floatvalue) *floatvalue = firstvalue->floatvalue; + } + for (o = firstoperator; o; o = lastoperator) + { + lastoperator = o->next; + //Z_Free(o); + FreeOperator(o); + } + for (v = firstvalue; v; v = lastvalue) + { + lastvalue = v->next; + //Z_Free(v); + FreeValue(v); + } + if (!error) return qtrue; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + return qfalse; +} + +/* +=============== +Parse_Evaluate +=============== +*/ +static int Parse_Evaluate(source_t *source, signed long int *intvalue, + double *floatvalue, int integer) +{ + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + int defined = qfalse; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "no value after #if/#elif"); + return qfalse; + } + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = qfalse; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if (!strcmp(token.string, "defined")) + { + defined = qtrue; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else + { + //then it must be a define + define = Parse_FindHashedDefine(source->definehash, token.string); + if (!define) + { + Parse_SourceError(source, "can't evaluate %s, not defined", token.string); + return qfalse; + } + if (!Parse_ExpandDefineIntoSource(source, &token, define)) return qfalse; + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else //can't evaluate the token + { + Parse_SourceError(source, "can't evaluate %s", token.string); + return qfalse; + } + } while(Parse_ReadLine(source, &token)); + // + if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; + // + for (t = firsttoken; t; t = nexttoken) + { + nexttoken = t->next; + Parse_FreeToken(t); + } + // + return qtrue; +} + +/* +=============== +Parse_DollarEvaluate +=============== +*/ +static int Parse_DollarEvaluate(source_t *source, signed long int *intvalue, + double *floatvalue, int integer) +{ + int indent, defined = qfalse; + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "no leading ( after $evalint/$evalfloat"); + return qfalse; + } + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "nothing to evaluate"); + return qfalse; + } + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = qfalse; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if (!strcmp(token.string, "defined")) + { + defined = qtrue; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else + { + //then it must be a define + define = Parse_FindHashedDefine(source->definehash, token.string); + if (!define) + { + Parse_SourceError(source, "can't evaluate %s, not defined", token.string); + return qfalse; + } + if (!Parse_ExpandDefineIntoSource(source, &token, define)) return qfalse; + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + if (*token.string == '(') indent++; + else if (*token.string == ')') indent--; + if (indent <= 0) break; + t = Parse_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else //can't evaluate the token + { + Parse_SourceError(source, "can't evaluate %s", token.string); + return qfalse; + } + } while(Parse_ReadSourceToken(source, &token)); + // + if (!Parse_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; + // + for (t = firsttoken; t; t = nexttoken) + { + nexttoken = t->next; + Parse_FreeToken(t); + } + // + return qtrue; +} + +/* +=============== +Parse_Directive_include +=============== +*/ +static int Parse_Directive_include(source_t *source) +{ + script_t *script; + token_t token; + char path[MAX_QPATH]; + + if (source->skip > 0) return qtrue; + // + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "#include without file name"); + return qfalse; + } + if (token.linescrossed > 0) + { + Parse_SourceError(source, "#include without file name"); + return qfalse; + } + if (token.type == TT_STRING) + { + Parse_StripDoubleQuotes(token.string); + Parse_ConvertPath(token.string); + script = Parse_LoadScriptFile(token.string); + if (!script) + { + strcpy(path, source->includepath); + strcat(path, token.string); + script = Parse_LoadScriptFile(path); + } + } + else if (token.type == TT_PUNCTUATION && *token.string == '<') + { + strcpy(path, source->includepath); + while(Parse_ReadSourceToken(source, &token)) + { + if (token.linescrossed > 0) + { + Parse_UnreadSourceToken(source, &token); + break; + } + if (token.type == TT_PUNCTUATION && *token.string == '>') break; + strncat(path, token.string, MAX_QPATH - 1); + } + if (*token.string != '>') + { + Parse_SourceWarning(source, "#include missing trailing >"); + } + if (!strlen(path)) + { + Parse_SourceError(source, "#include without file name between < >"); + return qfalse; + } + Parse_ConvertPath(path); + script = Parse_LoadScriptFile(path); + } + else + { + Parse_SourceError(source, "#include without file name"); + return qfalse; + } + if (!script) + { + Parse_SourceError(source, "file %s not found", path); + return qfalse; + } + Parse_PushScript(source, script); + return qtrue; +} + +/* +=============== +Parse_WhiteSpaceBeforeToken +=============== +*/ +static int Parse_WhiteSpaceBeforeToken(token_t *token) +{ + return token->endwhitespace_p - token->whitespace_p > 0; +} + +/* +=============== +Parse_ClearTokenWhiteSpace +=============== +*/ +static void Parse_ClearTokenWhiteSpace(token_t *token) +{ + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->linescrossed = 0; +} + +/* +=============== +Parse_Directive_undef +=============== +*/ +static int Parse_Directive_undef(source_t *source) +{ + token_t token; + define_t *define, *lastdefine; + int hash; + + if (source->skip > 0) return qtrue; + // + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "undef without name"); + return qfalse; + } + if (token.type != TT_NAME) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "expected name, found %s", token.string); + return qfalse; + } + + hash = Parse_NameHash(token.string); + for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext) + { + if (!strcmp(define->name, token.string)) + { + if (define->flags & DEFINE_FIXED) + { + Parse_SourceWarning(source, "can't undef %s", token.string); + } + else + { + if (lastdefine) lastdefine->hashnext = define->hashnext; + else source->definehash[hash] = define->hashnext; + Parse_FreeDefine(define); + } + break; + } + lastdefine = define; + } + return qtrue; +} + +/* +=============== +Parse_Directive_elif +=============== +*/ +static int Parse_Directive_elif(source_t *source) +{ + signed long int value; + int type, skip; + + Parse_PopIndent(source, &type, &skip); + if (!type || type == INDENT_ELSE) + { + Parse_SourceError(source, "misplaced #elif"); + return qfalse; + } + if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse; + skip = (value == 0); + Parse_PushIndent(source, INDENT_ELIF, skip); + return qtrue; +} + +/* +=============== +Parse_Directive_if +=============== +*/ +static int Parse_Directive_if(source_t *source) +{ + signed long int value; + int skip; + + if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse; + skip = (value == 0); + Parse_PushIndent(source, INDENT_IF, skip); + return qtrue; +} + +/* +=============== +Parse_Directive_line +=============== +*/ +static int Parse_Directive_line(source_t *source) +{ + Parse_SourceError(source, "#line directive not supported"); + return qfalse; +} + +/* +=============== +Parse_Directive_error +=============== +*/ +static int Parse_Directive_error(source_t *source) +{ + token_t token; + + strcpy(token.string, ""); + Parse_ReadSourceToken(source, &token); + Parse_SourceError(source, "#error directive: %s", token.string); + return qfalse; +} + +/* +=============== +Parse_Directive_pragma +=============== +*/ +static int Parse_Directive_pragma(source_t *source) +{ + token_t token; + + Parse_SourceWarning(source, "#pragma directive not supported"); + while(Parse_ReadLine(source, &token)) ; + return qtrue; +} + +/* +=============== +Parse_UnreadSignToken +=============== +*/ +static void Parse_UnreadSignToken(source_t *source) +{ + token_t token; + + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + strcpy(token.string, "-"); + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + Parse_UnreadSourceToken(source, &token); +} + +/* +=============== +Parse_Directive_eval +=============== +*/ +static int Parse_Directive_eval(source_t *source) +{ + signed long int value; + token_t token; + + if (!Parse_Evaluate(source, &value, NULL, qtrue)) return qfalse; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%d", abs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + Parse_UnreadSourceToken(source, &token); + if (value < 0) Parse_UnreadSignToken(source); + return qtrue; +} + +/* +=============== +Parse_Directive_evalfloat +=============== +*/ +static int Parse_Directive_evalfloat(source_t *source) +{ + double value; + token_t token; + + if (!Parse_Evaluate(source, NULL, &value, qfalse)) return qfalse; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + Parse_UnreadSourceToken(source, &token); + if (value < 0) Parse_UnreadSignToken(source); + return qtrue; +} + +/* +=============== +Parse_DollarDirective_evalint +=============== +*/ +static int Parse_DollarDirective_evalint(source_t *source) +{ + signed long int value; + token_t token; + + if (!Parse_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%d", abs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + token.intvalue = value; + token.floatvalue = value; + Parse_UnreadSourceToken(source, &token); + if (value < 0) Parse_UnreadSignToken(source); + return qtrue; +} + +/* +=============== +Parse_DollarDirective_evalfloat +=============== +*/ +static int Parse_DollarDirective_evalfloat(source_t *source) +{ + double value; + token_t token; + + if (!Parse_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + token.intvalue = (unsigned long) value; + token.floatvalue = value; + Parse_UnreadSourceToken(source, &token); + if (value < 0) Parse_UnreadSignToken(source); + return qtrue; +} + +/* +=============== +Parse_ReadDollarDirective +=============== +*/ +directive_t dollardirectives[20] = +{ + {"evalint", Parse_DollarDirective_evalint}, + {"evalfloat", Parse_DollarDirective_evalfloat}, + {NULL, NULL} +}; + +static int Parse_ReadDollarDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "found $ without name"); + return qfalse; + } + //directive name must be on the same line + if (token.linescrossed > 0) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "found $ at end of line"); + return qfalse; + } + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; dollardirectives[i].name; i++) + { + if (!strcmp(dollardirectives[i].name, token.string)) + { + return dollardirectives[i].func(source); + } + } + } + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "unknown precompiler directive %s", token.string); + return qfalse; +} + +/* +=============== +Parse_Directive_if_def +=============== +*/ +static int Parse_Directive_if_def(source_t *source, int type) +{ + token_t token; + define_t *d; + int skip; + + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "#ifdef without name"); + return qfalse; + } + if (token.type != TT_NAME) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "expected name after #ifdef, found %s", token.string); + return qfalse; + } + d = Parse_FindHashedDefine(source->definehash, token.string); + skip = (type == INDENT_IFDEF) == (d == NULL); + Parse_PushIndent(source, type, skip); + return qtrue; +} + +/* +=============== +Parse_Directive_ifdef +=============== +*/ +static int Parse_Directive_ifdef(source_t *source) +{ + return Parse_Directive_if_def(source, INDENT_IFDEF); +} + +/* +=============== +Parse_Directive_ifndef +=============== +*/ +static int Parse_Directive_ifndef(source_t *source) +{ + return Parse_Directive_if_def(source, INDENT_IFNDEF); +} + +/* +=============== +Parse_Directive_else +=============== +*/ +static int Parse_Directive_else(source_t *source) +{ + int type, skip; + + Parse_PopIndent(source, &type, &skip); + if (!type) + { + Parse_SourceError(source, "misplaced #else"); + return qfalse; + } + if (type == INDENT_ELSE) + { + Parse_SourceError(source, "#else after #else"); + return qfalse; + } + Parse_PushIndent(source, INDENT_ELSE, !skip); + return qtrue; +} + +/* +=============== +Parse_Directive_endif +=============== +*/ +static int Parse_Directive_endif(source_t *source) +{ + int type, skip; + + Parse_PopIndent(source, &type, &skip); + if (!type) + { + Parse_SourceError(source, "misplaced #endif"); + return qfalse; + } + return qtrue; +} + +/* +=============== +Parse_CheckTokenString +=============== +*/ +static int Parse_CheckTokenString(source_t *source, char *string) +{ + token_t tok; + + if (!Parse_ReadToken(source, &tok)) return qfalse; + //if the token is available + if (!strcmp(tok.string, string)) return qtrue; + // + Parse_UnreadSourceToken(source, &tok); + return qfalse; +} + +/* +=============== +Parse_Directive_define +=============== +*/ +static int Parse_Directive_define(source_t *source) +{ + token_t token, *t, *last; + define_t *define; + + if (source->skip > 0) return qtrue; + // + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "#define without name"); + return qfalse; + } + if (token.type != TT_NAME) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "expected name after #define, found %s", token.string); + return qfalse; + } + //check if the define already exists + define = Parse_FindHashedDefine(source->definehash, token.string); + if (define) + { + if (define->flags & DEFINE_FIXED) + { + Parse_SourceError(source, "can't redefine %s", token.string); + return qfalse; + } + Parse_SourceWarning(source, "redefinition of %s", token.string); + //unread the define name before executing the #undef directive + Parse_UnreadSourceToken(source, &token); + if (!Parse_Directive_undef(source)) return qfalse; + //if the define was not removed (define->flags & DEFINE_FIXED) + define = Parse_FindHashedDefine(source->definehash, token.string); + } + //allocate define + define = (define_t *) Z_Malloc(sizeof(define_t) + strlen(token.string) + 1); + Com_Memset(define, 0, sizeof(define_t)); + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, token.string); + //add the define to the source + Parse_AddDefineToHash(define, source->definehash); + //if nothing is defined, just return + if (!Parse_ReadLine(source, &token)) return qtrue; + //if it is a define with parameters + if (!Parse_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "(")) + { + //read the define parameters + last = NULL; + if (!Parse_CheckTokenString(source, ")")) + { + while(1) + { + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "expected define parameter"); + return qfalse; + } + //if it isn't a name + if (token.type != TT_NAME) + { + Parse_SourceError(source, "invalid define parameter"); + return qfalse; + } + // + if (Parse_FindDefineParm(define, token.string) >= 0) + { + Parse_SourceError(source, "two the same define parameters"); + return qfalse; + } + //add the define parm + t = Parse_CopyToken(&token); + Parse_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->parms = t; + last = t; + define->numparms++; + //read next token + if (!Parse_ReadLine(source, &token)) + { + Parse_SourceError(source, "define parameters not terminated"); + return qfalse; + } + // + if (!strcmp(token.string, ")")) break; + //then it must be a comma + if (strcmp(token.string, ",")) + { + Parse_SourceError(source, "define not terminated"); + return qfalse; + } + } + } + if (!Parse_ReadLine(source, &token)) return qtrue; + } + //read the defined stuff + last = NULL; + do + { + t = Parse_CopyToken(&token); + if (t->type == TT_NAME && !strcmp(t->string, define->name)) + { + Parse_SourceError(source, "recursive define (removed recursion)"); + continue; + } + Parse_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->tokens = t; + last = t; + } while(Parse_ReadLine(source, &token)); + // + if (last) + { + //check for merge operators at the beginning or end + if (!strcmp(define->tokens->string, "##") || + !strcmp(last->string, "##")) + { + Parse_SourceError(source, "define with misplaced ##"); + return qfalse; + } + } + return qtrue; +} + +/* +=============== +Parse_ReadDirective +=============== +*/ +directive_t directives[20] = +{ + {"if", Parse_Directive_if}, + {"ifdef", Parse_Directive_ifdef}, + {"ifndef", Parse_Directive_ifndef}, + {"elif", Parse_Directive_elif}, + {"else", Parse_Directive_else}, + {"endif", Parse_Directive_endif}, + {"include", Parse_Directive_include}, + {"define", Parse_Directive_define}, + {"undef", Parse_Directive_undef}, + {"line", Parse_Directive_line}, + {"error", Parse_Directive_error}, + {"pragma", Parse_Directive_pragma}, + {"eval", Parse_Directive_eval}, + {"evalfloat", Parse_Directive_evalfloat}, + {NULL, NULL} +}; + +static int Parse_ReadDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!Parse_ReadSourceToken(source, &token)) + { + Parse_SourceError(source, "found # without name"); + return qfalse; + } + //directive name must be on the same line + if (token.linescrossed > 0) + { + Parse_UnreadSourceToken(source, &token); + Parse_SourceError(source, "found # at end of line"); + return qfalse; + } + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; directives[i].name; i++) + { + if (!strcmp(directives[i].name, token.string)) + { + return directives[i].func(source); + } + } + } + Parse_SourceError(source, "unknown precompiler directive %s", token.string); + return qfalse; +} + +/* +=============== +Parse_UnreadToken +=============== +*/ +static void Parse_UnreadToken(source_t *source, token_t *token) +{ + Parse_UnreadSourceToken(source, token); +} + +/* +=============== +Parse_ReadEnumeration + +It is assumed that the 'enum' token has already been consumed +This is fairly basic: it doesn't catch some fairly obvious errors like nested +enums, and enumerated names conflict with #define parameters +=============== +*/ +static qboolean Parse_ReadEnumeration( source_t *source ) +{ + token_t newtoken; + int value; + + if( !Parse_ReadToken( source, &newtoken ) ) + return qfalse; + + if( newtoken.type != TT_PUNCTUATION || newtoken.subtype != P_BRACEOPEN ) + { + Parse_SourceError( source, "Found %s when expecting {\n", + newtoken.string ); + return qfalse; + } + + for( value = 0;; value++ ) + { + token_t name; + + // read the name + if( !Parse_ReadToken( source, &name ) ) + break; + + // it's ok for the enum to end immediately + if( name.type == TT_PUNCTUATION && name.subtype == P_BRACECLOSE ) + { + if( !Parse_ReadToken( source, &name ) ) + break; + + // ignore trailing semicolon + if( name.type != TT_PUNCTUATION || name.subtype != P_SEMICOLON ) + Parse_UnreadToken( source, &name ); + + return qtrue; + } + + // ... but not for it to do anything else + if( name.type != TT_NAME ) + { + Parse_SourceError( source, "Found %s when expecting identifier\n", + name.string ); + return qfalse; + } + + if( !Parse_ReadToken( source, &newtoken ) ) + break; + + if( newtoken.type != TT_PUNCTUATION ) + { + Parse_SourceError( source, "Found %s when expecting , or = or }\n", + newtoken.string ); + return qfalse; + } + + if( newtoken.subtype == P_ASSIGN ) + { + int neg = 1; + + if( !Parse_ReadToken( source, &newtoken ) ) + break; + + // Parse_ReadToken doesn't seem to read negative numbers, so we do it + // ourselves + if( newtoken.type == TT_PUNCTUATION && newtoken.subtype == P_SUB ) + { + neg = -1; + + // the next token should be the number + if( !Parse_ReadToken( source, &newtoken ) ) + break; + } + + if( newtoken.type != TT_NUMBER || !( newtoken.subtype & TT_INTEGER ) ) + { + Parse_SourceError( source, "Found %s when expecting integer\n", + newtoken.string ); + return qfalse; + } + + // this is somewhat silly, but cheap to check + if( neg == -1 && ( newtoken.subtype & TT_UNSIGNED ) ) + { + Parse_SourceWarning( source, "Value in enumeration is negative and " + "unsigned\n" ); + } + + // set the new define value + value = newtoken.intvalue * neg; + + if( !Parse_ReadToken( source, &newtoken ) ) + break; + } + + if( newtoken.type != TT_PUNCTUATION || ( newtoken.subtype != P_COMMA && + newtoken.subtype != P_BRACECLOSE ) ) + { + Parse_SourceError( source, "Found %s when expecting , or }\n", + newtoken.string ); + return qfalse; + } + + if( !Parse_AddDefineToSourceFromString( source, va( "%s %d\n", name.string, + value ) ) ) + { + Parse_SourceWarning( source, "Couldn't add define to source: %s = %d\n", + name.string, value ); + return qfalse; + } + + if( newtoken.subtype == P_BRACECLOSE ) + { + if( !Parse_ReadToken( source, &name ) ) + break; + + // ignore trailing semicolon + if( name.type != TT_PUNCTUATION || name.subtype != P_SEMICOLON ) + Parse_UnreadToken( source, &name ); + + return qtrue; + } + } + + // got here if a ReadToken returned false + return qfalse; +} + +/* +=============== +Parse_ReadToken +=============== +*/ +static int Parse_ReadToken(source_t *source, token_t *token) +{ + define_t *define; + + while(1) + { + if (!Parse_ReadSourceToken(source, token)) return qfalse; + //check for precompiler directives + if (token->type == TT_PUNCTUATION && *token->string == '#') + { + { + //read the precompiler directive + if (!Parse_ReadDirective(source)) return qfalse; + continue; + } + } + if (token->type == TT_PUNCTUATION && *token->string == '$') + { + { + //read the precompiler directive + if (!Parse_ReadDollarDirective(source)) return qfalse; + continue; + } + } + if( token->type == TT_NAME && !Q_stricmp( token->string, "enum" ) ) + { + if( !Parse_ReadEnumeration( source ) ) + return qfalse; + continue; + } + // recursively concatenate strings that are behind each other still resolving defines + if (token->type == TT_STRING) + { + token_t newtoken; + if (Parse_ReadToken(source, &newtoken)) + { + if (newtoken.type == TT_STRING) + { + token->string[strlen(token->string)-1] = '\0'; + if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN_CHARS) + { + Parse_SourceError(source, "string longer than MAX_TOKEN_CHARS %d\n", MAX_TOKEN_CHARS); + return qfalse; + } + strcat(token->string, newtoken.string+1); + } + else + { + Parse_UnreadToken(source, &newtoken); + } + } + } + //if skipping source because of conditional compilation + if (source->skip) continue; + //if the token is a name + if (token->type == TT_NAME) + { + //check if the name is a define macro + define = Parse_FindHashedDefine(source->definehash, token->string); + //if it is a define macro + if (define) + { + //expand the defined macro + if (!Parse_ExpandDefineIntoSource(source, token, define)) return qfalse; + continue; + } + } + //copy token for unreading + Com_Memcpy(&source->token, token, sizeof(token_t)); + //found a token + return qtrue; + } +} + +/* +=============== +Parse_DefineFromString +=============== +*/ +static define_t *Parse_DefineFromString(char *string) +{ + script_t *script; + source_t src; + token_t *t; + int res, i; + define_t *def; + + script = Parse_LoadScriptMemory(string, strlen(string), "*extern"); + //create a new source + Com_Memset(&src, 0, sizeof(source_t)); + strncpy(src.filename, "*extern", MAX_QPATH); + src.scriptstack = script; + src.definehash = Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *)); + Com_Memset( src.definehash, 0, DEFINEHASHSIZE * sizeof(define_t *)); + //create a define from the source + res = Parse_Directive_define(&src); + //free any tokens if left + for (t = src.tokens; t; t = src.tokens) + { + src.tokens = src.tokens->next; + Parse_FreeToken(t); + } + def = NULL; + for (i = 0; i < DEFINEHASHSIZE; i++) + { + if (src.definehash[i]) + { + def = src.definehash[i]; + break; + } + } + // + Z_Free(src.definehash); + // + Parse_FreeScript(script); + //if the define was created succesfully + if (res > 0) return def; + //free the define is created + if (src.defines) Parse_FreeDefine(def); + // + return NULL; +} + +/* +=============== +Parse_AddDefineToSourceFromString +=============== +*/ +static qboolean Parse_AddDefineToSourceFromString( source_t *source, + char *string ) +{ + Parse_PushScript( source, Parse_LoadScriptMemory( string, strlen( string ), + "*extern" ) ); + return Parse_Directive_define( source ); +} + +/* +=============== +Parse_AddGlobalDefine + +add a globals define that will be added to all opened sources +=============== +*/ +int Parse_AddGlobalDefine(char *string) +{ + define_t *define; + + define = Parse_DefineFromString(string); + if (!define) return qfalse; + define->next = globaldefines; + globaldefines = define; + return qtrue; +} + +/* +=============== +Parse_CopyDefine +=============== +*/ +static define_t *Parse_CopyDefine(source_t *source, define_t *define) +{ + define_t *newdefine; + token_t *token, *newtoken, *lasttoken; + + newdefine = (define_t *) Z_Malloc(sizeof(define_t) + strlen(define->name) + 1); + //copy the define name + newdefine->name = (char *) newdefine + sizeof(define_t); + strcpy(newdefine->name, define->name); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) + { + newtoken = Parse_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->tokens = newtoken; + lasttoken = newtoken; + } + //copy the define parameters + newdefine->parms = NULL; + for (lasttoken = NULL, token = define->parms; token; token = token->next) + { + newtoken = Parse_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->parms = newtoken; + lasttoken = newtoken; + } + return newdefine; +} + +/* +=============== +Parse_AddGlobalDefinesToSource +=============== +*/ +static void Parse_AddGlobalDefinesToSource(source_t *source) +{ + define_t *define, *newdefine; + + for (define = globaldefines; define; define = define->next) + { + newdefine = Parse_CopyDefine(source, define); + Parse_AddDefineToHash(newdefine, source->definehash); + } +} + +/* +=============== +Parse_LoadSourceFile +=============== +*/ +static source_t *Parse_LoadSourceFile(const char *filename) +{ + source_t *source; + script_t *script; + + script = Parse_LoadScriptFile(filename); + if (!script) return NULL; + + script->next = NULL; + + source = (source_t *) Z_Malloc(sizeof(source_t)); + Com_Memset(source, 0, sizeof(source_t)); + + strncpy(source->filename, filename, MAX_QPATH); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + + source->definehash = Z_Malloc(DEFINEHASHSIZE * sizeof(define_t *)); + Com_Memset( source->definehash, 0, DEFINEHASHSIZE * sizeof(define_t *)); + Parse_AddGlobalDefinesToSource(source); + return source; +} + +/* +=============== +Parse_FreeSource +=============== +*/ +static void Parse_FreeSource(source_t *source) +{ + script_t *script; + token_t *token; + define_t *define; + indent_t *indent; + int i; + + //Parse_PrintDefineHashTable(source->definehash); + //free all the scripts + while(source->scriptstack) + { + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + Parse_FreeScript(script); + } + //free all the tokens + while(source->tokens) + { + token = source->tokens; + source->tokens = source->tokens->next; + Parse_FreeToken(token); + } + for (i = 0; i < DEFINEHASHSIZE; i++) + { + while(source->definehash[i]) + { + define = source->definehash[i]; + source->definehash[i] = source->definehash[i]->hashnext; + Parse_FreeDefine(define); + } + } + //free all indents + while(source->indentstack) + { + indent = source->indentstack; + source->indentstack = source->indentstack->next; + Z_Free(indent); + } + // + if (source->definehash) Z_Free(source->definehash); + //free the source itself + Z_Free(source); +} + +#define MAX_SOURCEFILES 64 + +source_t *sourceFiles[MAX_SOURCEFILES]; + +/* +=============== +Parse_LoadSourceHandle +=============== +*/ +int Parse_LoadSourceHandle(const char *filename) +{ + source_t *source; + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (!sourceFiles[i]) + break; + } + if (i >= MAX_SOURCEFILES) + return 0; + source = Parse_LoadSourceFile(filename); + if (!source) + return 0; + sourceFiles[i] = source; + return i; +} + +/* +=============== +Parse_FreeSourceHandle +=============== +*/ +int Parse_FreeSourceHandle(int handle) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return qfalse; + if (!sourceFiles[handle]) + return qfalse; + + Parse_FreeSource(sourceFiles[handle]); + sourceFiles[handle] = NULL; + return qtrue; +} + +/* +=============== +Parse_ReadTokenHandle +=============== +*/ +int Parse_ReadTokenHandle(int handle, pc_token_t *pc_token) +{ + token_t token; + int ret; + + if (handle < 1 || handle >= MAX_SOURCEFILES) + return 0; + if (!sourceFiles[handle]) + return 0; + + ret = Parse_ReadToken(sourceFiles[handle], &token); + strcpy(pc_token->string, token.string); + pc_token->type = token.type; + pc_token->subtype = token.subtype; + pc_token->intvalue = token.intvalue; + pc_token->floatvalue = token.floatvalue; + if (pc_token->type == TT_STRING) + Parse_StripDoubleQuotes(pc_token->string); + return ret; +} + +/* +=============== +Parse_SourceFileAndLine +=============== +*/ +int Parse_SourceFileAndLine(int handle, char *filename, int *line) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return qfalse; + if (!sourceFiles[handle]) + return qfalse; + + strcpy(filename, sourceFiles[handle]->filename); + if (sourceFiles[handle]->scriptstack) + *line = sourceFiles[handle]->scriptstack->line; + else + *line = 0; + return qtrue; +} diff --git a/src/qcommon/puff.c b/src/qcommon/puff.c new file mode 100644 index 0000000..721854d --- /dev/null +++ b/src/qcommon/puff.c @@ -0,0 +1,758 @@ +/* + * This is a modified version of Mark Adlers work, + * see below for the original copyright. + * 2006 - Joerg Dietrich + */ + +/* + * puff.c + * Copyright (C) 2002-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 1.8, 9 Jan 2004 + * + * puff.c is a simple inflate written to be an unambiguous way to specify the + * deflate format. It is not written for speed but rather simplicity. As a + * side benefit, this code might actually be useful when small code is more + * important than speed, such as bootstrap applications. For typical deflate + * data, zlib's inflate() is about four times as fast as puff(). zlib's + * inflate compiles to around 20K on my machine, whereas puff.c compiles to + * around 4K on my machine (a PowerPC using GNU cc). If the faster decode() + * function here is used, then puff() is only twice as slow as zlib's + * inflate(). + * + * All dynamically allocated memory comes from the stack. The stack required + * is less than 2K bytes. This code is compatible with 16-bit int's and + * assumes that long's are at least 32 bits. puff.c uses the short data type, + * assumed to be 16 bits, for arrays in order to to conserve memory. The code + * works whether integers are stored big endian or little endian. + * + * In the comments below are "Format notes" that describe the inflate process + * and document some of the less obvious aspects of the format. This source + * code is meant to supplement RFC 1951, which formally describes the deflate + * format: + * + * http://www.zlib.org/rfc-deflate.html + */ + +/* + * Change history: + * + * 1.0 10 Feb 2002 - First version + * 1.1 17 Feb 2002 - Clarifications of some comments and notes + * - Update puff() dest and source pointers on negative + * errors to facilitate debugging deflators + * - Remove longest from struct huffman -- not needed + * - Simplify offs[] index in construct() + * - Add input size and checking, using longjmp() to + * maintain easy readability + * - Use short data type for large arrays + * - Use pointers instead of long to specify source and + * destination sizes to avoid arbitrary 4 GB limits + * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!), + * but leave simple version for readabilty + * - Make sure invalid distances detected if pointers + * are 16 bits + * - Fix fixed codes table error + * - Provide a scanning mode for determining size of + * uncompressed data + * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Jean-loup] + * - Add a puff.h file for the interface + * - Add braces in puff() for else do [Jean-loup] + * - Use indexes instead of pointers for readability + * 1.4 31 Mar 2002 - Simplify construct() code set check + * - Fix some comments + * - Add FIXLCODES #define + * 1.5 6 Apr 2002 - Minor comment fixes + * 1.6 7 Aug 2002 - Minor format changes + * 1.7 3 Mar 2003 - Added test code for distribution + * - Added zlib-like license + * 1.8 9 Jan 2004 - Added some comments on no distance codes case + */ + +#include /* for setjmp(), longjmp(), and jmp_buf */ +#include "puff.h" /* prototype for puff() */ + +#define local static /* for local function definitions */ + +/* + * Maximums for allocations and loops. It is not useful to change these -- + * they are fixed by the deflate format. + */ +#define MAXBITS 15 /* maximum bits in a code */ +#define MAXLCODES 286 /* maximum number of literal/length codes */ +#define MAXDCODES 30 /* maximum number of distance codes */ +#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */ +#define FIXLCODES 288 /* number of fixed literal/length codes */ + +/* input and output state */ +struct state { + /* output state */ + uint8_t *out; /* output buffer */ + uint32_t outlen; /* available space at out */ + uint32_t outcnt; /* bytes written to out so far */ + + /* input state */ + uint8_t *in; /* input buffer */ + uint32_t inlen; /* available input at in */ + uint32_t incnt; /* bytes read so far */ + int32_t bitbuf; /* bit buffer */ + int32_t bitcnt; /* number of bits in bit buffer */ + + /* input limit error return state for bits() and decode() */ + jmp_buf env; +}; + +/* + * Return need bits from the input stream. This always leaves less than + * eight bits in the buffer. bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + * significant bit. Therefore bits are dropped from the bottom of the bit + * buffer, using shift right, and new bytes are appended to the top of the + * bit buffer, using shift left. + */ +local int32_t bits(struct state *s, int32_t need) +{ + int32_t val; /* bit accumulator (can use up to 20 bits) */ + + /* load at least need bits into val */ + val = s->bitbuf; + while (s->bitcnt < need) { + if (s->incnt == s->inlen) longjmp(s->env, 1); /* out of input */ + val |= (int32_t)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */ + s->bitcnt += 8; + } + + /* drop need bits and update buffer, always zero to seven bits left */ + s->bitbuf = (int32_t)(val >> need); + s->bitcnt -= need; + + /* return need bits, zeroing the bits above that */ + return (int32_t)(val & ((1L << need) - 1)); +} + +/* + * Process a stored block. + * + * Format notes: + * + * - After the two-bit stored block type (00), the stored block length and + * stored bytes are byte-aligned for fast copying. Therefore any leftover + * bits in the byte that has the last bit of the type, as many as seven, are + * discarded. The value of the discarded bits are not defined and should not + * be checked against any expectation. + * + * - The second inverted copy of the stored block length does not have to be + * checked, but it's probably a good idea to do so anyway. + * + * - A stored block can have zero length. This is sometimes used to byte-align + * subsets of the compressed data for random access or partial recovery. + */ +local int32_t stored(struct state *s) +{ + uint32_t len; /* length of stored block */ + + /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ + s->bitbuf = 0; + s->bitcnt = 0; + + /* get length and check against its one's complement */ + if (s->incnt + 4 > s->inlen) return 2; /* not enough input */ + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if (s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff)) + return -2; /* didn't match complement! */ + + /* copy len bytes from in to out */ + if (s->incnt + len > s->inlen) return 2; /* not enough input */ + if (s->out != NULL) { + if (s->outcnt + len > s->outlen) + return 1; /* not enough output space */ + while (len--) + s->out[s->outcnt++] = s->in[s->incnt++]; + } + else { /* just scanning */ + s->outcnt += len; + s->incnt += len; + } + + /* done with a valid stored block */ + return 0; +} + +/* + * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[]. The decoding process can be + * seen in the function decode() below. + */ +struct huffman { + int16_t *count; /* number of symbols of each length */ + int16_t *symbol; /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h. Return the symbol or + * a negative value if there is an error. If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -9 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + * a simple integer ordering of codes of the same lengths. Hence below the + * bits are pulled from the compressed data one at a time and used to + * build the code value reversed from what is in the stream in order to + * permit simple integer comparisons for decoding. A table-based decoding + * scheme (as used in zlib) does not need to do this reversal. + * + * - The first code for the shortest length is all zeros. Subsequent codes of + * the same length are simply integer increments of the previous code. When + * moving up a length, a zero bit is appended to the code. For a complete + * code, the last code of the longest length will be all ones. + * + * - Incomplete codes are handled by this decoder, since they are permitted + * in the deflate format. See the format notes for fixed() and dynamic(). + */ +local int32_t decode(struct state *s, struct huffman *h) +{ + int32_t len; /* current number of bits in code */ + int32_t code; /* len bits being decoded */ + int32_t first; /* first code of length len */ + int32_t count; /* number of codes of length len */ + int32_t index; /* index of first code of length len in symbol table */ + int32_t bitbuf; /* bits from stream */ + int32_t left; /* bits left in next or left to process */ + int16_t *next; /* next number of codes */ + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while (1) { + while (left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if (code < first + count) { /* if length len, return symbol */ + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS+1) - len; + if (left == 0) break; + if (s->incnt == s->inlen) longjmp(s->env, 1); /* out of input */ + bitbuf = s->in[s->incnt++]; + if (left > 8) left = 8; + } + return -9; /* ran out of codes */ +} + +/* + * Given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes. Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length. The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set. The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative. If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol. If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + * + * Not used by decode(), but used for error checking, h->count[0] is the number + * of the n symbols not in the code. So n - h->count[0] is the number of + * codes. This is useful for checking for incomplete codes that have more than + * one symbol, which is an error in a dynamic block. + * + * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS + * This is assured by the construction of the length arrays in dynamic() and + * fixed() and is not verified by construct(). + * + * Format notes: + * + * - Permitted and expected examples of incomplete codes are one of the fixed + * codes and any code with a single symbol which in deflate is coded as one + * bit instead of zero bits. See the format notes for fixed() and dynamic(). + * + * - Within a given code length, the symbols are kept in ascending order for + * the code bits definition. + */ +local int32_t construct(struct huffman *h, int16_t *length, int32_t n) +{ + int32_t symbol; /* current symbol when stepping through length[] */ + int32_t len; /* current length when stepping through h->count[] */ + int32_t left; /* number of possible codes left of current length */ + int16_t offs[MAXBITS+1]; /* offsets in symbol table for each length */ + + /* count number of codes of each length */ + for (len = 0; len <= MAXBITS; len++) + h->count[len] = 0; + for (symbol = 0; symbol < n; symbol++) + (h->count[length[symbol]])++; /* assumes lengths are within bounds */ + if (h->count[0] == n) /* no codes! */ + return 0; /* complete, but decode() will fail */ + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; /* one possible code of zero length */ + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; /* one more bit, double codes left */ + left -= h->count[len]; /* deduct count from possible codes */ + if (left < 0) return left; /* over-subscribed--return negative */ + } /* left > 0 means incomplete */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + h->count[len]; + + /* + * put symbols in table sorted by length, by symbol order within each + * length + */ + for (symbol = 0; symbol < n; symbol++) + if (length[symbol] != 0) + h->symbol[offs[length[symbol]]++] = symbol; + + /* return zero for complete set, positive for incomplete set */ + return left; +} + +/* + * Decode literal/length and distance codes until an end-of-block code. + * + * Format notes: + * + * - Compressed data that is after the block type if fixed or after the code + * description if dynamic is a combination of literals and length/distance + * pairs terminated by and end-of-block code. Literals are simply Huffman + * coded bytes. A length/distance pair is a coded length followed by a + * coded distance to represent a string that occurs earlier in the + * uncompressed data that occurs again at the current location. + * + * - Literals, lengths, and the end-of-block code are combined into a single + * code of up to 286 symbols. They are 256 literals (0..255), 29 length + * symbols (257..285), and the end-of-block symbol (256). + * + * - There are 256 possible lengths (3..258), and so 29 symbols are not enough + * to represent all of those. Lengths 3..10 and 258 are in fact represented + * by just a length symbol. Lengths 11..257 are represented as a symbol and + * some number of extra bits that are added as an integer to the base length + * of the length symbol. The number of extra bits is determined by the base + * length symbol. These are in the static arrays below, lens[] for the base + * lengths and lext[] for the corresponding number of extra bits. + * + * - The reason that 258 gets its own symbol is that the longest length is used + * often in highly redundant files. Note that 258 can also be coded as the + * base value 227 plus the maximum extra value of 31. While a good deflate + * should never do this, it is not an error, and should be decoded properly. + * + * - If a length is decoded, including its extra bits if any, then it is + * followed a distance code. There are up to 30 distance symbols. Again + * there are many more possible distances (1..32768), so extra bits are added + * to a base value represented by the symbol. The distances 1..4 get their + * own symbol, but the rest require extra bits. The base distances and + * corresponding number of extra bits are below in the static arrays dist[] + * and dext[]. + * + * - Literal bytes are simply written to the output. A length/distance pair is + * an instruction to copy previously uncompressed bytes to the output. The + * copy is from distance bytes back in the output stream, copying for length + * bytes. + * + * - Distances pointing before the beginning of the output data are not + * permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + * allowed and common. For example, a distance of one and a length of 258 + * simply copies the last byte 258 times. A distance of four and a length of + * twelve copies the last four bytes three times. A simple forward copy + * ignoring whether the length is greater than the distance or not implements + * this correctly. You should not use memcpy() since its behavior is not + * defined for overlapped arrays. You should not use memmove() or bcopy() + * since though their behavior -is- defined for overlapping arrays, it is + * defined to do the wrong thing in this case. + */ +local int32_t codes(struct state *s, + struct huffman *lencode, + struct huffman *distcode) +{ + int32_t symbol; /* decoded symbol */ + int32_t len; /* length for copy */ + uint32_t dist; /* distance for copy */ + static const int16_t lens[29] = { /* Size base for length codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; + static const int16_t lext[29] = { /* Extra bits for length codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static const int16_t dists[30] = { /* Offset base for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; + static const int16_t dext[30] = { /* Extra bits for distance codes 0..29 */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + /* decode literals and length/distance pairs */ + do { + symbol = decode(s, lencode); + if (symbol < 0) return symbol; /* invalid symbol */ + if (symbol < 256) { /* literal: symbol is the byte */ + /* write out the literal */ + if (s->out != NULL) { + if (s->outcnt == s->outlen) return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } + else if (symbol > 256) { /* length */ + /* get and compute length */ + symbol -= 257; + if (symbol >= 29) return -9; /* invalid fixed code */ + len = lens[symbol] + bits(s, lext[symbol]); + + /* get and check distance */ + symbol = decode(s, distcode); + if (symbol < 0) return symbol; /* invalid symbol */ + dist = dists[symbol] + bits(s, dext[symbol]); + if (dist > s->outcnt) + return -10; /* distance too far back */ + + /* copy length bytes from distance bytes back */ + if (s->out != NULL) { + if (s->outcnt + len > s->outlen) return 1; + while (len--) { + s->out[s->outcnt] = s->out[s->outcnt - dist]; + s->outcnt++; + } + } + else + s->outcnt += len; + } + } while (symbol != 256); /* end of block symbol */ + + /* done with a valid fixed or dynamic block */ + return 0; +} + +/* + * Process a fixed codes block. + * + * Format notes: + * + * - This block type can be useful for compressing small amounts of data for + * which the size of the code descriptions in a dynamic block exceeds the + * benefit of custom codes for that block. For fixed codes, no bits are + * spent on code descriptions. Instead the code lengths for literal/length + * codes and distance codes are fixed. The specific lengths for each symbol + * can be seen in the "for" loops below. + * + * - The literal/length code is complete, but has two symbols that are invalid + * and should result in an error if received. This cannot be implemented + * simply as an incomplete code since those two symbols are in the "middle" + * of the code. They are eight bits long and the longest literal/length\ + * code is nine bits. Therefore the code must be constructed with those + * symbols, and the invalid symbols must be detected after decoding. + * + * - The fixed distance codes also have two invalid symbols that should result + * in an error if received. Since all of the distance codes are the same + * length, this can be implemented as an incomplete code. Then the invalid + * codes are detected while decoding. + */ +local int32_t fixed(struct state *s) +{ + static int32_t virgin = 1; + static int16_t lencnt[MAXBITS+1], lensym[FIXLCODES]; + static int16_t distcnt[MAXBITS+1], distsym[MAXDCODES]; + static struct huffman lencode = {lencnt, lensym}; + static struct huffman distcode = {distcnt, distsym}; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + int32_t symbol; + int16_t lengths[FIXLCODES]; + + /* literal/length table */ + for (symbol = 0; symbol < 144; symbol++) + lengths[symbol] = 8; + for (; symbol < 256; symbol++) + lengths[symbol] = 9; + for (; symbol < 280; symbol++) + lengths[symbol] = 7; + for (; symbol < FIXLCODES; symbol++) + lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + /* distance table */ + for (symbol = 0; symbol < MAXDCODES; symbol++) + lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + /* do this just once */ + virgin = 0; + } + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Process a dynamic codes block. + * + * Format notes: + * + * - A dynamic block starts with a description of the literal/length and + * distance codes for that block. New dynamic blocks allow the compressor to + * rapidly adapt to changing data with new codes optimized for that data. + * + * - The codes used by the deflate format are "canonical", which means that + * the actual bits of the codes are generated in an unambiguous way simply + * from the number of bits in each code. Therefore the code descriptions + * are simply a list of code lengths for each symbol. + * + * - The code lengths are stored in order for the symbols, so lengths are + * provided for each of the literal/length symbols, and for each of the + * distance symbols. + * + * - If a symbol is not used in the block, this is represented by a zero as + * as the code length. This does not mean a zero-length code, but rather + * that no code should be created for this symbol. There is no way in the + * deflate format to represent a zero-length code. + * + * - The maximum number of bits in a code is 15, so the possible lengths for + * any code are 1..15. + * + * - The fact that a length of zero is not permitted for a code has an + * interesting consequence. Normally if only one symbol is used for a given + * code, then in fact that code could be represented with zero bits. However + * in deflate, that code has to be at least one bit. So for example, if + * only a single distance base symbol appears in a block, then it will be + * represented by a single code of length one, in particular one 0 bit. This + * is an incomplete code, since if a 1 bit is received, it has no meaning, + * and should result in an error. So incomplete distance codes of one symbol + * should be permitted, and the receipt of invalid codes should be handled. + * + * - It is also possible to have a single literal/length code, but that code + * must be the end-of-block code, since every dynamic block has one. This + * is not the most efficient way to create an empty block (an empty fixed + * block is fewer bits), but it is allowed by the format. So incomplete + * literal/length codes of one symbol should also be permitted. + * + * - If there are only literal codes and no lengths, then there are no distance + * codes. This is represented by one distance code with zero bits. + * + * - The list of up to 286 length/literal lengths and up to 30 distance lengths + * are themselves compressed using Huffman codes and run-length encoding. In + * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means + * that length, and the symbols 16, 17, and 18 are run-length instructions. + * Each of 16, 17, and 18 are follwed by extra bits to define the length of + * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10 + * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols + * are common, hence the special coding for zero lengths. + * + * - The symbols for 0..18 are Huffman coded, and so that code must be + * described first. This is simply a sequence of up to 19 three-bit values + * representing no code (0) or the code length for that symbol (1..7). + * + * - A dynamic block starts with three fixed-size counts from which is computed + * the number of literal/length code lengths, the number of distance code + * lengths, and the number of code length code lengths (ok, you come up with + * a better name!) in the code descriptions. For the literal/length and + * distance codes, lengths after those provided are considered zero, i.e. no + * code. The code length code lengths are received in a permuted order (see + * the order[] array below) to make a short code length code length list more + * likely. As it turns out, very short and very long codes are less likely + * to be seen in a dynamic code description, hence what may appear initially + * to be a peculiar ordering. + * + * - Given the number of literal/length code lengths (nlen) and distance code + * lengths (ndist), then they are treated as one long list of nlen + ndist + * code lengths. Therefore run-length coding can and often does cross the + * boundary between the two sets of lengths. + * + * - So to summarize, the code description at the start of a dynamic block is + * three counts for the number of code lengths for the literal/length codes, + * the distance codes, and the code length codes. This is followed by the + * code length code lengths, three bits each. This is used to construct the + * code length code which is used to read the remainder of the lengths. Then + * the literal/length code lengths and distance lengths are read as a single + * set of lengths using the code length codes. Codes are constructed from + * the resulting two sets of lengths, and then finally you can start + * decoding actual compressed data in the block. + * + * - For reference, a "typical" size for the code description in a dynamic + * block is around 80 bytes. + */ +local int32_t dynamic(struct state *s) +{ + int32_t nlen, ndist, ncode; /* number of lengths in descriptor */ + int32_t index; /* index of lengths[] */ + int32_t err; /* construct() return value */ + int16_t lengths[MAXCODES]; /* descriptor code lengths */ + int16_t lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */ + int16_t distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */ + struct huffman lencode = {lencnt, lensym}; /* length code */ + struct huffman distcode = {distcnt, distsym}; /* distance code */ + static const int16_t order[19] = /* permutation of code length codes */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* get number of lengths in each table, check lengths */ + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if (nlen > MAXLCODES || ndist > MAXDCODES) + return -3; /* bad counts */ + + /* read code length code lengths (really), missing lengths are zero */ + for (index = 0; index < ncode; index++) + lengths[order[index]] = bits(s, 3); + for (; index < 19; index++) + lengths[order[index]] = 0; + + /* build huffman table for code lengths codes (use lencode temporarily) */ + err = construct(&lencode, lengths, 19); + if (err != 0) return -4; /* require complete code set here */ + + /* read length/literal and distance code length tables */ + index = 0; + while (index < nlen + ndist) { + int32_t symbol; /* decoded value */ + int32_t len; /* last length to repeat */ + + symbol = decode(s, &lencode); + if (symbol < 16) /* length in 0..15 */ + lengths[index++] = symbol; + else { /* repeat instruction */ + len = 0; /* assume repeating zeros */ + if (symbol == 16) { /* repeat last length 3..6 times */ + if (index == 0) return -5; /* no last length! */ + len = lengths[index - 1]; /* last length */ + symbol = 3 + bits(s, 2); + } + else if (symbol == 17) /* repeat zero 3..10 times */ + symbol = 3 + bits(s, 3); + else /* == 18, repeat zero 11..138 times */ + symbol = 11 + bits(s, 7); + if (index + symbol > nlen + ndist) + return -6; /* too many lengths! */ + while (symbol--) /* repeat last or zero symbol times */ + lengths[index++] = len; + } + } + + /* build huffman table for literal/length codes */ + err = construct(&lencode, lengths, nlen); + if (err < 0 || (err > 0 && nlen - lencode.count[0] != 1)) + return -7; /* only allow incomplete codes if just one code */ + + /* build huffman table for distance codes */ + err = construct(&distcode, lengths + nlen, ndist); + if (err < 0 || (err > 0 && ndist - distcode.count[0] != 1)) + return -8; /* only allow incomplete codes if just one code */ + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Inflate source to dest. On return, destlen and sourcelen are updated to the + * size of the uncompressed data and the size of the deflate data respectively. + * On success, the return value of puff() is zero. If there is an error in the + * source data, i.e. it is not in the deflate format, then a negative value is + * returned. If there is not enough input available or there is not enough + * output space, then a positive error is returned. In that case, destlen and + * sourcelen are not updated to facilitate retrying from the beginning with the + * provision of more input data or more output space. In the case of invalid + * inflate data (a negative error), the dest and source pointers are updated to + * facilitate the debugging of deflators. + * + * puff() also has a mode to determine the size of the uncompressed output with + * no output written. For this dest must be (uint8_t *)0. In this case, + * the input value of *destlen is ignored, and on return *destlen is set to the + * size of the uncompressed output. + * + * The return codes are: + * + * 2: available inflate data did not terminate + * 1: output space exhausted before completing inflate + * 0: successful inflate + * -1: invalid block type (type == 3) + * -2: stored block length did not match one's complement + * -3: dynamic block code description: too many length or distance codes + * -4: dynamic block code description: code lengths codes incomplete + * -5: dynamic block code description: repeat lengths with no first length + * -6: dynamic block code description: repeat more than specified lengths + * -7: dynamic block code description: invalid literal/length code lengths + * -8: dynamic block code description: invalid distance code lengths + * -9: invalid literal/length or distance code in fixed or dynamic block + * -10: distance is too far back in fixed or dynamic block + * + * Format notes: + * + * - Three bits are read for each block to determine the kind of block and + * whether or not it is the last block. Then the block is decoded and the + * process repeated if it was not the last block. + * + * - The leftover bits in the last byte of the deflate data after the last + * block (if it was a fixed or dynamic block) are undefined and have no + * expected values to check. + */ +int32_t puff(uint8_t *dest, /* pointer to destination pointer */ + uint32_t *destlen, /* amount of output space */ + uint8_t *source, /* pointer to source data pointer */ + uint32_t *sourcelen) /* amount of input available */ +{ + struct state s; /* input/output state */ + int32_t last, type; /* block information */ + int32_t err; /* return value */ + + /* initialize output state */ + s.out = dest; + s.outlen = *destlen; /* ignored if dest is NULL */ + s.outcnt = 0; + + /* initialize input state */ + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + /* return if bits() or decode() tries to read past available input */ + if (setjmp(s.env) != 0) /* if came back here via longjmp() */ + err = 2; /* then skip do-loop, return error */ + else { + /* process blocks until last block or error */ + do { + last = bits(&s, 1); /* one if last block */ + type = bits(&s, 2); /* block type 0..3 */ + err = type == 0 ? stored(&s) : + (type == 1 ? fixed(&s) : + (type == 2 ? dynamic(&s) : + -1)); /* type == 3, invalid */ + if (err != 0) break; /* return with error */ + } while (!last); + } + + /* update the lengths and return */ + if (err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + return err; +} diff --git a/src/qcommon/puff.h b/src/qcommon/puff.h new file mode 100644 index 0000000..14070f6 --- /dev/null +++ b/src/qcommon/puff.h @@ -0,0 +1,43 @@ +/* + * This is a modified version of Mark Adlers work, + * see below for the original copyright. + * 2006 - Joerg Dietrich + */ + +/* puff.h + Copyright (C) 2002, 2003 Mark Adler, all rights reserved + version 1.7, 3 Mar 2002 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +#ifndef __PUFF_H +#define __PUFF_H + +#include "q_shared.h" /* for definitions of the types */ + +/* + * See puff.c for purpose and usage. + */ +int32_t puff(uint8_t *dest, /* pointer to destination pointer */ + uint32_t *destlen, /* amount of output space */ + uint8_t *source, /* pointer to source data pointer */ + uint32_t *sourcelen); /* amount of input available */ + +#endif // __PUFF_H diff --git a/src/qcommon/q_math.c b/src/qcommon/q_math.c new file mode 100644 index 0000000..e435590 --- /dev/null +++ b/src/qcommon/q_math.c @@ -0,0 +1,1295 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// q_math.c -- stateless support routines that are included in each code module + +// Some of the vector functions are static inline in q_shared.h. q3asm +// doesn't understand static functions though, so we only want them in +// one file. That's what this is about. +#ifdef Q3_VM +#define __Q3_VM_MATH +#endif + +#include "q_shared.h" + +vec3_t vec3_origin = {0,0,0}; +vec3_t axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + +vec4_t colorBlack = {0, 0, 0, 1}; +vec4_t colorRed = {1, 0, 0, 1}; +vec4_t colorGreen = {0, 1, 0, 1}; +vec4_t colorBlue = {0, 0, 1, 1}; +vec4_t colorYellow = {1, 1, 0, 1}; +vec4_t colorMagenta= {1, 0, 1, 1}; +vec4_t colorCyan = {0, 1, 1, 1}; +vec4_t colorWhite = {1, 1, 1, 1}; +vec4_t colorLtGrey = {0.75, 0.75, 0.75, 1}; +vec4_t colorMdGrey = {0.5, 0.5, 0.5, 1}; +vec4_t colorDkGrey = {0.25, 0.25, 0.25, 1}; + +vec4_t g_color_table[8] = + { + {0.2, 0.2, 0.2, 1.0}, + {1.0, 0.0, 0.0, 1.0}, + {0.0, 1.0, 0.0, 1.0}, + {1.0, 1.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 1.0}, + {0.0, 1.0, 1.0, 1.0}, + {1.0, 0.0, 1.0, 1.0}, + {1.0, 1.0, 1.0, 1.0}, + }; + + +vec3_t bytedirs[NUMVERTEXNORMALS] = +{ +{-0.525731f, 0.000000f, 0.850651f}, {-0.442863f, 0.238856f, 0.864188f}, +{-0.295242f, 0.000000f, 0.955423f}, {-0.309017f, 0.500000f, 0.809017f}, +{-0.162460f, 0.262866f, 0.951056f}, {0.000000f, 0.000000f, 1.000000f}, +{0.000000f, 0.850651f, 0.525731f}, {-0.147621f, 0.716567f, 0.681718f}, +{0.147621f, 0.716567f, 0.681718f}, {0.000000f, 0.525731f, 0.850651f}, +{0.309017f, 0.500000f, 0.809017f}, {0.525731f, 0.000000f, 0.850651f}, +{0.295242f, 0.000000f, 0.955423f}, {0.442863f, 0.238856f, 0.864188f}, +{0.162460f, 0.262866f, 0.951056f}, {-0.681718f, 0.147621f, 0.716567f}, +{-0.809017f, 0.309017f, 0.500000f},{-0.587785f, 0.425325f, 0.688191f}, +{-0.850651f, 0.525731f, 0.000000f},{-0.864188f, 0.442863f, 0.238856f}, +{-0.716567f, 0.681718f, 0.147621f},{-0.688191f, 0.587785f, 0.425325f}, +{-0.500000f, 0.809017f, 0.309017f}, {-0.238856f, 0.864188f, 0.442863f}, +{-0.425325f, 0.688191f, 0.587785f}, {-0.716567f, 0.681718f, -0.147621f}, +{-0.500000f, 0.809017f, -0.309017f}, {-0.525731f, 0.850651f, 0.000000f}, +{0.000000f, 0.850651f, -0.525731f}, {-0.238856f, 0.864188f, -0.442863f}, +{0.000000f, 0.955423f, -0.295242f}, {-0.262866f, 0.951056f, -0.162460f}, +{0.000000f, 1.000000f, 0.000000f}, {0.000000f, 0.955423f, 0.295242f}, +{-0.262866f, 0.951056f, 0.162460f}, {0.238856f, 0.864188f, 0.442863f}, +{0.262866f, 0.951056f, 0.162460f}, {0.500000f, 0.809017f, 0.309017f}, +{0.238856f, 0.864188f, -0.442863f},{0.262866f, 0.951056f, -0.162460f}, +{0.500000f, 0.809017f, -0.309017f},{0.850651f, 0.525731f, 0.000000f}, +{0.716567f, 0.681718f, 0.147621f}, {0.716567f, 0.681718f, -0.147621f}, +{0.525731f, 0.850651f, 0.000000f}, {0.425325f, 0.688191f, 0.587785f}, +{0.864188f, 0.442863f, 0.238856f}, {0.688191f, 0.587785f, 0.425325f}, +{0.809017f, 0.309017f, 0.500000f}, {0.681718f, 0.147621f, 0.716567f}, +{0.587785f, 0.425325f, 0.688191f}, {0.955423f, 0.295242f, 0.000000f}, +{1.000000f, 0.000000f, 0.000000f}, {0.951056f, 0.162460f, 0.262866f}, +{0.850651f, -0.525731f, 0.000000f},{0.955423f, -0.295242f, 0.000000f}, +{0.864188f, -0.442863f, 0.238856f}, {0.951056f, -0.162460f, 0.262866f}, +{0.809017f, -0.309017f, 0.500000f}, {0.681718f, -0.147621f, 0.716567f}, +{0.850651f, 0.000000f, 0.525731f}, {0.864188f, 0.442863f, -0.238856f}, +{0.809017f, 0.309017f, -0.500000f}, {0.951056f, 0.162460f, -0.262866f}, +{0.525731f, 0.000000f, -0.850651f}, {0.681718f, 0.147621f, -0.716567f}, +{0.681718f, -0.147621f, -0.716567f},{0.850651f, 0.000000f, -0.525731f}, +{0.809017f, -0.309017f, -0.500000f}, {0.864188f, -0.442863f, -0.238856f}, +{0.951056f, -0.162460f, -0.262866f}, {0.147621f, 0.716567f, -0.681718f}, +{0.309017f, 0.500000f, -0.809017f}, {0.425325f, 0.688191f, -0.587785f}, +{0.442863f, 0.238856f, -0.864188f}, {0.587785f, 0.425325f, -0.688191f}, +{0.688191f, 0.587785f, -0.425325f}, {-0.147621f, 0.716567f, -0.681718f}, +{-0.309017f, 0.500000f, -0.809017f}, {0.000000f, 0.525731f, -0.850651f}, +{-0.525731f, 0.000000f, -0.850651f}, {-0.442863f, 0.238856f, -0.864188f}, +{-0.295242f, 0.000000f, -0.955423f}, {-0.162460f, 0.262866f, -0.951056f}, +{0.000000f, 0.000000f, -1.000000f}, {0.295242f, 0.000000f, -0.955423f}, +{0.162460f, 0.262866f, -0.951056f}, {-0.442863f, -0.238856f, -0.864188f}, +{-0.309017f, -0.500000f, -0.809017f}, {-0.162460f, -0.262866f, -0.951056f}, +{0.000000f, -0.850651f, -0.525731f}, {-0.147621f, -0.716567f, -0.681718f}, +{0.147621f, -0.716567f, -0.681718f}, {0.000000f, -0.525731f, -0.850651f}, +{0.309017f, -0.500000f, -0.809017f}, {0.442863f, -0.238856f, -0.864188f}, +{0.162460f, -0.262866f, -0.951056f}, {0.238856f, -0.864188f, -0.442863f}, +{0.500000f, -0.809017f, -0.309017f}, {0.425325f, -0.688191f, -0.587785f}, +{0.716567f, -0.681718f, -0.147621f}, {0.688191f, -0.587785f, -0.425325f}, +{0.587785f, -0.425325f, -0.688191f}, {0.000000f, -0.955423f, -0.295242f}, +{0.000000f, -1.000000f, 0.000000f}, {0.262866f, -0.951056f, -0.162460f}, +{0.000000f, -0.850651f, 0.525731f}, {0.000000f, -0.955423f, 0.295242f}, +{0.238856f, -0.864188f, 0.442863f}, {0.262866f, -0.951056f, 0.162460f}, +{0.500000f, -0.809017f, 0.309017f}, {0.716567f, -0.681718f, 0.147621f}, +{0.525731f, -0.850651f, 0.000000f}, {-0.238856f, -0.864188f, -0.442863f}, +{-0.500000f, -0.809017f, -0.309017f}, {-0.262866f, -0.951056f, -0.162460f}, +{-0.850651f, -0.525731f, 0.000000f}, {-0.716567f, -0.681718f, -0.147621f}, +{-0.716567f, -0.681718f, 0.147621f}, {-0.525731f, -0.850651f, 0.000000f}, +{-0.500000f, -0.809017f, 0.309017f}, {-0.238856f, -0.864188f, 0.442863f}, +{-0.262866f, -0.951056f, 0.162460f}, {-0.864188f, -0.442863f, 0.238856f}, +{-0.809017f, -0.309017f, 0.500000f}, {-0.688191f, -0.587785f, 0.425325f}, +{-0.681718f, -0.147621f, 0.716567f}, {-0.442863f, -0.238856f, 0.864188f}, +{-0.587785f, -0.425325f, 0.688191f}, {-0.309017f, -0.500000f, 0.809017f}, +{-0.147621f, -0.716567f, 0.681718f}, {-0.425325f, -0.688191f, 0.587785f}, +{-0.162460f, -0.262866f, 0.951056f}, {0.442863f, -0.238856f, 0.864188f}, +{0.162460f, -0.262866f, 0.951056f}, {0.309017f, -0.500000f, 0.809017f}, +{0.147621f, -0.716567f, 0.681718f}, {0.000000f, -0.525731f, 0.850651f}, +{0.425325f, -0.688191f, 0.587785f}, {0.587785f, -0.425325f, 0.688191f}, +{0.688191f, -0.587785f, 0.425325f}, {-0.955423f, 0.295242f, 0.000000f}, +{-0.951056f, 0.162460f, 0.262866f}, {-1.000000f, 0.000000f, 0.000000f}, +{-0.850651f, 0.000000f, 0.525731f}, {-0.955423f, -0.295242f, 0.000000f}, +{-0.951056f, -0.162460f, 0.262866f}, {-0.864188f, 0.442863f, -0.238856f}, +{-0.951056f, 0.162460f, -0.262866f}, {-0.809017f, 0.309017f, -0.500000f}, +{-0.864188f, -0.442863f, -0.238856f}, {-0.951056f, -0.162460f, -0.262866f}, +{-0.809017f, -0.309017f, -0.500000f}, {-0.681718f, 0.147621f, -0.716567f}, +{-0.681718f, -0.147621f, -0.716567f}, {-0.850651f, 0.000000f, -0.525731f}, +{-0.688191f, 0.587785f, -0.425325f}, {-0.587785f, 0.425325f, -0.688191f}, +{-0.425325f, 0.688191f, -0.587785f}, {-0.425325f, -0.688191f, -0.587785f}, +{-0.587785f, -0.425325f, -0.688191f}, {-0.688191f, -0.587785f, -0.425325f} +}; + +//============================================================== + +int Q_rand( int *seed ) { + *seed = (69069 * *seed + 1); + return *seed; +} + +float Q_random( int *seed ) { + return ( Q_rand( seed ) & 0xffff ) / (float)0x10000; +} + +float Q_crandom( int *seed ) { + return 2.0 * ( Q_random( seed ) - 0.5 ); +} + +//======================================================= + +signed char ClampChar( int i ) { + if ( i < -128 ) { + return -128; + } + if ( i > 127 ) { + return 127; + } + return i; +} + +signed short ClampShort( int i ) { + if ( i < -32768 ) { + return -32768; + } + if ( i > 0x7fff ) { + return 0x7fff; + } + return i; +} + + +// this isn't a real cheap function to call! +int DirToByte( vec3_t dir ) { + int i, best; + float d, bestd; + + if ( !dir ) { + return 0; + } + + bestd = 0; + best = 0; + for (i=0 ; i bestd) + { + bestd = d; + best = i; + } + } + + return best; +} + +void ByteToDir( int b, vec3_t dir ) { + if ( b < 0 || b >= NUMVERTEXNORMALS ) { + VectorCopy( vec3_origin, dir ); + return; + } + VectorCopy (bytedirs[b], dir); +} + + +unsigned ColorBytes3 (float r, float g, float b) { + unsigned i; + + ( (byte *)&i )[0] = r * 255; + ( (byte *)&i )[1] = g * 255; + ( (byte *)&i )[2] = b * 255; + + return i; +} + +unsigned ColorBytes4 (float r, float g, float b, float a) { + unsigned i; + + ( (byte *)&i )[0] = r * 255; + ( (byte *)&i )[1] = g * 255; + ( (byte *)&i )[2] = b * 255; + ( (byte *)&i )[3] = a * 255; + + return i; +} + +float NormalizeColor( const vec3_t in, vec3_t out ) { + float max; + + max = in[0]; + if ( in[1] > max ) { + max = in[1]; + } + if ( in[2] > max ) { + max = in[2]; + } + + if ( !max ) { + VectorClear( out ); + } else { + out[0] = in[0] / max; + out[1] = in[1] / max; + out[2] = in[2] / max; + } + return max; +} + + +/* +===================== +PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) { + vec3_t d1, d2; + + VectorSubtract( b, a, d1 ); + VectorSubtract( c, a, d2 ); + CrossProduct( d2, d1, plane ); + if ( VectorNormalize( plane ) == 0 ) { + return qfalse; + } + + plane[3] = DotProduct( a, plane ); + return qtrue; +} + +/* +=============== +RotatePointAroundVector + +This is not implemented very well... +=============== +*/ +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, + float degrees ) { + float sin_a; + float cos_a; + float cos_ia; + float i_i_ia; + float j_j_ia; + float k_k_ia; + float i_j_ia; + float i_k_ia; + float j_k_ia; + float a_sin; + float b_sin; + float c_sin; + float rot[3][3]; + + cos_ia = DEG2RAD(degrees); + sin_a = sin(cos_ia); + cos_a = cos(cos_ia); + cos_ia = 1.0F - cos_a; + + i_i_ia = dir[0] * dir[0] * cos_ia; + j_j_ia = dir[1] * dir[1] * cos_ia; + k_k_ia = dir[2] * dir[2] * cos_ia; + i_j_ia = dir[0] * dir[1] * cos_ia; + i_k_ia = dir[0] * dir[2] * cos_ia; + j_k_ia = dir[1] * dir[2] * cos_ia; + + a_sin = dir[0] * sin_a; + b_sin = dir[1] * sin_a; + c_sin = dir[2] * sin_a; + + rot[0][0] = i_i_ia + cos_a; + rot[0][1] = i_j_ia - c_sin; + rot[0][2] = i_k_ia + b_sin; + rot[1][0] = i_j_ia + c_sin; + rot[1][1] = j_j_ia + cos_a; + rot[1][2] = j_k_ia - a_sin; + rot[2][0] = i_k_ia - b_sin; + rot[2][1] = j_k_ia + a_sin; + rot[2][2] = k_k_ia + cos_a; + + dst[0] = point[0] * rot[0][0] + point[1] * rot[0][1] + point[2] * rot[0][2]; + dst[1] = point[0] * rot[1][0] + point[1] * rot[1][1] + point[2] * rot[1][2]; + dst[2] = point[0] * rot[2][0] + point[1] * rot[2][1] + point[2] * rot[2][2]; +} + +/* +=============== +RotateAroundDirection +=============== +*/ +void RotateAroundDirection( vec3_t axis[3], vec_t angle ) { + vec_t scale; + + angle = DEG2RAD( angle ); + + // create an arbitrary axis[1] + PerpendicularVector( axis[ 1 ], axis[ 0 ] ); + + // cross to get axis[2] + CrossProduct( axis[ 0 ], axis[ 1 ], axis[ 2 ] ); + + // rotate + scale = cos( angle ); + VectorScale( axis[ 1 ], scale, axis[ 1 ] ); + + scale = sin( angle ); + VectorMA( axis[ 1 ], scale, axis[ 2 ], axis[ 1 ] ); + + // recalculate axis[2] + CrossProduct( axis[ 0 ], axis[ 1 ], axis[ 2 ] ); +} + + + +void vectoangles( const vec3_t value1, vec3_t angles ) { + float forward; + float yaw, pitch; + + if ( value1[1] == 0 && value1[0] == 0 ) { + yaw = 0; + if ( value1[2] > 0 ) { + pitch = 90; + } + else { + pitch = 270; + } + } + else { + if ( value1[0] ) { + yaw = ( atan2 ( value1[1], value1[0] ) * 180 / M_PI ); + } + else if ( value1[1] > 0 ) { + yaw = 90; + } + else { + yaw = 270; + } + if ( yaw < 0 ) { + yaw += 360; + } + + forward = sqrt ( value1[0]*value1[0] + value1[1]*value1[1] ); + pitch = ( atan2(value1[2], forward) * 180 / M_PI ); + if ( pitch < 0 ) { + pitch += 360; + } + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + + +/* +================= +AxisToAngles + +Takes an axis (forward + right + up) +and returns angles -- including a roll +================= +*/ +void AxisToAngles( vec3_t axis[3], vec3_t angles ) { + float length1; + float yaw, pitch, roll = 0.0f; + + if ( axis[0][1] == 0 && axis[0][0] == 0 ) { + yaw = 0; + if ( axis[0][2] > 0 ) { + pitch = 90; + } + else { + pitch = 270; + } + } + else { + if ( axis[0][0] ) { + yaw = ( atan2 ( axis[0][1], axis[0][0] ) * 180 / M_PI ); + } + else if ( axis[0][1] > 0 ) { + yaw = 90; + } + else { + yaw = 270; + } + if ( yaw < 0 ) { + yaw += 360; + } + + length1 = sqrt ( axis[0][0]*axis[0][0] + axis[0][1]*axis[0][1] ); + pitch = ( atan2(axis[0][2], length1) * 180 / M_PI ); + if ( pitch < 0 ) { + pitch += 360; + } + + roll = ( atan2( axis[1][2], axis[2][2] ) * 180 / M_PI ); + if ( roll < 0 ) { + roll += 360; + } + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = roll; +} + +/* +================= +AnglesToAxis +================= +*/ +void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ) { + vec3_t right; + + // angle vectors returns "right" instead of "y axis" + AngleVectors( angles, axis[0], right, axis[2] ); + VectorSubtract( vec3_origin, right, axis[1] ); +} + +void AxisClear( vec3_t axis[3] ) { + axis[0][0] = 1; + axis[0][1] = 0; + axis[0][2] = 0; + axis[1][0] = 0; + axis[1][1] = 1; + axis[1][2] = 0; + axis[2][0] = 0; + axis[2][1] = 0; + axis[2][2] = 1; +} + +void AxisCopy( vec3_t in[3], vec3_t out[3] ) { + VectorCopy( in[0], out[0] ); + VectorCopy( in[1], out[1] ); + VectorCopy( in[2], out[2] ); +} + +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 ); +#ifndef Q3_VM + assert( Q_fabs(inv_denom) != 0.0f ); // zero vectors get here +#endif + inv_denom = 1.0f / inv_denom; + + 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]; +} + +/* +================ +MakeNormalVectors + +Given a normalized forward vector, create two +other perpendicular vectors +================ +*/ +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up) { + float d; + + // this rotate and negate guarantees a vector + // not colinear with the original + right[1] = -forward[0]; + right[2] = forward[1]; + right[0] = forward[2]; + + d = DotProduct (right, forward); + VectorMA (right, -d, forward, right); + VectorNormalize (right); + CrossProduct (right, forward, up); +} + + +void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out ) +{ + out[0] = DotProduct( in, matrix[0] ); + out[1] = DotProduct( in, matrix[1] ); + out[2] = DotProduct( in, matrix[2] ); +} + +//============================================================================ + +#if !idppc +/* +** float q_rsqrt( float number ) +*/ +float Q_rsqrt( float number ) +{ + floatint_t t; + float x2, y; + const float threehalfs = 1.5F; + + x2 = number * 0.5F; + t.f = number; + t.i = 0x5f3759df - ( t.i >> 1 ); // what the fuck? + y = t.f; + y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration +// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed + + return y; +} + +float Q_fabs( float f ) { + floatint_t fi; + fi.f = f; + fi.i &= 0x7FFFFFFF; + return fi.f; +} +#endif + +//============================================================ + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float from, float to, float frac) { + float a; + + if ( to - from > 180 ) { + to -= 360; + } + if ( to - from < -180 ) { + to += 360; + } + a = from + frac * (to - from); + + return a; +} + + +/* +================= +AngleSubtract + +Always returns a value from -180 to 180 +================= +*/ +float AngleSubtract( float a1, float a2 ) { + float a; + + a = a1 - a2; + while ( a > 180 ) { + a -= 360; + } + while ( a < -180 ) { + a += 360; + } + return a; +} + + +void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ) { + v3[0] = AngleSubtract( v1[0], v2[0] ); + v3[1] = AngleSubtract( v1[1], v2[1] ); + v3[2] = AngleSubtract( v1[2], v2[2] ); +} + + +float AngleMod(float a) { + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + +/* +================= +AngleNormalize360 + +returns angle normalized to the range [0 <= angle < 360] +================= +*/ +float AngleNormalize360 ( float angle ) { + return (360.0 / 65536) * ((int)(angle * (65536 / 360.0)) & 65535); +} + + +/* +================= +AngleNormalize180 + +returns angle normalized to the range [-180 < angle <= 180] +================= +*/ +float AngleNormalize180 ( float angle ) { + angle = AngleNormalize360( angle ); + if ( angle > 180.0 ) { + angle -= 360.0; + } + return angle; +} + + +/* +================= +AngleDelta + +returns the normalized delta from angle1 to angle2 +================= +*/ +float AngleDelta ( float angle1, float angle2 ) { + return AngleNormalize180( angle1 - angle2 ); +} + + +//============================================================ + + +/* +================= +SetPlaneSignbits +================= +*/ +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0) { + bits |= 1<signbits = bits; +} + + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +int BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + float dist[2]; + int sides, b, i; + + // 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; + } + + // general case + dist[0] = dist[1] = 0; + if (p->signbits < 8) // >= 8: default case is original code (dist[0]=dist[1]=0) + { + for (i=0 ; i<3 ; i++) + { + b = (p->signbits >> i) & 1; + dist[ b] += p->normal[i]*emaxs[i]; + dist[!b] += p->normal[i]*emins[i]; + } + } + + sides = 0; + if (dist[0] >= p->dist) + sides = 1; + if (dist[1] < p->dist) + sides |= 2; + + return sides; +} + + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) { + int i; + vec3_t corner; + float a, b; + + for (i=0 ; i<3 ; i++) { + a = fabs( mins[i] ); + b = fabs( maxs[i] ); + corner[i] = a > b ? a : b; + } + + return VectorLength (corner); +} + + +void ClearBounds( vec3_t mins, vec3_t maxs ) { + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) { + if ( v[0] < mins[0] ) { + mins[0] = v[0]; + } + if ( v[0] > maxs[0]) { + maxs[0] = v[0]; + } + + if ( v[1] < mins[1] ) { + mins[1] = v[1]; + } + if ( v[1] > maxs[1]) { + maxs[1] = v[1]; + } + + if ( v[2] < mins[2] ) { + mins[2] = v[2]; + } + if ( v[2] > maxs[2]) { + maxs[2] = v[2]; + } +} + +qboolean BoundsIntersect(const vec3_t mins, const vec3_t maxs, + const vec3_t mins2, const vec3_t maxs2) +{ + if ( maxs[0] < mins2[0] || + maxs[1] < mins2[1] || + maxs[2] < mins2[2] || + mins[0] > maxs2[0] || + mins[1] > maxs2[1] || + mins[2] > maxs2[2]) + { + return qfalse; + } + + return qtrue; +} + +qboolean BoundsIntersectSphere(const vec3_t mins, const vec3_t maxs, + const vec3_t origin, vec_t radius) +{ + if ( origin[0] - radius > maxs[0] || + origin[0] + radius < mins[0] || + origin[1] - radius > maxs[1] || + origin[1] + radius < mins[1] || + origin[2] - radius > maxs[2] || + origin[2] + radius < mins[2]) + { + return qfalse; + } + + return qtrue; +} + +qboolean BoundsIntersectPoint(const vec3_t mins, const vec3_t maxs, + const vec3_t origin) +{ + if ( origin[0] > maxs[0] || + origin[0] < mins[0] || + origin[1] > maxs[1] || + origin[1] < mins[1] || + origin[2] > maxs[2] || + origin[2] < mins[2]) + { + return qfalse; + } + + return qtrue; +} + +vec_t VectorNormalize( vec3_t v ) { + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); + + if ( length ) { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; +} + +vec_t VectorNormalize2( const vec3_t v, vec3_t out) { + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); + + if (length) + { + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } else { + VectorClear( out ); + } + + return length; + +} + +void _VectorMA( const vec3_t veca, float scale, const 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( const vec3_t v1, const vec3_t v2 ) { + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract( const vec3_t veca, const 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( const vec3_t veca, const 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( const vec3_t in, vec3_t out ) { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale( const vec3_t in, vec_t scale, vec3_t out ) { + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ) { + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; + out[3] = in[3]*scale; +} + + +int Q_log2( int val ) { + int answer; + + answer = 0; + while ( ( val>>=1 ) != 0 ) { + answer++; + } + return answer; +} + + + +/* +================= +PlaneTypeForNormal +================= +*/ +/* +int PlaneTypeForNormal (vec3_t normal) { + if ( normal[0] == 1.0 ) + return PLANE_X; + if ( normal[1] == 1.0 ) + return PLANE_Y; + if ( normal[2] == 1.0 ) + return PLANE_Z; + + return PLANE_NON_AXIAL; +} +*/ + + +/* +================ +MatrixMultiply +================ +*/ +void MatrixMultiply(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]; +} + +/* +================ +VectorMatrixMultiply +================ +*/ +void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out ) +{ + out[ 0 ] = m[ 0 ][ 0 ] * p[ 0 ] + m[ 1 ][ 0 ] * p[ 1 ] + m[ 2 ][ 0 ] * p[ 2 ]; + out[ 1 ] = m[ 0 ][ 1 ] * p[ 0 ] + m[ 1 ][ 1 ] * p[ 1 ] + m[ 2 ][ 1 ] * p[ 2 ]; + out[ 2 ] = m[ 0 ][ 2 ] * p[ 0 ] + m[ 1 ][ 2 ] * p[ 1 ] + m[ 2 ][ 2 ] * p[ 2 ]; +} + + +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) { + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + 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); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + +/* +** 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 ); +} + +/* +================= +pointToLineDistance + +Distance from a point to some line +================= +*/ +float pointToLineDistance( const vec3_t p0, const vec3_t p1, const vec3_t p2 ) +{ + vec3_t v, w, y; + float c1, c2; + + VectorSubtract( p2, p1, v ); + VectorSubtract( p1, p0, w ); + + CrossProduct( w, v, y ); + c1 = VectorLength( y ); + c2 = VectorLength( v ); + + if( c2 == 0.0f ) + return 0.0f; + else + return c1 / c2; +} + +/* +================= +GetPerpendicularViewVector + +Used to find an "up" vector for drawing a sprite so that it always faces the view as best as possible +================= +*/ +void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, const vec3_t p2, vec3_t up ) +{ + vec3_t v1, v2; + + VectorSubtract( point, p1, v1 ); + VectorNormalize( v1 ); + + VectorSubtract( point, p2, v2 ); + VectorNormalize( v2 ); + + CrossProduct( v1, v2, up ); + VectorNormalize( up ); +} + +/* +================ +ProjectPointOntoVector +================ +*/ +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) +{ + vec3_t pVec, vec; + + VectorSubtract( point, vStart, pVec ); + VectorSubtract( vEnd, vStart, vec ); + VectorNormalize( vec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); +} + +/* +================ +VectorMaxComponent + +Return the biggest component of some vector +================ +*/ +float VectorMaxComponent( vec3_t v ) +{ + float biggest = v[ 0 ]; + + if( v[ 1 ] > biggest ) + biggest = v[ 1 ]; + + if( v[ 2 ] > biggest ) + biggest = v[ 2 ]; + + return biggest; +} + +/* +================ +VectorMinComponent + +Return the smallest component of some vector +================ +*/ +float VectorMinComponent( vec3_t v ) +{ + float smallest = v[ 0 ]; + + if( v[ 1 ] < smallest ) + smallest = v[ 1 ]; + + if( v[ 2 ] < smallest ) + smallest = v[ 2 ]; + + return smallest; +} + + +#define LINE_DISTANCE_EPSILON 1e-05f + +/* +================ +DistanceBetweenLineSegmentsSquared + +Return the smallest distance between two line segments, squared +================ +*/ +vec_t DistanceBetweenLineSegmentsSquared( + const vec3_t sP0, const vec3_t sP1, + const vec3_t tP0, const vec3_t tP1, + float *s, float *t ) +{ + vec3_t sMag, tMag, diff; + float a, b, c, d, e; + float D; + float sN, sD; + float tN, tD; + vec3_t separation; + + VectorSubtract( sP1, sP0, sMag ); + VectorSubtract( tP1, tP0, tMag ); + VectorSubtract( sP0, tP0, diff ); + a = DotProduct( sMag, sMag ); + b = DotProduct( sMag, tMag ); + c = DotProduct( tMag, tMag ); + d = DotProduct( sMag, diff ); + e = DotProduct( tMag, diff ); + sD = tD = D = a * c - b * b; + + if( D < LINE_DISTANCE_EPSILON ) + { + // the lines are almost parallel + sN = 0.0; // force using point P0 on segment S1 + sD = 1.0; // to prevent possible division by 0.0 later + tN = e; + tD = c; + } + else + { + // get the closest points on the infinite lines + sN = ( b * e - c * d ); + tN = ( a * e - b * d ); + + if( sN < 0.0 ) + { + // sN < 0 => the s=0 edge is visible + sN = 0.0; + tN = e; + tD = c; + } + else if( sN > sD ) + { + // sN > sD => the s=1 edge is visible + sN = sD; + tN = e + b; + tD = c; + } + } + + if( tN < 0.0 ) + { + // tN < 0 => the t=0 edge is visible + tN = 0.0; + + // recompute sN for this edge + if( -d < 0.0 ) + sN = 0.0; + else if( -d > a ) + sN = sD; + else + { + sN = -d; + sD = a; + } + } + else if( tN > tD ) + { + // tN > tD => the t=1 edge is visible + tN = tD; + + // recompute sN for this edge + if( ( -d + b ) < 0.0 ) + sN = 0; + else if( ( -d + b ) > a ) + sN = sD; + else + { + sN = ( -d + b ); + sD = a; + } + } + + // finally do the division to get *s and *t + *s = ( fabs( sN ) < LINE_DISTANCE_EPSILON ? 0.0 : sN / sD ); + *t = ( fabs( tN ) < LINE_DISTANCE_EPSILON ? 0.0 : tN / tD ); + + // get the difference of the two closest points + VectorScale( sMag, *s, sMag ); + VectorScale( tMag, *t, tMag ); + VectorAdd( diff, sMag, separation ); + VectorSubtract( separation, tMag, separation ); + + return VectorLengthSquared( separation ); +} + +/* +================ +DistanceBetweenLineSegments + +Return the smallest distance between two line segments +================ +*/ +vec_t DistanceBetweenLineSegments( + const vec3_t sP0, const vec3_t sP1, + const vec3_t tP0, const vec3_t tP1, + float *s, float *t ) +{ + return (vec_t)sqrt( DistanceBetweenLineSegmentsSquared( + sP0, sP1, tP0, tP1, s, t ) ); +} + +/* +================= +Q_isnan + +Don't pass doubles to this +================ +*/ +int Q_isnan( float x ) +{ + floatint_t fi; + + fi.f = x; + fi.ui &= 0x7FFFFFFF; + fi.ui = 0x7F800000 - fi.ui; + + return (int)( (unsigned int)fi.ui >> 31 ); +} diff --git a/src/qcommon/q_platform.h b/src/qcommon/q_platform.h new file mode 100644 index 0000000..3e9619e --- /dev/null +++ b/src/qcommon/q_platform.h @@ -0,0 +1,380 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef __Q_PLATFORM_H +#define __Q_PLATFORM_H + +// this is for determining if we have an asm version of a C function +#ifdef Q3_VM + +#define id386 0 +#define idppc 0 +#define idppc_altivec 0 +#define idsparc 0 + +#else + +#if (defined _M_IX86 || defined __i386__) && !defined(C_ONLY) +#define id386 1 +#else +#define id386 0 +#endif + +#if (defined(powerc) || defined(powerpc) || defined(ppc) || \ + defined(__ppc) || defined(__ppc__)) && !defined(C_ONLY) +#define idppc 1 +#if defined(__VEC__) +#define idppc_altivec 1 +#ifdef MACOS_X // Apple's GCC does this differently than the FSF. +#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ + (vector unsigned char) (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) +#else +#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ + (vector unsigned char) {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p} +#endif +#else +#define idppc_altivec 0 +#endif +#else +#define idppc 0 +#define idppc_altivec 0 +#endif + +#if defined(__sparc__) && !defined(C_ONLY) +#define idsparc 1 +#else +#define idsparc 0 +#endif + +#endif + +#ifndef __ASM_I386__ // don't include the C bits if included from qasm.h + +// for windows fastcall option +#define QDECL + +//================================================================= WIN64/32 === + +#if defined(_WIN64) || defined(__WIN64__) + +#undef QDECL +#define QDECL __cdecl + +#if defined( _MSC_VER ) +#define OS_STRING "win_msvc64" +#elif defined __MINGW64__ +#define OS_STRING "win_mingw64" +#endif + +#define ID_INLINE inline +#define PATH_SEP '\\' + +#if defined( __WIN64__ ) +#define ARCH_STRING "x86_64" +#elif defined _M_ALPHA +#define ARCH_STRING "AXP" +#endif + +#define Q3_LITTLE_ENDIAN + +#define DLL_EXT ".dll" + +#elif defined(_WIN32) || defined(__WIN32__) + +#undef QDECL +#define QDECL __cdecl + +#if defined( _MSC_VER ) +#define OS_STRING "win_msvc" +#elif defined __MINGW32__ +#define OS_STRING "win_mingw" +#endif + +#define ID_INLINE __inline +#define PATH_SEP '\\' + +#if defined( _M_IX86 ) || defined( __i386__ ) +#define ARCH_STRING "x86" +#elif defined _M_ALPHA +#define ARCH_STRING "AXP" +#endif + +#define Q3_LITTLE_ENDIAN + +#define DLL_EXT ".dll" + +#endif + +//============================================================== MAC OS X === + +#if defined(MACOS_X) || defined(__APPLE_CC__) + +// make sure this is defined, just for sanity's sake... +#ifndef MACOS_X +#define MACOS_X +#endif + +#define OS_STRING "macosx" +#define ID_INLINE inline +#define PATH_SEP '/' + +#ifdef __ppc__ +#define ARCH_STRING "ppc" +#define Q3_BIG_ENDIAN +#elif defined __i386__ +#define ARCH_STRING "x86" +#define Q3_LITTLE_ENDIAN +#elif defined __x86_64__ +#define ARCH_STRING "x86_64" +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".dylib" + +#endif + +//================================================================= LINUX === + +#if defined(__linux__) || defined(__FreeBSD_kernel__) + +#include + +#if defined(__linux__) +#define OS_STRING "linux" +#else +#define OS_STRING "kFreeBSD" +#endif + +#define ID_INLINE inline +#define PATH_SEP '/' + +#if defined __i386__ +#define ARCH_STRING "x86" +#elif defined __x86_64__ +#define ARCH_STRING "x86_64" +#elif defined __powerpc64__ +#define ARCH_STRING "ppc64" +#elif defined __powerpc__ +#define ARCH_STRING "ppc" +#elif defined __s390__ +#define ARCH_STRING "s390" +#elif defined __s390x__ +#define ARCH_STRING "s390x" +#elif defined __ia64__ +#define ARCH_STRING "ia64" +#elif defined __alpha__ +#define ARCH_STRING "alpha" +#elif defined __sparc__ +#define ARCH_STRING "sparc" +#elif defined __arm__ +#define ARCH_STRING "arm" +#elif defined __cris__ +#define ARCH_STRING "cris" +#elif defined __hppa__ +#define ARCH_STRING "hppa" +#elif defined __mips__ +#define ARCH_STRING "mips" +#elif defined __sh__ +#define ARCH_STRING "sh" +#endif + +#if __FLOAT_WORD_ORDER == __BIG_ENDIAN +#define Q3_BIG_ENDIAN +#else +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".so" + +#endif + +//=================================================================== BSD === + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + +#include +#include + +#ifndef __BSD__ + #define __BSD__ +#endif + +#if defined(__FreeBSD__) +#define OS_STRING "freebsd" +#elif defined(__OpenBSD__) +#define OS_STRING "openbsd" +#elif defined(__NetBSD__) +#define OS_STRING "netbsd" +#endif + +#define ID_INLINE inline +#define PATH_SEP '/' + +#ifdef __i386__ +#define ARCH_STRING "x86" +#elif defined __amd64__ +#define ARCH_STRING "amd64" +#elif defined __axp__ +#define ARCH_STRING "alpha" +#endif + +#if BYTE_ORDER == BIG_ENDIAN +#define Q3_BIG_ENDIAN +#else +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".so" + +#endif + +//================================================================= SUNOS === + +#ifdef __sun + +#include +#include + +#define OS_STRING "solaris" +#define ID_INLINE inline +#define PATH_SEP '/' + +#ifdef __i386__ +#define ARCH_STRING "x86" +#elif defined __sparc +#define ARCH_STRING "sparc" +#endif + +#if defined( _BIG_ENDIAN ) +#define Q3_BIG_ENDIAN +#elif defined( _LITTLE_ENDIAN ) +#define Q3_LITTLE_ENDIAN +#endif + +#define DLL_EXT ".so" + +#endif + +//================================================================== IRIX === + +#ifdef __sgi + +#define OS_STRING "irix" +#define ID_INLINE __inline +#define PATH_SEP '/' + +#define ARCH_STRING "mips" + +#define Q3_BIG_ENDIAN // SGI's MIPS are always big endian + +#define DLL_EXT ".so" + +#endif + +//================================================================== Q3VM === + +#ifdef Q3_VM + +#define OS_STRING "q3vm" +#define ID_INLINE +#define PATH_SEP '/' + +#define ARCH_STRING "bytecode" + +#define DLL_EXT ".qvm" + +#endif + +//=========================================================================== + +//catch missing defines in above blocks +#if !defined( OS_STRING ) +#error "Operating system not supported" +#endif + +#if !defined( ARCH_STRING ) +#error "Architecture not supported" +#endif + +#ifndef ID_INLINE +#error "ID_INLINE not defined" +#endif + +#ifndef PATH_SEP +#error "PATH_SEP not defined" +#endif + +#ifndef DLL_EXT +#error "DLL_EXT not defined" +#endif + + +//endianness +short ShortSwap (short l); +int LongSwap (int l); +float FloatSwap (const float *f); + +#if defined( Q3_BIG_ENDIAN ) && defined( Q3_LITTLE_ENDIAN ) +#error "Endianness defined as both big and little" +#elif defined( Q3_BIG_ENDIAN ) + +#define LittleShort(x) ShortSwap(x) +#define LittleLong(x) LongSwap(x) +#define LittleFloat(x) FloatSwap(&x) +#define BigShort +#define BigLong +#define BigFloat + +#elif defined( Q3_LITTLE_ENDIAN ) + +#define LittleShort +#define LittleLong +#define LittleFloat +#define BigShort(x) ShortSwap(x) +#define BigLong(x) LongSwap(x) +#define BigFloat(x) FloatSwap(&x) + +#elif defined( Q3_VM ) + +#define LittleShort +#define LittleLong +#define LittleFloat +#define BigShort +#define BigLong +#define BigFloat + +#else +#error "Endianness not defined" +#endif + + +//platform string +#ifdef NDEBUG +#define PLATFORM_STRING OS_STRING "-" ARCH_STRING +#else +#define PLATFORM_STRING OS_STRING "-" ARCH_STRING "-debug" +#endif + +#endif + +#endif diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c new file mode 100644 index 0000000..8b13aba --- /dev/null +++ b/src/qcommon/q_shared.c @@ -0,0 +1,1522 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// q_shared.c -- stateless support routines that are included in each code dll +#include "q_shared.h" + +float Com_Clamp( float min, float max, float value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + + +/* +============ +COM_SkipPath +============ +*/ +char *COM_SkipPath (char *pathname) +{ + char *last; + + last = pathname; + while (*pathname) + { + if (*pathname=='/') + last = pathname+1; + pathname++; + } + return last; +} + +/* +============ +COM_GetExtension +============ +*/ +const char *COM_GetExtension( const char *name ) { + int length, i; + + length = strlen(name)-1; + i = length; + + while (name[i] != '.') + { + i--; + if (name[i] == '/' || i == 0) + return ""; // no extension + } + + return &name[i+1]; +} + + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension( const char *in, char *out, int destsize ) { + int length; + + Q_strncpyz(out, in, destsize); + + length = strlen(out)-1; + while (length > 0 && out[length] != '.') + { + length--; + if (out[length] == '/') + return; // no extension + } + if (length) + out[length] = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension (char *path, int maxSize, const char *extension ) { + char oldPath[MAX_QPATH]; + char *src; + +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) { + if ( *src == '.' ) { + return; // it has an extension + } + src--; + } + + Q_strncpyz( oldPath, path, sizeof( oldPath ) ); + Com_sprintf( path, maxSize, "%s%s", oldPath, extension ); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ +/* +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +static short (*_BigShort) (short l); +static short (*_LittleShort) (short l); +static int (*_BigLong) (int l); +static int (*_LittleLong) (int l); +static qint64 (*_BigLong64) (qint64 l); +static qint64 (*_LittleLong64) (qint64 l); +static float (*_BigFloat) (const float *l); +static float (*_LittleFloat) (const float *l); + +short BigShort(short l){return _BigShort(l);} +short LittleShort(short l) {return _LittleShort(l);} +int BigLong (int l) {return _BigLong(l);} +int LittleLong (int l) {return _LittleLong(l);} +qint64 BigLong64 (qint64 l) {return _BigLong64(l);} +qint64 LittleLong64 (qint64 l) {return _LittleLong64(l);} +float BigFloat (const float *l) {return _BigFloat(l);} +float LittleFloat (const float *l) {return _LittleFloat(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; +} + +qint64 Long64Swap (qint64 ll) +{ + qint64 result; + + result.b0 = ll.b7; + result.b1 = ll.b6; + result.b2 = ll.b5; + result.b3 = ll.b4; + result.b4 = ll.b3; + result.b5 = ll.b2; + result.b6 = ll.b1; + result.b7 = ll.b0; + + return result; +} + +qint64 Long64NoSwap (qint64 ll) +{ + return ll; +} + +float FloatSwap (const float *f) { + floatint_t out; + + out.f = *f; + out.ui = LongSwap(out.ui); + + return out.f; +} + +float FloatNoSwap (const float *f) +{ + return *f; +} + +/* +================ +Swap_Init +================ +*/ +/* +void Swap_Init (void) +{ + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) + { + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigLong64 = Long64Swap; + _LittleLong64 = Long64NoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigLong64 = Long64NoSwap; + _LittleLong64 = Long64Swap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} +*/ + +/* +============================================================================ + +PARSING + +============================================================================ +*/ + +static char com_token[MAX_TOKEN_CHARS]; +static char com_parsename[MAX_TOKEN_CHARS]; +static int com_lines; + +void COM_BeginParseSession( const char *name ) +{ + com_lines = 0; + Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name); +} + +int COM_GetCurrentParseLine( void ) +{ + return com_lines; +} + +char *COM_Parse( char **data_p ) +{ + return COM_ParseExt( data_p, qtrue ); +} + +void COM_ParseError( char *format, ... ) +{ + va_list argptr; + static char string[4096]; + + va_start (argptr, format); + Q_vsnprintf (string, sizeof(string), format, argptr); + va_end (argptr); + + Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, com_lines, string); +} + +void COM_ParseWarning( char *format, ... ) +{ + va_list argptr; + static char string[4096]; + + va_start (argptr, format); + Q_vsnprintf (string, sizeof(string), format, argptr); + va_end (argptr); + + Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, com_lines, string); +} + +/* +============== +COM_Parse + +Parse a token out of a string +Will never return NULL, just empty strings + +If "allowLineBreaks" is qtrue then an empty +string will be returned if the next token is +a newline. +============== +*/ +static char *SkipWhitespace( char *data, qboolean *hasNewLines ) { + int c; + + while( (c = *data) <= ' ') { + if( !c ) { + return NULL; + } + if( c == '\n' ) { + com_lines++; + *hasNewLines = qtrue; + } + data++; + } + + return data; +} + +int COM_Compress( char *data_p ) { + char *in, *out; + int c; + qboolean newline = qfalse, whitespace = qfalse; + + in = out = data_p; + if (in) { + while ((c = *in) != 0) { + // skip double slash comments + if ( c == '/' && in[1] == '/' ) { + while (*in && *in != '\n') { + in++; + } + // skip /* */ comments + } else if ( c == '/' && in[1] == '*' ) { + while ( *in && ( *in != '*' || in[1] != '/' ) ) + in++; + if ( *in ) + in += 2; + // record when we hit a newline + } else if ( c == '\n' || c == '\r' ) { + newline = qtrue; + in++; + // record when we hit whitespace + } else if ( c == ' ' || c == '\t') { + whitespace = qtrue; + in++; + // an actual token + } else { + // if we have a pending newline, emit it (and it counts as whitespace) + if (newline) { + *out++ = '\n'; + newline = qfalse; + whitespace = qfalse; + } if (whitespace) { + *out++ = ' '; + whitespace = qfalse; + } + + // copy quoted strings unmolested + if (c == '"') { + *out++ = c; + in++; + while (1) { + c = *in; + if (c && c != '"') { + *out++ = c; + in++; + } else { + break; + } + } + if (c == '"') { + *out++ = c; + in++; + } + } else { + *out = c; + out++; + in++; + } + } + } + } + *out = 0; + return out - data_p; +} + +char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) +{ + int c = 0, len; + qboolean hasNewLines = qfalse; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) + { + *data_p = NULL; + return com_token; + } + + while ( 1 ) + { + // skip whitespace + data = SkipWhitespace( data, &hasNewLines ); + if ( !data ) + { + *data_p = NULL; + return com_token; + } + if ( hasNewLines && !allowLineBreaks ) + { + *data_p = data; + return com_token; + } + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + data += 2; + while (*data && *data != '\n') { + data++; + } + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + data += 2; + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) data; + return com_token; + } + if (len < MAX_TOKEN_CHARS - 1) + { + com_token[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if (len < MAX_TOKEN_CHARS - 1) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + if ( c == '\n' ) + com_lines++; + } while (c>32); + + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + +/* +================== +COM_MatchToken +================== +*/ +void COM_MatchToken( char **buf_p, char *match ) { + char *token; + + token = COM_Parse( buf_p ); + if ( strcmp( token, match ) ) { + Com_Error( ERR_DROP, "MatchToken: %s != %s", token, match ); + } +} + + +/* +================= +SkipBracedSection + +The next token should be an open brace. +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +void SkipBracedSection (char **program) { + char *token; + int depth; + + depth = 0; + do { + token = COM_ParseExt( program, qtrue ); + if( token[1] == 0 ) { + if( token[0] == '{' ) { + depth++; + } + else if( token[0] == '}' ) { + depth--; + } + } + } while( depth && *program ); +} + +/* +================= +SkipRestOfLine +================= +*/ +void SkipRestOfLine ( char **data ) { + char *p; + int c; + + p = *data; + while ( (c = *p++) != 0 ) { + if ( c == '\n' ) { + com_lines++; + break; + } + } + + *data = p; +} + + +void Parse1DMatrix (char **buf_p, int x, float *m) { + char *token; + int i; + + COM_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < x ; i++) { + token = COM_Parse(buf_p); + m[i] = atof(token); + } + + COM_MatchToken( buf_p, ")" ); +} + +void Parse2DMatrix (char **buf_p, int y, int x, float *m) { + int i; + + COM_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < y ; i++) { + Parse1DMatrix (buf_p, x, m + i * x); + } + + COM_MatchToken( buf_p, ")" ); +} + +void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m) { + int i; + + COM_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < z ; i++) { + Parse2DMatrix (buf_p, y, x, m + i * x*y); + } + + COM_MatchToken( buf_p, ")" ); +} + +/* +=================== +Com_HexStrToInt +=================== +*/ +int Com_HexStrToInt( const char *str ) +{ + if ( !str || !str[ 0 ] ) + return -1; + + // check for hex code + if( str[ 0 ] == '0' && str[ 1 ] == 'x' ) + { + int i, n = 0; + + for( i = 2; i < strlen( str ); i++ ) + { + char digit; + + n *= 16; + + digit = tolower( str[ i ] ); + + if( digit >= '0' && digit <= '9' ) + digit -= '0'; + else if( digit >= 'a' && digit <= 'f' ) + digit = digit - 'a' + 10; + else + return -1; + + n += digit; + } + + return n; + } + + return -1; +} + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +int Q_isprint( int c ) +{ + if ( c >= 0x20 && c <= 0x7E ) + return ( 1 ); + return ( 0 ); +} + +int Q_islower( int c ) +{ + if (c >= 'a' && c <= 'z') + return ( 1 ); + return ( 0 ); +} + +int Q_isupper( int c ) +{ + if (c >= 'A' && c <= 'Z') + return ( 1 ); + return ( 0 ); +} + +int Q_isalpha( int c ) +{ + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + return ( 1 ); + return ( 0 ); +} + +char* Q_strrchr( const char* string, int c ) +{ + char cc = c; + char *s; + char *sp=(char *)0; + + s = (char*)string; + + while (*s) + { + if (*s == cc) + sp = s; + s++; + } + if (cc == 0) + sp = s; + + return sp; +} + +qboolean Q_isanumber( const char *s ) +{ + char *p; + double d; + + if( *s == '\0' ) + return qfalse; + + d = strtod( s, &p ); + + return *p == '\0'; +} + +qboolean Q_isintegral( float f ) +{ + return (int)f == f; +} + +#ifdef _MSC_VER +/* +============= +Q_vsnprintf + +Special wrapper function for Microsoft's broken _vsnprintf() function. +MinGW comes with its own snprintf() which is not broken. +============= +*/ + +int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ + int retval; + + retval = _vsnprintf(str, size, format, ap); + + if(retval < 0 || retval == size) + { + // Microsoft doesn't adhere to the C99 standard of vsnprintf, + // which states that the return value must be the number of + // bytes written if the output string had sufficient length. + // + // Obviously we cannot determine that value from Microsoft's + // implementation, so we have no choice but to return size. + + str[size - 1] = '\0'; + return size; + } + + return retval; +} +#endif + +/* +============= +Q_strncpyz + +Safe strncpy that ensures a trailing zero +============= +*/ +void Q_strncpyz( char *dest, const char *src, int destsize ) { + if ( !dest ) { + Com_Error( ERR_FATAL, "Q_strncpyz: NULL dest" ); + } + if ( !src ) { + Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" ); + } + if ( destsize < 1 ) { + Com_Error(ERR_FATAL,"Q_strncpyz: destsize < 1" ); + } + + strncpy( dest, src, destsize-1 ); + dest[destsize-1] = 0; +} + +int Q_stricmpn (const char *s1, const char *s2, int n) { + int c1, c2; + + if ( s1 == NULL ) { + if ( s2 == NULL ) + return 0; + else + return -1; + } + else if ( s2==NULL ) + return 1; + + + + do { + c1 = *s1++; + c2 = *s2++; + + if (!n--) { + return 0; // strings are equal until end point + } + + if (c1 != c2) { + if (c1 >= 'a' && c1 <= 'z') { + c1 -= ('a' - 'A'); + } + if (c2 >= 'a' && c2 <= 'z') { + c2 -= ('a' - 'A'); + } + if (c1 != c2) { + return c1 < c2 ? -1 : 1; + } + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strncmp (const char *s1, const char *s2, int n) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if (!n--) { + return 0; // strings are equal until end point + } + + if (c1 != c2) { + return c1 < c2 ? -1 : 1; + } + } while (c1); + + return 0; // strings are equal +} + +int Q_stricmp (const char *s1, const char *s2) { + return (s1 && s2) ? Q_stricmpn (s1, s2, 99999) : -1; +} + + +char *Q_strlwr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = tolower(*s); + s++; + } + return s1; +} + +char *Q_strupr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = toupper(*s); + s++; + } + return s1; +} + + +// never goes past bounds or leaves without a terminating 0 +void Q_strcat( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + Com_Error( ERR_FATAL, "Q_strcat: already overflowed" ); + } + Q_strncpyz( dest + l1, src, size - l1 ); +} + +/* +* Find the first occurrence of find in s. +*/ +const char *Q_stristr( const char *s, const char *find) +{ + char c, sc; + size_t len; + + if ((c = *find++) != 0) + { + if (c >= 'a' && c <= 'z') + { + c -= ('a' - 'A'); + } + len = strlen(find); + do + { + do + { + if ((sc = *s++) == 0) + return NULL; + if (sc >= 'a' && sc <= 'z') + { + sc -= ('a' - 'A'); + } + } while (sc != c); + } while (Q_stricmpn(s, find, len) != 0); + s--; + } + return s; +} + + +int Q_PrintStrlen( const char *string ) { + int len; + const char *p; + + if( !string ) { + return 0; + } + + len = 0; + p = string; + while( *p ) { + if( Q_IsColorString( p ) ) { + p += 2; + continue; + } + p++; + len++; + } + + return len; +} + + +char *Q_CleanStr( char *string ) { + char* d; + char* s; + int c; + + s = string; + d = string; + while ((c = *s) != 0 ) { + if ( Q_IsColorString( s ) ) { + s++; + } + else if ( c >= 0x20 && c <= 0x7E ) { + *d++ = c; + } + s++; + } + *d = '\0'; + + return string; +} + +int Q_CountChar(const char *string, char tocount) +{ + int count; + + for(count = 0; *string; string++) + { + if(*string == tocount) + count++; + } + + return count; +} + +void Q_StripIndentMarker(char *string) +{ + int i, j; + + for (i = j = 0; string[i]; i++) { + if (string[i] != INDENT_MARKER) { + string[j++] = string[i]; + } + } + string[j] = 0; +} + +void Q_ParseNewlines( char *dest, const char *src, int destsize ) +{ + for( ; *src && destsize > 1; src++, destsize-- ) + *dest++ = ( ( *src == '\\' && *( ++src ) == 'n' ) ? '\n' : *src ); + *dest++ = '\0'; +} + +void QDECL Com_sprintf(char *dest, int size, const char *fmt, ...) +{ + int len; + va_list argptr; + + va_start (argptr,fmt); + len = Q_vsnprintf(dest, size, fmt, argptr); + va_end (argptr); + + if(len >= size) + Com_Printf("Com_sprintf: Output length %d too short, require %d bytes.\n", size, len); +} + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +============ +*/ +char * QDECL va( char *format, ... ) { + va_list argptr; + static char string[2][32000]; // in case va is called by nested functions + static int index = 0; + char *buf; + + buf = string[index & 1]; + index++; + + va_start (argptr, format); + Q_vsnprintf (buf, sizeof(*string), format, argptr); + va_end (argptr); + + return buf; +} + +/* +============ +Com_TruncateLongString + +Assumes buffer is atleast TRUNCATE_LENGTH big +============ +*/ +void Com_TruncateLongString( char *buffer, const char *s ) +{ + int length = strlen( s ); + + if( length <= TRUNCATE_LENGTH ) + Q_strncpyz( buffer, s, TRUNCATE_LENGTH ); + else + { + Q_strncpyz( buffer, s, ( TRUNCATE_LENGTH / 2 ) - 3 ); + Q_strcat( buffer, TRUNCATE_LENGTH, " ... " ); + Q_strcat( buffer, TRUNCATE_LENGTH, s + length - ( TRUNCATE_LENGTH / 2 ) + 3 ); + } +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +FIXME: overflow check? +=============== +*/ +char *Info_ValueForKey( const char *s, const char *key ) { + char pkey[BIG_INFO_KEY]; + static char value[2][BIG_INFO_VALUE]; // use two buffers so compares + // work without stomping on each other + static int valueindex = 0; + char *o; + + if ( !s || !key ) { + return ""; + } + + if ( strlen( s ) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" ); + } + + valueindex ^= 1; + if (*s == '\\') + s++; + while (1) + { + o = pkey; + while (*s != '\\') + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while (*s != '\\' && *s) + { + *o++ = *s++; + } + *o = 0; + + if (!Q_stricmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + break; + s++; + } + + return ""; +} + + +/* +=================== +Info_NextPair + +Used to itterate through all the key/value pairs in an info string +=================== +*/ +void Info_NextPair( const char **head, char *key, char *value ) { + char *o; + const char *s; + + s = *head; + + if ( *s == '\\' ) { + s++; + } + key[0] = 0; + value[0] = 0; + + o = key; + while ( *s != '\\' ) { + if ( !*s ) { + *o = 0; + *head = s; + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) { + *o++ = *s++; + } + *o = 0; + + *head = s; +} + + +/* +=================== +Info_RemoveKey +=================== +*/ +void Info_RemoveKey( char *s, const char *key ) { + char *start; + char pkey[MAX_INFO_KEY]; + char value[MAX_INFO_VALUE]; + char *o; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); + } + + if (strchr (key, '\\')) { + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + memmove(start, s, strlen(s) + 1); // remove this part + + return; + } + + if (!*s) + return; + } + +} + +/* +=================== +Info_RemoveKey_Big +=================== +*/ +void Info_RemoveKey_Big( char *s, const char *key ) { + char *start; + char pkey[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + char *o; + + if ( strlen( s ) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey_Big: oversize infostring" ); + } + + if (strchr (key, '\\')) { + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + strcpy (start, s); // remove this part + return; + } + + if (!*s) + return; + } + +} + + + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate( const char *s ) { + const char* ch = s; + + while ( *ch != '\0' ) + { + if( !Q_isprint( *ch ) ) + return qfalse; + + if( *ch == '\"' ) + return qfalse; + + if( *ch == ';' ) + return qfalse; + + ++ch; + } + + return qtrue; +} + +/* +================== +Info_SetValueForKey + +Changes or adds a key/value pair +================== +*/ +void Info_SetValueForKey( char *s, const char *key, const char *value ) { + char newi[MAX_INFO_STRING]; + const char* blacklist = "\\;\""; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + for(; *blacklist; ++blacklist) + { + if (strchr (key, *blacklist) || strchr (value, *blacklist)) + { + Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value); + return; + } + } + + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) >= MAX_INFO_STRING) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + strcat (newi, s); + strcpy (s, newi); +} + +/* +================== +Info_SetValueForKey_Big + +Changes or adds a key/value pair +Includes and retains zero-length values +================== +*/ +void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) { + char newi[BIG_INFO_STRING]; + const char* blacklist = "\\;\""; + + if ( strlen( s ) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + for(; *blacklist; ++blacklist) + { + if (strchr (key, *blacklist) || strchr (value, *blacklist)) + { + Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value); + return; + } + } + + Info_RemoveKey_Big (s, key); + if (!value) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) >= BIG_INFO_STRING) + { + Com_Printf ("BIG Info string length exceeded: setting %s to %s " + "failed\n", key, value); + return; + } + + strcat (s, newi); +} + + + + +//==================================================================== + +/* +================== +Com_CharIsOneOfCharset +================== +*/ +static qboolean Com_CharIsOneOfCharset( char c, char *set ) +{ + int i; + + for( i = 0; i < strlen( set ); i++ ) + { + if( set[ i ] == c ) + return qtrue; + } + + return qfalse; +} + +/* +================== +Com_SkipCharset +================== +*/ +char *Com_SkipCharset( char *s, char *sep ) +{ + char *p = s; + + while( p ) + { + if( Com_CharIsOneOfCharset( *p, sep ) ) + p++; + else + break; + } + + return p; +} + +/* +================== +Com_SkipTokens +================== +*/ +char *Com_SkipTokens( char *s, int numTokens, char *sep ) +{ + int sepCount = 0; + char *p = s; + + while( sepCount < numTokens ) + { + if( Com_CharIsOneOfCharset( *p++, sep ) ) + { + sepCount++; + while( Com_CharIsOneOfCharset( *p, sep ) ) + p++; + } + else if( *p == '\0' ) + break; + } + + if( sepCount == numTokens ) + return p; + else + return s; +} + +/* +============ +Com_ClientListContains +============ +*/ +qboolean Com_ClientListContains( const clientList_t *list, int clientNum ) +{ + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) + return qfalse; + if( clientNum < 32 ) + return ( ( list->lo & ( 1 << clientNum ) ) != 0 ); + else + return ( ( list->hi & ( 1 << ( clientNum - 32 ) ) ) != 0 ); +} + +/* +============ +Com_ClientListAdd +============ +*/ +void Com_ClientListAdd( clientList_t *list, int clientNum ) +{ + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) + return; + if( clientNum < 32 ) + list->lo |= ( 1 << clientNum ); + else + list->hi |= ( 1 << ( clientNum - 32 ) ); +} + +/* +============ +Com_ClientListRemove +============ +*/ +void Com_ClientListRemove( clientList_t *list, int clientNum ) +{ + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) + return; + if( clientNum < 32 ) + list->lo &= ~( 1 << clientNum ); + else + list->hi &= ~( 1 << ( clientNum - 32 ) ); +} + +/* +============ +Com_ClientListString +============ +*/ +char *Com_ClientListString( const clientList_t *list ) +{ + static char s[ 17 ]; + + s[ 0 ] = '\0'; + if( !list ) + return s; + Com_sprintf( s, sizeof( s ), "%08x%08x", list->hi, list->lo ); + return s; +} + +/* +============ +Com_ClientListParse +============ +*/ +void Com_ClientListParse( clientList_t *list, const char *s ) +{ + if( !list ) + return; + list->lo = 0; + list->hi = 0; + if( !s ) + return; + if( strlen( s ) != 16 ) + return; + sscanf( s, "%x%x", &list->hi, &list->lo ); +} diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h new file mode 100644 index 0000000..d80f254 --- /dev/null +++ b/src/qcommon/q_shared.h @@ -0,0 +1,1424 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#ifndef __Q_SHARED_H +#define __Q_SHARED_H + +// q_shared.h -- included first by ALL program modules. +// A user mod should never modify this file + +#define PRODUCT_NAME "tremulous" + +#ifdef _MSC_VER +# define PRODUCT_VERSION "gpp1" +#endif + +#define CLIENT_WINDOW_TITLE "Tremulous " PRODUCT_VERSION +#define CLIENT_WINDOW_MIN_TITLE "Tremulous" +#define Q3_VERSION PRODUCT_NAME " " PRODUCT_VERSION + +#define GAMENAME_FOR_MASTER "Tremulous" +#define HEARTBEAT_FOR_MASTER GAMENAME_FOR_MASTER +#define FLATLINE_FOR_MASTER GAMENAME_FOR_MASTER "dead" + +#define MAX_TEAMNAME 32 +#define MAX_MASTER_SERVERS 5 // number of supported master servers + +#define DEMOEXT "dm_" // standard demo extension + +#ifdef _MSC_VER + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4152) // nonstandard extension, function/data pointer conversion in expression +//#pragma warning(disable : 4201) +//#pragma warning(disable : 4214) +#pragma warning(disable : 4244) +#pragma warning(disable : 4142) // benign redefinition +//#pragma warning(disable : 4305) // truncation from const double to float +//#pragma warning(disable : 4310) // cast truncates constant value +//#pragma warning(disable: 4505) // unreferenced local function has been removed +#pragma warning(disable : 4514) +#pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters +//#pragma intrinsic( memset, memcpy ) +#endif + +//Ignore __attribute__ on non-gcc platforms +#ifndef __GNUC__ +#ifndef __attribute__ +#define __attribute__(x) +#endif +#endif + +#if (defined _MSC_VER) +#define Q_EXPORT __declspec(dllexport) +#elif (defined __SUNPRO_C) +#define Q_EXPORT __global +#elif ((__GNUC__ >= 3) && (!__EMX__) && (!sun)) +#define Q_EXPORT __attribute__((visibility("default"))) +#else +#define Q_EXPORT +#endif + +/********************************************************************** + VM Considerations + + The VM can not use the standard system headers because we aren't really + using the compiler they were meant for. We use bg_lib.h which contains + prototypes for the functions we define for our own use in bg_lib.c. + + When writing mods, please add needed headers HERE, do not start including + stuff like in the various .c files that make up each of the VMs + since you will be including system headers files can will have issues. + + Remember, if you use a C library function that is not defined in bg_lib.c, + you will have to add your own version for support in the VM. + + **********************************************************************/ + +#ifdef Q3_VM + +#include "../game/bg_lib.h" + +typedef int intptr_t; + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #include + + typedef __int64 int64_t; + typedef __int32 int32_t; + typedef __int16 int16_t; + typedef __int8 int8_t; + typedef unsigned __int64 uint64_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int8 uint8_t; + + // vsnprintf is ISO/IEC 9899:1999 + // abstracting this to make it portable + int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap); +#else + #include + + #define Q_vsnprintf vsnprintf + #define Q_snprintf snprintf +#endif + +#endif + + +#include "q_platform.h" + +//============================================================= + +typedef unsigned char byte; + +typedef enum {qfalse, qtrue} qboolean; + +typedef union { + float f; + int i; + unsigned int ui; +} floatint_t; + +typedef int qhandle_t; +typedef int sfxHandle_t; +typedef int fileHandle_t; +typedef int clipHandle_t; + +#define PAD(x,y) (((x)+(y)-1) & ~((y)-1)) +#define PADLEN(x,y) (PAD((x), (y)) - (x)) + +#ifdef __GNUC__ +#define QALIGN(x) __attribute__((aligned(x))) +#else +#define QALIGN(x) +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define STRING(s) #s +// expand constants before stringifying them +#define XSTRING(s) STRING(s) + +#define MAX_QINT 0x7fffffff +#define MIN_QINT (-MAX_QINT-1) + +#define ARRAY_LEN(x) (sizeof(x) / sizeof(*(x))) + + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +// the game guarantees that no string from the network will ever +// exceed MAX_STRING_CHARS +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 1024 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 1024 // max length of an individual token + +#define MAX_INFO_STRING 1024 +#define MAX_INFO_KEY 1024 +#define MAX_INFO_VALUE 1024 + +#define BIG_INFO_STRING 8192 // used for system info key only +#define BIG_INFO_KEY 8192 +#define BIG_INFO_VALUE 8192 + +#define MAX_NEWS_STRING 10000 + +#define MAX_QPATH 64 // max length of a quake game pathname +#ifdef PATH_MAX +#define MAX_OSPATH PATH_MAX +#else +#define MAX_OSPATH 256 // max length of a filesystem pathname +#endif + +#define MAX_NAME_LENGTH 32 // max length of a client name +#define MAX_HOSTNAME_LENGTH 80 // max length of a host name + +#define MAX_SAY_TEXT 150 + +// paramters for command buffer stuffing +typedef enum { + EXEC_NOW, // don't return until completed, a VM should NEVER use this, + // because some commands might cause the VM to be unloaded... + EXEC_INSERT, // insert at current position, but don't run yet + EXEC_APPEND // add to end of the command buffer (normal case) +} cbufExec_t; + + +// +// these aren't needed by any of the VMs. put in another header? +// +#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility + + +// print levels from renderer (FIXME: set up for game / cgame?) +typedef enum { + PRINT_ALL, + PRINT_DEVELOPER, // only print when "developer 1" + PRINT_WARNING, + PRINT_ERROR +} printParm_t; + + +#ifdef ERR_FATAL +#undef ERR_FATAL // this is be defined in malloc.h +#endif + +// parameters to the main Error routine +typedef enum { + ERR_FATAL, // exit the entire game with a popup window + ERR_DROP, // print to console and disconnect from game + ERR_SERVERDISCONNECT, // don't kill server + ERR_DISCONNECT, // client disconnected from the server + ERR_NEED_CD // pop up the need-cd dialog +} errorParm_t; + + +// font rendering values used by ui and cgame + +#define PROP_GAP_WIDTH 3 +#define PROP_SPACE_WIDTH 8 +#define PROP_HEIGHT 27 +#define PROP_SMALL_SIZE_SCALE 0.75 + +#define BLINK_DIVISOR 200 +#define PULSE_DIVISOR 75 + +#define UI_LEFT 0x00000000 // default +#define UI_CENTER 0x00000001 +#define UI_RIGHT 0x00000002 +#define UI_FORMATMASK 0x00000007 +#define UI_SMALLFONT 0x00000010 +#define UI_BIGFONT 0x00000020 // default +#define UI_GIANTFONT 0x00000040 +#define UI_DROPSHADOW 0x00000800 +#define UI_BLINK 0x00001000 +#define UI_INVERSE 0x00002000 +#define UI_PULSE 0x00004000 + +#if defined(_DEBUG) && !defined(BSPC) + #define HUNK_DEBUG +#endif + +typedef enum { + h_high, + h_low, + h_dontcare +} ha_pref; + +#ifdef HUNK_DEBUG +#define Hunk_Alloc( size, preference ) Hunk_AllocDebug(size, preference, #size, __FILE__, __LINE__) +void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ); +#else +void *Hunk_Alloc( int size, ha_pref preference ); +#endif + +#define Com_Memset memset +#define Com_Memcpy memcpy + +#define CIN_system 1 +#define CIN_loop 2 +#define CIN_hold 4 +#define CIN_silent 8 +#define CIN_shader 16 + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + + +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef vec_t vec5_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846f // matches value in gcc v2 math.h +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 1.414213562f +#endif + +#ifndef M_ROOT3 +#define M_ROOT3 1.732050808f +#endif + +#define NUMVERTEXNORMALS 162 +extern vec3_t bytedirs[NUMVERTEXNORMALS]; + +// all drawing is done to a 640*480 virtual screen size +// and will be automatically scaled to the real resolution +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define TINYCHAR_WIDTH (SMALLCHAR_WIDTH) +#define TINYCHAR_HEIGHT (SMALLCHAR_HEIGHT/2) + +#define SMALLCHAR_WIDTH 8 +#define SMALLCHAR_HEIGHT 16 + +#define BIGCHAR_WIDTH 16 +#define BIGCHAR_HEIGHT 16 + +#define GIANTCHAR_WIDTH 32 +#define GIANTCHAR_HEIGHT 48 + +extern vec4_t colorBlack; +extern vec4_t colorRed; +extern vec4_t colorGreen; +extern vec4_t colorBlue; +extern vec4_t colorYellow; +extern vec4_t colorMagenta; +extern vec4_t colorCyan; +extern vec4_t colorWhite; +extern vec4_t colorLtGrey; +extern vec4_t colorMdGrey; +extern vec4_t colorDkGrey; + +#define Q_COLOR_ESCAPE '^' +#define Q_IsColorString(p) ((p) && *(p) == Q_COLOR_ESCAPE && *((p)+1) && isalnum(*((p)+1))) // ^[0-9a-zA-Z] + +#define COLOR_BLACK '0' +#define COLOR_RED '1' +#define COLOR_GREEN '2' +#define COLOR_YELLOW '3' +#define COLOR_BLUE '4' +#define COLOR_CYAN '5' +#define COLOR_MAGENTA '6' +#define COLOR_WHITE '7' +#define ColorIndex(c) (((c) - '0') & 0x07) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" + +#define INDENT_MARKER '\v' +void Q_StripIndentMarker(char *string); + +extern vec4_t g_color_table[8]; + +#define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b +#define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a + +#define DEG2RAD( a ) ( ( (a) * M_PI ) / 180.0F ) +#define RAD2DEG( a ) ( ( (a) * 180.0f ) / M_PI ) + +struct cplane_s; + +extern vec3_t vec3_origin; +extern vec3_t axisDefault[3]; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +#if idppc + +static ID_INLINE float Q_rsqrt( float number ) { + float x = 0.5f * number; + float y; +#ifdef __GNUC__ + asm("frsqrte %0,%1" : "=f" (y) : "f" (number)); +#else + y = __frsqrte( number ); +#endif + return y * (1.5f - (x * y * y)); + } + +#ifdef __GNUC__ +static ID_INLINE float Q_fabs(float x) { + float abs_x; + + asm("fabs %0,%1" : "=f" (abs_x) : "f" (x)); + return abs_x; +} +#else +#define Q_fabs __fabsf +#endif + +#else +float Q_fabs( float f ); +float Q_rsqrt( float f ); // reciprocal square root +#endif + +#define SQRTFAST( x ) ( (x) * Q_rsqrt( x ) ) + +signed char ClampChar( int i ); +signed short ClampShort( int i ); + +// this isn't a real cheap function to call! +int DirToByte( vec3_t dir ); +void ByteToDir( int b, vec3_t dir ); + +#if 1 + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) +#define VectorLerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\ + (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\ + (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2])) + +#else + +#define DotProduct(x,y) _DotProduct(x,y) +#define VectorSubtract(a,b,c) _VectorSubtract(a,b,c) +#define VectorAdd(a,b,c) _VectorAdd(a,b,c) +#define VectorCopy(a,b) _VectorCopy(a,b) +#define VectorScale(v, s, o) _VectorScale(v,s,o) +#define VectorMA(v, s, b, o) _VectorMA(v,s,b,o) + +#endif + +#ifdef Q3_VM +#ifdef VectorCopy +#undef VectorCopy +// this is a little hack to get more efficient copies in our interpreter +typedef struct { + float v[3]; +} vec3struct_t; +#define VectorCopy(a,b) (*(vec3struct_t *)b=*(vec3struct_t *)a) +#endif +#endif + +#define Vector2Set(v, x, y) ((v)[0]=(x), (v)[1]=(y)) +#define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0) +#define VectorNegate(a,b) ((b)[0]=-(a)[0],(b)[1]=-(a)[1],(b)[2]=-(a)[2]) +#define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z)) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define Vector4Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3]) +#define Vector4Lerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\ + (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\ + (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2]),\ + (r)[3]=(s)[3]+(f)*((e)[3]-(s)[3])) + +#define SnapVector(v) ( (v)[0] = (int)(v)[0],\ + (v)[1] = (int)(v)[1],\ + (v)[2] = (int)(v)[2] ) + +// just in case you do't want to use the macros +vec_t _DotProduct( const vec3_t v1, const vec3_t v2 ); +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ); +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ); +void _VectorCopy( const vec3_t in, vec3_t out ); +void _VectorScale( const vec3_t in, float scale, vec3_t out ); +void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc ); + +unsigned ColorBytes3 (float r, float g, float b); +unsigned ColorBytes4 (float r, float g, float b, float a); + +float NormalizeColor( const vec3_t in, vec3_t out ); + +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ); +void ClearBounds( vec3_t mins, vec3_t maxs ); +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); + +#if !defined( Q3_VM ) || ( defined( Q3_VM ) && defined( __Q3_VM_MATH ) ) +static ID_INLINE int VectorCompare( const vec3_t v1, const vec3_t v2 ) { + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) { + return 0; + } + return 1; +} + +static ID_INLINE int VectorCompareEpsilon( + const vec3_t v1, const vec3_t v2, float epsilon ) +{ + vec3_t d; + + VectorSubtract( v1, v2, d ); + d[ 0 ] = fabs( d[ 0 ] ); + d[ 1 ] = fabs( d[ 1 ] ); + d[ 2 ] = fabs( d[ 2 ] ); + + if( d[ 0 ] > epsilon || d[ 1 ] > epsilon || d[ 2 ] > epsilon ) + return 0; + + return 1; +} + +static ID_INLINE vec_t VectorLength( const vec3_t v ) { + return (vec_t)sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); +} + +static ID_INLINE vec_t VectorLengthSquared( const vec3_t v ) { + return (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); +} + +static ID_INLINE vec_t Distance( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract (p2, p1, v); + return VectorLength( v ); +} + +static ID_INLINE vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract (p2, p1, v); + return v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; +} + +// fast vector normalize routine that does not check to make sure +// that length != 0, nor does it return length, uses rsqrt approximation +static ID_INLINE void VectorNormalizeFast( vec3_t v ) +{ + float ilength; + + ilength = Q_rsqrt( DotProduct( v, v ) ); + + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; +} + +static ID_INLINE void VectorInverse( vec3_t v ){ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +static ID_INLINE void CrossProduct( const vec3_t v1, const 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]; +} + +#else +int VectorCompare( const vec3_t v1, const vec3_t v2 ); + +vec_t VectorLength( const vec3_t v ); + +vec_t VectorLengthSquared( const vec3_t v ); + +vec_t Distance( const vec3_t p1, const vec3_t p2 ); + +vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ); + +void VectorNormalizeFast( vec3_t v ); + +void VectorInverse( vec3_t v ); + +void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ); + +#endif + +vec_t VectorNormalize (vec3_t v); // returns vector length +vec_t VectorNormalize2( const vec3_t v, vec3_t out ); +void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ); +void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out ); +int Q_log2(int val); + +float Q_acos(float c); + +int Q_rand( int *seed ); +float Q_random( int *seed ); +float Q_crandom( int *seed ); + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +void vectoangles( const vec3_t value1, vec3_t angles); +void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ); +void AxisToAngles( vec3_t axis[3], vec3_t angles ); + +void AxisClear( vec3_t axis[3] ); +void AxisCopy( vec3_t in[3], vec3_t out[3] ); + +void SetPlaneSignbits( struct cplane_s *out ); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); + +qboolean BoundsIntersect(const vec3_t mins, const vec3_t maxs, + const vec3_t mins2, const vec3_t maxs2); +qboolean BoundsIntersectSphere(const vec3_t mins, const vec3_t maxs, + const vec3_t origin, vec_t radius); +qboolean BoundsIntersectPoint(const vec3_t mins, const vec3_t maxs, + const vec3_t origin); + +float AngleMod(float a); +float LerpAngle (float from, float to, float frac); +float AngleSubtract( float a1, float a2 ); +void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ); + +float AngleNormalize360 ( float angle ); +float AngleNormalize180 ( float angle ); +float AngleDelta ( float angle1, float angle2 ); + +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ); +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); +void RotateAroundDirection( vec3_t axis[3], vec_t angle ); +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up ); +// perpendicular vector could be replaced by this + +//int PlaneTypeForNormal (vec3_t normal); + +void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]); +void VectorMatrixMultiply( const vec3_t p, vec3_t m[ 3 ], vec3_t out ); +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +void PerpendicularVector( vec3_t dst, const vec3_t src ); +int Q_isnan( float x ); + +void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, + const vec3_t p2, vec3_t up ); +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, + vec3_t vEnd, vec3_t vProj ); +float VectorDistance( vec3_t v1, vec3_t v2 ); + +float pointToLineDistance( const vec3_t point, const vec3_t p1, const vec3_t p2 ); +float VectorMinComponent( vec3_t v ); +float VectorMaxComponent( vec3_t v ); + +vec_t DistanceBetweenLineSegmentsSquared( + const vec3_t sP0, const vec3_t sP1, + const vec3_t tP0, const vec3_t tP1, + float *s, float *t ); +vec_t DistanceBetweenLineSegments( + const vec3_t sP0, const vec3_t sP1, + const vec3_t tP0, const vec3_t tP1, + float *s, float *t ); + +#ifndef MAX +#define MAX(x,y) ((x)>(y)?(x):(y)) +#endif + +#ifndef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) +#endif + +//============================================= + +float Com_Clamp( float min, float max, float value ); + +char *COM_SkipPath( char *pathname ); +const char *COM_GetExtension( const char *name ); +void COM_StripExtension(const char *in, char *out, int destsize); +void COM_DefaultExtension( char *path, int maxSize, const char *extension ); + +void COM_BeginParseSession( const char *name ); +int COM_GetCurrentParseLine( void ); +char *COM_Parse( char **data_p ); +char *COM_ParseExt( char **data_p, qboolean allowLineBreak ); +int COM_Compress( char *data_p ); +void COM_ParseError( char *format, ... ) __attribute__ ((format (printf, 1, 2))); +void COM_ParseWarning( char *format, ... ) __attribute__ ((format (printf, 1, 2))); +//int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] ); + +#define MAX_TOKENLENGTH 1024 + +#ifndef TT_STRING +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation +#endif + +typedef struct pc_token_s +{ + int type; + int subtype; + int intvalue; + float floatvalue; + char string[MAX_TOKENLENGTH]; +} pc_token_t; + +// data is an in/out parm, returns a parsed out token + +void COM_MatchToken( char**buf_p, char *match ); + +void SkipBracedSection (char **program); +void SkipRestOfLine ( char **data ); + +void Parse1DMatrix (char **buf_p, int x, float *m); +void Parse2DMatrix (char **buf_p, int y, int x, float *m); +void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m); +int Com_HexStrToInt( const char *str ); + +void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); + +char *Com_SkipTokens( char *s, int numTokens, char *sep ); +char *Com_SkipCharset( char *s, char *sep ); + +void Com_RandomBytes( byte *string, int len ); + +typedef struct +{ + unsigned int hi; + unsigned int lo; +} clientList_t; + +qboolean Com_ClientListContains( const clientList_t *list, int clientNum ); +void Com_ClientListAdd( clientList_t *list, int clientNum ); +void Com_ClientListRemove( clientList_t *list, int clientNum ); +char *Com_ClientListString( const clientList_t *list ); +void Com_ClientListParse( clientList_t *list, const char *s ); + +// mode parm for FS_FOpenFile +typedef enum { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC +} fsMode_t; + +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +//============================================= + +int Q_isprint( int c ); +int Q_islower( int c ); +int Q_isupper( int c ); +int Q_isalpha( int c ); +qboolean Q_isanumber( const char *s ); +qboolean Q_isintegral( float f ); + +// portable case insensitive compare +int Q_stricmp (const char *s1, const char *s2); +int Q_strncmp (const char *s1, const char *s2, int n); +int Q_stricmpn (const char *s1, const char *s2, int n); +char *Q_strlwr( char *s1 ); +char *Q_strupr( char *s1 ); +char *Q_strrchr( const char* string, int c ); +const char *Q_stristr( const char *s, const char *find); + +// buffer size safe library replacements +void Q_strncpyz( char *dest, const char *src, int destsize ); +void Q_strcat( char *dest, int size, const char *src ); + +// strlen that discounts Quake color sequences +int Q_PrintStrlen( const char *string ); +// removes color sequences from string +char *Q_CleanStr( char *string ); +// parse "\n" into '\n' +void Q_ParseNewlines( char *dest, const char *src, int destsize ); +// Count the number of char tocount encountered in string +int Q_CountChar(const char *string, char tocount); + +//============================================= + +// 64-bit integers for global rankings interface +// implemented as a struct for qvm compatibility +typedef struct +{ + byte b0; + byte b1; + byte b2; + byte b3; + byte b4; + byte b5; + byte b6; + byte b7; +} qint64; + +//============================================= +/* +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +qint64 BigLong64 (qint64 l); +qint64 LittleLong64 (qint64 l); +float BigFloat (const float *l); +float LittleFloat (const float *l); + +void Swap_Init (void); +*/ +char * QDECL va(char *format, ...) __attribute__ ((format (printf, 1, 2))); + +#define TRUNCATE_LENGTH 64 +void Com_TruncateLongString( char *buffer, const char *s ); + +//============================================= + +// +// key / value info strings +// +char *Info_ValueForKey( const char *s, const char *key ); +void Info_RemoveKey( char *s, const char *key ); +void Info_RemoveKey_big( char *s, const char *key ); +void Info_SetValueForKey( char *s, const char *key, const char *value ); +void Info_SetValueForKey_Big( char *s, const char *key, const char *value ); +qboolean Info_Validate( const char *s ); +void Info_NextPair( const char **s, char *key, char *value ); + +// this is only here so the functions in q_shared.c and bg_*.c can link +void QDECL Com_Error( int level, const char *error, ... ) __attribute__ ((format (printf, 2, 3))); +void QDECL Com_Printf( const char *msg, ... ) __attribute__ ((format (printf, 1, 2))); + + +/* +========================================================== + +CVARS (console variables) + +Many variables can be used for cheating purposes, so when +cheats is zero, force all unspecified variables to their +default values. +========================================================== +*/ + +#define CVAR_ARCHIVE 0x0001 // set to cause it to be saved to vars.rc + // used for system variables, not for player + // specific configurations +#define CVAR_USERINFO 0x0002 // sent to server on connect or change +#define CVAR_SERVERINFO 0x0004 // sent in response to front end requests +#define CVAR_SYSTEMINFO 0x0008 // these cvars will be duplicated on all clients +#define CVAR_INIT 0x0010 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 0x0020 // will only change when C code next does + // a Cvar_Get(), so it can't be changed + // without proper initialization. modified + // will be set, even though the value hasn't + // changed yet +#define CVAR_ROM 0x0040 // display only, cannot be set by user at all +#define CVAR_USER_CREATED 0x0080 // created by a set command +#define CVAR_TEMP 0x0100 // can be set even when cheats are disabled, but is not archived +#define CVAR_CHEAT 0x0200 // can not be changed if cheats are disabled +#define CVAR_NORESTART 0x0400 // do not clear when a cvar_restart is issued + +#define CVAR_SERVER_CREATED 0x0800 // cvar was created by a server the client connected to. +#define CVAR_VM_CREATED 0x1000 // cvar was created exclusively in one of the VMs. +#define CVAR_PROTECTED 0x2000 // prevent modifying this var from VMs or the server +#define CVAR_NONEXISTENT 0xFFFFFFFF // Cvar doesn't exist. + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s cvar_t; + +struct cvar_s { + char *name; + char *string; + char *resetString; // cvar_restart will reset to this value + char *latchedString; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + int modificationCount; // incremented each time the cvar is changed + float value; // atof( string ) + int integer; // atoi( string ) + qboolean validate; + qboolean integral; + float min; + float max; + + cvar_t *next; + cvar_t *prev; + cvar_t *hashNext; + cvar_t *hashPrev; + int hashIndex; +}; + +#define MAX_CVAR_VALUE_STRING 256 + +typedef int cvarHandle_t; + +// the modules that run in the virtual machine can't access the cvar_t directly, +// so they must ask for structured updates +typedef struct { + cvarHandle_t handle; + int modificationCount; + float value; + int integer; + char string[MAX_CVAR_VALUE_STRING]; +} vmCvar_t; + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +#include "surfaceflags.h" // shared with the q3map utility + +// plane types are used to speed some tests +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 +#define PLANE_NON_AXIAL 3 + + +/* +================= +PlaneTypeForNormal +================= +*/ + +#define PlaneTypeForNormal(x) (x[0] == 1.0 ? PLANE_X : (x[1] == 1.0 ? PLANE_Y : (x[2] == 1.0 ? PLANE_Z : PLANE_NON_AXIAL) ) ) + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s { + vec3_t normal; + float dist; + byte type; // for fast side tests: 0,1,2 = axial, 3 = nonaxial + byte signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision + byte pad[2]; +} cplane_t; + +typedef enum { + TT_NONE, + + TT_AABB, + TT_CAPSULE, + TT_BISPHERE, + + TT_NUM_TRACE_TYPES +} traceType_t; + +// a trace is returned when a box is swept through the world +typedef struct { + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact, transformed to world space + int surfaceFlags; // surface hit + int contents; // contents on other side of surface hit + int entityNum; // entity the contacted sirface is a part of + float lateralFraction; // fraction of collision tangetially to the trace direction +} trace_t; + +// trace->entityNum can also be 0 to (MAX_GENTITIES-1) +// or ENTITYNUM_NONE, ENTITYNUM_WORLD + + +// markfragments are returned by CM_MarkFragments() +typedef struct { + int firstPoint; + int numPoints; +} markFragment_t; + + + +typedef struct { + vec3_t origin; + vec3_t axis[3]; +} orientation_t; + +//===================================================================== + + +// in order from highest priority to lowest +// if none of the catchers are active, bound key strings will be executed +#define KEYCATCH_CONSOLE 0x0001 +#define KEYCATCH_UI 0x0002 +#define KEYCATCH_CGAME 0x0008 + + +// sound channels +// channel 0 never willingly overrides +// other channels will allways override a playing sound on that channel +typedef enum { + CHAN_AUTO, + CHAN_LOCAL, // menu sounds, etc + CHAN_WEAPON, + CHAN_VOICE, + CHAN_ITEM, + CHAN_BODY, + CHAN_LOCAL_SOUND, // chat messages, etc + CHAN_ANNOUNCER // announcer voices, etc +} soundChannel_t; + + +/* +======================================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +======================================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + +#define SNAPFLAG_RATE_DELAYED 1 +#define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies +#define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected + +// +// per-level limits +// +#define MAX_CLIENTS 64 // absolute limit +#define MAX_LOCATIONS 64 + +#define GENTITYNUM_BITS 10 // don't need to send any more +#define MAX_GENTITIES (1<serverTime of last executed command + int pm_type; + int bobCycle; // for view bobbing and footstep generation + int pm_flags; // ducked, jump_held, etc + int pm_time; + + vec3_t origin; + vec3_t velocity; + int weaponTime; + int gravity; + int speed; + int delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters + + int groundEntityNum;// ENTITYNUM_NONE = in air + + int legsTimer; // don't change low priority animations until this runs out + int legsAnim; // mask off ANIM_TOGGLEBIT + + int torsoTimer; // don't change low priority animations until this runs out + int torsoAnim; // mask off ANIM_TOGGLEBIT + + int tauntTimer; // don't allow another taunt until this runs out + + int weaponAnim; // mask off ANIM_TOGGLEBIT + + int movementDir; // a number 0 to 7 that represents the reletive angle + // of movement to the view angle (axial and diagonals) + // when at rest, the value will remain unchanged + // used to twist the legs during strafing + + vec3_t grapplePoint; // location of grapple to pull towards if PMF_GRAPPLE_PULL + + int eFlags; // copied to entityState_t->eFlags + + int eventSequence; // pmove generated events + int events[MAX_PS_EVENTS]; + int eventParms[MAX_PS_EVENTS]; + + int externalEvent; // events set on player from another source + int externalEventParm; + int externalEventTime; + + int clientNum; // ranges from 0 to MAX_CLIENTS-1 + int weapon; // copied to entityState_t->weapon + int weaponstate; + + vec3_t viewangles; // for fixed views + int viewheight; + + // damage feedback + int damageEvent; // when it changes, latch the other parms + int damageYaw; + int damagePitch; + int damageCount; + + int stats[MAX_STATS]; + int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death + int misc[MAX_MISC]; // misc data + int ammo; // ammo held + int clips; // clips held + + int generic1; + int loopSound; + int otherEntityNum; + + // not communicated over the net at all + int ping; // server to game info for scoreboard + int pmove_framecount; // FIXME: don't transmit over the network + int jumppad_frame; + int entityEventSequence; +} playerState_t; + + +//==================================================================== + + +// +// usercmd_t->button bits, many of which are generated by the client system, +// so they aren't game/cgame only definitions +// +#define BUTTON_ATTACK 1 +#define BUTTON_TALK 2 // displays talk balloon and disables actions +#define BUTTON_USE_HOLDABLE 4 // activate upgrade +#define BUTTON_GESTURE 8 +#define BUTTON_WALKING 16 // walking can't just be infered from MOVE_RUN + // because a key pressed late in the frame will + // only generate a small move value for that frame + // walking will use different animations and + // won't generate footsteps +#define BUTTON_ATTACK2 32 +#define BUTTON_DODGE 64 // start a dodge or sprint motion +#define BUTTON_USE_EVOLVE 128 // use target or open evolve menu +#define BUTTON_SPRINT 256 + +#define BUTTON_ANY 2048 // any key whatsoever + +#define MOVE_RUN 120 // if forwardmove or rightmove are >= MOVE_RUN, + // then BUTTON_WALKING should be set + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s { + int serverTime; + int angles[3]; + int buttons; + byte weapon; // weapon + signed char forwardmove, rightmove, upmove; +} usercmd_t; + +//=================================================================== + +// if entityState->solid == SOLID_BMODEL, modelindex is an inline model number +#define SOLID_BMODEL 0xffffff + +typedef enum { + TR_STATIONARY, + TR_INTERPOLATE, // non-parametric, but interpolate between snapshots + TR_LINEAR, + TR_LINEAR_STOP, + TR_SINE, // value = base + sin( time / duration ) * delta + TR_GRAVITY, + TR_BUOYANCY +} trType_t; + +typedef struct { + trType_t trType; + int trTime; + int trDuration; // if non 0, trTime + trDuration = stop time + vec3_t trBase; + vec3_t trDelta; // velocity, etc +} trajectory_t; + +// entityState_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +// Different eTypes may use the information in different ways +// The messages are delta compressed, so it doesn't really matter if +// the structure size is fairly large + +typedef struct entityState_s { + int number; // entity index + int eType; // entityType_t + int eFlags; + + trajectory_t pos; // for calculating position + trajectory_t apos; // for calculating angles + + int time; + int time2; + + vec3_t origin; + vec3_t origin2; + + vec3_t angles; + vec3_t angles2; + + int otherEntityNum; // shotgun sources, etc + int otherEntityNum2; + + int groundEntityNum; // -1 = in air + + int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24) + int loopSound; // constantly loop this sound + + int modelindex; + int modelindex2; + int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses + int frame; + + int solid; // for client side prediction, trap_linkentity sets this properly + + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; + + // for players + int misc; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT + int weaponAnim; // mask off ANIM_TOGGLEBIT + + int generic1; +} entityState_t; + +typedef enum { + CA_UNINITIALIZED, + CA_DISCONNECTED, // not talking to a server + CA_AUTHORIZING, // not used any more, was checking cd key + CA_CONNECTING, // sending request packets to the server + CA_CHALLENGING, // sending challenge packets to the server + CA_CONNECTED, // netchan_t established, getting gamestate + CA_LOADING, // only during cgame initialization, never during main loop + CA_PRIMED, // got gamestate, waiting for first frame + CA_ACTIVE, // game views should be displayed + CA_CINEMATIC // playing a cinematic or a static pic, not connected to a server +} connstate_t; + +// font support + +#define GLYPH_START 0 +#define GLYPH_END 255 +#define GLYPH_CHARSTART 32 +#define GLYPH_CHAREND 127 +#define GLYPHS_PER_FONT GLYPH_END - GLYPH_START + 1 +typedef struct { + int height; // number of scan lines + int top; // top of glyph in buffer + int bottom; // bottom of glyph in buffer + int pitch; // width for copying + int xSkip; // x adjustment + int imageWidth; // width of actual image + int imageHeight; // height of actual image + float s; // x offset in image where glyph starts + float t; // y offset in image where glyph starts + float s2; + float t2; + qhandle_t glyph; // handle to the shader with the glyph + char shaderName[32]; +} glyphInfo_t; + +typedef struct { + glyphInfo_t glyphs [GLYPHS_PER_FONT]; + float glyphScale; + char name[MAX_QPATH]; +} fontInfo_t; + +#define Square(x) ((x)*(x)) + +// real time +//============================================= + + +typedef struct qtime_s { + int tm_sec; /* seconds after the minute - [0,59] */ + int tm_min; /* minutes after the hour - [0,59] */ + int tm_hour; /* hours since midnight - [0,23] */ + int tm_mday; /* day of the month - [1,31] */ + int tm_mon; /* months since January - [0,11] */ + int tm_year; /* years since 1900 */ + int tm_wday; /* days since Sunday - [0,6] */ + int tm_yday; /* days since January 1 - [0,365] */ + int tm_isdst; /* daylight savings time flag */ +} qtime_t; + + +// server browser sources +// AS_MPLAYER is no longer used +#define AS_GLOBAL 0 +#define AS_MPLAYER 1 +#define AS_LOCAL 2 +#define AS_FAVORITES 3 + + +// cinematic states +typedef enum { + FMV_IDLE, + FMV_PLAY, // play + FMV_EOF, // all other conditions, i.e. stop/EOF/abort + FMV_ID_BLT, + FMV_ID_IDLE, + FMV_LOOPED, + FMV_ID_WAIT +} e_status; + +typedef enum _flag_status { + FLAG_ATBASE = 0, + FLAG_TAKEN, // CTF + FLAG_TAKEN_RED, // One Flag CTF + FLAG_TAKEN_BLUE, // One Flag CTF + FLAG_DROPPED +} flagStatus_t; + +typedef enum { + DS_NONE, + + DS_PLAYBACK, + DS_RECORDING, + + DS_NUM_DEMO_STATES +} demoState_t; + + +#define MAX_GLOBAL_SERVERS 4096 +#define MAX_OTHER_SERVERS 128 +#define MAX_PINGREQUESTS 32 +#define MAX_SERVERSTATUSREQUESTS 16 + +#define MAX_EMOTICON_NAME_LEN 16 +#define MAX_EMOTICONS 64 + +typedef struct +{ + char name[ MAX_EMOTICON_NAME_LEN ]; +#ifndef GAME + int width; + qhandle_t shader; +#endif +} emoticon_t; + +// flags for com_downloadPrompt +#define DLP_TYPE_MASK 0x0f +#define DLP_IGNORE 0x01 // don't download anything +#define DLP_CURL 0x02 // download via HTTP redirect +#define DLP_UDP 0x04 // download from server +#define DLP_SHOW 0x10 // prompt needs to be shown +#define DLP_PROMPTED 0x20 // prompt has been processed by client +#define DLP_STALE 0x40 // prompt is not being shown by UI VM + +#define LERP( a, b, w ) ( ( a ) * ( 1.0f - ( w ) ) + ( b ) * ( w ) ) +#define LUMA( red, green, blue ) ( 0.2126f * ( red ) + 0.7152f * ( green ) + 0.0722f * ( blue ) ) + +#endif // __Q_SHARED_H diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h new file mode 100644 index 0000000..db5812f --- /dev/null +++ b/src/qcommon/qcommon.h @@ -0,0 +1,1194 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// qcommon.h -- definitions common between client and server, but not game.or ref modules +#ifndef _QCOMMON_H_ +#define _QCOMMON_H_ + +#include "../qcommon/cm_public.h" + +//Ignore __attribute__ on non-gcc platforms +#ifndef __GNUC__ +#ifndef __attribute__ +#define __attribute__(x) +#endif +#endif + +//============================================================================ + +// +// msg.c +// +typedef struct { + qboolean allowoverflow; // if false, do a Com_Error + qboolean overflowed; // set to true if the buffer size failed (with allowoverflow set) + qboolean oob; // set to true if the buffer size failed (with allowoverflow set) + byte *data; + int maxsize; + int cursize; + int readcount; + int bit; // for bitwise reads and writes +} msg_t; + +void MSG_Init (msg_t *buf, byte *data, int length); +void MSG_InitOOB( msg_t *buf, byte *data, int length ); +void MSG_Clear (msg_t *buf); +void MSG_WriteData (msg_t *buf, const void *data, int length); +void MSG_Bitstream( msg_t *buf ); + +// TTimo +// copy a msg_t in case we need to store it as is for a bit +// (as I needed this to keep an msg_t from a static var for later use) +// sets data buffer as MSG_Init does prior to do the copy +void MSG_Copy(msg_t *buf, byte *data, int length, msg_t *src); + +struct usercmd_s; +struct entityState_s; +struct playerState_s; + +void MSG_WriteBits( msg_t *msg, int value, int bits ); + +void MSG_WriteChar (msg_t *sb, int c); +void MSG_WriteByte (msg_t *sb, int c); +void MSG_WriteShort (msg_t *sb, int c); +void MSG_WriteLong (msg_t *sb, int c); +void MSG_WriteFloat (msg_t *sb, float f); +void MSG_WriteString (msg_t *sb, const char *s); +void MSG_WriteBigString (msg_t *sb, const char *s); +void MSG_WriteAngle16 (msg_t *sb, float f); +int MSG_HashKey(const char *string, int maxlen); + +void MSG_BeginReading (msg_t *sb); +void MSG_BeginReadingOOB(msg_t *sb); + +int MSG_ReadBits( msg_t *msg, int bits ); + +int MSG_ReadChar (msg_t *sb); +int MSG_ReadByte (msg_t *sb); +int MSG_ReadShort (msg_t *sb); +int MSG_ReadLong (msg_t *sb); +float MSG_ReadFloat (msg_t *sb); +char *MSG_ReadString (msg_t *sb); +char *MSG_ReadBigString (msg_t *sb); +char *MSG_ReadStringLine (msg_t *sb); +float MSG_ReadAngle16 (msg_t *sb); +void MSG_ReadData (msg_t *sb, void *buffer, int size); +int MSG_LookaheadByte (msg_t *msg); + +void MSG_WriteDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to ); +void MSG_ReadDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to ); + +void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ); +void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ); + +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to + , qboolean force ); +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, + int number ); + +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ); +void MSG_ReadDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ); + + +void MSG_ReportChangeVectors_f( void ); + +//============================================================================ + +/* +============================================================== + +NET + +============================================================== +*/ + +#define NET_ENABLEV4 0x01 +#define NET_ENABLEV6 0x02 +// if this flag is set, always attempt ipv6 connections instead of ipv4 if a v6 address is found. +#define NET_PRIOV6 0x04 +// disables ipv6 multicast support if set. +#define NET_DISABLEMCAST 0x08 + + +#define PACKET_BACKUP 32 // number of old messages that must be kept on client and + // server for delta comrpession and ping estimation +#define PACKET_MASK (PACKET_BACKUP-1) + +#define MAX_PACKET_USERCMDS 32 // max number of usercmd_t in a packet + +#define PORT_ANY -1 + +#define MAX_RELIABLE_COMMANDS 128 // max string commands buffered for restransmit + +typedef enum { + NA_BAD = 0, // an address lookup failed + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + NA_IP6, + NA_MULTICAST6, + NA_UNSPEC +} netadrtype_t; + +typedef enum { + NS_CLIENT, + NS_SERVER +} netsrc_t; + +#define NET_ADDRSTRMAXLEN 48 // maximum length of an IPv6 address string including trailing '\0' +typedef struct { + netadrtype_t type; + + byte ip[4]; + byte ip6[16]; + + unsigned short port; + unsigned long scope_id; // Needed for IPv6 link-local addresses +} netadr_t; + +void NET_Init( void ); +void NET_Shutdown( void ); +void NET_Restart_f( void ); +void NET_Config( qboolean enableNetworking ); +void NET_FlushPacketQueue(void); +void NET_SendPacket (netsrc_t sock, int length, const void *data, netadr_t to); +void QDECL NET_OutOfBandPrint( netsrc_t net_socket, netadr_t adr, const char *format, ...) __attribute__ ((format (printf, 3, 4))); +void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ); + +qboolean NET_CompareAdr (netadr_t a, netadr_t b); +qboolean NET_CompareBaseAdrMask(netadr_t a, netadr_t b, int netmask); +qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b); +qboolean NET_IsLocalAddress (netadr_t adr); +const char *NET_AdrToString (netadr_t a); +const char *NET_AdrToStringwPort (netadr_t a); +int NET_StringToAdr ( const char *s, netadr_t *a, netadrtype_t family); +qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message); +void NET_JoinMulticast6(void); +void NET_LeaveMulticast6(void); +void NET_Sleep(int msec); + + +#define MAX_MSGLEN 16384 // max length of a message, which may + // be fragmented into multiple packets + +#define MAX_DOWNLOAD_WINDOW 8 // max of eight download frames +#define MAX_DOWNLOAD_BLKSIZE 2048 // 2048 byte block chunks + + +/* +Netchan handles packet fragmentation and out of order / duplicate suppression +*/ + +typedef struct { + netsrc_t sock; + + int dropped; // between last packet and previous + + netadr_t remoteAddress; + int qport; // qport value to write when transmitting + + // sequencing variables + int incomingSequence; + int outgoingSequence; + + // incoming fragment assembly buffer + int fragmentSequence; + int fragmentLength; + byte fragmentBuffer[MAX_MSGLEN]; + + // outgoing fragment buffer + // we need to space out the sending of large fragmented messages + qboolean unsentFragments; + int unsentFragmentStart; + int unsentLength; + byte unsentBuffer[MAX_MSGLEN]; +} netchan_t; + +void Netchan_Init( int qport ); +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ); + +void Netchan_Transmit( netchan_t *chan, int length, const byte *data ); +void Netchan_TransmitNextFragment( netchan_t *chan ); + +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ); + + +/* +============================================================== + +PROTOCOL + +============================================================== +*/ + +#define PROTOCOL_VERSION 70 + +// maintain a list of compatible protocols for demo playing +// NOTE: that stuff only works with two digits protocols +extern int demo_protocols[]; + +// override on command line, config files etc. +#ifndef MASTER_SERVER_NAME +#define MASTER_SERVER_NAME "master.tremulous.net" +#endif + +#define PORT_MASTER 30700 +#define PORT_SERVER 30720 +#define NUM_SERVER_PORTS 4 // broadcast scan this many ports after + // PORT_SERVER so a single machine can + // run multiple servers + + +// the svc_strings[] array in cl_parse.c should mirror this +// +// server to client +// +enum svc_ops_e { + svc_bad, + svc_nop, + svc_gamestate, + svc_configstring, // [short] [string] only in gamestate messages + svc_baseline, // only in gamestate messages + svc_serverCommand, // [string] to be executed by client game module + svc_download, // [short] size [size bytes] + svc_snapshot, + svc_EOF, + + // svc_extension follows a svc_EOF, followed by another svc_* ... + // this keeps legacy clients compatible. + svc_extension, + svc_voip, // not wrapped in USE_VOIP, so this value is reserved. +}; + + +// +// client to server +// +enum clc_ops_e { + clc_bad, + clc_nop, + clc_move, // [[usercmd_t] + clc_moveNoDelta, // [[usercmd_t] + clc_clientCommand, // [string] message + clc_EOF, + + // clc_extension follows a clc_EOF, followed by another clc_* ... + // this keeps legacy servers compatible. + clc_extension, + clc_voip, // not wrapped in USE_VOIP, so this value is reserved. +}; + +/* +============================================================== + +VIRTUAL MACHINE + +============================================================== +*/ + +typedef struct vm_s vm_t; + +typedef enum { + VMI_NATIVE, + VMI_BYTECODE, + VMI_COMPILED +} vmInterpret_t; + +typedef enum { + TRAP_MEMSET = 100, + TRAP_MEMCPY, + TRAP_STRNCPY, + TRAP_SIN, + TRAP_COS, + TRAP_ATAN2, + TRAP_SQRT, + TRAP_MATRIXMULTIPLY, + TRAP_ANGLEVECTORS, + TRAP_PERPENDICULARVECTOR, + TRAP_FLOOR, + TRAP_CEIL, + + TRAP_TESTPRINTINT, + TRAP_TESTPRINTFLOAT +} sharedTraps_t; + +void VM_Init( void ); +vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *), + vmInterpret_t interpret ); +// module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm" + +void VM_Free( vm_t *vm ); +void VM_Clear(void); +void VM_Forced_Unload_Start(void); +void VM_Forced_Unload_Done(void); +vm_t *VM_Restart( vm_t *vm ); + +intptr_t QDECL VM_Call( vm_t *vm, int callNum, ... ); + +void VM_Debug( int level ); + +void *VM_ArgPtr( intptr_t intValue ); +void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ); + +#define VMA(x) VM_ArgPtr(args[x]) +static ID_INLINE float _vmf(intptr_t x) +{ + floatint_t fi; + fi.i = (int) x; + return fi.f; +} +#define VMF(x) _vmf(args[x]) + + +/* +============================================================== + +CMD + +Command text buffering and command execution + +============================================================== +*/ + +/* + +Any number of commands can be added in a frame, from several different sources. +Most commands come from either keybindings or console line input, but entire text +files can be execed. + +*/ + +void Cbuf_Init (void); +// allocates an initial text buffer that will grow as needed + +void Cbuf_AddText( const char *text ); +// Adds command text at the end of the buffer, does NOT add a final \n + +void Cbuf_ExecuteText( int exec_when, const char *text ); +// this can be used in place of either Cbuf_AddText or Cbuf_InsertText + +void Cbuf_Execute (void); +// Pulls off \n terminated lines of text from the command buffer and sends +// them through Cmd_ExecuteString. Stops when the buffer is empty. +// Normally called once per frame, but may be explicitly invoked. +// Do not call inside a command function, or current args will be destroyed. + +//=========================================================================== + +/* + +Command execution takes a null terminated string, breaks it into tokens, +then searches for a command or variable that matches the first token. + +*/ + +typedef void (*xcommand_t) (void); + +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 +// if function is NULL, the command will be forwarded to the server +// as a clc_clientCommand instead of executed locally + +void Cmd_RemoveCommand( const char *cmd_name ); + +typedef void (*completionFunc_t)( char *args, int argNum ); + +// don't allow VMs to remove system commands +void Cmd_RemoveCommandSafe( const char *cmd_name ); + +void Cmd_CommandCompletion( void(*callback)(const char *s) ); +// callback with each valid string +void Cmd_SetCommandCompletionFunc( const char *command, + completionFunc_t complete ); +void Cmd_CompleteArgument( const char *command, char *args, int argNum ); +void Cmd_CompleteCfgName( char *args, int argNum ); + +int Cmd_Argc (void); +char *Cmd_Argv (int arg); +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ); +char *Cmd_Args (void); +char *Cmd_ArgsFrom( int arg ); +void Cmd_ArgsBuffer( char *buffer, int bufferLength ); +void Cmd_LiteralArgsBuffer( char *buffer, int bufferLength ); +char *Cmd_Cmd (void); +void Cmd_Args_Sanitize( void ); +// The functions that execute commands get their parameters with these +// functions. Cmd_Argv () will return an empty string, not a NULL +// if arg > argc, so string operations are allways safe. + +void Cmd_TokenizeString( const char *text ); +void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ); +// Takes a null terminated string. Does not need to be /n terminated. +// breaks the string up into arg tokens. + +void Cmd_ExecuteString( const char *text ); +// Parses a single line of text into arguments and tries to execute it +// as if it was typed at the console + +void Cmd_SaveCmdContext( void ); +void Cmd_RestoreCmdContext( void ); + +/* +============================================================== + +CVAR + +============================================================== +*/ + +/* + +cvar_t variables are used to hold scalar or string variables that can be changed +or displayed at the console or prog code as well as accessed directly +in C code. + +The user can access cvars from the console in three ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 +set r_draworder 0 as above, but creates the cvar if not present + +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. + +The are also occasionally used to communicated information between different +modules of the program. + +*/ + +cvar_t *Cvar_Get( const char *var_name, const char *value, int flags ); +// creates the variable if it doesn't exist, or returns the existing one +// if it exists, the value will not be changed, but flags will be ORed in +// that allows variables to be unarchived without needing bitflags +// if value is "", the value will not override a previously set value. + +void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +// basically a slightly modified Cvar_Get for the interpreted modules + +void Cvar_Update( vmCvar_t *vmCvar ); +// updates an interpreted modules' version of a cvar + +void Cvar_Set( const char *var_name, const char *value ); +// will create the variable with no flags if it doesn't exist + +void Cvar_SetSafe( const char *var_name, const char *value ); +// sometimes we set variables from an untrusted source: fail if flags & CVAR_PROTECTED + +void Cvar_SetLatched( const char *var_name, const char *value); +// don't set the cvar immediately + +void Cvar_SetValue( const char *var_name, float value ); +void Cvar_SetValueSafe( const char *var_name, float value ); +// expands value to a string and calls Cvar_Set/Cvar_SetSafe + +float Cvar_VariableValue( const char *var_name ); +int Cvar_VariableIntegerValue( const char *var_name ); +// returns 0 if not defined or non numeric + +char *Cvar_VariableString( const char *var_name ); +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +// returns an empty string if not defined + +int Cvar_Flags(const char *var_name); +// returns CVAR_NONEXISTENT if cvar doesn't exist or the flags of that particular CVAR. + +void Cvar_CommandCompletion( void(*callback)(const char *s) ); +// callback with each valid string + +void Cvar_Reset( const char *var_name ); +void Cvar_ForceReset(const char *var_name); + +void Cvar_SetCheatState( void ); +// reset all testing vars to a safe value + +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( fileHandle_t f ); +// writes lines containing "set variable value" for all variables +// with the archive flag set to true. + +void Cvar_Init( void ); + +char *Cvar_InfoString( int bit ); +char *Cvar_InfoString_Big( int bit ); +// returns an info string containing all the cvars that have the given bit set +// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc ) +void Cvar_InfoStringBuffer( int bit, char *buff, int buffsize ); +void Cvar_CheckRange( cvar_t *cv, float minVal, float maxVal, qboolean shouldBeIntegral ); + +void Cvar_Restart(qboolean unsetVM); +void Cvar_Restart_f( void ); + +void Cvar_CompleteCvarName( char *args, int argNum ); + +extern int cvar_modifiedFlags; +// whenever a cvar is modifed, its flags will be OR'd into this, so +// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO, +// etc, variables have been modified since the last check. The bit +// can then be cleared to allow another change detection. + +/* +============================================================== + +FILESYSTEM + +No stdio calls should be used by any part of the game, because +we need to deal with all sorts of directory and seperator char +issues. +============================================================== +*/ + +// referenced flags +// these are in loop specific order so don't change the order +#define FS_GENERAL_REF 0x01 +#define FS_UI_REF 0x02 +#define FS_CGAME_REF 0x04 +#define FS_QAGAME_REF 0x08 + +#define MAX_FILE_HANDLES 64 + +#define BASEGAME "base" + +#ifdef DEDICATED +# define Q3CONFIG_CFG "autogen_server.cfg" +#else +# define Q3CONFIG_CFG "autogen.cfg" +#endif + +qboolean FS_Initialized( void ); + +void FS_InitFilesystem ( void ); +void FS_Shutdown( qboolean closemfp ); + +qboolean FS_ConditionalRestart( int checksumFeed ); +void FS_Restart( int checksumFeed ); +// shutdown and restart the filesystem so changes to fs_gamedir can take effect + +void FS_AddGameDirectory( const char *path, const char *dir ); + +char **FS_ListFiles( const char *directory, const char *extension, int *numfiles ); +// directory should not have either a leading or trailing / +// if extension is "/", only subdirectories will be returned +// the returned files will not include any directories or / + +void FS_FreeFileList( char **list ); + +qboolean FS_FileExists( const char *file ); + +qboolean FS_CreatePath (char *OSPath); + +char *FS_FindDll( const char *filename ); + +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ); +qboolean FS_CompareZipChecksum(const char *zipfile); + +int FS_LoadStack( void ); + +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int FS_GetModList( char *listbuf, int bufsize ); + +fileHandle_t FS_FOpenFileWrite( const char *qpath ); +fileHandle_t FS_FOpenFileAppend( const char *filename ); +fileHandle_t FS_FCreateOpenPipeFile( const char *filename ); +// will properly create any needed paths and deal with seperater character issues + +fileHandle_t FS_SV_FOpenFileWrite( const char *filename ); +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ); +void FS_SV_Rename( const char *from, const char *to ); +int FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE ); +// if uniqueFILE is true, then a new FILE will be fopened even if the file +// is found in an already open pak file. If uniqueFILE is false, you must call +// FS_FCloseFile instead of fclose, otherwise the pak FILE would be improperly closed +// It is generally safe to always set uniqueFILE to true, because the majority of +// file IO goes through FS_ReadFile, which Does The Right Thing already. + +int FS_FileIsInPAK(const char *filename, int *pChecksum ); +// returns 1 if a file is in the PAK file, otherwise -1 + +int FS_Write( const void *buffer, int len, fileHandle_t f ); + +int FS_Read2( void *buffer, int len, fileHandle_t f ); +int FS_Read( void *buffer, int len, fileHandle_t f ); +// properly handles partial reads and reads from other dlls + +void FS_FCloseFile( fileHandle_t f ); +// note: you can't just fclose from another DLL, due to MS libc issues + +int FS_ReadFile( const char *qpath, void **buffer ); +// returns the length of the file +// a null buffer will just return the file length without loading +// as a quick check for existance. -1 length == not present +// A 0 byte will always be appended at the end, so string ops are safe. +// the buffer should be considered read-only, because it may be cached +// for other uses. + +void FS_ForceFlush( fileHandle_t f ); +// forces flush on files we're writing to. + +void FS_FreeFile( void *buffer ); +// frees the memory returned by FS_ReadFile + +void FS_WriteFile( const char *qpath, const void *buffer, int size ); +// writes a complete file, creating any subdirectories needed + +int FS_filelength( fileHandle_t f ); +// doesn't work for files that are opened from a pack file + +int FS_FTell( fileHandle_t f ); +// where are we? + +void FS_Flush( fileHandle_t f ); + +void QDECL FS_Printf( fileHandle_t f, const char *fmt, ... ) __attribute__ ((format (printf, 2, 3))); +// like fprintf + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ); +// opens a file for reading, writing, or appending depending on the value of mode + +int FS_Seek( fileHandle_t f, long offset, int origin ); +// seek on a file (doesn't work for zip files!!!!!!!!) + +qboolean FS_FilenameCompare( const char *s1, const char *s2 ); + +const char *FS_GamePureChecksum( void ); +// Returns the checksum of the pk3 from which the server loaded the qagame.qvm + +const char *FS_LoadedPakNames( void ); +const char *FS_LoadedPakChecksums( void ); +const char *FS_LoadedPakPureChecksums( void ); +// Returns a space separated string containing the checksums of all loaded pk3 files. +// Servers with sv_pure set will get this string and pass it to clients. + +const char *FS_ReferencedPakNames( void ); +const char *FS_ReferencedPakChecksums( void ); +const char *FS_ReferencedPakPureChecksums( void ); +// Returns a space separated string containing the checksums of all loaded +// AND referenced pk3 files. Servers with sv_pure set will get this string +// back from clients for pure validation + +void FS_ClearPakReferences( int flags ); +// clears referenced booleans on loaded pk3s + +void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ); +void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ); +// If the string is empty, all data sources will be allowed. +// If not empty, only pk3 files that match one of the space +// separated checksums will be checked for files, with the +// sole exception of .cfg files. + +qboolean FS_CheckDirTraversal(const char *checkdir); +qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ); + +void FS_Rename( const char *from, const char *to ); + +void FS_Remove( const char *osPath ); +void FS_HomeRemove( const char *homePath ); + +void FS_FilenameCompletion( const char *dir, const char *ext, + qboolean stripExt, void(*callback)(const char *s), qboolean allowNonPureFilesOnDisk ); + +const char *FS_GetCurrentGameDir(void); + +/* +============================================================== + +Edit fields and command line history/completion + +============================================================== +*/ + +#define MAX_EDIT_LINE 256 +typedef struct { + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; +} field_t; + +void Field_Clear( field_t *edit ); +void Field_AutoComplete( field_t *edit ); +void Field_CompleteKeyname( void ); +void Field_CompleteFilename( const char *dir, + const char *ext, qboolean stripExt, qboolean allowNonPureFilesOnDisk ); +void Field_CompleteCommand( char *cmd, + qboolean doCommands, qboolean doCvars ); + +/* +============================================================== + +MISC + +============================================================== +*/ + +// returned by Sys_GetProcessorFeatures +typedef enum +{ + CF_RDTSC = 1 << 0, + CF_MMX = 1 << 1, + CF_MMX_EXT = 1 << 2, + CF_3DNOW = 1 << 3, + CF_3DNOW_EXT = 1 << 4, + CF_SSE = 1 << 5, + CF_SSE2 = 1 << 6, + CF_ALTIVEC = 1 << 7 +} cpuFeatures_t; + +// centralized and cleaned, that's the max string you can send to a Com_Printf / Com_DPrintf (above gets truncated) +#define MAXPRINTMSG 4096 + + +typedef enum { + // SE_NONE must be zero + SE_NONE = 0, // evTime is still valid + SE_KEY, // evValue is a key code, evValue2 is the down flag + SE_CHAR, // evValue is an ascii char + SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_CONSOLE // evPtr is a char* +} sysEventType_t; + +typedef struct { + int evTime; + sysEventType_t evType; + int evValue, evValue2; + int evPtrLength; // bytes of data pointed to by evPtr, for journaling + void *evPtr; // this must be manually freed if not NULL +} sysEvent_t; + +void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); +int Com_EventLoop( void ); +sysEvent_t Com_GetSystemEvent( void ); + +char *CopyString( const char *in ); +void Info_Print( const char *s ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)(char *)); +void Com_EndRedirect( void ); +void QDECL Com_Printf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2))); +void QDECL Com_DPrintf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2))); +void QDECL Com_Error( int code, const char *fmt, ... ) __attribute__ ((format (printf, 2, 3))); +void Com_Quit_f( void ); +void Com_GameRestart(int checksumFeed, qboolean clientRestart); + +int Com_Milliseconds( void ); // will be journaled properly +unsigned Com_BlockChecksum( const void *buffer, int length ); +char *Com_MD5File(const char *filename, int length, const char *prefix, int prefix_len); +int Com_Filter(char *filter, char *name, int casesensitive); +int Com_FilterPath(char *filter, char *name, int casesensitive); +int Com_RealTime(qtime_t *qtime); +qboolean Com_SafeMode( void ); +void Com_RunAndTimeServerPacket(netadr_t *evFrom, msg_t *buf); + +void Com_StartupVariable( const char *match ); +// checks for and removes command line "+set var arg" constructs +// if match is NULL, all set commands will be executed, otherwise +// only a set with the exact name. Only used during startup. + + +extern cvar_t *com_developer; +extern cvar_t *com_dedicated; +extern cvar_t *com_speeds; +extern cvar_t *com_timescale; +extern cvar_t *com_sv_running; +extern cvar_t *com_cl_running; +extern cvar_t *com_version; +extern cvar_t *com_blood; +extern cvar_t *com_buildScript; // for building release pak files +extern cvar_t *com_journal; +extern cvar_t *com_cameraMode; +extern cvar_t *com_ansiColor; +extern cvar_t *com_unfocused; +extern cvar_t *com_maxfpsUnfocused; +extern cvar_t *com_minimized; +extern cvar_t *com_maxfpsMinimized; +extern cvar_t *com_altivec; +extern cvar_t *com_homepath; + +// both client and server must agree to pause +extern cvar_t *cl_paused; +extern cvar_t *sv_paused; + +extern cvar_t *cl_packetdelay; +extern cvar_t *sv_packetdelay; + +// com_speeds times +extern int time_game; +extern int time_frontend; +extern int time_backend; // renderer backend time + +extern int com_frameTime; + +extern qboolean com_errorEntered; + +extern fileHandle_t com_journalFile; +extern fileHandle_t com_journalDataFile; + +typedef enum { + TAG_FREE, + TAG_GENERAL, + TAG_BOTLIB, + TAG_RENDERER, + TAG_SMALL, + TAG_STATIC +} memtag_t; + +/* + +--- low memory ---- +server vm +server clipmap +---mark--- +renderer initialization (shaders, etc) +UI vm +cgame vm +renderer map +renderer models + +---free--- + +temp file loading +--- high memory --- + +*/ + +#if defined(_DEBUG) && !defined(BSPC) + #define ZONE_DEBUG +#endif + +#ifdef ZONE_DEBUG +#define Z_TagMalloc(size, tag) Z_TagMallocDebug(size, tag, #size, __FILE__, __LINE__) +#define Z_Malloc(size) Z_MallocDebug(size, #size, __FILE__, __LINE__) +#define S_Malloc(size) S_MallocDebug(size, #size, __FILE__, __LINE__) +void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ); // NOT 0 filled memory +void *Z_MallocDebug( int size, char *label, char *file, int line ); // returns 0 filled memory +void *S_MallocDebug( int size, char *label, char *file, int line ); // returns 0 filled memory +#else +void *Z_TagMalloc( int size, int tag ); // NOT 0 filled memory +void *Z_Malloc( int size ); // returns 0 filled memory +void *S_Malloc( int size ); // NOT 0 filled memory only for small allocations +#endif +void Z_Free( void *ptr ); +void Z_FreeTags( int tag ); +int Z_AvailableMemory( void ); +void Z_LogHeap( void ); + +void Hunk_Clear( void ); +void Hunk_ClearToMark( void ); +void Hunk_SetMark( void ); +qboolean Hunk_CheckMark( void ); +void Hunk_ClearTempMemory( void ); +void *Hunk_AllocateTempMemory( int size ); +void Hunk_FreeTempMemory( void *buf ); +int Hunk_MemoryRemaining( void ); +void Hunk_Log( void); +void Hunk_Trash( void ); + +void Com_TouchMemory( void ); + +// commandLine should not include the executable name (argv[0]) +void Com_Init( char *commandLine ); +void Com_Frame( void ); +void Com_Shutdown( void ); + + +/* +============================================================== + +CLIENT / SERVER SYSTEMS + +============================================================== +*/ + +// +// client interface +// +void CL_InitKeyCommands( void ); +// the keyboard binding interface must be setup before execing +// config files, but the rest of client startup will happen later + +void CL_Init( void ); +void CL_Disconnect( qboolean showMainMenu ); +void CL_Shutdown( char *finalmsg ); +void CL_Frame( int msec ); +qboolean CL_GameCommand( void ); +void CL_KeyEvent (int key, qboolean down, unsigned time); + +void CL_CharEvent( int key ); +// char events are for field typing, not game control + +void CL_MouseEvent( int dx, int dy, int time ); + +void CL_JoystickEvent( int axis, int value, int time ); + +void CL_PacketEvent( netadr_t from, msg_t *msg ); + +void CL_ConsolePrint( char *text ); + +void CL_MapLoading( void ); +// do a screen update before starting to load a map +// when the server is going to load a new map, the entire hunk +// will be cleared, so the client must shutdown cgame, ui, and +// the renderer + +void CL_ForwardCommandToServer( const char *string ); +// adds the current command line as a clc_clientCommand to the client message. +// things like godmode, noclip, etc, are commands directed to the server, +// so when they are typed in at the console, they will need to be forwarded. + +void CL_CDDialog( void ); +// bring up the "need a cd to play" dialog + +void CL_ShutdownAll( void ); +// shutdown all the client stuff + +void CL_FlushMemory( void ); +// dump all memory on an error + +void CL_StartHunkUsers( qboolean rendererOnly ); +// start all the client stuff using the hunk + +void CL_Snd_Restart(void); +// Restart sound subsystem + +void Key_KeynameCompletion( void(*callback)(const char *s) ); +// for keyname autocompletion + +void Key_WriteBindings( fileHandle_t f ); +// for writing the config files + +void S_ClearSoundBuffer( void ); +// call before filesystem access + +void SCR_DebugGraph (float value, int color); // FIXME: move logging to common? + +// AVI files have the start of pixel lines 4 byte-aligned +#define AVI_LINE_PADDING 4 + +// +// server interface +// +void SV_Init( void ); +void SV_Shutdown( char *finalmsg ); +void SV_Frame( int msec ); +void SV_PacketEvent( netadr_t from, msg_t *msg ); +int SV_FrameMsec(void); +qboolean SV_GameCommand( void ); + + +// +// UI interface +// +qboolean UI_GameCommand( void ); + +/* +============================================================== + +NON-PORTABLE SYSTEM SERVICES + +============================================================== +*/ + +typedef enum { + AXIS_SIDE, + AXIS_FORWARD, + AXIS_UP, + AXIS_ROLL, + AXIS_YAW, + AXIS_PITCH, + MAX_JOYSTICK_AXIS +} joystickAxis_t; + +void Sys_Init (void); + +// general development dll loading for virtual machine testing +void * QDECL Sys_LoadDll( const char *name, intptr_t (QDECL **entryPoint)(int, ...), + intptr_t (QDECL *systemcalls)(intptr_t, ...) ); +void Sys_UnloadDll( void *dllHandle ); + +void Sys_UnloadGame( void ); +void *Sys_GetGameAPI( void *parms ); + +void Sys_UnloadCGame( void ); +void *Sys_GetCGameAPI( void ); + +void Sys_UnloadUI( void ); +void *Sys_GetUIAPI( void ); + +//bot libraries +void Sys_UnloadBotLib( void ); +void *Sys_GetBotLibAPI( void *parms ); + +char *Sys_GetCurrentUser( void ); + +void QDECL Sys_Error( const char *error, ...) __attribute__ ((format (printf, 1, 2))); +void Sys_Quit (void); +char *Sys_GetClipboardData( void ); // note that this isn't journaled... + +void Sys_Print( const char *msg ); + +// Sys_Milliseconds should only be used for profiling purposes, +// any game related timing information should come from event timestamps +int Sys_Milliseconds (void); + +void Sys_SnapVector( float *v ); + +qboolean Sys_RandomBytes( byte *string, int len ); + +// the system console is shown when a dedicated server is running +void Sys_DisplaySystemConsole( qboolean show ); + +cpuFeatures_t Sys_GetProcessorFeatures( void ); + +void Sys_SetErrorText( const char *text ); + +void Sys_SendPacket( int length, const void *data, netadr_t to ); + +qboolean Sys_StringToAdr( const char *s, netadr_t *a, netadrtype_t family ); +//Does NOT parse port numbers, only base addresses. + +qboolean Sys_IsLANAddress (netadr_t adr); +void Sys_ShowIP(void); + +qboolean Sys_Mkdir( const char *path ); +FILE *Sys_Mkfifo( const char *ospath ); +char *Sys_Cwd( void ); +void Sys_SetDefaultInstallPath(const char *path); +char *Sys_DefaultInstallPath(void); + +#ifdef MACOS_X +char *Sys_DefaultAppPath(void); +#endif + +void Sys_SetDefaultHomePath(const char *path); +char *Sys_DefaultHomePath(void); +const char *Sys_TempPath(void); +const char *Sys_Dirname( char *path ); +const char *Sys_Basename( char *path ); +char *Sys_ConsoleInput(void); + +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ); +void Sys_FreeFileList( char **list ); +void Sys_Sleep(int msec); + +qboolean Sys_LowPhysicalMemory( void ); + +void Sys_SetEnv(const char *name, const char *value); + +typedef enum +{ + DR_YES = 0, + DR_NO = 1, + DR_OK = 0, + DR_CANCEL = 1 +} dialogResult_t; + +typedef enum +{ + DT_INFO, + DT_WARNING, + DT_ERROR, + DT_YES_NO, + DT_OK_CANCEL +} dialogType_t; + +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ); + +qboolean Sys_WritePIDFile( void ); + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book. The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +#define NYT HMAX /* NYT = Not Yet Transmitted */ +#define INTERNAL_NODE (HMAX+1) + +typedef struct nodetype { + struct nodetype *left, *right, *parent; /* tree structure */ + struct nodetype *next, *prev; /* doubly-linked list */ + struct nodetype **head; /* highest ranked node in block */ + int weight; + int symbol; +} node_t; + +#define HMAX 256 /* Maximum symbol */ + +typedef struct { + int blocNode; + int blocPtrs; + + node_t* tree; + node_t* lhead; + node_t* ltail; + node_t* loc[HMAX+1]; + node_t** freelist; + + node_t nodeList[768]; + node_t* nodePtrs[768]; +} huff_t; + +typedef struct { + huff_t compressor; + huff_t decompressor; +} huffman_t; + +void Huff_Compress(msg_t *buf, int offset); +void Huff_Decompress(msg_t *buf, int offset); +void Huff_Init(huffman_t *huff); +void Huff_addRef(huff_t* huff, byte ch); +int Huff_Receive (node_t *node, int *ch, byte *fin); +void Huff_transmit (huff_t *huff, int ch, byte *fout); +void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset); +void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset); +void Huff_putBit( int bit, byte *fout, int *offset); +int Huff_getBit( byte *fout, int *offset); + +// don't use if you don't know what you're doing. +int Huff_getBloc(void); +void Huff_setBloc(int _bloc); + +extern huffman_t clientHuffTables; + +int Parse_AddGlobalDefine(char *string); +int Parse_LoadSourceHandle(const char *filename); +int Parse_FreeSourceHandle(int handle); +int Parse_ReadTokenHandle(int handle, pc_token_t *pc_token); +int Parse_SourceFileAndLine(int handle, char *filename, int *line); + +#define SV_ENCODE_START 4 +#define SV_DECODE_START 12 +#define CL_ENCODE_START 12 +#define CL_DECODE_START 4 + +// flags for sv_allowDownload and cl_allowDownload +#define DLF_ENABLE 1 +#define DLF_NO_REDIRECT 2 +#define DLF_NO_UDP 4 +#define DLF_NO_DISCONNECT 8 + +#endif // _QCOMMON_H_ diff --git a/src/qcommon/qfiles.h b/src/qcommon/qfiles.h new file mode 100644 index 0000000..f6bd292 --- /dev/null +++ b/src/qcommon/qfiles.h @@ -0,0 +1,582 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +//Ignore __attribute__ on non-gcc platforms +#ifndef __GNUC__ +#ifndef __attribute__ +#define __attribute__(x) +#endif +#endif + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + + +// the maximum size of game relative pathnames +#define MAX_QPATH 64 + +/* +======================================================================== + +QVM files + +======================================================================== +*/ + +#define VM_MAGIC 0x12721444 +#define VM_MAGIC_VER2 0x12721445 +typedef struct { + int vmMagic; + + int instructionCount; + + int codeOffset; + int codeLength; + + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength + + //!!! below here is VM_MAGIC_VER2 !!! + int jtrgLength; // number of jump table targets +} vmHeader_t; + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 3 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE (1.0/64) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_QPATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + +/* +============================================================================== + +MD4 file format + +============================================================================== +*/ + +#define MD4_IDENT (('4'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD4_VERSION 1 +#define MD4_MAX_BONES 128 + +typedef struct { + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list + vec3_t offset; +} md4Weight_t; + +typedef struct { + vec3_t normal; + vec2_t texCoords; + int numWeights; + md4Weight_t weights[1]; // variable sized +} md4Vertex_t; + +typedef struct { + int indexes[3]; +} md4Triangle_t; + +typedef struct { + int ident; + + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use + + int ofsHeader; // this will be a negative number + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows +} md4Surface_t; + +typedef struct { + float matrix[3][4]; +} md4Bone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + md4Bone_t bones[1]; // [numBones] +} md4Frame_t; + +typedef struct { + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows +} md4LOD_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsBoneNames; // char name[ MAX_QPATH ] + int ofsFrames; // md4Frame_t[numFrames] + + // each level of detail has completely separate sets of surfaces + int numLODs; + int ofsLODs; + + int ofsEnd; // end of file +} md4Header_t; + +/* + * Here are the definitions for Ravensoft's model format of md4. Raven stores their + * playermodels in .mdr files, in some games, which are pretty much like the md4 + * format implemented by ID soft. It seems like ID's original md4 stuff is not used at all. + * MDR is being used in EliteForce, JediKnight2 and Soldiers of Fortune2 (I think). + * So this comes in handy for anyone who wants to make it possible to load player + * models from these games. + * This format has bone tags, which is similar to the thing you have in md3 I suppose. + * Raven has released their version of md3view under GPL enabling me to add support + * to this codebase. Thanks to Steven Howes aka Skinner for helping with example + * source code. + * + * - Thilo Schulz (arny@ats.s.bawue.de) + */ + +// If you want to enable support for Raven's .mdr / md4 format, uncomment the next +// line. +//#define RAVENMD4 + +#ifdef RAVENMD4 + +#define MDR_IDENT (('5'<<24)+('M'<<16)+('D'<<8)+'R') +#define MDR_VERSION 2 +#define MDR_MAX_BONES 128 + +typedef struct { + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list + vec3_t offset; +} mdrWeight_t; + +typedef struct { + vec3_t normal; + vec2_t texCoords; + int numWeights; + mdrWeight_t weights[1]; // variable sized +} mdrVertex_t; + +typedef struct { + int indexes[3]; +} mdrTriangle_t; + +typedef struct { + int ident; + + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use + + int ofsHeader; // this will be a negative number + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows +} mdrSurface_t; + +typedef struct { + float matrix[3][4]; +} mdrBone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + char name[16]; + mdrBone_t bones[1]; // [numBones] +} mdrFrame_t; + +typedef struct { + unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple +} mdrCompBone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + mdrCompBone_t bones[1]; // [numBones] +} mdrCompFrame_t; + +typedef struct { + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows +} mdrLOD_t; + +typedef struct { + int boneIndex; + char name[32]; +} mdrTag_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsFrames; // mdrFrame_t[numFrames] + + // each level of detail has completely separate sets of surfaces + int numLODs; + int ofsLODs; + + int numTags; + int ofsTags; + + int ofsEnd; // end of file +} mdrHeader_t; + +#endif + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + + +#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I') + // little-endian "IBSP" + +#define BSP_VERSION 46 + + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x20000 +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 0x800000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +#define MAX_WORLD_COORD ( 128*1024 ) +#define MIN_WORLD_COORD ( -128*1024 ) +#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +//============================================================================= + + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define HEADER_LUMPS 17 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; +} drawVert_t; + +#define drawVert_t_cleared(x) drawVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0, 0}} + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; + + +#endif diff --git a/src/qcommon/surfaceflags.h b/src/qcommon/surfaceflags.h new file mode 100644 index 0000000..c1f2a0d --- /dev/null +++ b/src/qcommon/surfaceflags.h @@ -0,0 +1,91 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// This file must be identical in the quake and utils directories + +// contents flags are seperate bits +// a given brush can contribute multiple content bits + +// these definitions also need to be in q_shared.h! + +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_FOG 64 + +#define CONTENTS_NOTTEAM1 0x0080 +#define CONTENTS_NOTTEAM2 0x0100 +#define CONTENTS_NOBOTCLIP 0x0200 + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 +//bot specific contents types +#define CONTENTS_TELEPORTER 0x40000 +#define CONTENTS_JUMPPAD 0x80000 +#define CONTENTS_CLUSTERPORTAL 0x100000 +#define CONTENTS_DONOTENTER 0x200000 +#define CONTENTS_BOTCLIP 0x400000 +#define CONTENTS_MOVER 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_BODY 0x2000000 // should never be on a brush, only in game +#define CONTENTS_CORPSE 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes not used for the bsp +#define CONTENTS_STRUCTURAL 0x10000000 // brushes used for the bsp +#define CONTENTS_TRANSLUCENT 0x20000000 // don't consume surface fragments inside +#define CONTENTS_TRIGGER 0x40000000 +#define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava) + +// custominfoparms below +#define CONTENTS_NOALIENBUILD 0x1000 //disallow alien building +#define CONTENTS_NOHUMANBUILD 0x2000 //disallow human building +#define CONTENTS_NOBUILD 0x4000 //disallow building + +#define SURF_NODAMAGE 0x1 // never give falling damage +#define SURF_SLICK 0x2 // effects game physics +#define SURF_SKY 0x4 // lighting from environment map +#define SURF_LADDER 0x8 +#define SURF_NOIMPACT 0x10 // don't make missile explosions +#define SURF_NOMARKS 0x20 // don't leave missile marks +#define SURF_FLESH 0x40 // make flesh sounds and effects +#define SURF_NODRAW 0x80 // don't generate a drawsurface at all +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes +#define SURF_NOLIGHTMAP 0x400 // surface doesn't need a lightmap +#define SURF_POINTLIGHT 0x800 // generate lighting info at vertexes +#define SURF_METALSTEPS 0x1000 // clanking footsteps +#define SURF_NOSTEPS 0x2000 // no footstep sounds +#define SURF_NONSOLID 0x4000 // don't collide against curves with this set +#define SURF_LIGHTFILTER 0x8000 // act as a light filter during q3map -light +#define SURF_ALPHASHADOW 0x10000 // do per-pixel light shadow casting in q3map +#define SURF_NODLIGHT 0x20000 // don't dlight even if solid (solid lava, skies) +#define SURF_DUST 0x40000 // leave a dust trail when walking on this surface + +// custominfoparms below +#define SURF_NOALIENBUILD 0x80000 //disallow alien building +#define SURF_NOHUMANBUILD 0x100000 //disallow human building +#define SURF_NOBUILD 0x200000 //disallow building diff --git a/src/qcommon/unzip.c b/src/qcommon/unzip.c new file mode 100644 index 0000000..b307e98 --- /dev/null +++ b/src/qcommon/unzip.c @@ -0,0 +1,1608 @@ +/* unzip.c -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + Read unzip.h for more info +*/ + +/* Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of +compatibility with older software. The following is from the original crypt.c. Code +woven in by Terry Thorsen 1/2003. +*/ +/* + Copyright (c) 1990-2000 Info-ZIP. All rights reserved. + + See the accompanying file LICENSE, version 2000-Apr-09 or later + (the contents of which are also included in zip.h) for terms of use. + If, for some reason, all these files are missing, the Info-ZIP license + also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html +*/ +/* + crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + */ + +/* + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + */ + + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "unzip.h" + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + + +#ifndef CASESENSITIVITYDEFAULT_NO +# if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) +# define CASESENSITIVITYDEFAULT_NO +# endif +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (16384) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (Z_Malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) Z_Free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + + +const char unz_copyright[] = + " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + uLong offset_curfile;/* relative offset of local header 4 bytes */ +} unz_file_info_internal; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + uLong pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ + uLong stream_initialised; /* flag set if stream structure is initialised*/ + + uLong offset_local_extrafield;/* offset of the local extra field */ + uInt size_local_extrafield;/* size of the local extra field */ + uLong pos_local_extrafield; /* position in the local extra field in read*/ + + uLong crc32; /* crc32 of all data uncompressed */ + uLong crc32_wait; /* crc32 we must obtain after decompress all */ + uLong rest_read_compressed; /* number of byte to be decompressed */ + uLong rest_read_uncompressed;/*number of byte to be obtained after decomp*/ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + uLong compression_method; /* compression method (0==store) */ + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + int raw; +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + uLong num_file; /* number of the current file in the zipfile*/ + uLong pos_in_central_dir; /* pos of the current file in the central dir*/ + uLong current_file_ok; /* flag about the usability of the current file*/ + uLong central_pos; /* position of the beginning of the central dir*/ + + uLong size_central_dir; /* size of the central directory */ + uLong offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ + int encrypted; +# ifndef NOUNCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; +# endif +} unz_s; + + +#ifndef NOUNCRYPT +#include "crypt.h" +#endif + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + + +local int unzlocal_getByte OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + int *pi)); + +local int unzlocal_getByte(pzlib_filefunc_def,filestream,pi) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + int *pi; +{ + unsigned char c; + int err = (int)ZREAD(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ZERROR(*pzlib_filefunc_def,filestream)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int unzlocal_getShort OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unzlocal_getShort (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i = 0; + int err; + + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unzlocal_getLong OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unzlocal_getLong (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i = 0; + int err; + + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int strcmpcasenosensitive_internal OF(( + const char* fileName1, + const char* fileName2)); + +/* My own strcmpi / strcasecmp */ +local int strcmpcasenosensitive_internal (fileName1,fileName2) + const char* fileName1; + const char* fileName2; +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int ZEXPORT unzStringFileNameCompare (fileName1,fileName2,iCaseSensitivity) + const char* fileName1; + const char* fileName2; + int iCaseSensitivity; +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local uLong unzlocal_SearchCentralDir OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream)); + +local uLong unzlocal_SearchCentralDir(pzlib_filefunc_def,filestream) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (ZSEEK(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (ZSEEK(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer + "zlib/zlib114.zip". + If the zipfile cannot be opened (file doesn't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile ZEXPORT unzOpen2 (path, pzlib_filefunc_def) + const char *path; + zlib_filefunc_def* pzlib_filefunc_def; +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + if (unz_copyright[0]!=' ') + return NULL; + + if (pzlib_filefunc_def==NULL) + fill_fopen_filefunc(&us.z_filefunc); + else + us.z_filefunc = *pzlib_filefunc_def; + + us.filestream= (*(us.z_filefunc.zopen_file))(us.z_filefunc.opaque, + path, + ZLIB_FILEFUNC_MODE_READ | + ZLIB_FILEFUNC_MODE_EXISTING); + if (us.filestream==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(&us.z_filefunc,us.filestream); + if (central_pos==0) + err=UNZ_ERRNO; + + if (ZSEEK(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZCLOSE(s->z_filefunc, s->filestream); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int ZEXPORT unzGetGlobalInfo (file,pglobal_info) + unzFile file; + unz_global_info *pglobal_info; +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ + +local void unzlocal_DosDateToTmuDate OF(( + uLong ulDosDate, + tm_unz* ptm)); + + +local void unzlocal_DosDateToTmuDate (ulDosDate, ptm) + uLong ulDosDate; + tm_unz* ptm; +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +local int unzlocal_GetCurrentFileInfoInternal OF((unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +local int unzlocal_GetCurrentFileInfoInternal (file, + pfile_info, + pfile_info_internal, + szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, + szComment, commentBufferSize) + unzFile file; + unz_file_info *pfile_info; + unz_file_info_internal *pfile_info_internal; + char *szFileName; + uLong fileNameBufferSize; + void *extraField; + uLong extraFieldBufferSize; + char *szComment; + uLong commentBufferSize; +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (ZSEEK(s->z_filefunc, s->filestream, + s->pos_in_central_dir+s->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + { + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + } + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extraz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,extraField,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int ZEXPORT unzGetCurrentFileInfo (file, + pfile_info, + szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, + szComment, commentBufferSize) + unzFile file; + unz_file_info *pfile_info; + char *szFileName; + uLong fileNameBufferSize; + void *extraField; + uLong extraFieldBufferSize; + char *szComment; + uLong commentBufferSize; +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int ZEXPORT unzGoToFirstFile (file) + unzFile file; +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int ZEXPORT unzGoToNextFile (file) + unzFile file; +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int ZEXPORT unzLocateFile (file, szFileName, iCaseSensitivity) + unzFile file; + const char *szFileName; + int iCaseSensitivity; +{ + unz_s* s; + int err; + + /* We remember the 'current' position in the file so that we can jump + * back there if we fail. + */ + unz_file_info cur_file_infoSaved; + unz_file_info_internal cur_file_info_internalSaved; + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + /* Save the current state */ + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + cur_file_infoSaved = s->cur_file_info; + cur_file_info_internalSaved = s->cur_file_info_internal; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + err = unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (err == UNZ_OK) + { + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + } + + /* We failed, so restore the state of the 'current file' to where we + * were. + */ + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + s->cur_file_info = cur_file_infoSaved; + s->cur_file_info_internal = cur_file_info_internalSaved; + return err; +} + + +/* +/////////////////////////////////////////// +// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) +// I need random access +// +// Further optimization could be realized by adding an ability +// to cache the directory in memory. The goal being a single +// comprehensive file read to put the file I need in a memory. +*/ + +/* +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; // offset in file + uLong num_of_file; // # of file +} unz_file_pos; +*/ + +extern int ZEXPORT unzGetFilePos(file, file_pos) + unzFile file; + unz_file_pos* file_pos; +{ + unz_s* s; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + file_pos->pos_in_zip_directory = s->pos_in_central_dir; + file_pos->num_of_file = s->num_file; + + return UNZ_OK; +} + +extern int ZEXPORT unzGoToFilePos(file, file_pos) + unzFile file; + unz_file_pos* file_pos; +{ + unz_s* s; + int err; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + /* jump to the right spot */ + s->pos_in_central_dir = file_pos->pos_in_zip_directory; + s->num_file = file_pos->num_of_file; + + /* set the current file */ + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + /* return results */ + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* +// Unzip Helper Functions - should be here? +/////////////////////////////////////////// +*/ + +/* + Read the local header of the current zipfile + Check the coherency of the local header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in local header + (filename and size of extra field data) +*/ + +local int unzlocal_CheckCurrentFileCoherencyHeader OF(( + unz_s* s, + uInt* piSizeVar, + uLong *poffset_local_extrafield, + uInt *psize_local_extrafield)); + + +local int unzlocal_CheckCurrentFileCoherencyHeader (s,piSizeVar, + poffset_local_extrafield, + psize_local_extrafield) + unz_s* s; + uInt* piSizeVar; + uLong *poffset_local_extrafield; + uInt *psize_local_extrafield; +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZSEEK(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + { + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + } + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int ZEXPORT unzOpenCurrentFile3 (file, method, level, raw, password) + unzFile file; + int* method; + int* level; + int raw; + const char* password; +{ + int err=UNZ_OK; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the local extra field */ + uInt size_local_extrafield; /* size of the local extra field */ +# ifndef NOUNCRYPT + char source[12]; +# else + if (password != NULL) + return UNZ_PARAMERROR; +# endif + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) + ALLOC(sizeof(file_in_zip_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + pfile_in_zip_read_info->raw=raw; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if (method!=NULL) + *method = (int)s->cur_file_info.compression_method; + + if (level!=NULL) + { + *level = 6; + switch (s->cur_file_info.flag & 0x06) + { + case 6 : *level = 1; break; + case 4 : *level = 2; break; + case 2 : *level = 9; break; + } + } + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->filestream=s->filestream; + pfile_in_zip_read_info->z_filefunc=s->z_filefunc; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if ((s->cur_file_info.compression_method==Z_DEFLATED) && + (!raw)) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = (voidpf)0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + +# ifndef NOUNCRYPT + if (password != NULL) + { + int i; + s->pcrc_32_tab = get_crc_table(); + init_keys(password,s->keys,s->pcrc_32_tab); + if (ZSEEK(s->z_filefunc, s->filestream, + s->pfile_in_zip_read->pos_in_zipfile + + s->pfile_in_zip_read->byte_before_the_zipfile, + SEEK_SET)!=0) + return UNZ_INTERNALERROR; + if(ZREAD(s->z_filefunc, s->filestream,source, 12)<12) + return UNZ_INTERNALERROR; + + for (i = 0; i<12; i++) + zdecode(s->keys,s->pcrc_32_tab,source[i]); + + s->pfile_in_zip_read->pos_in_zipfile+=12; + s->encrypted=1; + } +# endif + + + return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile (file) + unzFile file; +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword (file, password) + unzFile file; + const char* password; +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2 (file,method,level,raw) + unzFile file; + int* method; + int* level; + int raw; +{ + return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int ZEXPORT unzReadCurrentFile (file, buf, len) + unzFile file; + voidp buf; + unsigned len; +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && + (!(pfile_in_zip_read_info->raw))) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + if ((len>pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in) && + (pfile_in_zip_read_info->raw)) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (ZSEEK(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZREAD(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->read_buffer, + uReadThis)!=uReadThis) + return UNZ_ERRNO; + + +# ifndef NOUNCRYPT + if(s->encrypted) + { + uInt i; + for(i=0;iread_buffer[i] = + zdecode(s->keys,s->pcrc_32_tab, + pfile_in_zip_read_info->read_buffer[i]); + } +# endif + + + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Bytef*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) + { + uInt uDoCopy,i ; + + if ((pfile_in_zip_read_info->stream.avail_in == 0) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + return (iRead==0) ? UNZ_EOF : iRead; + + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + uLong uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) + err = Z_DATA_ERROR; + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern z_off_t ZEXPORT unztell (file) + unzFile file; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (z_off_t)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int ZEXPORT unzeof (file) + unzFile file; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int ZEXPORT unzGetLocalExtrafield (file,buf,len) + unzFile file; + voidp buf; + unsigned len; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZSEEK(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZREAD(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + buf,read_now)!=read_now) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int ZEXPORT unzCloseCurrentFile (file) + unzFile file; +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && + (!pfile_in_zip_read_info->raw)) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int ZEXPORT unzGetGlobalComment (file, szComment, uSizeBuf) + unzFile file; + char *szComment; + uLong uSizeBuf; +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZSEEK(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZREAD(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* Additions by RX '2004 */ +extern uLong ZEXPORT unzGetOffset (file) + unzFile file; +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return 0; + if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) + if (s->num_file==s->gi.number_entry) + return 0; + return s->pos_in_central_dir; +} + +extern int ZEXPORT unzSetOffset (file, pos) + unzFile file; + uLong pos; +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + s->num_file = s->gi.number_entry; /* hack */ + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} diff --git a/src/qcommon/unzip.h b/src/qcommon/unzip.h new file mode 100644 index 0000000..b4a839b --- /dev/null +++ b/src/qcommon/unzip.h @@ -0,0 +1,355 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + 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. + + +*/ + +/* for more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _unz_H +#define _unz_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef USE_LOCAL_HEADERS + #include "../zlib/zlib.h" +#else + #include +#endif +#include "ioapi.h" + +#define NOUNCRYPT + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, + const char* fileName2, + int iCaseSensitivity)); +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + + +extern unzFile ZEXPORT unzOpen OF((const char *path)); +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer + "zlib/zlib113.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern unzFile ZEXPORT unzOpen2 OF((const char *path, + zlib_filefunc_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unzOpen, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, + unz_global_info *pglobal_info)); +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, + char *szComment, + uLong uSizeBuf)); +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int ZEXPORT unzLocateFile OF((unzFile file, + const char *szFileName, + int iCaseSensitivity)); +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, + const char* password)); +/* + Open for reading data the current file in the zipfile. + password is a crypting password + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, + int* method, + int* level, + int raw)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, + int* method, + int* level, + int raw, + const char* password)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); +/* + Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz_H */ diff --git a/src/qcommon/vm.c b/src/qcommon/vm.c new file mode 100644 index 0000000..225119f --- /dev/null +++ b/src/qcommon/vm.c @@ -0,0 +1,917 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// vm.c -- virtual machine + +/* + + +intermix code and data +symbol table + +a dll has one imported function: VM_SystemCall +and one exported function: Perform + + +*/ + +#include "vm_local.h" + + +vm_t *currentVM = NULL; +vm_t *lastVM = NULL; +int vm_debugLevel; + +// used by Com_Error to get rid of running vm's before longjmp +static int forced_unload; + +#define MAX_VM 3 +vm_t vmTable[MAX_VM]; + + +void VM_VmInfo_f( void ); +void VM_VmProfile_f( void ); + + + +#if 0 // 64bit! +// converts a VM pointer to a C pointer and +// checks to make sure that the range is acceptable +void *VM_VM2C( vmptr_t p, int length ) { + return (void *)p; +} +#endif + +void VM_Debug( int level ) { + vm_debugLevel = level; +} + +/* +============== +VM_Init +============== +*/ +void VM_Init( void ) { + Cvar_Get( "vm_cgame", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 + Cvar_Get( "vm_game", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 + Cvar_Get( "vm_ui", "2", CVAR_ARCHIVE ); // !@# SHIP WITH SET TO 2 + + Cmd_AddCommand ("vmprofile", VM_VmProfile_f ); + Cmd_AddCommand ("vminfo", VM_VmInfo_f ); + + Com_Memset( vmTable, 0, sizeof( vmTable ) ); +} + + +/* +=============== +VM_ValueToSymbol + +Assumes a program counter value +=============== +*/ +const char *VM_ValueToSymbol( vm_t *vm, int value ) { + vmSymbol_t *sym; + static char text[MAX_TOKEN_CHARS]; + + sym = vm->symbols; + if ( !sym ) { + return "NO SYMBOLS"; + } + + // find the symbol + while ( sym->next && sym->next->symValue <= value ) { + sym = sym->next; + } + + if ( value == sym->symValue ) { + return sym->symName; + } + + Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue ); + + return text; +} + +/* +=============== +VM_ValueToFunctionSymbol + +For profiling, find the symbol behind this value +=============== +*/ +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) { + vmSymbol_t *sym; + static vmSymbol_t nullSym; + + sym = vm->symbols; + if ( !sym ) { + return &nullSym; + } + + while ( sym->next && sym->next->symValue <= value ) { + sym = sym->next; + } + + return sym; +} + + +/* +=============== +VM_SymbolToValue +=============== +*/ +int VM_SymbolToValue( vm_t *vm, const char *symbol ) { + vmSymbol_t *sym; + + for ( sym = vm->symbols ; sym ; sym = sym->next ) { + if ( !strcmp( symbol, sym->symName ) ) { + return sym->symValue; + } + } + return 0; +} + + +/* +===================== +VM_SymbolForCompiledPointer +===================== +*/ +#if 0 // 64bit! +const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) { + int i; + + if ( code < (void *)vm->codeBase ) { + return "Before code block"; + } + if ( code >= (void *)(vm->codeBase + vm->codeLength) ) { + return "After code block"; + } + + // find which original instruction it is after + for ( i = 0 ; i < vm->codeLength ; i++ ) { + if ( (void *)vm->instructionPointers[i] > code ) { + break; + } + } + i--; + + // now look up the bytecode instruction pointer + return VM_ValueToSymbol( vm, i ); +} +#endif + + + +/* +=============== +ParseHex +=============== +*/ +int ParseHex( const char *text ) { + int value; + int c; + + value = 0; + while ( ( c = *text++ ) != 0 ) { + if ( c >= '0' && c <= '9' ) { + value = value * 16 + c - '0'; + continue; + } + if ( c >= 'a' && c <= 'f' ) { + value = value * 16 + 10 + c - 'a'; + continue; + } + if ( c >= 'A' && c <= 'F' ) { + value = value * 16 + 10 + c - 'A'; + continue; + } + } + + return value; +} + +/* +=============== +VM_LoadSymbols +=============== +*/ +void VM_LoadSymbols( vm_t *vm ) { + int len; + union { + char *c; + void *v; + } mapfile; + char *text_p, *token; + char name[MAX_QPATH]; + char symbols[MAX_QPATH]; + vmSymbol_t **prev, *sym; + int count; + int value; + int chars; + int segment; + int numInstructions; + + // don't load symbols if not developer + if ( !com_developer->integer ) { + return; + } + + COM_StripExtension(vm->name, name, sizeof(name)); + Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name ); + len = FS_ReadFile( symbols, &mapfile.v ); + if ( !mapfile.c ) { + Com_Printf( "Couldn't load symbol file: %s\n", symbols ); + return; + } + + numInstructions = vm->instructionCount; + + // parse the symbols + text_p = mapfile.c; + prev = &vm->symbols; + count = 0; + + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + segment = ParseHex( token ); + if ( segment ) { + COM_Parse( &text_p ); + COM_Parse( &text_p ); + continue; // only load code segment values + } + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + value = ParseHex( token ); + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + chars = strlen( token ); + sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high ); + *prev = sym; + prev = &sym->next; + sym->next = NULL; + + // convert value from an instruction number to a code offset + if ( value >= 0 && value < numInstructions ) { + value = vm->instructionPointers[value]; + } + + sym->symValue = value; + Q_strncpyz( sym->symName, token, chars + 1 ); + + count++; + } + + vm->numSymbols = count; + Com_Printf( "%i symbols parsed from %s\n", count, symbols ); + FS_FreeFile( mapfile.v ); +} + +/* +============ +VM_DllSyscall + +Dlls will call this directly + + rcg010206 The horror; the horror. + + The syscall mechanism relies on stack manipulation to get its args. + This is likely due to C's inability to pass "..." parameters to + a function in one clean chunk. On PowerPC Linux, these parameters + are not necessarily passed on the stack, so while (&arg[0] == arg) + is true, (&arg[1] == 2nd function parameter) is not necessarily + accurate, as arg's value might have been stored to the stack or + other piece of scratch memory to give it a valid address, but the + next parameter might still be sitting in a register. + + Quake's syscall system also assumes that the stack grows downward, + and that any needed types can be squeezed, safely, into a signed int. + + This hack below copies all needed values for an argument to a + array in memory, so that Quake can get the correct values. This can + also be used on systems where the stack grows upwards, as the + presumably standard and safe stdargs.h macros are used. + + As for having enough space in a signed int for your datatypes, well, + it might be better to wait for DOOM 3 before you start porting. :) + + The original code, while probably still inherently dangerous, seems + to work well enough for the platforms it already works on. Rather + than add the performance hit for those platforms, the original code + is still in use there. + + For speed, we just grab 15 arguments, and don't worry about exactly + how many the syscall actually needs; the extra is thrown away. + +============ +*/ +intptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) { +#if !id386 + // rcg010206 - see commentary above + intptr_t args[16]; + int i; + va_list ap; + + args[0] = arg; + + va_start(ap, arg); + for (i = 1; i < ARRAY_LEN (args); i++) + args[i] = va_arg(ap, intptr_t); + va_end(ap); + + return currentVM->systemCall( args ); +#else // original id code + return currentVM->systemCall( &arg ); +#endif +} + + +/* +================= +VM_LoadQVM + +Load a .qvm file +================= +*/ +vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc ) { + int length; + int dataLength; + int i; + char filename[MAX_QPATH]; + union { + vmHeader_t *h; + void *v; + } header; + + // load the image + Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name ); + Com_Printf( "Loading vm file %s...\n", filename ); + length = FS_ReadFile( filename, &header.v ); + if ( !header.h ) { + Com_Printf( "Failed.\n" ); + VM_Free( vm ); + return NULL; + } + + // show where the qvm was loaded from + Cmd_ExecuteString( va( "which %s\n", filename ) ); + + if( LittleLong( header.h->vmMagic ) == VM_MAGIC_VER2 ) { + Com_Printf( "...which has vmMagic VM_MAGIC_VER2\n" ); + + // byte swap the header + for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) { + ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] ); + } + + // validate + if ( header.h->jtrgLength < 0 + || header.h->bssLength < 0 + || header.h->dataLength < 0 + || header.h->litLength < 0 + || header.h->codeLength <= 0 ) { + VM_Free( vm ); + Com_Error( ERR_FATAL, "%s has bad header", filename ); + } + } else if( LittleLong( header.h->vmMagic ) == VM_MAGIC ) { + // byte swap the header + // sizeof( vmHeader_t ) - sizeof( int ) is the 1.32b vm header size + for ( i = 0 ; i < ( sizeof( vmHeader_t ) - sizeof( int ) ) / 4 ; i++ ) { + ((int *)header.h)[i] = LittleLong( ((int *)header.h)[i] ); + } + + // validate + if ( header.h->bssLength < 0 + || header.h->dataLength < 0 + || header.h->litLength < 0 + || header.h->codeLength <= 0 ) { + VM_Free( vm ); + Com_Error( ERR_FATAL, "%s has bad header", filename ); + } + } else { + VM_Free( vm ); + Com_Error( ERR_FATAL, "%s does not have a recognisable " + "magic number in its header", filename ); + } + + // round up to next power of 2 so all data operations can + // be mask protected + dataLength = header.h->dataLength + header.h->litLength + + header.h->bssLength; + for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { + } + dataLength = 1 << i; + + if( alloc ) { + // allocate zero filled space for initialized and uninitialized data + vm->dataBase = Hunk_Alloc( dataLength, h_high ); + vm->dataMask = dataLength - 1; + } else { + // clear the data + Com_Memset( vm->dataBase, 0, dataLength ); + } + + // copy the intialized data + Com_Memcpy( vm->dataBase, (byte *)header.h + header.h->dataOffset, + header.h->dataLength + header.h->litLength ); + + // byte swap the longs + for ( i = 0 ; i < header.h->dataLength ; i += 4 ) { + *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); + } + + if( header.h->vmMagic == VM_MAGIC_VER2 ) { + vm->numJumpTableTargets = header.h->jtrgLength >> 2; + Com_Printf( "Loading %d jump table targets\n", vm->numJumpTableTargets ); + + if( alloc ) { + vm->jumpTableTargets = Hunk_Alloc( header.h->jtrgLength, h_high ); + } else { + Com_Memset( vm->jumpTableTargets, 0, header.h->jtrgLength ); + } + + Com_Memcpy( vm->jumpTableTargets, (byte *)header.h + header.h->dataOffset + + header.h->dataLength + header.h->litLength, header.h->jtrgLength ); + + // byte swap the longs + for ( i = 0 ; i < header.h->jtrgLength ; i += 4 ) { + *(int *)(vm->jumpTableTargets + i) = LittleLong( *(int *)(vm->jumpTableTargets + i ) ); + } + } + + return header.h; +} + +/* +================= +VM_Restart + +Reload the data, but leave everything else in place +This allows a server to do a map_restart without changing memory allocation +================= +*/ +vm_t *VM_Restart( vm_t *vm ) { + vmHeader_t *header; + + // DLL's can't be restarted in place + if ( vm->dllHandle ) { + char name[MAX_QPATH]; + intptr_t (*systemCall)( intptr_t *parms ); + + systemCall = vm->systemCall; + Q_strncpyz( name, vm->name, sizeof( name ) ); + + VM_Free( vm ); + + vm = VM_Create( name, systemCall, VMI_NATIVE ); + return vm; + } + + // load the image + Com_Printf( "VM_Restart()\n" ); + + if( !( header = VM_LoadQVM( vm, qfalse ) ) ) { + Com_Error( ERR_DROP, "VM_Restart failed.\n" ); + return NULL; + } + + // free the original file + FS_FreeFile( header ); + + return vm; +} + +/* +================ +VM_Create + +If image ends in .qvm it will be interpreted, otherwise +it will attempt to load as a system dll +================ +*/ +vm_t *VM_Create( const char *module, intptr_t (*systemCalls)(intptr_t *), + vmInterpret_t interpret ) { + vm_t *vm; + vmHeader_t *header; + int i, remaining; + + if ( !module || !module[0] || !systemCalls ) { + Com_Error( ERR_FATAL, "VM_Create: bad parms" ); + } + + remaining = Hunk_MemoryRemaining(); + + // see if we already have the VM + for ( i = 0 ; i < MAX_VM ; i++ ) { + if (!Q_stricmp(vmTable[i].name, module)) { + vm = &vmTable[i]; + return vm; + } + } + + // find a free vm + for ( i = 0 ; i < MAX_VM ; i++ ) { + if ( !vmTable[i].name[0] ) { + break; + } + } + + if ( i == MAX_VM ) { + Com_Error( ERR_FATAL, "VM_Create: no free vm_t" ); + } + + vm = &vmTable[i]; + + Q_strncpyz( vm->name, module, sizeof( vm->name ) ); + vm->systemCall = systemCalls; + + if ( interpret == VMI_NATIVE ) { + // try to load as a system dll + Com_Printf( "Loading dll file %s.\n", vm->name ); + vm->dllHandle = Sys_LoadDll( module, &vm->entryPoint, VM_DllSyscall ); + if ( vm->dllHandle ) { + return vm; + } + + Com_Printf( "Failed to load dll, looking for qvm.\n" ); + interpret = VMI_COMPILED; + } + + // load the image + if( !( header = VM_LoadQVM( vm, qtrue ) ) ) { + return NULL; + } + + // allocate space for the jump targets, which will be filled in by the compile/prep functions + vm->instructionCount = header->instructionCount; + vm->instructionPointers = Hunk_Alloc( vm->instructionCount*4, h_high ); + + // copy or compile the instructions + vm->codeLength = header->codeLength; + + vm->compiled = qfalse; + +#ifdef NO_VM_COMPILED + if(interpret >= VMI_COMPILED) { + Com_Printf("Architecture doesn't have a bytecode compiler, using interpreter\n"); + interpret = VMI_BYTECODE; + } +#else + if ( interpret >= VMI_COMPILED ) { + vm->compiled = qtrue; + VM_Compile( vm, header ); + } +#endif + // VM_Compile may have reset vm->compiled if compilation failed + if (!vm->compiled) + { + VM_PrepareInterpreter( vm, header ); + } + + // free the original file + FS_FreeFile( header ); + + // load the map file + VM_LoadSymbols( vm ); + + // the stack is implicitly at the end of the image + vm->programStack = vm->dataMask + 1; + vm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE; + + Com_Printf("%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining()); + + return vm; +} + +/* +============== +VM_Free +============== +*/ +void VM_Free( vm_t *vm ) { + + if(!vm) { + return; + } + + if(vm->callLevel) { + if(!forced_unload) { + Com_Error( ERR_FATAL, "VM_Free(%s) on running vm", vm->name ); + return; + } else { + Com_Printf( "forcefully unloading %s vm\n", vm->name ); + } + } + + if(vm->destroy) + vm->destroy(vm); + + if ( vm->dllHandle ) { + Sys_UnloadDll( vm->dllHandle ); + Com_Memset( vm, 0, sizeof( *vm ) ); + } +#if 0 // now automatically freed by hunk + if ( vm->codeBase ) { + Z_Free( vm->codeBase ); + } + if ( vm->dataBase ) { + Z_Free( vm->dataBase ); + } + if ( vm->instructionPointers ) { + Z_Free( vm->instructionPointers ); + } +#endif + Com_Memset( vm, 0, sizeof( *vm ) ); + + currentVM = NULL; + lastVM = NULL; +} + +void VM_Clear(void) { + int i; + for (i=0;ientryPoint ) { + return (void *)(currentVM->dataBase + intValue); + } + else { + return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask)); + } +} + +void *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) { + if ( !intValue ) { + return NULL; + } + + // currentVM is missing on reconnect here as well? + if ( currentVM==NULL ) + return NULL; + + // + if ( vm->entryPoint ) { + return (void *)(vm->dataBase + intValue); + } + else { + return (void *)(vm->dataBase + (intValue & vm->dataMask)); + } +} + + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return value +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ + +intptr_t QDECL VM_Call( vm_t *vm, int callnum, ... ) { + vm_t *oldVM; + intptr_t r; + int i; + + if ( !vm ) { + Com_Error( ERR_FATAL, "VM_Call with NULL vm" ); + } + + oldVM = currentVM; + currentVM = vm; + lastVM = vm; + + if ( vm_debugLevel ) { + Com_Printf( "VM_Call( %d )\n", callnum ); + } + + ++vm->callLevel; + // if we have a dll loaded, call it directly + if ( vm->entryPoint ) { + //rcg010207 - see dissertation at top of VM_DllSyscall() in this file. + int args[10]; + va_list ap; + va_start(ap, callnum); + for (i = 0; i < ARRAY_LEN(args); i++) { + args[i] = va_arg(ap, int); + } + va_end(ap); + + r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7], + args[8], args[9]); + } else { +#if id386 || idsparc // i386/sparc calling convention doesn't need conversion +#ifndef NO_VM_COMPILED + if ( vm->compiled ) + r = VM_CallCompiled( vm, (int*)&callnum ); + else +#endif + r = VM_CallInterpreted( vm, (int*)&callnum ); +#else + struct { + int callnum; + int args[10]; + } a; + va_list ap; + + a.callnum = callnum; + va_start(ap, callnum); + for (i = 0; i < ARRAY_LEN(a.args); i++) { + a.args[i] = va_arg(ap, int); + } + va_end(ap); +#ifndef NO_VM_COMPILED + if ( vm->compiled ) + r = VM_CallCompiled( vm, &a.callnum ); + else +#endif + r = VM_CallInterpreted( vm, &a.callnum ); +#endif + } + --vm->callLevel; + + if ( oldVM != NULL ) + currentVM = oldVM; + return r; +} + +//================================================================= + +static int QDECL VM_ProfileSort( const void *a, const void *b ) { + vmSymbol_t *sa, *sb; + + sa = *(vmSymbol_t **)a; + sb = *(vmSymbol_t **)b; + + if ( sa->profileCount < sb->profileCount ) { + return -1; + } + if ( sa->profileCount > sb->profileCount ) { + return 1; + } + return 0; +} + +/* +============== +VM_VmProfile_f + +============== +*/ +void VM_VmProfile_f( void ) { + vm_t *vm; + vmSymbol_t **sorted, *sym; + int i; + double total; + + if ( !lastVM ) { + return; + } + + vm = lastVM; + + if ( !vm->numSymbols ) { + return; + } + + sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) ); + sorted[0] = vm->symbols; + total = sorted[0]->profileCount; + for ( i = 1 ; i < vm->numSymbols ; i++ ) { + sorted[i] = sorted[i-1]->next; + total += sorted[i]->profileCount; + } + + qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort ); + + for ( i = 0 ; i < vm->numSymbols ; i++ ) { + int perc; + + sym = sorted[i]; + + perc = 100 * (float) sym->profileCount / total; + Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName ); + sym->profileCount = 0; + } + + Com_Printf(" %9.0f total\n", total ); + + Z_Free( sorted ); +} + +/* +============== +VM_VmInfo_f + +============== +*/ +void VM_VmInfo_f( void ) { + vm_t *vm; + int i; + + Com_Printf( "Registered virtual machines:\n" ); + for ( i = 0 ; i < MAX_VM ; i++ ) { + vm = &vmTable[i]; + if ( !vm->name[0] ) { + break; + } + Com_Printf( "%s : ", vm->name ); + if ( vm->dllHandle ) { + Com_Printf( "native\n" ); + continue; + } + if ( vm->compiled ) { + Com_Printf( "compiled on load\n" ); + } else { + Com_Printf( "interpreted\n" ); + } + Com_Printf( " code length : %7i\n", vm->codeLength ); + Com_Printf( " table length: %7i\n", vm->instructionCount*4 ); + Com_Printf( " data length : %7i\n", vm->dataMask + 1 ); + } +} + +/* +=============== +VM_LogSyscalls + +Insert calls to this while debugging the vm compiler +=============== +*/ +void VM_LogSyscalls( int *args ) { + static int callnum; + static FILE *f; + + if ( !f ) { + f = fopen("syscalls.log", "w" ); + } + callnum++; + fprintf(f, "%i: %p (%i) = %i %i %i %i\n", callnum, (void*)(args - (int *)currentVM->dataBase), + args[0], args[1], args[2], args[3], args[4] ); +} diff --git a/src/qcommon/vm_interpreted.c b/src/qcommon/vm_interpreted.c new file mode 100644 index 0000000..2b9ffea --- /dev/null +++ b/src/qcommon/vm_interpreted.c @@ -0,0 +1,909 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "vm_local.h" + +//#define DEBUG_VM +#ifdef DEBUG_VM +static char *opnames[256] = { + "OP_UNDEF", + + "OP_IGNORE", + + "OP_BREAK", + + "OP_ENTER", + "OP_LEAVE", + "OP_CALL", + "OP_PUSH", + "OP_POP", + + "OP_CONST", + + "OP_LOCAL", + + "OP_JUMP", + + //------------------- + + "OP_EQ", + "OP_NE", + + "OP_LTI", + "OP_LEI", + "OP_GTI", + "OP_GEI", + + "OP_LTU", + "OP_LEU", + "OP_GTU", + "OP_GEU", + + "OP_EQF", + "OP_NEF", + + "OP_LTF", + "OP_LEF", + "OP_GTF", + "OP_GEF", + + //------------------- + + "OP_LOAD1", + "OP_LOAD2", + "OP_LOAD4", + "OP_STORE1", + "OP_STORE2", + "OP_STORE4", + "OP_ARG", + + "OP_BLOCK_COPY", + + //------------------- + + "OP_SEX8", + "OP_SEX16", + + "OP_NEGI", + "OP_ADD", + "OP_SUB", + "OP_DIVI", + "OP_DIVU", + "OP_MODI", + "OP_MODU", + "OP_MULI", + "OP_MULU", + + "OP_BAND", + "OP_BOR", + "OP_BXOR", + "OP_BCOM", + + "OP_LSH", + "OP_RSHI", + "OP_RSHU", + + "OP_NEGF", + "OP_ADDF", + "OP_SUBF", + "OP_DIVF", + "OP_MULF", + + "OP_CVIF", + "OP_CVFI" +}; +#endif + +#if idppc + +//FIXME: these, um... look the same to me +#if defined(__GNUC__) +static ID_INLINE unsigned int loadWord(void *addr) { + unsigned int word; + + asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr)); + return word; +} +#else +static ID_INLINE unsigned int __lwbrx(register void *addr, + register int offset) { + register unsigned int word; + + asm("lwbrx %0,%2,%1" : "=r" (word) : "r" (addr), "b" (offset)); + return word; +} +#define loadWord(addr) __lwbrx(addr,0) +#endif + +#else + static ID_INLINE int loadWord(void *addr) { + int word; + memcpy(&word, addr, 4); + return LittleLong(word); + } +#endif + +char *VM_Indent( vm_t *vm ) { + static char *string = " "; + if ( vm->callLevel > 20 ) { + return string; + } + return string + 2 * ( 20 - vm->callLevel ); +} + +void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) { + int count; + + count = 0; + do { + Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) ); + programStack = *(int *)&vm->dataBase[programStack+4]; + programCounter = *(int *)&vm->dataBase[programStack]; + } while ( programCounter != -1 && ++count < 32 ); + +} + + +/* +==================== +VM_PrepareInterpreter +==================== +*/ +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) { + int op; + int byte_pc; + int int_pc; + byte *code; + int instruction; + int *codeBase; + + vm->codeBase = Hunk_Alloc( vm->codeLength*4, h_high ); // we're now int aligned +// memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength ); + + // we don't need to translate the instructions, but we still need + // to find each instructions starting point for jumps + int_pc = byte_pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + codeBase = (int *)vm->codeBase; + + // Copy and expand instructions to words while building instruction table + while ( instruction < header->instructionCount ) { + vm->instructionPointers[ instruction ] = int_pc; + instruction++; + + op = (int)code[ byte_pc ]; + codeBase[int_pc] = op; + if ( byte_pc > header->codeLength ) { + Com_Error( ERR_FATAL, "VM_PrepareInterpreter: pc > header->codeLength" ); + } + + byte_pc++; + int_pc++; + + // these are the only opcodes that aren't a single byte + switch ( op ) { + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + case OP_BLOCK_COPY: + codeBase[int_pc] = loadWord(&code[byte_pc]); + byte_pc += 4; + int_pc++; + break; + case OP_ARG: + codeBase[int_pc] = (int)code[byte_pc]; + byte_pc++; + int_pc++; + break; + default: + break; + } + + } + int_pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + + // Now that the code has been expanded to int-sized opcodes, we'll translate instruction index + //into an index into codeBase[], which contains opcodes and operands. + while ( instruction < header->instructionCount ) { + op = codeBase[ int_pc ]; + instruction++; + int_pc++; + + switch ( op ) { + // These ops need to translate addresses in jumps from instruction index to int index + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + // codeBase[pc] is the instruction index. Convert that into an offset into + //the int-aligned codeBase[] by the lookup table. + codeBase[int_pc] = vm->instructionPointers[codeBase[int_pc]]; + int_pc++; + break; + + // These opcodes have an operand that isn't an instruction index + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_BLOCK_COPY: + case OP_ARG: + int_pc++; + break; + + default: + break; + } + + } +} + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return stack +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ + +#define DEBUGSTR va("%s%i", VM_Indent(vm), opStack-stack ) + +int VM_CallInterpreted( vm_t *vm, int *args ) { + int stack[OPSTACK_SIZE]; + int *opStack; + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + int *codeImage; + int v1; + int dataMask; +#ifdef DEBUG_VM + vmSymbol_t *profileSymbol; +#endif + + // interpret the code + vm->currentlyInterpreting = qtrue; + + // we might be called recursively, so this might not be the very top + programStack = stackOnEntry = vm->programStack; + +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, 0 ); + // uncomment this for debugging breakpoints + vm->breakFunction = 0; +#endif + // set up the stack frame + + image = vm->dataBase; + codeImage = (int *)vm->codeBase; + dataMask = vm->dataMask; + + // leave a free spot at start of stack so + // that as long as opStack is valid, opStack-1 will + // not corrupt anything + opStack = stack; + programCounter = 0; + + programStack -= 48; + + *(int *)&image[ programStack + 44] = args[9]; + *(int *)&image[ programStack + 40] = args[8]; + *(int *)&image[ programStack + 36] = args[7]; + *(int *)&image[ programStack + 32] = args[6]; + *(int *)&image[ programStack + 28] = args[5]; + *(int *)&image[ programStack + 24] = args[4]; + *(int *)&image[ programStack + 20] = args[3]; + *(int *)&image[ programStack + 16] = args[2]; + *(int *)&image[ programStack + 12] = args[1]; + *(int *)&image[ programStack + 8 ] = args[0]; + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + VM_Debug(0); + +// vm_debugLevel=2; + // main interpreter loop, will exit when a LEAVE instruction + // grabs the -1 program counter + +#define r2 codeImage[programCounter] + + while ( 1 ) { + int opcode, r0, r1; +// unsigned int r2; + +nextInstruction: + r0 = ((int *)opStack)[0]; + r1 = ((int *)opStack)[-1]; +nextInstruction2: +#ifdef DEBUG_VM + if ( (unsigned)programCounter >= vm->codeLength ) { + Com_Error( ERR_DROP, "VM pc out of range" ); + } + + if ( opStack < stack ) { + Com_Error( ERR_DROP, "VM opStack underflow" ); + } + if ( opStack >= stack+OPSTACK_SIZE ) { + Com_Error( ERR_DROP, "VM opStack overflow" ); + } + + if ( programStack <= vm->stackBottom ) { + Com_Error( ERR_DROP, "VM stack overflow" ); + } + + if ( programStack & 3 ) { + Com_Error( ERR_DROP, "VM program stack misaligned" ); + } + + if ( vm_debugLevel > 1 ) { + Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] ); + } + profileSymbol->profileCount++; +#endif + opcode = codeImage[ programCounter++ ]; + + switch ( opcode ) { +#ifdef DEBUG_VM + default: + Com_Error( ERR_DROP, "Bad VM instruction" ); // this should be scanned on load! +#endif + case OP_BREAK: + vm->breakCount++; + goto nextInstruction2; + case OP_CONST: + opStack++; + r1 = r0; + r0 = *opStack = r2; + + programCounter += 1; + goto nextInstruction2; + case OP_LOCAL: + opStack++; + r1 = r0; + r0 = *opStack = r2+programStack; + + programCounter += 1; + goto nextInstruction2; + + case OP_LOAD4: +#ifdef DEBUG_VM + if ( *opStack & 3 ) { + Com_Error( ERR_DROP, "OP_LOAD4 misaligned" ); + } +#endif + r0 = *opStack = *(int *)&image[ r0&dataMask&~3 ]; + goto nextInstruction2; + case OP_LOAD2: + r0 = *opStack = *(unsigned short *)&image[ r0&dataMask&~1 ]; + goto nextInstruction2; + case OP_LOAD1: + r0 = *opStack = image[ r0&dataMask ]; + goto nextInstruction2; + + case OP_STORE4: + *(int *)&image[ r1&(dataMask & ~3) ] = r0; + opStack -= 2; + goto nextInstruction; + case OP_STORE2: + *(short *)&image[ r1&(dataMask & ~1) ] = r0; + opStack -= 2; + goto nextInstruction; + case OP_STORE1: + image[ r1&dataMask ] = r0; + opStack -= 2; + goto nextInstruction; + + case OP_ARG: + // single byte offset from programStack + *(int *)&image[ (codeImage[programCounter] + programStack)&dataMask&~3 ] = r0; + opStack--; + programCounter += 1; + goto nextInstruction; + + case OP_BLOCK_COPY: + { + int *src, *dest; + int count, srci, desti; + + count = r2; + // MrE: copy range check + srci = r0 & dataMask; + desti = r1 & dataMask; + count = ((srci + count) & dataMask) - srci; + count = ((desti + count) & dataMask) - desti; + + src = (int *)&image[ srci ]; + dest = (int *)&image[ desti ]; + + memcpy(dest, src, count); + programCounter += 1; + opStack -= 2; + } + goto nextInstruction; + + case OP_CALL: + // save current program counter + *(int *)&image[ programStack ] = programCounter; + + // jump to the location on the stack + programCounter = r0; + opStack--; + if ( programCounter < 0 ) { + // system call + int r; +// int temp; +#ifdef DEBUG_VM + int stomped; + + if ( vm_debugLevel ) { + Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter ); + } +#endif + // save the stack to allow recursive VM entry +// temp = vm->callLevel; + vm->programStack = programStack - 4; +#ifdef DEBUG_VM + stomped = *(int *)&image[ programStack + 4 ]; +#endif + *(int *)&image[ programStack + 4 ] = -1 - programCounter; + +//VM_LogSyscalls( (int *)&image[ programStack + 4 ] ); + { + // the vm has ints on the stack, we expect + // pointers so we might have to convert it + if (sizeof(intptr_t) != sizeof(int)) { + intptr_t argarr[16]; + int *imagePtr = (int *)&image[programStack]; + int i; + for (i = 0; i < 16; ++i) { + argarr[i] = *(++imagePtr); + } + r = vm->systemCall( argarr ); + } else { + intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ]; + r = vm->systemCall( argptr ); + } + } + +#ifdef DEBUG_VM + // this is just our stack frame pointer, only needed + // for debugging + *(int *)&image[ programStack + 4 ] = stomped; +#endif + + // save return value + opStack++; + *opStack = r; + programCounter = *(int *)&image[ programStack ]; +// vm->callLevel = temp; +#ifdef DEBUG_VM + if ( vm_debugLevel ) { + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } +#endif + } else if ( (unsigned)programCounter >= vm->instructionCount ) { + Com_Error( ERR_DROP, "VM program counter out of range in OP_CALL" ); + } else { + programCounter = vm->instructionPointers[ programCounter ]; + } + goto nextInstruction; + + // push and pop are only needed for discarded or bad function return values + case OP_PUSH: + opStack++; + goto nextInstruction; + case OP_POP: + opStack--; + goto nextInstruction; + + case OP_ENTER: +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); +#endif + // get size of stack frame + v1 = r2; + + programCounter += 1; + programStack -= v1; +#ifdef DEBUG_VM + // save old stack frame for debugging traces + *(int *)&image[programStack+4] = programStack + v1; + if ( vm_debugLevel ) { + Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) ); + if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) { + // this is to allow setting breakpoints here in the debugger + vm->breakCount++; +// vm_debugLevel = 2; +// VM_StackTrace( vm, programCounter, programStack ); + } +// vm->callLevel++; + } +#endif + goto nextInstruction; + case OP_LEAVE: + // remove our stack frame + v1 = r2; + + programStack += v1; + + // grab the saved program counter + programCounter = *(int *)&image[ programStack ]; +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); + if ( vm_debugLevel ) { +// vm->callLevel--; + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } +#endif + // check for leaving the VM + if ( programCounter == -1 ) { + goto done; + } else if ( (unsigned)programCounter >= vm->codeLength ) { + Com_Error( ERR_DROP, "VM program counter out of range in OP_LEAVE" ); + } + goto nextInstruction; + + /* + =================================================================== + BRANCHES + =================================================================== + */ + + case OP_JUMP: + if ( (unsigned)r0 >= vm->instructionCount ) + Com_Error( ERR_DROP, "VM program counter out of range in OP_JUMP" ); + + programCounter = vm->instructionPointers[ r0 ]; + + opStack--; + goto nextInstruction; + + case OP_EQ: + opStack -= 2; + if ( r1 == r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_NE: + opStack -= 2; + if ( r1 != r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LTI: + opStack -= 2; + if ( r1 < r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LEI: + opStack -= 2; + if ( r1 <= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GTI: + opStack -= 2; + if ( r1 > r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GEI: + opStack -= 2; + if ( r1 >= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LTU: + opStack -= 2; + if ( ((unsigned)r1) < ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_LEU: + opStack -= 2; + if ( ((unsigned)r1) <= ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GTU: + opStack -= 2; + if ( ((unsigned)r1) > ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_GEU: + opStack -= 2; + if ( ((unsigned)r1) >= ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 1; + goto nextInstruction; + } + + case OP_EQF: + if ( ((float *)opStack)[-1] == *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 1; + opStack -= 2; + goto nextInstruction; + } + + case OP_NEF: + if ( ((float *)opStack)[-1] != *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 1; + opStack -= 2; + goto nextInstruction; + } + + case OP_LTF: + if ( ((float *)opStack)[-1] < *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 1; + opStack -= 2; + goto nextInstruction; + } + + case OP_LEF: + if ( ((float *)opStack)[-1] <= *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 1; + opStack -= 2; + goto nextInstruction; + } + + case OP_GTF: + if ( ((float *)opStack)[-1] > *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 1; + opStack -= 2; + goto nextInstruction; + } + + case OP_GEF: + if ( ((float *)opStack)[-1] >= *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 1; + opStack -= 2; + goto nextInstruction; + } + + + //=================================================================== + + case OP_NEGI: + *opStack = -r0; + goto nextInstruction; + case OP_ADD: + opStack[-1] = r1 + r0; + opStack--; + goto nextInstruction; + case OP_SUB: + opStack[-1] = r1 - r0; + opStack--; + goto nextInstruction; + case OP_DIVI: + opStack[-1] = r1 / r0; + opStack--; + goto nextInstruction; + case OP_DIVU: + opStack[-1] = ((unsigned)r1) / ((unsigned)r0); + opStack--; + goto nextInstruction; + case OP_MODI: + opStack[-1] = r1 % r0; + opStack--; + goto nextInstruction; + case OP_MODU: + opStack[-1] = ((unsigned)r1) % (unsigned)r0; + opStack--; + goto nextInstruction; + case OP_MULI: + opStack[-1] = r1 * r0; + opStack--; + goto nextInstruction; + case OP_MULU: + opStack[-1] = ((unsigned)r1) * ((unsigned)r0); + opStack--; + goto nextInstruction; + + case OP_BAND: + opStack[-1] = ((unsigned)r1) & ((unsigned)r0); + opStack--; + goto nextInstruction; + case OP_BOR: + opStack[-1] = ((unsigned)r1) | ((unsigned)r0); + opStack--; + goto nextInstruction; + case OP_BXOR: + opStack[-1] = ((unsigned)r1) ^ ((unsigned)r0); + opStack--; + goto nextInstruction; + case OP_BCOM: + *opStack = ~ ((unsigned)r0); + goto nextInstruction; + + case OP_LSH: + opStack[-1] = r1 << r0; + opStack--; + goto nextInstruction; + case OP_RSHI: + opStack[-1] = r1 >> r0; + opStack--; + goto nextInstruction; + case OP_RSHU: + opStack[-1] = ((unsigned)r1) >> r0; + opStack--; + goto nextInstruction; + + case OP_NEGF: + *(float *)opStack = -*(float *)opStack; + goto nextInstruction; + case OP_ADDF: + *(float *)(opStack-1) = *(float *)(opStack-1) + *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_SUBF: + *(float *)(opStack-1) = *(float *)(opStack-1) - *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_DIVF: + *(float *)(opStack-1) = *(float *)(opStack-1) / *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_MULF: + *(float *)(opStack-1) = *(float *)(opStack-1) * *(float *)opStack; + opStack--; + goto nextInstruction; + + case OP_CVIF: + *(float *)opStack = (float)*opStack; + goto nextInstruction; + case OP_CVFI: + *opStack = (int) *(float *)opStack; + goto nextInstruction; + case OP_SEX8: + *opStack = (signed char)*opStack; + goto nextInstruction; + case OP_SEX16: + *opStack = (short)*opStack; + goto nextInstruction; + } + } + +done: + vm->currentlyInterpreting = qfalse; + + if ( opStack != &stack[1] ) { + Com_Error( ERR_DROP, "Interpreter error: opStack = %ld", (long int) (opStack - stack) ); + } + + vm->programStack = stackOnEntry; + + // return the result + return *opStack; +} diff --git a/src/qcommon/vm_local.h b/src/qcommon/vm_local.h new file mode 100644 index 0000000..80430be --- /dev/null +++ b/src/qcommon/vm_local.h @@ -0,0 +1,189 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "q_shared.h" +#include "qcommon.h" + +#define OPSTACK_SIZE 256 +#define OPSTACK_MASK (OPSTACK_SIZE-1) + +// don't change +// Hardcoded in q3asm an reserved at end of bss +#define PROGRAM_STACK_SIZE 0x10000 +#define PROGRAM_STACK_MASK (PROGRAM_STACK_SIZE-1) + +typedef enum { + OP_UNDEF, + + OP_IGNORE, + + OP_BREAK, + + OP_ENTER, + OP_LEAVE, + OP_CALL, + OP_PUSH, + OP_POP, + + OP_CONST, + OP_LOCAL, + + OP_JUMP, + + //------------------- + + OP_EQ, + OP_NE, + + OP_LTI, + OP_LEI, + OP_GTI, + OP_GEI, + + OP_LTU, + OP_LEU, + OP_GTU, + OP_GEU, + + OP_EQF, + OP_NEF, + + OP_LTF, + OP_LEF, + OP_GTF, + OP_GEF, + + //------------------- + + OP_LOAD1, + OP_LOAD2, + OP_LOAD4, + OP_STORE1, + OP_STORE2, + OP_STORE4, // *(stack[top-1]) = stack[top] + OP_ARG, + + OP_BLOCK_COPY, + + //------------------- + + OP_SEX8, + OP_SEX16, + + OP_NEGI, + OP_ADD, + OP_SUB, + OP_DIVI, + OP_DIVU, + OP_MODI, + OP_MODU, + OP_MULI, + OP_MULU, + + OP_BAND, + OP_BOR, + OP_BXOR, + OP_BCOM, + + OP_LSH, + OP_RSHI, + OP_RSHU, + + OP_NEGF, + OP_ADDF, + OP_SUBF, + OP_DIVF, + OP_MULF, + + OP_CVIF, + OP_CVFI +} opcode_t; + + + +typedef int vmptr_t; + +typedef struct vmSymbol_s { + struct vmSymbol_s *next; + int symValue; + int profileCount; + char symName[1]; // variable sized +} vmSymbol_t; + +#define VM_OFFSET_PROGRAM_STACK 0 +#define VM_OFFSET_SYSTEM_CALL 4 + +struct vm_s { + // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES + // USED BY THE ASM CODE + int programStack; // the vm may be recursively entered + intptr_t (*systemCall)( intptr_t *parms ); + + //------------------------------------ + + char name[MAX_QPATH]; + + // for dynamic linked modules + void *dllHandle; + intptr_t (QDECL *entryPoint)( int callNum, ... ); + void (*destroy)(vm_t* self); + + // for interpreted modules + qboolean currentlyInterpreting; + + qboolean compiled; + byte *codeBase; + int codeLength; + + int *instructionPointers; + int instructionCount; + + byte *dataBase; + int dataMask; + + int stackBottom; // if programStack < stackBottom, error + + int numSymbols; + struct vmSymbol_s *symbols; + + int callLevel; // counts recursive VM_Call + int breakFunction; // increment breakCount on function entry to this + int breakCount; + + byte *jumpTableTargets; + int numJumpTableTargets; +}; + + +extern vm_t *currentVM; +extern int vm_debugLevel; + +void VM_Compile( vm_t *vm, vmHeader_t *header ); +int VM_CallCompiled( vm_t *vm, int *args ); + +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ); +int VM_CallInterpreted( vm_t *vm, int *args ); + +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ); +int VM_SymbolToValue( vm_t *vm, const char *symbol ); +const char *VM_ValueToSymbol( vm_t *vm, int value ); +void VM_LogSyscalls( int *args ); diff --git a/src/qcommon/vm_none.c b/src/qcommon/vm_none.c new file mode 100644 index 0000000..c7e5ba0 --- /dev/null +++ b/src/qcommon/vm_none.c @@ -0,0 +1,10 @@ +#include "vm_local.h" + +int VM_CallCompiled( vm_t *vm, int *args ) { + exit(99); + return 0; +} + +void VM_Compile( vm_t *vm, vmHeader_t *header ) { + exit(99); +} diff --git a/src/qcommon/vm_powerpc.c b/src/qcommon/vm_powerpc.c new file mode 100644 index 0000000..831e567 --- /dev/null +++ b/src/qcommon/vm_powerpc.c @@ -0,0 +1,2170 @@ +/* +=========================================================================== +Copyright (C) 2008 Przemyslaw Iskra + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include /* needed by sys/mman.h on OSX */ +#include +#include +#include +#include + +#ifndef MAP_ANONYMOUS +# define MAP_ANONYMOUS MAP_ANON +#endif + +#include "vm_local.h" +#include "vm_powerpc_asm.h" + +/* + * VM_TIMES enables showing information about time spent inside + * and outside generated code + */ +//#define VM_TIMES +#ifdef VM_TIMES +#include +static clock_t time_outside_vm = 0; +static clock_t time_total_vm = 0; +#endif + +/* exit() won't be called but use it because it is marked with noreturn */ +#define DIE( reason ) \ + do { \ + Com_Error(ERR_DROP, "vm_powerpc compiler error: " reason "\n"); \ + exit(1); \ + } while(0) + +/* + * vm_powerpc uses large quantities of memory during compilation, + * Z_Malloc memory may not be enough for some big qvm files + */ + +//#define VM_SYSTEM_MALLOC +#ifdef VM_SYSTEM_MALLOC +static inline void * +PPC_Malloc( size_t size ) +{ + void *mem = malloc( size ); + if ( ! mem ) + DIE( "Not enough memory" ); + + return mem; +} +# define PPC_Free free +#else +# define PPC_Malloc Z_Malloc +# define PPC_Free Z_Free +#endif + +/* + * optimizations: + * - hole: bubble optimization (OP_CONST+instruction) + * - copy: inline OP_BLOCK_COPY for lengths under 16/32 bytes + * - mask: use rlwinm instruction as dataMask + */ + +#ifdef __OPTIMIZE__ +# define OPTIMIZE_HOLE 1 +# define OPTIMIZE_COPY 1 +# define OPTIMIZE_MASK 1 +#else +# define OPTIMIZE_HOLE 0 +# define OPTIMIZE_COPY 0 +# define OPTIMIZE_MASK 0 +#endif + +/* + * SUPPORTED TARGETS: + * - Linux 32 bits + * ( http://refspecs.freestandards.org/elf/elfspec_ppc.pdf ) + * * LR at r0 + 4 + * * Local variable space not needed + * -> store caller safe regs at 16+ + * + * - Linux 64 bits (not fully conformant) + * ( http://www.ibm.com/developerworks/linux/library/l-powasm4.html ) + * * needs "official procedure descriptors" (only first function has one) + * * LR at r0 + 16 + * * local variable space required, min 64 bytes, starts at 48 + * -> store caller safe regs at 128+ + * + * - OS X 32 bits + * ( http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/32bitPowerPC.html ) + * * LR at r0 + 8 + * * local variable space required, min 32 bytes (?), starts at 24 + * -> store caller safe regs at 64+ + * + * - OS X 64 bits (completely untested) + * ( http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/64bitPowerPC.html ) + * * LR at r0 + 16 + * * local variable space required, min 64 bytes (?), starts at 48 + * -> store caller safe regs at 128+ + */ + +/* Select Length - first value on 32 bits, second on 64 */ +#ifdef __PPC64__ +# define SL( a, b ) (b) +#else +# define SL( a, b ) (a) +#endif + +/* Select ABI - first for ELF, second for OS X */ +#ifdef __ELF__ +# define SA( a, b ) (a) +#else +# define SA( a, b ) (b) +#endif + +#define ELF32 SL( SA( 1, 0 ), 0 ) +#define ELF64 SL( 0, SA( 1, 0 ) ) +#define OSX32 SL( SA( 0, 1 ), 0 ) +#define OSX64 SL( 0, SA( 0, 1 ) ) + +/* native length load/store instructions ( L stands for long ) */ +#define iSTLU SL( iSTWU, iSTDU ) +#define iSTL SL( iSTW, iSTD ) +#define iLL SL( iLWZ, iLD ) +#define iLLX SL( iLWZX, iLDX ) + +/* register length */ +#define GPRLEN SL( 4, 8 ) +#define FPRLEN (8) +/* shift that many bits to obtain value miltiplied by GPRLEN */ +#define GPRLEN_SHIFT SL( 2, 3 ) + +/* Link register position */ +#define STACK_LR SL( SA( 4, 8 ), 16 ) +/* register save position */ +#define STACK_SAVE SL( SA( 16, 64 ), 128 ) +/* temporary space, for float<->int exchange */ +#define STACK_TEMP SL( SA( 8, 24 ), 48 ) +/* red zone temporary space, used instead of STACK_TEMP if stack isn't + * prepared properly */ +#define STACK_RTEMP (-16) + +#if ELF64 +/* + * Official Procedure Descriptor + * we need to prepare one for generated code if we want to call it + * as function + */ +typedef struct { + void *function; + void *toc; + void *env; +} opd_t; +#endif + + +/* + * opcode information table: + * - length of immediate value + * - returned register type + * - required register(s) type + */ +#define opImm0 0x0000 /* no immediate */ +#define opImm1 0x0001 /* 1 byte immadiate value after opcode */ +#define opImm4 0x0002 /* 4 bytes immediate value after opcode */ + +#define opRet0 0x0000 /* returns nothing */ +#define opRetI 0x0004 /* returns integer */ +#define opRetF 0x0008 /* returns float */ +#define opRetIF (opRetI | opRetF) /* returns integer or float */ + +#define opArg0 0x0000 /* requires nothing */ +#define opArgI 0x0010 /* requires integer(s) */ +#define opArgF 0x0020 /* requires float(s) */ +#define opArgIF (opArgI | opArgF) /* requires integer or float */ + +#define opArg2I 0x0040 /* requires second argument, integer */ +#define opArg2F 0x0080 /* requires second argument, float */ +#define opArg2IF (opArg2I | opArg2F) /* requires second argument, integer or float */ + +static const unsigned char vm_opInfo[256] = +{ + [OP_UNDEF] = opImm0, + [OP_IGNORE] = opImm0, + [OP_BREAK] = opImm0, + [OP_ENTER] = opImm4, + /* OP_LEAVE has to accept floats, they will be converted to ints */ + [OP_LEAVE] = opImm4 | opRet0 | opArgIF, + /* only STORE4 and POP use values from OP_CALL, + * no need to convert floats back */ + [OP_CALL] = opImm0 | opRetI | opArgI, + [OP_PUSH] = opImm0 | opRetIF, + [OP_POP] = opImm0 | opRet0 | opArgIF, + [OP_CONST] = opImm4 | opRetIF, + [OP_LOCAL] = opImm4 | opRetI, + [OP_JUMP] = opImm0 | opRet0 | opArgI, + + [OP_EQ] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_NE] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_LTI] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_LEI] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_GTI] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_GEI] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_LTU] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_LEU] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_GTU] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_GEU] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_EQF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_NEF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_LTF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_LEF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_GTF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_GEF] = opImm4 | opRet0 | opArgF | opArg2F, + + [OP_LOAD1] = opImm0 | opRetI | opArgI, + [OP_LOAD2] = opImm0 | opRetI | opArgI, + [OP_LOAD4] = opImm0 | opRetIF| opArgI, + [OP_STORE1] = opImm0 | opRet0 | opArgI | opArg2I, + [OP_STORE2] = opImm0 | opRet0 | opArgI | opArg2I, + [OP_STORE4] = opImm0 | opRet0 | opArgIF| opArg2I, + [OP_ARG] = opImm1 | opRet0 | opArgIF, + [OP_BLOCK_COPY] = opImm4 | opRet0 | opArgI | opArg2I, + + [OP_SEX8] = opImm0 | opRetI | opArgI, + [OP_SEX16] = opImm0 | opRetI | opArgI, + [OP_NEGI] = opImm0 | opRetI | opArgI, + [OP_ADD] = opImm0 | opRetI | opArgI | opArg2I, + [OP_SUB] = opImm0 | opRetI | opArgI | opArg2I, + [OP_DIVI] = opImm0 | opRetI | opArgI | opArg2I, + [OP_DIVU] = opImm0 | opRetI | opArgI | opArg2I, + [OP_MODI] = opImm0 | opRetI | opArgI | opArg2I, + [OP_MODU] = opImm0 | opRetI | opArgI | opArg2I, + [OP_MULI] = opImm0 | opRetI | opArgI | opArg2I, + [OP_MULU] = opImm0 | opRetI | opArgI | opArg2I, + [OP_BAND] = opImm0 | opRetI | opArgI | opArg2I, + [OP_BOR] = opImm0 | opRetI | opArgI | opArg2I, + [OP_BXOR] = opImm0 | opRetI | opArgI | opArg2I, + [OP_BCOM] = opImm0 | opRetI | opArgI, + [OP_LSH] = opImm0 | opRetI | opArgI | opArg2I, + [OP_RSHI] = opImm0 | opRetI | opArgI | opArg2I, + [OP_RSHU] = opImm0 | opRetI | opArgI | opArg2I, + [OP_NEGF] = opImm0 | opRetF | opArgF, + [OP_ADDF] = opImm0 | opRetF | opArgF | opArg2F, + [OP_SUBF] = opImm0 | opRetF | opArgF | opArg2F, + [OP_DIVF] = opImm0 | opRetF | opArgF | opArg2F, + [OP_MULF] = opImm0 | opRetF | opArgF | opArg2F, + [OP_CVIF] = opImm0 | opRetF | opArgI, + [OP_CVFI] = opImm0 | opRetI | opArgF, +}; + +/* + * source instruction data + */ +typedef struct source_instruction_s source_instruction_t; +struct source_instruction_s { + // opcode + unsigned long int op; + + // number of instruction + unsigned long int i_count; + + // immediate value (if any) + union { + unsigned int i; + signed int si; + signed short ss[2]; + unsigned short us[2]; + unsigned char b; + } arg; + + // required and returned registers + unsigned char regA1; + unsigned char regA2; + unsigned char regR; + unsigned char regPos; + + // next instruction + source_instruction_t *next; +}; + + + +/* + * read-only data needed by the generated code + */ +typedef struct VM_Data { + // length of this struct + data + size_t dataLength; + // compiled code size (in bytes) + // it only is code size, without the data + size_t codeLength; + + // function pointers, no use to waste registers for them + long int (* AsmCall)( int, int ); + void (* BlockCopy )( unsigned int, unsigned int, unsigned int ); + + // instruction pointers, rarely used so don't waste register + ppc_instruction_t *iPointers; + + // data mask for load and store, not used if optimized + unsigned int dataMask; + + // fixed number used to convert from integer to float + unsigned int floatBase; // 0x59800004 + +#if ELF64 + // official procedure descriptor + opd_t opd; +#endif + + // additional constants, for floating point OP_CONST + // this data has dynamic length, thus '0' here + unsigned int data[0]; +} vm_data_t; + +#ifdef offsetof +# define VM_Data_Offset( field ) offsetof( vm_data_t, field ) +#else +# define OFFSET( structName, field ) \ + ( (void *)&(((structName *)NULL)->field) - NULL ) +# define VM_Data_Offset( field ) OFFSET( vm_data_t, field ) +#endif + + +/* + * functions used by generated code + */ +static long int +VM_AsmCall( int callSyscallInvNum, int callProgramStack ) +{ + vm_t *savedVM = currentVM; + long int i, ret; +#ifdef VM_TIMES + struct tms start_time, stop_time; + clock_t saved_time = time_outside_vm; + times( &start_time ); +#endif + + // save the stack to allow recursive VM entry + currentVM->programStack = callProgramStack - 4; + + // we need to convert ints to longs on 64bit powerpcs + if ( sizeof( intptr_t ) == sizeof( int ) ) { + intptr_t *argPosition = (intptr_t *)((byte *)currentVM->dataBase + callProgramStack + 4); + + // generated code does not invert syscall number + argPosition[ 0 ] = -1 - callSyscallInvNum; + + ret = currentVM->systemCall( argPosition ); + } else { + intptr_t args[11]; + + // generated code does not invert syscall number + args[0] = -1 - callSyscallInvNum; + + int *argPosition = (int *)((byte *)currentVM->dataBase + callProgramStack + 4); + for( i = 1; i < 11; i++ ) + args[ i ] = argPosition[ i ]; + + ret = currentVM->systemCall( args ); + } + + currentVM = savedVM; + +#ifdef VM_TIMES + times( &stop_time ); + time_outside_vm = saved_time + ( stop_time.tms_utime - start_time.tms_utime ); +#endif + + return ret; +} + +static void +VM_BlockCopy( unsigned int dest, unsigned int src, unsigned int count ) +{ + unsigned dataMask = currentVM->dataMask; + + if ( (dest & dataMask) != dest + || (src & dataMask) != src + || ((dest+count) & dataMask) != dest + count + || ((src+count) & dataMask) != src + count) + { + DIE( "OP_BLOCK_COPY out of range!"); + } + + memcpy( currentVM->dataBase+dest, currentVM->dataBase+src, count ); +} + + +/* + * code-block descriptors + */ +typedef struct dest_instruction dest_instruction_t; +typedef struct symbolic_jump symbolic_jump_t; + +struct symbolic_jump { + // number of source instruction it has to jump to + unsigned long int jump_to; + + // jump condition true/false, (4*cr7+(eq|gt..)) + long int bo, bi; + + // extensions / modifiers (branch-link) + unsigned long ext; + + // dest_instruction refering to this jump + dest_instruction_t *parent; + + // next jump + symbolic_jump_t *nextJump; +}; + +struct dest_instruction { + // position in the output chain + unsigned long int count; + + // source instruction number + unsigned long int i_count; + + // exact (for instructins), or maximum (for jump) length + unsigned short length; + + dest_instruction_t *next; + + // if the instruction is a jump than jump will be non NULL + symbolic_jump_t *jump; + + // if jump is NULL than all the instructions will be here + ppc_instruction_t code[0]; +}; + +// first and last instruction, +// di_first is a dummy instruction +static dest_instruction_t *di_first = NULL, *di_last = NULL; +// number of instructions +static unsigned long int di_count = 0; +// pointers needed to compute local jumps, those aren't pointers to +// actual instructions, just used to check how long the jump is going +// to be and whether it is positive or negative +static dest_instruction_t **di_pointers = NULL; + +// output instructions which does not come from source code +// use false i_count value +#define FALSE_ICOUNT 0xffffffff + + +/* + * append specified instructions at the end of instruction chain + */ +static void +PPC_Append( + dest_instruction_t *di_now, + unsigned long int i_count + ) +{ + di_now->count = di_count++; + di_now->i_count = i_count; + di_now->next = NULL; + + di_last->next = di_now; + di_last = di_now; + + if ( i_count != FALSE_ICOUNT ) { + if ( ! di_pointers[ i_count ] ) + di_pointers[ i_count ] = di_now; + } +} + +/* + * make space for instructions and append + */ +static void +PPC_AppendInstructions( + unsigned long int i_count, + size_t num_instructions, + const ppc_instruction_t *is + ) +{ + if ( num_instructions < 0 ) + num_instructions = 0; + size_t iBytes = sizeof( ppc_instruction_t ) * num_instructions; + dest_instruction_t *di_now = PPC_Malloc( sizeof( dest_instruction_t ) + iBytes ); + + di_now->length = num_instructions; + di_now->jump = NULL; + + if ( iBytes > 0 ) + memcpy( &(di_now->code[0]), is, iBytes ); + + PPC_Append( di_now, i_count ); +} + +/* + * create symbolic jump and append + */ +static symbolic_jump_t *sj_first = NULL, *sj_last = NULL; +static void +PPC_PrepareJump( + unsigned long int i_count, + unsigned long int dest, + long int bo, + long int bi, + unsigned long int ext + ) +{ + dest_instruction_t *di_now = PPC_Malloc( sizeof( dest_instruction_t ) ); + symbolic_jump_t *sj = PPC_Malloc( sizeof( symbolic_jump_t ) ); + + sj->jump_to = dest; + sj->bo = bo; + sj->bi = bi; + sj->ext = ext; + sj->parent = di_now; + sj->nextJump = NULL; + + sj_last->nextJump = sj; + sj_last = sj; + + di_now->length = (bo == branchAlways ? 1 : 2); + di_now->jump = sj; + + PPC_Append( di_now, i_count ); +} + +/* + * simplyfy instruction emission + */ +#define emitStart( i_cnt ) \ + unsigned long int i_count = i_cnt; \ + size_t num_instructions = 0; \ + long int force_emit = 0; \ + ppc_instruction_t instructions[50]; + +#define pushIn( inst ) \ + (instructions[ num_instructions++ ] = inst) +#define in( inst, args... ) pushIn( IN( inst, args ) ) + +#define emitEnd() \ + do{ \ + if ( num_instructions || force_emit ) \ + PPC_AppendInstructions( i_count, num_instructions, instructions );\ + num_instructions = 0; \ + } while(0) + +#define emitJump( dest, bo, bi, ext ) \ + do { \ + emitEnd(); \ + PPC_PrepareJump( i_count, dest, bo, bi, ext ); \ + } while(0) + + +/* + * definitions for creating .data section, + * used in cases where constant float is needed + */ +#define LOCAL_DATA_CHUNK 50 +typedef struct local_data_s local_data_t; +struct local_data_s { + // number of data in this structure + long int count; + + // data placeholder + unsigned int data[ LOCAL_DATA_CHUNK ]; + + // next chunk, if this one wasn't enough + local_data_t *next; +}; + +// first data chunk +static local_data_t *data_first = NULL; +// total number of data +static long int data_acc = 0; + +/* + * append the data and return its offset + */ +static size_t +PPC_PushData( unsigned int datum ) +{ + local_data_t *d_now = data_first; + long int accumulated = 0; + + // check whether we have this one already + do { + long int i; + for ( i = 0; i < d_now->count; i++ ) { + if ( d_now->data[ i ] == datum ) { + accumulated += i; + return VM_Data_Offset( data[ accumulated ] ); + } + } + if ( !d_now->next ) + break; + + accumulated += d_now->count; + d_now = d_now->next; + } while (1); + + // not found, need to append + accumulated += d_now->count; + + // last chunk is full, create new one + if ( d_now->count >= LOCAL_DATA_CHUNK ) { + d_now->next = PPC_Malloc( sizeof( local_data_t ) ); + d_now = d_now->next; + d_now->count = 0; + d_now->next = NULL; + } + + d_now->data[ d_now->count ] = datum; + d_now->count += 1; + + data_acc = accumulated + 1; + + return VM_Data_Offset( data[ accumulated ] ); +} + +/* + * find leading zeros in dataMask to implement it with + * "rotate and mask" instruction + */ +static long int fastMaskHi = 0, fastMaskLo = 31; +static void +PPC_MakeFastMask( int mask ) +{ +#if defined( __GNUC__ ) && ( __GNUC__ >= 4 || ( __GNUC__ == 3 && __GNUC_MINOR__ >= 4 ) ) + /* count leading zeros */ + fastMaskHi = __builtin_clz( mask ); + + /* count trailing zeros */ + fastMaskLo = 31 - __builtin_ctz( mask ); +#else + fastMaskHi = 0; + while ( ( mask & ( 0x80000000 >> fastMaskHi ) ) == 0 ) + fastMaskHi++; + + fastMaskLo = 31; + while ( ( mask & ( 0x80000000 >> fastMaskLo ) ) == 0 ) + fastMaskLo--; +#endif +} + + +/* + * register definitions + */ + +/* registers which are global for generated code */ + +// pointer to VM_Data (constant) +#define rVMDATA r14 +// vm->dataBase (constant) +#define rDATABASE r15 +// programStack (variable) +#define rPSTACK r16 + +/* + * function local registers, + * + * normally only volatile registers are used, but if there aren't enough + * or function has to preserve some value while calling annother one + * then caller safe registers are used as well + */ +static const long int gpr_list[] = { + /* caller safe registers, normally only one is used */ + r24, r23, r22, r21, + r20, r19, r18, r17, + /* volatile registers (preferred), + * normally no more than 5 is used */ + r3, r4, r5, r6, + r7, r8, r9, r10, +}; +static const long int gpr_vstart = 8; /* position of first volatile register */ +static const long int gpr_total = ARRAY_LEN( gpr_list ); + +static const long int fpr_list[] = { + /* static registers, normally none is used */ + f20, f21, f19, f18, + f17, f16, f15, f14, + /* volatile registers (preferred), + * normally no more than 7 is used */ + f0, f1, f2, f3, + f4, f5, f6, f7, + f8, f9, f10, f11, + f12, f13, +}; +static const long int fpr_vstart = 8; +static const long int fpr_total = ARRAY_LEN( fpr_list ); + +/* + * prepare some dummy structures and emit init code + */ +static void +PPC_CompileInit( void ) +{ + di_first = di_last = PPC_Malloc( sizeof( dest_instruction_t ) ); + di_first->count = 0; + di_first->next = NULL; + di_first->jump = NULL; + + sj_first = sj_last = PPC_Malloc( sizeof( symbolic_jump_t ) ); + sj_first->nextJump = NULL; + + data_first = PPC_Malloc( sizeof( local_data_t ) ); + data_first->count = 0; + data_first->next = NULL; + + /* + * init function: + * saves old values of global registers and sets our values + * function prototype is: + * int begin( void *data, int programStack, void *vm->dataBase ) + */ + + /* first instruction must not be placed on instruction list */ + emitStart( FALSE_ICOUNT ); + + long int stack = STACK_SAVE + 4 * GPRLEN; + + in( iMFLR, r0 ); + in( iSTLU, r1, -stack, r1 ); + in( iSTL, rVMDATA, STACK_SAVE + 0 * GPRLEN, r1 ); + in( iSTL, rPSTACK, STACK_SAVE + 1 * GPRLEN, r1 ); + in( iSTL, rDATABASE, STACK_SAVE + 2 * GPRLEN, r1 ); + in( iSTL, r0, stack + STACK_LR, r1 ); + in( iMR, rVMDATA, r3 ); + in( iMR, rPSTACK, r4 ); + in( iMR, rDATABASE, r5 ); + in( iBL, +4*8 ); // LINK JUMP: first generated instruction | XXX jump ! + in( iLL, rVMDATA, STACK_SAVE + 0 * GPRLEN, r1 ); + in( iLL, rPSTACK, STACK_SAVE + 1 * GPRLEN, r1 ); + in( iLL, rDATABASE, STACK_SAVE + 2 * GPRLEN, r1 ); + in( iLL, r0, stack + STACK_LR, r1 ); + in( iMTLR, r0 ); + in( iADDI, r1, r1, stack ); + in( iBLR ); + + emitEnd(); +} + +// rFIRST is the copy of the top value on the opstack +#define rFIRST (gpr_list[ gpr_pos - 1]) +// second value on the opstack +#define rSECOND (gpr_list[ gpr_pos - 2 ]) +// temporary registers, not on the opstack +#define rTEMP(x) (gpr_list[ gpr_pos + x ]) +#define rTMP rTEMP(0) + +#define fFIRST (fpr_list[ fpr_pos - 1 ]) +#define fSECOND (fpr_list[ fpr_pos - 2 ]) +#define fTEMP(x) (fpr_list[ fpr_pos + x ]) +#define fTMP fTEMP(0) + +// register types +#define rTYPE_STATIC 0x01 +#define rTYPE_FLOAT 0x02 + +// what type should this opcode return +#define RET_INT ( !(i_now->regR & rTYPE_FLOAT) ) +#define RET_FLOAT ( i_now->regR & rTYPE_FLOAT ) +// what type should it accept +#define ARG_INT ( ! i_now->regA1 ) +#define ARG_FLOAT ( i_now->regA1 ) +#define ARG2_INT ( ! i_now->regA2 ) +#define ARG2_FLOAT ( i_now->regA2 ) + +/* + * emit OP_CONST, called if nothing has used the const value directly + */ +static void +PPC_EmitConst( source_instruction_t * const i_const ) +{ + emitStart( i_const->i_count ); + + if ( !(i_const->regR & rTYPE_FLOAT) ) { + // gpr_pos needed for "rFIRST" to work + long int gpr_pos = i_const->regPos; + + if ( i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) { + in( iLI, rFIRST, i_const->arg.si ); + } else if ( i_const->arg.i < 0x10000 ) { + in( iLI, rFIRST, 0 ); + in( iORI, rFIRST, rFIRST, i_const->arg.i ); + } else { + in( iLIS, rFIRST, i_const->arg.ss[ 0 ] ); + if ( i_const->arg.us[ 1 ] != 0 ) + in( iORI, rFIRST, rFIRST, i_const->arg.us[ 1 ] ); + } + + } else { + // fpr_pos needed for "fFIRST" to work + long int fpr_pos = i_const->regPos; + + // there's no good way to generate the data, + // just read it from data section + in( iLFS, fFIRST, PPC_PushData( i_const->arg.i ), rVMDATA ); + } + + emitEnd(); +} +#define MAYBE_EMIT_CONST() if ( i_const ) PPC_EmitConst( i_const ) + +/* + * emit empty instruction, just sets the needed pointers + */ +static inline void +PPC_EmitNull( source_instruction_t * const i_null ) +{ + PPC_AppendInstructions( i_null->i_count, 0, NULL ); +} +#define EMIT_FALSE_CONST() PPC_EmitNull( i_const ) + + +/* + * analize function for register usage and whether it needs stack (r1) prepared + */ +static void +VM_AnalyzeFunction( + source_instruction_t * const i_first, + long int *prepareStack, + long int *gpr_start_pos, + long int *fpr_start_pos + ) +{ + source_instruction_t *i_now = i_first; + + source_instruction_t *value_provider[20] = { NULL }; + unsigned long int opstack_depth = 0; + + /* + * first step: + * remember what codes returned some value and mark the value type + * when we get to know what it should be + */ + while ( (i_now = i_now->next) ) { + unsigned long int op = i_now->op; + unsigned long int opi = vm_opInfo[ op ]; + + if ( opi & opArgIF ) { + assert( opstack_depth > 0 ); + + opstack_depth--; + source_instruction_t *vp = value_provider[ opstack_depth ]; + unsigned long int vpopi = vm_opInfo[ vp->op ]; + + if ( (opi & opArgI) && (vpopi & opRetI) ) { + // instruction accepts integer, provider returns integer + //vp->regR |= rTYPE_INT; + //i_now->regA1 = rTYPE_INT; + } else if ( (opi & opArgF) && (vpopi & opRetF) ) { + // instruction accepts float, provider returns float + vp->regR |= rTYPE_FLOAT; // use OR here - could be marked as static + i_now->regA1 = rTYPE_FLOAT; + } else { + // instruction arg type does not agree with + // provider return type + DIE( "unrecognized instruction combination" ); + } + + } + if ( opi & opArg2IF ) { + assert( opstack_depth > 0 ); + + opstack_depth--; + source_instruction_t *vp = value_provider[ opstack_depth ]; + unsigned long int vpopi = vm_opInfo[ vp->op ]; + + if ( (opi & opArg2I) && (vpopi & opRetI) ) { + // instruction accepts integer, provider returns integer + //vp->regR |= rTYPE_INT; + //i_now->regA2 = rTYPE_INT; + } else if ( (opi & opArg2F) && (vpopi & opRetF) ) { + // instruction accepts float, provider returns float + vp->regR |= rTYPE_FLOAT; // use OR here - could be marked as static + i_now->regA2 = rTYPE_FLOAT; + } else { + // instruction arg type does not agree with + // provider return type + DIE( "unrecognized instruction combination" ); + } + } + + + if ( + ( op == OP_CALL ) + || + ( op == OP_BLOCK_COPY && ( i_now->arg.i > SL( 16, 32 ) || !OPTIMIZE_COPY ) ) + ) { + long int i; + *prepareStack = 1; + // force caller safe registers so we won't have to save them + for ( i = 0; i < opstack_depth; i++ ) { + source_instruction_t *vp = value_provider[ i ]; + vp->regR |= rTYPE_STATIC; + } + } + + + if ( opi & opRetIF ) { + value_provider[ opstack_depth ] = i_now; + opstack_depth++; + } + } + + /* + * second step: + * now that we know register types; compute exactly how many registers + * of each type we need + */ + + i_now = i_first; + long int needed_reg[4] = {0,0,0,0}, max_reg[4] = {0,0,0,0}; + opstack_depth = 0; + while ( (i_now = i_now->next) ) { + unsigned long int op = i_now->op; + unsigned long int opi = vm_opInfo[ op ]; + + if ( opi & opArgIF ) { + assert( opstack_depth > 0 ); + opstack_depth--; + source_instruction_t *vp = value_provider[ opstack_depth ]; + + needed_reg[ ( vp->regR & 2 ) ] -= 1; + if ( vp->regR & 1 ) // static + needed_reg[ ( vp->regR & 3 ) ] -= 1; + } + if ( opi & opArg2IF ) { + assert( opstack_depth > 0 ); + opstack_depth--; + source_instruction_t *vp = value_provider[ opstack_depth ]; + + needed_reg[ ( vp->regR & 2 ) ] -= 1; + if ( vp->regR & 1 ) // static + needed_reg[ ( vp->regR & 3 ) ] -= 1; + } + + if ( opi & opRetIF ) { + long int i; + value_provider[ opstack_depth ] = i_now; + opstack_depth++; + + i = i_now->regR & 2; + needed_reg[ i ] += 1; + if ( max_reg[ i ] < needed_reg[ i ] ) + max_reg[ i ] = needed_reg[ i ]; + + i = i_now->regR & 3; + if ( i & 1 ) { + needed_reg[ i ] += 1; + if ( max_reg[ i ] < needed_reg[ i ] ) + max_reg[ i ] = needed_reg[ i ]; + } + } + } + + long int gpr_start = gpr_vstart; + const long int gpr_volatile = gpr_total - gpr_vstart; + if ( max_reg[ 1 ] > 0 || max_reg[ 0 ] > gpr_volatile ) { + // max_reg[ 0 ] - all gprs needed + // max_reg[ 1 ] - static gprs needed + long int max = max_reg[ 0 ] - gpr_volatile; + if ( max_reg[ 1 ] > max ) + max = max_reg[ 1 ]; + if ( max > gpr_vstart ) { + /* error */ + DIE( "Need more GPRs" ); + } + + gpr_start -= max; + + // need stack to save caller safe registers + *prepareStack = 1; + } + *gpr_start_pos = gpr_start; + + long int fpr_start = fpr_vstart; + const long int fpr_volatile = fpr_total - fpr_vstart; + if ( max_reg[ 3 ] > 0 || max_reg[ 2 ] > fpr_volatile ) { + // max_reg[ 2 ] - all fprs needed + // max_reg[ 3 ] - static fprs needed + long int max = max_reg[ 2 ] - fpr_volatile; + if ( max_reg[ 3 ] > max ) + max = max_reg[ 3 ]; + if ( max > fpr_vstart ) { + /* error */ + DIE( "Need more FPRs" ); + } + + fpr_start -= max; + + // need stack to save caller safe registers + *prepareStack = 1; + } + *fpr_start_pos = fpr_start; +} + +/* + * translate opcodes to ppc instructions, + * it works on functions, not on whole code at once + */ +static void +VM_CompileFunction( source_instruction_t * const i_first ) +{ + long int prepareStack = 0; + long int gpr_start_pos, fpr_start_pos; + + VM_AnalyzeFunction( i_first, &prepareStack, &gpr_start_pos, &fpr_start_pos ); + + long int gpr_pos = gpr_start_pos, fpr_pos = fpr_start_pos; + + // OP_CONST combines well with many opcodes so we treat it in a special way + source_instruction_t *i_const = NULL; + source_instruction_t *i_now = i_first; + + // how big the stack has to be + long int save_space = STACK_SAVE; + { + if ( gpr_start_pos < gpr_vstart ) + save_space += (gpr_vstart - gpr_start_pos) * GPRLEN; + save_space = ( save_space + 15 ) & ~0x0f; + + if ( fpr_start_pos < fpr_vstart ) + save_space += (fpr_vstart - fpr_start_pos) * FPRLEN; + save_space = ( save_space + 15 ) & ~0x0f; + } + + long int stack_temp = prepareStack ? STACK_TEMP : STACK_RTEMP; + + while ( (i_now = i_now->next) ) { + emitStart( i_now->i_count ); + + switch ( i_now->op ) + { + default: + case OP_UNDEF: + case OP_IGNORE: + MAYBE_EMIT_CONST(); + in( iNOP ); + break; + + case OP_BREAK: + MAYBE_EMIT_CONST(); + // force SEGV + in( iLWZ, r0, 0, r0 ); + break; + + case OP_ENTER: + if ( i_const ) + DIE( "Weird opcode order" ); + + // don't prepare stack if not needed + if ( prepareStack ) { + long int i, save_pos = STACK_SAVE; + + in( iMFLR, r0 ); + in( iSTLU, r1, -save_space, r1 ); + in( iSTL, r0, save_space + STACK_LR, r1 ); + + /* save registers */ + for ( i = gpr_start_pos; i < gpr_vstart; i++ ) { + in( iSTL, gpr_list[ i ], save_pos, r1 ); + save_pos += GPRLEN; + } + save_pos = ( save_pos + 15 ) & ~0x0f; + + for ( i = fpr_start_pos; i < fpr_vstart; i++ ) { + in( iSTFD, fpr_list[ i ], save_pos, r1 ); + save_pos += FPRLEN; + } + prepareStack = 2; + } + + in( iADDI, rPSTACK, rPSTACK, - i_now->arg.si ); + break; + + case OP_LEAVE: + if ( i_const ) { + EMIT_FALSE_CONST(); + + if ( i_const->regR & rTYPE_FLOAT) + DIE( "constant float in OP_LEAVE" ); + + if ( i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) { + in( iLI, r3, i_const->arg.si ); + } else if ( i_const->arg.i < 0x10000 ) { + in( iLI, r3, 0 ); + in( iORI, r3, r3, i_const->arg.i ); + } else { + in( iLIS, r3, i_const->arg.ss[ 0 ] ); + if ( i_const->arg.us[ 1 ] != 0 ) + in( iORI, r3, r3, i_const->arg.us[ 1 ] ); + } + gpr_pos--; + } else { + MAYBE_EMIT_CONST(); + + /* place return value in r3 */ + if ( ARG_INT ) { + if ( rFIRST != r3 ) + in( iMR, r3, rFIRST ); + gpr_pos--; + } else { + in( iSTFS, fFIRST, stack_temp, r1 ); + in( iLWZ, r3, stack_temp, r1 ); + fpr_pos--; + } + } + + // don't undo stack if not prepared + if ( prepareStack >= 2 ) { + long int i, save_pos = STACK_SAVE; + + in( iLL, r0, save_space + STACK_LR, r1 ); + + + /* restore registers */ + for ( i = gpr_start_pos; i < gpr_vstart; i++ ) { + in( iLL, gpr_list[ i ], save_pos, r1 ); + save_pos += GPRLEN; + } + save_pos = ( save_pos + 15 ) & ~0x0f; + for ( i = fpr_start_pos; i < fpr_vstart; i++ ) { + in( iLFD, fpr_list[ i ], save_pos, r1 ); + save_pos += FPRLEN; + } + + in( iMTLR, r0 ); + in( iADDI, r1, r1, save_space ); + } + in( iADDI, rPSTACK, rPSTACK, i_now->arg.si); + in( iBLR ); + assert( gpr_pos == gpr_start_pos ); + assert( fpr_pos == fpr_start_pos ); + break; + + case OP_CALL: + if ( i_const ) { + EMIT_FALSE_CONST(); + + if ( i_const->arg.si >= 0 ) { + emitJump( + i_const->arg.i, + branchAlways, 0, branchExtLink + ); + } else { + /* syscall */ + in( iLL, r0, VM_Data_Offset( AsmCall ), rVMDATA ); + + in( iLI, r3, i_const->arg.si ); // negative value + in( iMR, r4, rPSTACK ); // push PSTACK on argument list + + in( iMTCTR, r0 ); + in( iBCTRL ); + } + if ( rFIRST != r3 ) + in( iMR, rFIRST, r3 ); + } else { + MAYBE_EMIT_CONST(); + + in( iCMPWI, cr7, rFIRST, 0 ); + in( iBLTm, cr7, +4*5 /* syscall */ ); // XXX jump ! + /* instruction call */ + + // get instruction address + in( iLL, r0, VM_Data_Offset( iPointers ), rVMDATA ); + in( iRLWINM, rFIRST, rFIRST, GPRLEN_SHIFT, 0, 31-GPRLEN_SHIFT ); // mul * GPRLEN + in( iLLX, r0, rFIRST, r0 ); // load pointer + + in( iB, +4*(3 + (rFIRST != r3 ? 1 : 0) ) ); // XXX jump ! + + /* syscall */ + in( iLL, r0, VM_Data_Offset( AsmCall ), rVMDATA ); // get asmCall pointer + /* rFIRST can be r3 or some static register */ + if ( rFIRST != r3 ) + in( iMR, r3, rFIRST ); // push OPSTACK top value on argument list + in( iMR, r4, rPSTACK ); // push PSTACK on argument list + + /* common code */ + in( iMTCTR, r0 ); + in( iBCTRL ); + + if ( rFIRST != r3 ) + in( iMR, rFIRST, r3 ); // push return value on the top of the opstack + } + break; + + case OP_PUSH: + MAYBE_EMIT_CONST(); + if ( RET_INT ) + gpr_pos++; + else + fpr_pos++; + /* no instructions here */ + force_emit = 1; + break; + + case OP_POP: + MAYBE_EMIT_CONST(); + if ( ARG_INT ) + gpr_pos--; + else + fpr_pos--; + /* no instructions here */ + force_emit = 1; + break; + + case OP_CONST: + MAYBE_EMIT_CONST(); + /* nothing here */ + break; + + case OP_LOCAL: + MAYBE_EMIT_CONST(); + { + signed long int hi, lo; + hi = i_now->arg.ss[ 0 ]; + lo = i_now->arg.ss[ 1 ]; + if ( lo < 0 ) + hi += 1; + + gpr_pos++; + if ( hi == 0 ) { + in( iADDI, rFIRST, rPSTACK, lo ); + } else { + in( iADDIS, rFIRST, rPSTACK, hi ); + if ( lo != 0 ) + in( iADDI, rFIRST, rFIRST, lo ); + } + } + break; + + case OP_JUMP: + if ( i_const ) { + EMIT_FALSE_CONST(); + + emitJump( + i_const->arg.i, + branchAlways, 0, 0 + ); + } else { + MAYBE_EMIT_CONST(); + + in( iLL, r0, VM_Data_Offset( iPointers ), rVMDATA ); + in( iRLWINM, rFIRST, rFIRST, GPRLEN_SHIFT, 0, 31-GPRLEN_SHIFT ); // mul * GPRLEN + in( iLLX, r0, rFIRST, r0 ); // load pointer + in( iMTCTR, r0 ); + in( iBCTR ); + } + gpr_pos--; + break; + + case OP_EQ: + case OP_NE: + if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x10000 ) { + EMIT_FALSE_CONST(); + if ( i_const->arg.si >= 0x8000 ) + in( iCMPLWI, cr7, rSECOND, i_const->arg.i ); + else + in( iCMPWI, cr7, rSECOND, i_const->arg.si ); + } else { + MAYBE_EMIT_CONST(); + in( iCMPW, cr7, rSECOND, rFIRST ); + } + emitJump( + i_now->arg.i, + (i_now->op == OP_EQ ? branchTrue : branchFalse), + 4*cr7+eq, 0 + ); + gpr_pos -= 2; + break; + + case OP_LTI: + case OP_GEI: + if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) { + EMIT_FALSE_CONST(); + in( iCMPWI, cr7, rSECOND, i_const->arg.si ); + } else { + MAYBE_EMIT_CONST(); + in( iCMPW, cr7, rSECOND, rFIRST ); + } + emitJump( + i_now->arg.i, + ( i_now->op == OP_LTI ? branchTrue : branchFalse ), + 4*cr7+lt, 0 + ); + gpr_pos -= 2; + break; + + case OP_GTI: + case OP_LEI: + if ( i_const && i_const->arg.si >= -0x8000 && i_const->arg.si < 0x8000 ) { + EMIT_FALSE_CONST(); + in( iCMPWI, cr7, rSECOND, i_const->arg.si ); + } else { + MAYBE_EMIT_CONST(); + in( iCMPW, cr7, rSECOND, rFIRST ); + } + emitJump( + i_now->arg.i, + ( i_now->op == OP_GTI ? branchTrue : branchFalse ), + 4*cr7+gt, 0 + ); + gpr_pos -= 2; + break; + + case OP_LTU: + case OP_GEU: + if ( i_const && i_const->arg.i < 0x10000 ) { + EMIT_FALSE_CONST(); + in( iCMPLWI, cr7, rSECOND, i_const->arg.i ); + } else { + MAYBE_EMIT_CONST(); + in( iCMPLW, cr7, rSECOND, rFIRST ); + } + emitJump( + i_now->arg.i, + ( i_now->op == OP_LTU ? branchTrue : branchFalse ), + 4*cr7+lt, 0 + ); + gpr_pos -= 2; + break; + + case OP_GTU: + case OP_LEU: + if ( i_const && i_const->arg.i < 0x10000 ) { + EMIT_FALSE_CONST(); + in( iCMPLWI, cr7, rSECOND, i_const->arg.i ); + } else { + MAYBE_EMIT_CONST(); + in( iCMPLW, cr7, rSECOND, rFIRST ); + } + emitJump( + i_now->arg.i, + ( i_now->op == OP_GTU ? branchTrue : branchFalse ), + 4*cr7+gt, 0 + ); + gpr_pos -= 2; + break; + + case OP_EQF: + case OP_NEF: + MAYBE_EMIT_CONST(); + in( iFCMPU, cr7, fSECOND, fFIRST ); + emitJump( + i_now->arg.i, + ( i_now->op == OP_EQF ? branchTrue : branchFalse ), + 4*cr7+eq, 0 + ); + fpr_pos -= 2; + break; + + case OP_LTF: + case OP_GEF: + MAYBE_EMIT_CONST(); + in( iFCMPU, cr7, fSECOND, fFIRST ); + emitJump( + i_now->arg.i, + ( i_now->op == OP_LTF ? branchTrue : branchFalse ), + 4*cr7+lt, 0 + ); + fpr_pos -= 2; + break; + + case OP_GTF: + case OP_LEF: + MAYBE_EMIT_CONST(); + in( iFCMPU, cr7, fSECOND, fFIRST ); + emitJump( + i_now->arg.i, + ( i_now->op == OP_GTF ? branchTrue : branchFalse ), + 4*cr7+gt, 0 + ); + fpr_pos -= 2; + break; + + case OP_LOAD1: + MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK + in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo ); +#else + in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); + in( iAND, rFIRST, rFIRST, r0 ); +#endif + in( iLBZX, rFIRST, rFIRST, rDATABASE ); + break; + + case OP_LOAD2: + MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK + in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo ); +#else + in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); + in( iAND, rFIRST, rFIRST, r0 ); +#endif + in( iLHZX, rFIRST, rFIRST, rDATABASE ); + break; + + case OP_LOAD4: + MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK + in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo ); +#else + in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); + in( iAND, rFIRST, rFIRST, r0 ); +#endif + if ( RET_INT ) { + in( iLWZX, rFIRST, rFIRST, rDATABASE ); + } else { + fpr_pos++; + in( iLFSX, fFIRST, rFIRST, rDATABASE ); + gpr_pos--; + } + break; + + case OP_STORE1: + MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK + in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo ); +#else + in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); + in( iAND, rSECOND, rSECOND, r0 ); +#endif + in( iSTBX, rFIRST, rSECOND, rDATABASE ); + gpr_pos -= 2; + break; + + case OP_STORE2: + MAYBE_EMIT_CONST(); +#if OPTIMIZE_MASK + in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo ); +#else + in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); + in( iAND, rSECOND, rSECOND, r0 ); +#endif + in( iSTHX, rFIRST, rSECOND, rDATABASE ); + gpr_pos -= 2; + break; + + case OP_STORE4: + MAYBE_EMIT_CONST(); + if ( ARG_INT ) { +#if OPTIMIZE_MASK + in( iRLWINM, rSECOND, rSECOND, 0, fastMaskHi, fastMaskLo ); +#else + in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); + in( iAND, rSECOND, rSECOND, r0 ); +#endif + + in( iSTWX, rFIRST, rSECOND, rDATABASE ); + gpr_pos--; + } else { +#if OPTIMIZE_MASK + in( iRLWINM, rFIRST, rFIRST, 0, fastMaskHi, fastMaskLo ); +#else + in( iLWZ, r0, VM_Data_Offset( dataMask ), rVMDATA ); + in( iAND, rFIRST, rFIRST, r0 ); +#endif + + in( iSTFSX, fFIRST, rFIRST, rDATABASE ); + fpr_pos--; + } + gpr_pos--; + break; + + case OP_ARG: + MAYBE_EMIT_CONST(); + in( iADDI, r0, rPSTACK, i_now->arg.b ); + if ( ARG_INT ) { + in( iSTWX, rFIRST, rDATABASE, r0 ); + gpr_pos--; + } else { + in( iSTFSX, fFIRST, rDATABASE, r0 ); + fpr_pos--; + } + break; + + case OP_BLOCK_COPY: + MAYBE_EMIT_CONST(); +#if OPTIMIZE_COPY + if ( i_now->arg.i <= SL( 16, 32 ) ) { + /* block is very short so copy it in-place */ + + unsigned int len = i_now->arg.i; + unsigned int copied = 0, left = len; + + in( iADD, rFIRST, rFIRST, rDATABASE ); + in( iADD, rSECOND, rSECOND, rDATABASE ); + + if ( len >= GPRLEN ) { + long int i, words = len / GPRLEN; + in( iLL, r0, 0, rFIRST ); + for ( i = 1; i < words; i++ ) + in( iLL, rTEMP( i - 1 ), GPRLEN * i, rFIRST ); + + in( iSTL, r0, 0, rSECOND ); + for ( i = 1; i < words; i++ ) + in( iSTL, rTEMP( i - 1 ), GPRLEN * i, rSECOND ); + + copied += words * GPRLEN; + left -= copied; + } + + if ( SL( 0, left >= 4 ) ) { + in( iLWZ, r0, copied+0, rFIRST ); + in( iSTW, r0, copied+0, rSECOND ); + copied += 4; + left -= 4; + } + if ( left >= 4 ) { + DIE("Bug in OP_BLOCK_COPY"); + } + if ( left == 3 ) { + in( iLHZ, r0, copied+0, rFIRST ); + in( iLBZ, rTMP, copied+2, rFIRST ); + in( iSTH, r0, copied+0, rSECOND ); + in( iSTB, rTMP, copied+2, rSECOND ); + } else if ( left == 2 ) { + in( iLHZ, r0, copied+0, rFIRST ); + in( iSTH, r0, copied+0, rSECOND ); + } else if ( left == 1 ) { + in( iLBZ, r0, copied+0, rFIRST ); + in( iSTB, r0, copied+0, rSECOND ); + } + } else +#endif + { + unsigned long int r5_ori = 0; + if ( i_now->arg.si >= -0x8000 && i_now->arg.si < 0x8000 ) { + in( iLI, r5, i_now->arg.si ); + } else if ( i_now->arg.i < 0x10000 ) { + in( iLI, r5, 0 ); + r5_ori = i_now->arg.i; + } else { + in( iLIS, r5, i_now->arg.ss[ 0 ] ); + r5_ori = i_now->arg.us[ 1 ]; + } + + in( iLL, r0, VM_Data_Offset( BlockCopy ), rVMDATA ); // get blockCopy pointer + + if ( r5_ori ) + in( iORI, r5, r5, r5_ori ); + + in( iMTCTR, r0 ); + + if ( rFIRST != r4 ) + in( iMR, r4, rFIRST ); + if ( rSECOND != r3 ) + in( iMR, r3, rSECOND ); + + in( iBCTRL ); + } + + gpr_pos -= 2; + break; + + case OP_SEX8: + MAYBE_EMIT_CONST(); + in( iEXTSB, rFIRST, rFIRST ); + break; + + case OP_SEX16: + MAYBE_EMIT_CONST(); + in( iEXTSH, rFIRST, rFIRST ); + break; + + case OP_NEGI: + MAYBE_EMIT_CONST(); + in( iNEG, rFIRST, rFIRST ); + break; + + case OP_ADD: + if ( i_const ) { + EMIT_FALSE_CONST(); + + signed short int hi, lo; + hi = i_const->arg.ss[ 0 ]; + lo = i_const->arg.ss[ 1 ]; + if ( lo < 0 ) + hi += 1; + + if ( hi != 0 ) + in( iADDIS, rSECOND, rSECOND, hi ); + if ( lo != 0 ) + in( iADDI, rSECOND, rSECOND, lo ); + + // if both are zero no instruction will be written + if ( hi == 0 && lo == 0 ) + force_emit = 1; + } else { + MAYBE_EMIT_CONST(); + in( iADD, rSECOND, rSECOND, rFIRST ); + } + gpr_pos--; + break; + + case OP_SUB: + MAYBE_EMIT_CONST(); + in( iSUB, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_DIVI: + MAYBE_EMIT_CONST(); + in( iDIVW, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_DIVU: + MAYBE_EMIT_CONST(); + in( iDIVWU, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_MODI: + MAYBE_EMIT_CONST(); + in( iDIVW, r0, rSECOND, rFIRST ); + in( iMULLW, r0, r0, rFIRST ); + in( iSUB, rSECOND, rSECOND, r0 ); + gpr_pos--; + break; + + case OP_MODU: + MAYBE_EMIT_CONST(); + in( iDIVWU, r0, rSECOND, rFIRST ); + in( iMULLW, r0, r0, rFIRST ); + in( iSUB, rSECOND, rSECOND, r0 ); + gpr_pos--; + break; + + case OP_MULI: + case OP_MULU: + MAYBE_EMIT_CONST(); + in( iMULLW, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_BAND: + MAYBE_EMIT_CONST(); + in( iAND, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_BOR: + MAYBE_EMIT_CONST(); + in( iOR, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_BXOR: + MAYBE_EMIT_CONST(); + in( iXOR, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_BCOM: + MAYBE_EMIT_CONST(); + in( iNOT, rFIRST, rFIRST ); + break; + + case OP_LSH: + MAYBE_EMIT_CONST(); + in( iSLW, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_RSHI: + MAYBE_EMIT_CONST(); + in( iSRAW, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_RSHU: + MAYBE_EMIT_CONST(); + in( iSRW, rSECOND, rSECOND, rFIRST ); + gpr_pos--; + break; + + case OP_NEGF: + MAYBE_EMIT_CONST(); + in( iFNEG, fFIRST, fFIRST ); + break; + + case OP_ADDF: + MAYBE_EMIT_CONST(); + in( iFADDS, fSECOND, fSECOND, fFIRST ); + fpr_pos--; + break; + + case OP_SUBF: + MAYBE_EMIT_CONST(); + in( iFSUBS, fSECOND, fSECOND, fFIRST ); + fpr_pos--; + break; + + case OP_DIVF: + MAYBE_EMIT_CONST(); + in( iFDIVS, fSECOND, fSECOND, fFIRST ); + fpr_pos--; + break; + + case OP_MULF: + MAYBE_EMIT_CONST(); + in( iFMULS, fSECOND, fSECOND, fFIRST ); + fpr_pos--; + break; + + case OP_CVIF: + MAYBE_EMIT_CONST(); + fpr_pos++; + in( iXORIS, rFIRST, rFIRST, 0x8000 ); + in( iLIS, r0, 0x4330 ); + in( iSTW, rFIRST, stack_temp + 4, r1 ); + in( iSTW, r0, stack_temp, r1 ); + in( iLFS, fTMP, VM_Data_Offset( floatBase ), rVMDATA ); + in( iLFD, fFIRST, stack_temp, r1 ); + in( iFSUB, fFIRST, fFIRST, fTMP ); + in( iFRSP, fFIRST, fFIRST ); + gpr_pos--; + break; + + case OP_CVFI: + MAYBE_EMIT_CONST(); + gpr_pos++; + in( iFCTIWZ, fFIRST, fFIRST ); + in( iSTFD, fFIRST, stack_temp, r1 ); + in( iLWZ, rFIRST, stack_temp + 4, r1 ); + fpr_pos--; + break; + } + + i_const = NULL; + + if ( i_now->op != OP_CONST ) { + // emit the instructions if it isn't OP_CONST + emitEnd(); + } else { + // mark in what register the value should be saved + if ( RET_INT ) + i_now->regPos = ++gpr_pos; + else + i_now->regPos = ++fpr_pos; + +#if OPTIMIZE_HOLE + i_const = i_now; +#else + PPC_EmitConst( i_now ); +#endif + } + } + if ( i_const ) + DIE( "left (unused) OP_CONST" ); + + { + // free opcode information, don't free first dummy one + source_instruction_t *i_next = i_first->next; + while ( i_next ) { + i_now = i_next; + i_next = i_now->next; + PPC_Free( i_now ); + } + } +} + + +/* + * check which jumps are short enough to use signed 16bit immediate branch + */ +static void +PPC_ShrinkJumps( void ) +{ + symbolic_jump_t *sj_now = sj_first; + while ( (sj_now = sj_now->nextJump) ) { + if ( sj_now->bo == branchAlways ) + // non-conditional branch has 26bit immediate + sj_now->parent->length = 1; + + else { + dest_instruction_t *di = di_pointers[ sj_now->jump_to ]; + dest_instruction_t *ji = sj_now->parent; + long int jump_length = 0; + if ( ! di ) + DIE( "No instruction to jump to" ); + if ( ji->count > di->count ) { + do { + jump_length += di->length; + } while ( ( di = di->next ) != ji ); + } else { + jump_length = 1; + while ( ( ji = ji->next ) != di ) + jump_length += ji->length; + } + if ( jump_length < 0x2000 ) + // jump is short, use normal instruction + sj_now->parent->length = 1; + } + } +} + +/* + * puts all the data in one place, it consists of many different tasks + */ +static void +PPC_ComputeCode( vm_t *vm ) +{ + dest_instruction_t *di_now = di_first; + + unsigned long int codeInstructions = 0; + // count total instruciton number + while ( (di_now = di_now->next ) ) + codeInstructions += di_now->length; + + size_t codeLength = sizeof( vm_data_t ) + + sizeof( unsigned int ) * data_acc + + sizeof( ppc_instruction_t ) * codeInstructions; + + // get the memory for the generated code, smarter ppcs need the + // mem to be marked as executable (whill change later) + unsigned char *dataAndCode = mmap( NULL, codeLength, + PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0 ); + + if (dataAndCode == MAP_FAILED) + DIE( "Not enough memory" ); + + ppc_instruction_t *codeNow, *codeBegin; + codeNow = codeBegin = (ppc_instruction_t *)( dataAndCode + VM_Data_Offset( data[ data_acc ] ) ); + + ppc_instruction_t nop = IN( iNOP ); + + // copy instructions to the destination + // fills the jump instructions with nops + // saves pointers of all instructions + di_now = di_first; + while ( (di_now = di_now->next ) ) { + unsigned long int i_count = di_now->i_count; + if ( i_count != FALSE_ICOUNT ) { + if ( ! di_pointers[ i_count ] ) + di_pointers[ i_count ] = (void *) codeNow; + } + + if ( di_now->jump == NULL ) { + memcpy( codeNow, &(di_now->code[0]), di_now->length * sizeof( ppc_instruction_t ) ); + codeNow += di_now->length; + } else { + long int i; + symbolic_jump_t *sj; + for ( i = 0; i < di_now->length; i++ ) + codeNow[ i ] = nop; + codeNow += di_now->length; + + sj = di_now->jump; + // save position of jumping instruction + sj->parent = (void *)(codeNow - 1); + } + } + + // compute the jumps and write corresponding instructions + symbolic_jump_t *sj_now = sj_first; + while ( (sj_now = sj_now->nextJump ) ) { + ppc_instruction_t *jumpFrom = (void *) sj_now->parent; + ppc_instruction_t *jumpTo = (void *) di_pointers[ sj_now->jump_to ]; + signed long int jumpLength = jumpTo - jumpFrom; + + // if jump is short, just write it + if ( jumpLength >= - 8192 && jumpLength < 8192 ) { + powerpc_iname_t branchConditional = sj_now->ext & branchExtLink ? iBCL : iBC; + *jumpFrom = IN( branchConditional, sj_now->bo, sj_now->bi, jumpLength * 4 ); + continue; + } + + // jump isn't short so write it as two instructions + // + // the letter one is a non-conditional branch instruction which + // accepts immediate values big enough (26 bits) + *jumpFrom = IN( (sj_now->ext & branchExtLink ? iBL : iB), jumpLength * 4 ); + if ( sj_now->bo == branchAlways ) + continue; + + // there should have been additional space prepared for this case + if ( jumpFrom[ -1 ] != nop ) + DIE( "additional space for long jump not prepared" ); + + // invert instruction condition + long int bo = 0; + switch ( sj_now->bo ) { + case branchTrue: + bo = branchFalse; + break; + case branchFalse: + bo = branchTrue; + break; + default: + DIE( "unrecognized branch type" ); + break; + } + + // the former instruction is an inverted conditional branch which + // jumps over the non-conditional one + jumpFrom[ -1 ] = IN( iBC, bo, sj_now->bi, +2*4 ); + } + + vm->codeBase = dataAndCode; + vm->codeLength = codeLength; + + vm_data_t *data = (vm_data_t *)dataAndCode; + +#if ELF64 + // prepare Official Procedure Descriptor for the generated code + // and retrieve real function pointer for helper functions + + opd_t *ac = (void *)VM_AsmCall, *bc = (void *)VM_BlockCopy; + data->opd.function = codeBegin; + // trick it into using the same TOC + // this way we won't have to switch TOC before calling AsmCall or BlockCopy + data->opd.toc = ac->toc; + data->opd.env = ac->env; + + data->AsmCall = ac->function; + data->BlockCopy = bc->function; +#else + data->AsmCall = VM_AsmCall; + data->BlockCopy = VM_BlockCopy; +#endif + + data->dataMask = vm->dataMask; + data->iPointers = (ppc_instruction_t *)vm->instructionPointers; + data->dataLength = VM_Data_Offset( data[ data_acc ] ); + data->codeLength = ( codeNow - codeBegin ) * sizeof( ppc_instruction_t ); + data->floatBase = 0x59800004; + + + /* write dynamic data (float constants) */ + { + local_data_t *d_next, *d_now = data_first; + long int accumulated = 0; + + do { + long int i; + for ( i = 0; i < d_now->count; i++ ) + data->data[ accumulated + i ] = d_now->data[ i ]; + + accumulated += d_now->count; + d_next = d_now->next; + PPC_Free( d_now ); + + if ( !d_next ) + break; + d_now = d_next; + } while (1); + data_first = NULL; + } + + /* free most of the compilation memory */ + { + di_now = di_first->next; + PPC_Free( di_first ); + PPC_Free( sj_first ); + + while ( di_now ) { + di_first = di_now->next; + if ( di_now->jump ) + PPC_Free( di_now->jump ); + PPC_Free( di_now ); + di_now = di_first; + } + } + + return; +} + +static void +VM_Destroy_Compiled( vm_t *self ) +{ + if ( self->codeBase ) { + if ( munmap( self->codeBase, self->codeLength ) ) + Com_Printf( S_COLOR_RED "Memory unmap failed, possible memory leak\n" ); + } + self->codeBase = NULL; +} + +void +VM_Compile( vm_t *vm, vmHeader_t *header ) +{ + long int pc = 0; + unsigned long int i_count; + char* code; + struct timeval tvstart = {0, 0}; + source_instruction_t *i_first /* dummy */, *i_last = NULL, *i_now; + + vm->compiled = qfalse; + + gettimeofday(&tvstart, NULL); + + PPC_MakeFastMask( vm->dataMask ); + + i_first = PPC_Malloc( sizeof( source_instruction_t ) ); + i_first->next = NULL; + + // realloc instructionPointers with correct size + // use Z_Malloc so vm.c will be able to free the memory + if ( sizeof( void * ) != sizeof( int ) ) { + Z_Free( vm->instructionPointers ); + vm->instructionPointers = Z_Malloc( header->instructionCount * sizeof( void * ) ); + } + di_pointers = (void *)vm->instructionPointers; + memset( di_pointers, 0, header->instructionCount * sizeof( void * ) ); + + + PPC_CompileInit(); + + /* + * read the input program + * divide it into functions and send each function to compiler + */ + code = (char *)header + header->codeOffset; + for ( i_count = 0; i_count < header->instructionCount; ++i_count ) + { + unsigned char op = code[ pc++ ]; + + if ( op == OP_ENTER ) { + if ( i_first->next ) + VM_CompileFunction( i_first ); + i_first->next = NULL; + i_last = i_first; + } + + i_now = PPC_Malloc( sizeof( source_instruction_t ) ); + i_now->op = op; + i_now->i_count = i_count; + i_now->arg.i = 0; + i_now->regA1 = 0; + i_now->regA2 = 0; + i_now->regR = 0; + i_now->regPos = 0; + i_now->next = NULL; + + if ( vm_opInfo[op] & opImm4 ) { + union { + unsigned char b[4]; + unsigned int i; + } c = { { code[ pc + 3 ], code[ pc + 2 ], code[ pc + 1 ], code[ pc + 0 ] }, }; + + i_now->arg.i = c.i; + pc += 4; + } else if ( vm_opInfo[op] & opImm1 ) { + i_now->arg.b = code[ pc++ ]; + } + + i_last->next = i_now; + i_last = i_now; + } + VM_CompileFunction( i_first ); + PPC_Free( i_first ); + + PPC_ShrinkJumps(); + memset( di_pointers, 0, header->instructionCount * sizeof( void * ) ); + PPC_ComputeCode( vm ); + + /* check for uninitialized pointers */ +#ifdef DEBUG_VM + long int i; + for ( i = 0; i < header->instructionCount; i++ ) + if ( di_pointers[ i ] == 0 ) + Com_Printf( S_COLOR_RED "Pointer %ld not initialized !\n", i ); +#endif + + /* mark memory as executable and not writeable */ + if ( mprotect( vm->codeBase, vm->codeLength, PROT_READ|PROT_EXEC ) ) { + + // it has failed, make sure memory is unmapped before throwing the error + VM_Destroy_Compiled( vm ); + DIE( "mprotect failed" ); + } + + vm->destroy = VM_Destroy_Compiled; + vm->compiled = qtrue; + + { + struct timeval tvdone = {0, 0}; + struct timeval dur = {0, 0}; + Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n", + vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength ); + + gettimeofday(&tvdone, NULL); + timersub(&tvdone, &tvstart, &dur); + Com_Printf( "compilation took %lu.%06lu seconds\n", + (long unsigned int)dur.tv_sec, (long unsigned int)dur.tv_usec ); + } +} + +int +VM_CallCompiled( vm_t *vm, int *args ) +{ + int retVal; + int *argPointer; + + vm_data_t *vm_dataAndCode = (void *)( vm->codeBase ); + int programStack = vm->programStack; + int stackOnEntry = programStack; + + byte *image = vm->dataBase; + + currentVM = vm; + + vm->currentlyInterpreting = qtrue; + + programStack -= 48; + argPointer = (int *)&image[ programStack + 8 ]; + memcpy( argPointer, args, 4 * 9 ); + argPointer[ -1 ] = 0; + argPointer[ -2 ] = -1; + +#ifdef VM_TIMES + struct tms start_time, stop_time; + clock_t time_diff; + + times( &start_time ); + time_outside_vm = 0; +#endif + + /* call generated code */ + { + int ( *entry )( void *, int, void * ); +#ifdef __PPC64__ + entry = (void *)&(vm_dataAndCode->opd); +#else + entry = (void *)(vm->codeBase + vm_dataAndCode->dataLength); +#endif + retVal = entry( vm->codeBase, programStack, vm->dataBase ); + } + +#ifdef VM_TIMES + times( &stop_time ); + time_diff = stop_time.tms_utime - start_time.tms_utime; + time_total_vm += time_diff - time_outside_vm; + if ( time_diff > 100 ) { + printf( "App clock: %ld, vm total: %ld, vm this: %ld, vm real: %ld, vm out: %ld\n" + "Inside VM %f%% of app time\n", + stop_time.tms_utime, + time_total_vm, + time_diff, + time_diff - time_outside_vm, + time_outside_vm, + (double)100 * time_total_vm / stop_time.tms_utime ); + } +#endif + + vm->programStack = stackOnEntry; + vm->currentlyInterpreting = qfalse; + + return retVal; +} diff --git a/src/qcommon/vm_powerpc_asm.c b/src/qcommon/vm_powerpc_asm.c new file mode 100644 index 0000000..58d65a0 --- /dev/null +++ b/src/qcommon/vm_powerpc_asm.c @@ -0,0 +1,1006 @@ +/* +=========================================================================== +Copyright (C) 2008 Przemyslaw Iskra + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== + + * File includes code from GNU binutils, exactly: + * - include/opcode/ppc.h - licensed under GPL v1 or later + * - opcodes/ppc-opc.c - licensed under GPL v2 or later + * + * ppc.h -- Header file for PowerPC opcode table + * Copyright 1994, 1995, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, + * 2007 Free Software Foundation, Inc. + * Written by Ian Lance Taylor, Cygnus Suppor + * + * This file is part of GDB, GAS, and the GNU binutils. + * + * ppc-opc.c -- PowerPC opcode list + * Copyright 1994, 1995, 1996, 1997, 1998, 2000, 2001, 2002, 2003, 2004, + * 2005, 2006, 2007 Free Software Foundation, Inc. + * Written by Ian Lance Taylor, Cygnus Support + * + * This file is part of GDB, GAS, and the GNU binutils. + * + */ + +#include "vm_local.h" +#include "vm_powerpc_asm.h" + +#include +#include +#include + +/* return nop on error */ +#define ASM_ERROR_OPC (0x60000000) + +/* + * BEGIN OF ppc.h + */ + +#define ppc_cpu_t int + +struct powerpc_opcode +{ + const char *name; + unsigned long opcode; + unsigned long mask; + ppc_cpu_t flags; + unsigned char operands[8]; +}; + +static const struct powerpc_opcode powerpc_opcodes[]; +static const int powerpc_num_opcodes; + +#define PPC_OPCODE_PPC 1 +#define PPC_OPCODE_POWER 2 +#define PPC_OPCODE_POWER2 4 +#define PPC_OPCODE_32 8 +#define PPC_OPCODE_64 0x10 +#define PPC_OPCODE_601 0x20 +#define PPC_OPCODE_COMMON 0x40 +#define PPC_OPCODE_ANY 0x80 +#define PPC_OPCODE_64_BRIDGE 0x100 +#define PPC_OPCODE_ALTIVEC 0x200 +#define PPC_OPCODE_403 0x400 +#define PPC_OPCODE_BOOKE 0x800 +#define PPC_OPCODE_BOOKE64 0x1000 +#define PPC_OPCODE_440 0x2000 +#define PPC_OPCODE_POWER4 0x4000 +#define PPC_OPCODE_NOPOWER4 0x8000 +#define PPC_OPCODE_CLASSIC 0x10000 +#define PPC_OPCODE_SPE 0x20000 +#define PPC_OPCODE_ISEL 0x40000 +#define PPC_OPCODE_EFS 0x80000 +#define PPC_OPCODE_BRLOCK 0x100000 +#define PPC_OPCODE_PMR 0x200000 +#define PPC_OPCODE_CACHELCK 0x400000 +#define PPC_OPCODE_RFMCI 0x800000 +#define PPC_OPCODE_POWER5 0x1000000 +#define PPC_OPCODE_E300 0x2000000 +#define PPC_OPCODE_POWER6 0x4000000 +#define PPC_OPCODE_CELL 0x8000000 +#define PPC_OPCODE_PPCPS 0x10000000 +#define PPC_OPCODE_E500MC 0x20000000 +#define PPC_OPCODE_405 0x40000000 +#define PPC_OPCODE_VSX 0x80000000 + +#define PPC_OP(i) (((i) >> 26) & 0x3f) + +struct powerpc_operand +{ + unsigned int bitm; + int shift; + unsigned long (*insert) + (unsigned long, long, int, const char **); + unsigned long flags; +}; + +static const struct powerpc_operand powerpc_operands[]; +static const unsigned int num_powerpc_operands; + +#define PPC_OPERAND_SIGNED (0x1) +#define PPC_OPERAND_SIGNOPT (0x2) +#define PPC_OPERAND_FAKE (0x4) +#define PPC_OPERAND_PARENS (0x8) +#define PPC_OPERAND_CR (0x10) +#define PPC_OPERAND_GPR (0x20) +#define PPC_OPERAND_GPR_0 (0x40) +#define PPC_OPERAND_FPR (0x80) +#define PPC_OPERAND_RELATIVE (0x100) +#define PPC_OPERAND_ABSOLUTE (0x200) +#define PPC_OPERAND_OPTIONAL (0x400) +#define PPC_OPERAND_NEXT (0x800) +#define PPC_OPERAND_NEGATIVE (0x1000) +#define PPC_OPERAND_VR (0x2000) +#define PPC_OPERAND_DS (0x4000) +#define PPC_OPERAND_DQ (0x8000) +#define PPC_OPERAND_PLUS1 (0x10000) +#define PPC_OPERAND_FSL (0x20000) +#define PPC_OPERAND_FCR (0x40000) +#define PPC_OPERAND_UDI (0x80000) +#define PPC_OPERAND_VSR (0x100000) + +/* + * END OF ppc.h + */ + +#define PPC_DEST_ARCH PPC_OPCODE_PPC + +ppc_instruction_t +asm_instruction( powerpc_iname_t sname, const int argc, const long int *argv ) +{ + const char *errmsg = NULL; + const char *name; + unsigned long int ret; + const struct powerpc_opcode *opcode = NULL; + int argi, argj; + + opcode = &powerpc_opcodes[ sname ]; + name = opcode->name; + + if ( ! opcode ) { + printf( "Can't find opcode %d\n", sname ); + return ASM_ERROR_OPC; + } + if ( ( opcode->flags & PPC_DEST_ARCH ) != PPC_DEST_ARCH ) { + printf( "opcode %s not defined for this arch\n", name ); + return ASM_ERROR_OPC; + } + + ret = opcode->opcode; + + argi = argj = 0; + while ( opcode->operands[ argi ] != 0 ) { + long int op = 0; + const struct powerpc_operand *operand = &powerpc_operands[ opcode->operands[ argi ] ]; + + if ( ! (operand->flags & PPC_OPERAND_FAKE) ) { + if ( argj >= argc ) { + printf( "Not enough arguments for %s, got %d\n", name, argc ); + return ASM_ERROR_OPC; + } + + op = argv[ argj++ ]; + } + + if ( operand->insert ) { + errmsg = NULL; + ret = operand->insert( ret, op, PPC_DEST_ARCH, &errmsg ); + if ( errmsg ) { + printf( "%s: error while inserting operand %d (0x%.2lx): %s\n", + name, argi, op, errmsg ); + } + } else { + unsigned long int opu = *(unsigned long int *)&op; + unsigned long int bitm = operand->bitm; + unsigned long int bitm_full = bitm | ( bitm & 1 ? 0 : 0xf ); + + if ( operand->flags & PPC_OPERAND_SIGNED ) { + bitm_full >>= 1; + + if ( ( opu & ~bitm_full ) != 0 && ( opu | bitm_full ) != -1 ) + printf( "%s: signed operand nr.%d to wide. op: %.8lx, mask: %.8lx\n", + name, argi, opu, bitm ); + } else { + if ( ( opu & ~bitm_full ) != 0 ) + printf( "%s: unsigned operand nr.%d to wide. op: %.8lx, mask: %.8lx\n", + name, argi, opu, bitm ); + } + if ( (bitm & 1) == 0 ) { + if ( opu & 0xf & ~bitm ) + printf( "%s: operand nr.%d not aligned correctly. op: %.8lx, mask: %.8lx\n", + name, argi, opu, bitm ); + } + + ret |= ( op & operand->bitm ) << operand->shift; + } + argi++; + } + if ( argc > argj ) { + printf( "Too many arguments for %s, got %d\n", name, argc ); + return ASM_ERROR_OPC; + } + + return ret; +} + + +/* + * BEGIN OF ppc-opc.c + */ + +#define ATTRIBUTE_UNUSED +#define _(x) (x) + +/* Local insertion and extraction functions. */ + +static unsigned long insert_bdm (unsigned long, long, int, const char **); +static unsigned long insert_bo (unsigned long, long, int, const char **); +static unsigned long insert_ras (unsigned long, long, int, const char **); +static unsigned long insert_rbs (unsigned long, long, int, const char **); + +/* The operands table. + + The fields are bitm, shift, insert, extract, flags. + */ + +static const struct powerpc_operand powerpc_operands[] = +{ + /* The zero index is used to indicate the end of the list of + operands. */ +#define UNUSED 0 + { 0, 0, NULL, 0 }, + + /* The BA field in an XL form instruction. */ +#define BA UNUSED + 1 + /* The BI field in a B form or XL form instruction. */ +#define BI BA +#define BI_MASK (0x1f << 16) + { 0x1f, 16, NULL, PPC_OPERAND_CR }, + + /* The BD field in a B form instruction. The lower two bits are + forced to zero. */ +#define BD BA + 1 + { 0xfffc, 0, NULL, PPC_OPERAND_RELATIVE | PPC_OPERAND_SIGNED }, + + /* The BD field in a B form instruction when the - modifier is used. + This sets the y bit of the BO field appropriately. */ +#define BDM BD + 1 + { 0xfffc, 0, insert_bdm, + PPC_OPERAND_RELATIVE | PPC_OPERAND_SIGNED }, + + /* The BF field in an X or XL form instruction. */ +#define BF BDM + 1 + /* The CRFD field in an X form instruction. */ +#define CRFD BF + { 0x7, 23, NULL, PPC_OPERAND_CR }, + + /* An optional BF field. This is used for comparison instructions, + in which an omitted BF field is taken as zero. */ +#define OBF BF + 1 + { 0x7, 23, NULL, PPC_OPERAND_CR | PPC_OPERAND_OPTIONAL }, + + /* The BO field in a B form instruction. Certain values are + illegal. */ +#define BO OBF + 1 +#define BO_MASK (0x1f << 21) + { 0x1f, 21, insert_bo, 0 }, + + /* The condition register number portion of the BI field in a B form + or XL form instruction. This is used for the extended + conditional branch mnemonics, which set the lower two bits of the + BI field. This field is optional. */ +#define CR BO + 1 + { 0x7, 18, NULL, PPC_OPERAND_CR | PPC_OPERAND_OPTIONAL }, + + /* The D field in a D form instruction. This is a displacement off + a register, and implies that the next operand is a register in + parentheses. */ +#define D CR + 1 + { 0xffff, 0, NULL, PPC_OPERAND_PARENS | PPC_OPERAND_SIGNED }, + + /* The DS field in a DS form instruction. This is like D, but the + lower two bits are forced to zero. */ +#define DS D + 1 + { 0xfffc, 0, NULL, + PPC_OPERAND_PARENS | PPC_OPERAND_SIGNED | PPC_OPERAND_DS }, + + /* The FRA field in an X or A form instruction. */ +#define FRA DS + 1 +#define FRA_MASK (0x1f << 16) + { 0x1f, 16, NULL, PPC_OPERAND_FPR }, + + /* The FRB field in an X or A form instruction. */ +#define FRB FRA + 1 +#define FRB_MASK (0x1f << 11) + { 0x1f, 11, NULL, PPC_OPERAND_FPR }, + + /* The FRC field in an A form instruction. */ +#define FRC FRB + 1 +#define FRC_MASK (0x1f << 6) + { 0x1f, 6, NULL, PPC_OPERAND_FPR }, + + /* The FRS field in an X form instruction or the FRT field in a D, X + or A form instruction. */ +#define FRS FRC + 1 +#define FRT FRS + { 0x1f, 21, NULL, PPC_OPERAND_FPR }, + + /* The LI field in an I form instruction. The lower two bits are + forced to zero. */ +#define LI FRS + 1 + { 0x3fffffc, 0, NULL, PPC_OPERAND_RELATIVE | PPC_OPERAND_SIGNED }, + + /* The ME field in an M form instruction. */ +#define ME LI + 1 +#define ME_MASK (0x1f << 1) + { 0x1f, 1, NULL, 0 }, + + /* The MB and ME fields in an M form instruction expressed a single + operand which is a bitmask indicating which bits to select. This + is a two operand form using PPC_OPERAND_NEXT. See the + description in opcode/ppc.h for what this means. */ +#define MBE ME + 1 + { 0x1f, 6, NULL, PPC_OPERAND_OPTIONAL | PPC_OPERAND_NEXT }, + + /* The RA field in an D, DS, DQ, X, XO, M, or MDS form instruction. */ +#define RA MBE + 1 +#define RA_MASK (0x1f << 16) + { 0x1f, 16, NULL, PPC_OPERAND_GPR }, + + /* As above, but 0 in the RA field means zero, not r0. */ +#define RA0 RA + 1 + { 0x1f, 16, NULL, PPC_OPERAND_GPR_0 }, + + /* The RA field in a D or X form instruction which is an updating + store or an updating floating point load, which means that the RA + field may not be zero. */ +#define RAS RA0 + 1 + { 0x1f, 16, insert_ras, PPC_OPERAND_GPR_0 }, + + /* The RB field in an X, XO, M, or MDS form instruction. */ +#define RB RAS + 1 +#define RB_MASK (0x1f << 11) + { 0x1f, 11, NULL, PPC_OPERAND_GPR }, + + /* The RB field in an X form instruction when it must be the same as + the RS field in the instruction. This is used for extended + mnemonics like mr. */ +#define RBS RB + 1 + { 0x1f, 11, insert_rbs, PPC_OPERAND_FAKE }, + + /* The RS field in a D, DS, X, XFX, XS, M, MD or MDS form + instruction or the RT field in a D, DS, X, XFX or XO form + instruction. */ +#define RS RBS + 1 +#define RT RS +#define RT_MASK (0x1f << 21) + { 0x1f, 21, NULL, PPC_OPERAND_GPR }, + + /* The SH field in an X or M form instruction. */ +#define SH RS + 1 +#define SH_MASK (0x1f << 11) + /* The other UIMM field in a EVX form instruction. */ +#define EVUIMM SH + { 0x1f, 11, NULL, 0 }, + + /* The SI field in a D form instruction. */ +#define SI SH + 1 + { 0xffff, 0, NULL, PPC_OPERAND_SIGNED }, + + /* The UI field in a D form instruction. */ +#define UI SI + 1 + { 0xffff, 0, NULL, 0 }, + +}; + +static const unsigned int num_powerpc_operands = ARRAY_LEN (powerpc_operands); + +/* The functions used to insert and extract complicated operands. */ + +/* The BD field in a B form instruction when the - modifier is used. + This modifier means that the branch is not expected to be taken. + For chips built to versions of the architecture prior to version 2 + (ie. not Power4 compatible), we set the y bit of the BO field to 1 + if the offset is negative. When extracting, we require that the y + bit be 1 and that the offset be positive, since if the y bit is 0 + we just want to print the normal form of the instruction. + Power4 compatible targets use two bits, "a", and "t", instead of + the "y" bit. "at" == 00 => no hint, "at" == 01 => unpredictable, + "at" == 10 => not taken, "at" == 11 => taken. The "t" bit is 00001 + in BO field, the "a" bit is 00010 for branch on CR(BI) and 01000 + for branch on CTR. We only handle the taken/not-taken hint here. + Note that we don't relax the conditions tested here when + disassembling with -Many because insns using extract_bdm and + extract_bdp always occur in pairs. One or the other will always + be valid. */ + +static unsigned long +insert_bdm (unsigned long insn, + long value, + int dialect, + const char **errmsg ATTRIBUTE_UNUSED) +{ + if ((dialect & PPC_OPCODE_POWER4) == 0) + { + if ((value & 0x8000) != 0) + insn |= 1 << 21; + } + else + { + if ((insn & (0x14 << 21)) == (0x04 << 21)) + insn |= 0x02 << 21; + else if ((insn & (0x14 << 21)) == (0x10 << 21)) + insn |= 0x08 << 21; + } + return insn | (value & 0xfffc); +} + + +/* Check for legal values of a BO field. */ + +static int +valid_bo (long value, int dialect, int extract) +{ + if ((dialect & PPC_OPCODE_POWER4) == 0) + { + int valid; + /* Certain encodings have bits that are required to be zero. + These are (z must be zero, y may be anything): + 001zy + 011zy + 1z00y + 1z01y + 1z1zz + */ + switch (value & 0x14) + { + default: + case 0: + valid = 1; + break; + case 0x4: + valid = (value & 0x2) == 0; + break; + case 0x10: + valid = (value & 0x8) == 0; + break; + case 0x14: + valid = value == 0x14; + break; + } + /* When disassembling with -Many, accept power4 encodings too. */ + if (valid + || (dialect & PPC_OPCODE_ANY) == 0 + || !extract) + return valid; + } + + /* Certain encodings have bits that are required to be zero. + These are (z must be zero, a & t may be anything): + 0000z + 0001z + 0100z + 0101z + 001at + 011at + 1a00t + 1a01t + 1z1zz + */ + if ((value & 0x14) == 0) + return (value & 0x1) == 0; + else if ((value & 0x14) == 0x14) + return value == 0x14; + else + return 1; +} + +/* The BO field in a B form instruction. Warn about attempts to set + the field to an illegal value. */ + +static unsigned long +insert_bo (unsigned long insn, + long value, + int dialect, + const char **errmsg) +{ + if (!valid_bo (value, dialect, 0)) + *errmsg = _("invalid conditional option"); + return insn | ((value & 0x1f) << 21); +} + +/* The RA field in a D or X form instruction which is an updating + store or an updating floating point load, which means that the RA + field may not be zero. */ + +static unsigned long +insert_ras (unsigned long insn, + long value, + int dialect ATTRIBUTE_UNUSED, + const char **errmsg) +{ + if (value == 0) + *errmsg = _("invalid register operand when updating"); + return insn | ((value & 0x1f) << 16); +} + +/* The RB field in an X form instruction when it must be the same as + the RS field in the instruction. This is used for extended + mnemonics like mr. This operand is marked FAKE. The insertion + function just copies the BT field into the BA field, and the + extraction function just checks that the fields are the same. */ + +static unsigned long +insert_rbs (unsigned long insn, + long value ATTRIBUTE_UNUSED, + int dialect ATTRIBUTE_UNUSED, + const char **errmsg ATTRIBUTE_UNUSED) +{ + return insn | (((insn >> 21) & 0x1f) << 11); +} + + +/* Macros used to form opcodes. */ + +/* The main opcode. */ +#define OP(x) ((((unsigned long)(x)) & 0x3f) << 26) +#define OP_MASK OP (0x3f) + +/* The main opcode combined with a trap code in the TO field of a D + form instruction. Used for extended mnemonics for the trap + instructions. */ +#define OPTO(x,to) (OP (x) | ((((unsigned long)(to)) & 0x1f) << 21)) +#define OPTO_MASK (OP_MASK | TO_MASK) + +/* The main opcode combined with a comparison size bit in the L field + of a D form or X form instruction. Used for extended mnemonics for + the comparison instructions. */ +#define OPL(x,l) (OP (x) | ((((unsigned long)(l)) & 1) << 21)) +#define OPL_MASK OPL (0x3f,1) + +/* An A form instruction. */ +#define A(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0x1f) << 1) | (((unsigned long)(rc)) & 1)) +#define A_MASK A (0x3f, 0x1f, 1) + +/* An A_MASK with the FRB field fixed. */ +#define AFRB_MASK (A_MASK | FRB_MASK) + +/* An A_MASK with the FRC field fixed. */ +#define AFRC_MASK (A_MASK | FRC_MASK) + +/* An A_MASK with the FRA and FRC fields fixed. */ +#define AFRAFRC_MASK (A_MASK | FRA_MASK | FRC_MASK) + +/* An AFRAFRC_MASK, but with L bit clear. */ +#define AFRALFRC_MASK (AFRAFRC_MASK & ~((unsigned long) 1 << 16)) + +/* A B form instruction. */ +#define B(op, aa, lk) (OP (op) | ((((unsigned long)(aa)) & 1) << 1) | ((lk) & 1)) +#define B_MASK B (0x3f, 1, 1) + +/* A B form instruction setting the BO field. */ +#define BBO(op, bo, aa, lk) (B ((op), (aa), (lk)) | ((((unsigned long)(bo)) & 0x1f) << 21)) +#define BBO_MASK BBO (0x3f, 0x1f, 1, 1) + +/* A BBO_MASK with the y bit of the BO field removed. This permits + matching a conditional branch regardless of the setting of the y + bit. Similarly for the 'at' bits used for power4 branch hints. */ +#define Y_MASK (((unsigned long) 1) << 21) +#define AT1_MASK (((unsigned long) 3) << 21) +#define AT2_MASK (((unsigned long) 9) << 21) +#define BBOY_MASK (BBO_MASK &~ Y_MASK) +#define BBOAT_MASK (BBO_MASK &~ AT1_MASK) + +/* A B form instruction setting the BO field and the condition bits of + the BI field. */ +#define BBOCB(op, bo, cb, aa, lk) \ + (BBO ((op), (bo), (aa), (lk)) | ((((unsigned long)(cb)) & 0x3) << 16)) +#define BBOCB_MASK BBOCB (0x3f, 0x1f, 0x3, 1, 1) + +/* A BBOCB_MASK with the y bit of the BO field removed. */ +#define BBOYCB_MASK (BBOCB_MASK &~ Y_MASK) +#define BBOATCB_MASK (BBOCB_MASK &~ AT1_MASK) +#define BBOAT2CB_MASK (BBOCB_MASK &~ AT2_MASK) + +/* A BBOYCB_MASK in which the BI field is fixed. */ +#define BBOYBI_MASK (BBOYCB_MASK | BI_MASK) +#define BBOATBI_MASK (BBOAT2CB_MASK | BI_MASK) + +/* An Context form instruction. */ +#define CTX(op, xop) (OP (op) | (((unsigned long)(xop)) & 0x7)) +#define CTX_MASK CTX(0x3f, 0x7) + +/* An User Context form instruction. */ +#define UCTX(op, xop) (OP (op) | (((unsigned long)(xop)) & 0x1f)) +#define UCTX_MASK UCTX(0x3f, 0x1f) + +/* The main opcode mask with the RA field clear. */ +#define DRA_MASK (OP_MASK | RA_MASK) + +/* A DS form instruction. */ +#define DSO(op, xop) (OP (op) | ((xop) & 0x3)) +#define DS_MASK DSO (0x3f, 3) + +/* A DE form instruction. */ +#define DEO(op, xop) (OP (op) | ((xop) & 0xf)) +#define DE_MASK DEO (0x3e, 0xf) + +/* An EVSEL form instruction. */ +#define EVSEL(op, xop) (OP (op) | (((unsigned long)(xop)) & 0xff) << 3) +#define EVSEL_MASK EVSEL(0x3f, 0xff) + +/* An M form instruction. */ +#define M(op, rc) (OP (op) | ((rc) & 1)) +#define M_MASK M (0x3f, 1) + +/* An M form instruction with the ME field specified. */ +#define MME(op, me, rc) (M ((op), (rc)) | ((((unsigned long)(me)) & 0x1f) << 1)) + +/* An M_MASK with the MB and ME fields fixed. */ +#define MMBME_MASK (M_MASK | MB_MASK | ME_MASK) + +/* An M_MASK with the SH and ME fields fixed. */ +#define MSHME_MASK (M_MASK | SH_MASK | ME_MASK) + +/* An MD form instruction. */ +#define MD(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0x7) << 2) | ((rc) & 1)) +#define MD_MASK MD (0x3f, 0x7, 1) + +/* An MD_MASK with the MB field fixed. */ +#define MDMB_MASK (MD_MASK | MB6_MASK) + +/* An MD_MASK with the SH field fixed. */ +#define MDSH_MASK (MD_MASK | SH6_MASK) + +/* An MDS form instruction. */ +#define MDS(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0xf) << 1) | ((rc) & 1)) +#define MDS_MASK MDS (0x3f, 0xf, 1) + +/* An MDS_MASK with the MB field fixed. */ +#define MDSMB_MASK (MDS_MASK | MB6_MASK) + +/* An SC form instruction. */ +#define SC(op, sa, lk) (OP (op) | ((((unsigned long)(sa)) & 1) << 1) | ((lk) & 1)) +#define SC_MASK (OP_MASK | (((unsigned long)0x3ff) << 16) | (((unsigned long)1) << 1) | 1) + +/* An VX form instruction. */ +#define VX(op, xop) (OP (op) | (((unsigned long)(xop)) & 0x7ff)) + +/* The mask for an VX form instruction. */ +#define VX_MASK VX(0x3f, 0x7ff) + +/* An VA form instruction. */ +#define VXA(op, xop) (OP (op) | (((unsigned long)(xop)) & 0x03f)) + +/* The mask for an VA form instruction. */ +#define VXA_MASK VXA(0x3f, 0x3f) + +/* An VXR form instruction. */ +#define VXR(op, xop, rc) (OP (op) | (((rc) & 1) << 10) | (((unsigned long)(xop)) & 0x3ff)) + +/* The mask for a VXR form instruction. */ +#define VXR_MASK VXR(0x3f, 0x3ff, 1) + +/* An X form instruction. */ +#define X(op, xop) (OP (op) | ((((unsigned long)(xop)) & 0x3ff) << 1)) + +/* A Z form instruction. */ +#define Z(op, xop) (OP (op) | ((((unsigned long)(xop)) & 0x1ff) << 1)) + +/* An X form instruction with the RC bit specified. */ +#define XRC(op, xop, rc) (X ((op), (xop)) | ((rc) & 1)) + +/* A Z form instruction with the RC bit specified. */ +#define ZRC(op, xop, rc) (Z ((op), (xop)) | ((rc) & 1)) + +/* The mask for an X form instruction. */ +#define X_MASK XRC (0x3f, 0x3ff, 1) + +/* The mask for a Z form instruction. */ +#define Z_MASK ZRC (0x3f, 0x1ff, 1) +#define Z2_MASK ZRC (0x3f, 0xff, 1) + +/* An X_MASK with the RA field fixed. */ +#define XRA_MASK (X_MASK | RA_MASK) + +/* An XRA_MASK with the W field clear. */ +#define XWRA_MASK (XRA_MASK & ~((unsigned long) 1 << 16)) + +/* An X_MASK with the RB field fixed. */ +#define XRB_MASK (X_MASK | RB_MASK) + +/* An X_MASK with the RT field fixed. */ +#define XRT_MASK (X_MASK | RT_MASK) + +/* An XRT_MASK mask with the L bits clear. */ +#define XLRT_MASK (XRT_MASK & ~((unsigned long) 0x3 << 21)) + +/* An X_MASK with the RA and RB fields fixed. */ +#define XRARB_MASK (X_MASK | RA_MASK | RB_MASK) + +/* An XRARB_MASK, but with the L bit clear. */ +#define XRLARB_MASK (XRARB_MASK & ~((unsigned long) 1 << 16)) + +/* An X_MASK with the RT and RA fields fixed. */ +#define XRTRA_MASK (X_MASK | RT_MASK | RA_MASK) + +/* An XRTRA_MASK, but with L bit clear. */ +#define XRTLRA_MASK (XRTRA_MASK & ~((unsigned long) 1 << 21)) + +/* An X form instruction with the L bit specified. */ +#define XOPL(op, xop, l) (X ((op), (xop)) | ((((unsigned long)(l)) & 1) << 21)) + +/* The mask for an X form comparison instruction. */ +#define XCMP_MASK (X_MASK | (((unsigned long)1) << 22)) + +/* The mask for an X form comparison instruction with the L field + fixed. */ +#define XCMPL_MASK (XCMP_MASK | (((unsigned long)1) << 21)) + +/* An X form trap instruction with the TO field specified. */ +#define XTO(op, xop, to) (X ((op), (xop)) | ((((unsigned long)(to)) & 0x1f) << 21)) +#define XTO_MASK (X_MASK | TO_MASK) + +/* An X form tlb instruction with the SH field specified. */ +#define XTLB(op, xop, sh) (X ((op), (xop)) | ((((unsigned long)(sh)) & 0x1f) << 11)) +#define XTLB_MASK (X_MASK | SH_MASK) + +/* An X form sync instruction. */ +#define XSYNC(op, xop, l) (X ((op), (xop)) | ((((unsigned long)(l)) & 3) << 21)) + +/* An X form sync instruction with everything filled in except the LS field. */ +#define XSYNC_MASK (0xff9fffff) + +/* An X_MASK, but with the EH bit clear. */ +#define XEH_MASK (X_MASK & ~((unsigned long )1)) + +/* An X form AltiVec dss instruction. */ +#define XDSS(op, xop, a) (X ((op), (xop)) | ((((unsigned long)(a)) & 1) << 25)) +#define XDSS_MASK XDSS(0x3f, 0x3ff, 1) + +/* An XFL form instruction. */ +#define XFL(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0x3ff) << 1) | (((unsigned long)(rc)) & 1)) +#define XFL_MASK XFL (0x3f, 0x3ff, 1) + +/* An X form isel instruction. */ +#define XISEL(op, xop) (OP (op) | ((((unsigned long)(xop)) & 0x1f) << 1)) +#define XISEL_MASK XISEL(0x3f, 0x1f) + +/* An XL form instruction with the LK field set to 0. */ +#define XL(op, xop) (OP (op) | ((((unsigned long)(xop)) & 0x3ff) << 1)) + +/* An XL form instruction which uses the LK field. */ +#define XLLK(op, xop, lk) (XL ((op), (xop)) | ((lk) & 1)) + +/* The mask for an XL form instruction. */ +#define XL_MASK XLLK (0x3f, 0x3ff, 1) + +/* An XL form instruction which explicitly sets the BO field. */ +#define XLO(op, bo, xop, lk) \ + (XLLK ((op), (xop), (lk)) | ((((unsigned long)(bo)) & 0x1f) << 21)) +#define XLO_MASK (XL_MASK | BO_MASK) + +/* An XL form instruction which explicitly sets the y bit of the BO + field. */ +#define XLYLK(op, xop, y, lk) (XLLK ((op), (xop), (lk)) | ((((unsigned long)(y)) & 1) << 21)) +#define XLYLK_MASK (XL_MASK | Y_MASK) + +/* An XL form instruction which sets the BO field and the condition + bits of the BI field. */ +#define XLOCB(op, bo, cb, xop, lk) \ + (XLO ((op), (bo), (xop), (lk)) | ((((unsigned long)(cb)) & 3) << 16)) +#define XLOCB_MASK XLOCB (0x3f, 0x1f, 0x3, 0x3ff, 1) + +#define BB_MASK (0x1f << 11) +/* An XL_MASK or XLYLK_MASK or XLOCB_MASK with the BB field fixed. */ +#define XLBB_MASK (XL_MASK | BB_MASK) +#define XLYBB_MASK (XLYLK_MASK | BB_MASK) +#define XLBOCBBB_MASK (XLOCB_MASK | BB_MASK) + +/* A mask for branch instructions using the BH field. */ +#define XLBH_MASK (XL_MASK | (0x1c << 11)) + +/* An XL_MASK with the BO and BB fields fixed. */ +#define XLBOBB_MASK (XL_MASK | BO_MASK | BB_MASK) + +/* An XL_MASK with the BO, BI and BB fields fixed. */ +#define XLBOBIBB_MASK (XL_MASK | BO_MASK | BI_MASK | BB_MASK) + +/* An XO form instruction. */ +#define XO(op, xop, oe, rc) \ + (OP (op) | ((((unsigned long)(xop)) & 0x1ff) << 1) | ((((unsigned long)(oe)) & 1) << 10) | (((unsigned long)(rc)) & 1)) +#define XO_MASK XO (0x3f, 0x1ff, 1, 1) + +/* An XO_MASK with the RB field fixed. */ +#define XORB_MASK (XO_MASK | RB_MASK) + +/* An XS form instruction. */ +#define XS(op, xop, rc) (OP (op) | ((((unsigned long)(xop)) & 0x1ff) << 2) | (((unsigned long)(rc)) & 1)) +#define XS_MASK XS (0x3f, 0x1ff, 1) + +/* A mask for the FXM version of an XFX form instruction. */ +#define XFXFXM_MASK (X_MASK | (1 << 11) | (1 << 20)) + +/* An XFX form instruction with the FXM field filled in. */ +#define XFXM(op, xop, fxm, p4) \ + (X ((op), (xop)) | ((((unsigned long)(fxm)) & 0xff) << 12) \ + | ((unsigned long)(p4) << 20)) + +#define SPR_MASK (0x3ff << 11) +/* An XFX form instruction with the SPR field filled in. */ +#define XSPR(op, xop, spr) \ + (X ((op), (xop)) | ((((unsigned long)(spr)) & 0x1f) << 16) | ((((unsigned long)(spr)) & 0x3e0) << 6)) +#define XSPR_MASK (X_MASK | SPR_MASK) + +/* An XFX form instruction with the SPR field filled in except for the + SPRBAT field. */ +#define XSPRBAT_MASK (XSPR_MASK &~ SPRBAT_MASK) + +/* An XFX form instruction with the SPR field filled in except for the + SPRG field. */ +#define XSPRG_MASK (XSPR_MASK & ~(0x1f << 16)) + +/* An X form instruction with everything filled in except the E field. */ +#define XE_MASK (0xffff7fff) + +/* An X form user context instruction. */ +#define XUC(op, xop) (OP (op) | (((unsigned long)(xop)) & 0x1f)) +#define XUC_MASK XUC(0x3f, 0x1f) + +/* The BO encodings used in extended conditional branch mnemonics. */ +#define BODNZF (0x0) +#define BODNZFP (0x1) +#define BODZF (0x2) +#define BODZFP (0x3) +#define BODNZT (0x8) +#define BODNZTP (0x9) +#define BODZT (0xa) +#define BODZTP (0xb) + +#define BOF (0x4) +#define BOFP (0x5) +#define BOFM4 (0x6) +#define BOFP4 (0x7) +#define BOT (0xc) +#define BOTP (0xd) +#define BOTM4 (0xe) +#define BOTP4 (0xf) + +#define BODNZ (0x10) +#define BODNZP (0x11) +#define BODZ (0x12) +#define BODZP (0x13) +#define BODNZM4 (0x18) +#define BODNZP4 (0x19) +#define BODZM4 (0x1a) +#define BODZP4 (0x1b) + +#define BOU (0x14) + +/* The BI condition bit encodings used in extended conditional branch + mnemonics. */ +#define CBLT (0) +#define CBGT (1) +#define CBEQ (2) +#define CBSO (3) + +/* The TO encodings used in extended trap mnemonics. */ +#define TOLGT (0x1) +#define TOLLT (0x2) +#define TOEQ (0x4) +#define TOLGE (0x5) +#define TOLNL (0x5) +#define TOLLE (0x6) +#define TOLNG (0x6) +#define TOGT (0x8) +#define TOGE (0xc) +#define TONL (0xc) +#define TOLT (0x10) +#define TOLE (0x14) +#define TONG (0x14) +#define TONE (0x18) +#define TOU (0x1f) + +/* Smaller names for the flags so each entry in the opcodes table will + fit on a single line. */ +#undef PPC +#define PPC PPC_OPCODE_PPC +#define PPCCOM PPC_OPCODE_PPC | PPC_OPCODE_COMMON +#define PPC64 PPC_OPCODE_64 | PPC_OPCODE_PPC +#define COM PPC_OPCODE_POWER | PPC_OPCODE_PPC | PPC_OPCODE_COMMON +#define COM32 PPC_OPCODE_POWER | PPC_OPCODE_PPC | PPC_OPCODE_COMMON | PPC_OPCODE_32 + +/* The opcode table. + + The format of the opcode table is: + + NAME OPCODE MASK FLAGS { OPERANDS } + + NAME is the name of the instruction. + OPCODE is the instruction opcode. + MASK is the opcode mask; this is used to tell the disassembler + which bits in the actual opcode must match OPCODE. + FLAGS are flags indicated what processors support the instruction. + OPERANDS is the list of operands. + + The disassembler reads the table in order and prints the first + instruction which matches, so this table is sorted to put more + specific instructions before more general instructions. It is also + sorted by major opcode. */ + +static const struct powerpc_opcode powerpc_opcodes[] = { + +{ "cmplwi", OPL(10,0), OPL_MASK, PPCCOM, { OBF, RA, UI } }, +{ "cmpwi", OPL(11,0), OPL_MASK, PPCCOM, { OBF, RA, SI } }, +{ "cmpw", XOPL(31,0,0), XCMPL_MASK, PPCCOM, { OBF, RA, RB } }, +{ "cmplw", XOPL(31,32,0), XCMPL_MASK, PPCCOM, { OBF, RA, RB } }, +{ "fcmpu", X(63,0), X_MASK|(3<<21), COM, { BF, FRA, FRB } }, + +{ "li", OP(14), DRA_MASK, PPCCOM, { RT, SI } }, +{ "lis", OP(15), DRA_MASK, PPCCOM, { RT, SI } }, + +{ "addi", OP(14), OP_MASK, PPCCOM, { RT, RA0, SI } }, +{ "addis", OP(15), OP_MASK, PPCCOM, { RT,RA0,SI } }, +{ "blt-", BBOCB(16,BOT,CBLT,0,0), BBOATCB_MASK, PPCCOM, { CR, BDM } }, +{ "bc", B(16,0,0), B_MASK, COM, { BO, BI, BD } }, +{ "bcl", B(16,0,1), B_MASK, COM, { BO, BI, BD } }, +{ "b", B(18,0,0), B_MASK, COM, { LI } }, +{ "bl", B(18,0,1), B_MASK, COM, { LI } }, +{ "blr", XLO(19,BOU,16,0), XLBOBIBB_MASK, PPCCOM, { 0 } }, +{ "bctr", XLO(19,BOU,528,0), XLBOBIBB_MASK, COM, { 0 } }, +{ "bctrl", XLO(19,BOU,528,1), XLBOBIBB_MASK, COM, { 0 } }, + +{ "rlwinm", M(21,0), M_MASK, PPCCOM, { RA,RS,SH,MBE,ME } }, +{ "nop", OP(24), 0xffffffff, PPCCOM, { 0 } }, +{ "ori", OP(24), OP_MASK, PPCCOM, { RA, RS, UI } }, +{ "xoris", OP(27), OP_MASK, PPCCOM, { RA, RS, UI } }, +{ "ldx", X(31,21), X_MASK, PPC64, { RT, RA0, RB } }, +{ "lwzx", X(31,23), X_MASK, PPCCOM, { RT, RA0, RB } }, +{ "slw", XRC(31,24,0), X_MASK, PPCCOM, { RA, RS, RB } }, +{ "and", XRC(31,28,0), X_MASK, COM, { RA, RS, RB } }, +{ "sub", XO(31,40,0,0), XO_MASK, PPC, { RT, RB, RA } }, +{ "lbzx", X(31,87), X_MASK, COM, { RT, RA0, RB } }, +{ "neg", XO(31,104,0,0), XORB_MASK, COM, { RT, RA } }, +{ "not", XRC(31,124,0), X_MASK, COM, { RA, RS, RBS } }, +{ "stwx", X(31,151), X_MASK, PPCCOM, { RS, RA0, RB } }, +{ "stbx", X(31,215), X_MASK, COM, { RS, RA0, RB } }, +{ "mullw", XO(31,235,0,0), XO_MASK, PPCCOM, { RT, RA, RB } }, +{ "add", XO(31,266,0,0), XO_MASK, PPCCOM, { RT, RA, RB } }, +{ "lhzx", X(31,279), X_MASK, COM, { RT, RA0, RB } }, +{ "xor", XRC(31,316,0), X_MASK, COM, { RA, RS, RB } }, +{ "mflr", XSPR(31,339,8), XSPR_MASK, COM, { RT } }, +{ "sthx", X(31,407), X_MASK, COM, { RS, RA0, RB } }, +{ "mr", XRC(31,444,0), X_MASK, COM, { RA, RS, RBS } }, +{ "or", XRC(31,444,0), X_MASK, COM, { RA, RS, RB } }, +{ "divwu", XO(31,459,0,0), XO_MASK, PPC, { RT, RA, RB } }, +{ "mtlr", XSPR(31,467,8), XSPR_MASK, COM, { RS } }, +{ "mtctr", XSPR(31,467,9), XSPR_MASK, COM, { RS } }, +{ "divw", XO(31,491,0,0), XO_MASK, PPC, { RT, RA, RB } }, +{ "lfsx", X(31,535), X_MASK, COM, { FRT, RA0, RB } }, +{ "srw", XRC(31,536,0), X_MASK, PPCCOM, { RA, RS, RB } }, +{ "stfsx", X(31,663), X_MASK, COM, { FRS, RA0, RB } }, +{ "sraw", XRC(31,792,0), X_MASK, PPCCOM, { RA, RS, RB } }, +{ "extsh", XRC(31,922,0), XRB_MASK, PPCCOM, { RA, RS } }, +{ "extsb", XRC(31,954,0), XRB_MASK, PPC, { RA, RS} }, + +{ "lwz", OP(32), OP_MASK, PPCCOM, { RT, D, RA0 } }, +{ "lbz", OP(34), OP_MASK, COM, { RT, D, RA0 } }, +{ "stw", OP(36), OP_MASK, PPCCOM, { RS, D, RA0 } }, +{ "stwu", OP(37), OP_MASK, PPCCOM, { RS, D, RAS } }, +{ "stb", OP(38), OP_MASK, COM, { RS, D, RA0 } }, +{ "lhz", OP(40), OP_MASK, COM, { RT, D, RA0 } }, +{ "sth", OP(44), OP_MASK, COM, { RS, D, RA0 } }, +{ "lfs", OP(48), OP_MASK, COM, { FRT, D, RA0 } }, +{ "lfd", OP(50), OP_MASK, COM, { FRT, D, RA0 } }, +{ "stfs", OP(52), OP_MASK, COM, { FRS, D, RA0 } }, +{ "stfd", OP(54), OP_MASK, COM, { FRS, D, RA0 } }, +{ "ld", DSO(58,0), DS_MASK, PPC64, { RT, DS, RA0 } }, + +{ "fdivs", A(59,18,0), AFRC_MASK, PPC, { FRT, FRA, FRB } }, +{ "fsubs", A(59,20,0), AFRC_MASK, PPC, { FRT, FRA, FRB } }, +{ "fadds", A(59,21,0), AFRC_MASK, PPC, { FRT, FRA, FRB } }, +{ "fmuls", A(59,25,0), AFRB_MASK, PPC, { FRT, FRA, FRC } }, +{ "std", DSO(62,0), DS_MASK, PPC64, { RS, DS, RA0 } }, +{ "stdu", DSO(62,1), DS_MASK, PPC64, { RS, DS, RAS } }, +{ "frsp", XRC(63,12,0), XRA_MASK, COM, { FRT, FRB } }, +{ "fctiwz", XRC(63,15,0), XRA_MASK, PPCCOM, { FRT, FRB } }, +{ "fsub", A(63,20,0), AFRC_MASK, PPCCOM, { FRT, FRA, FRB } }, +{ "fneg", XRC(63,40,0), XRA_MASK, COM, { FRT, FRB } }, +}; diff --git a/src/qcommon/vm_powerpc_asm.h b/src/qcommon/vm_powerpc_asm.h new file mode 100644 index 0000000..68e6984 --- /dev/null +++ b/src/qcommon/vm_powerpc_asm.h @@ -0,0 +1,156 @@ +/* +=========================================================================== +Copyright (C) 2008 Przemyslaw Iskra + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef VM_POWERPC_ASM_H +#define VM_POWERPC_ASM_H + +/* + * Register information according to: + * http://refspecs.freestandards.org/elf/elfspec_ppc.pdf + */ + +#define r0 0 // volatile +#define r1 1 // caller safe ( stack pointer ) +#define r2 2 // reserved +#define r3 3 // callee safe +#define r4 4 // callee safe +#define r5 5 // callee safe +#define r6 6 // callee safe +#define r7 7 // callee safe +#define r8 8 // callee safe +#define r9 9 // callee safe +#define r10 10 // callee safe +#define r11 11 // volatile +#define r12 12 // volatile +#define r13 13 // reserved ( small data area ) +#define r14 14 // caller safe +#define r15 15 // caller safe +#define r16 16 // caller safe +#define r17 17 // caller safe +#define r18 18 // caller safe +#define r19 19 // caller safe +#define r20 20 // caller safe +#define r21 21 // caller safe +#define r22 22 // caller safe +#define r23 23 // caller safe +#define r24 24 // caller safe +#define r25 25 // caller safe +#define r26 26 // caller safe +#define r27 27 // caller safe +#define r28 28 // caller safe +#define r29 29 // caller safe +#define r30 30 // caller safe +#define r31 31 // caller safe ( environment pointers ) + +#define f0 0 // callee safe +#define f1 1 // callee safe +#define f2 2 // callee safe +#define f3 3 // callee safe +#define f4 4 // callee safe +#define f5 5 // callee safe +#define f6 6 // callee safe +#define f7 7 // callee safe +#define f8 8 // callee safe +#define f9 9 // callee safe +#define f10 10 // callee safe +#define f11 11 // callee safe +#define f12 12 // callee safe +#define f13 13 // callee safe +#define f14 14 // caller safe +#define f15 15 // caller safe +#define f16 16 // caller safe +#define f17 17 // caller safe +#define f18 18 // caller safe +#define f19 19 // caller safe +#define f20 20 // caller safe +#define f21 21 // caller safe +#define f22 22 // caller safe +#define f23 23 // caller safe +#define f24 24 // caller safe +#define f25 25 // caller safe +#define f26 26 // caller safe +#define f27 27 // caller safe +#define f28 28 // caller safe +#define f29 29 // caller safe +#define f30 30 // caller safe +#define f31 31 // caller safe + +#define cr0 0 // volatile +#define cr1 1 // volatile +#define cr2 2 // caller safe +#define cr3 3 // caller safe +#define cr4 4 // caller safe +#define cr5 5 // volatile +#define cr6 6 // volatile +#define cr7 7 // volatile + +#define lt 0 +#define gt 1 +#define eq 2 +#define so 3 + +// branch bo field values +#define branchLikely 1 +#define branchFalse 4 +#define branchTrue 12 +#define branchAlways 20 + +// branch extensions (change branch type) +#define branchExtLink 0x0001 + + +/* + * This list must match exactly the powerpc_opcodes list from vm_powerpc_asm.c + * If you're changing the original list remember to regenerate this one. You + * may do so using this perl script: + perl -p -e 'BEGIN{%t=("-"=>m=>"+"=>p=>"."=>d=>);$l=""}$o=0 if/^}/; + if($o && s/^{ "(.*?)([\.+-])?".+/i\U$1\E$t{$2}/s){$_.="_" while$l{$_}; + $l{$_}=1;if(length $l.$_ > 70){$s=$_;$_="\t$l\n";$l="$s,"}else + {$l.=" $_,";$_=undef}}else{$o=1 if/powerpc_opcodes.*=/;$_=undef}; + END{print "\t$l\n"}' < vm_powerpc_asm.c + */ + +typedef enum powerpc_iname { + iCMPLWI, iCMPWI, iCMPW, iCMPLW, iFCMPU, iLI, iLIS, iADDI, iADDIS, + iBLTm, iBC, iBCL, iB, iBL, iBLR, iBCTR, iBCTRL, iRLWINM, iNOP, iORI, + iXORIS, iLDX, iLWZX, iSLW, iAND, iSUB, iLBZX, iNEG, iNOT, iSTWX, iSTBX, + iMULLW, iADD, iLHZX, iXOR, iMFLR, iSTHX, iMR, iOR, iDIVWU, iMTLR, + iMTCTR, iDIVW, iLFSX, iSRW, iSTFSX, iSRAW, iEXTSH, iEXTSB, iLWZ, iLBZ, + iSTW, iSTWU, iSTB, iLHZ, iSTH, iLFS, iLFD, iSTFS, iSTFD, iLD, iFDIVS, + iFSUBS, iFADDS, iFMULS, iSTD, iSTDU, iFRSP, iFCTIWZ, iFSUB, iFNEG, +} powerpc_iname_t; + +#include + +typedef uint32_t ppc_instruction_t; + +extern ppc_instruction_t +asm_instruction( powerpc_iname_t, const int, const long int * ); + +#define IN( inst, args... ) \ +({\ + const long int argv[] = { args };\ + const int argc = sizeof( argv ) / sizeof( argv[0] ); \ + asm_instruction( inst, argc, argv );\ +}) + +#endif diff --git a/src/qcommon/vm_sparc.c b/src/qcommon/vm_sparc.c new file mode 100644 index 0000000..370ee20 --- /dev/null +++ b/src/qcommon/vm_sparc.c @@ -0,0 +1,1648 @@ +/* +=========================================================================== +Copyright (C) 2009 David S. Miller + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/* This code is based almost entirely upon the vm_powerpc.c code by + * Przemyslaw Iskra. All I did was make it work on Sparc :-) -DaveM + */ + +#include +#include +#include +#include +#include + +#include "vm_local.h" +#include "vm_sparc.h" + +/* exit() won't be called but use it because it is marked with noreturn */ +#define DIE( reason ) \ + do { \ + Com_Error(ERR_DROP, "vm_sparc compiler error: " reason "\n"); \ + exit(1); \ + } while(0) + +/* Select Length - first value on 32 bits, second on 64 */ +#ifdef __arch64__ +#define SL(a, b) (b) +#else +#define SL(a, b) (a) +#endif + +#define rTMP G1 +#define rVMDATA G2 +#define rPSTACK G3 +#define rDATABASE G4 +#define rDATAMASK G5 + +struct sparc_opcode { + const char *name; + unsigned int opcode; + unsigned int mask; + unsigned char args[4]; +#define ARG_NONE 0 +#define ARG_RS1 1 +#define ARG_RS2 2 +#define ARG_RD 3 +#define ARG_SIMM13 4 +#define ARG_DISP30 5 +#define ARG_IMM22 6 +#define ARG_DISP22 7 +#define ARG_SWTRAP 8 +}; + +#define ARG_RS1_RS2_RD { ARG_RS1, ARG_RS2, ARG_RD } +#define ARG_RS1_SIMM13_RD { ARG_RS1, ARG_SIMM13, ARG_RD } +#define ARG_RS1_RS2 { ARG_RS1, ARG_RS2 } +#define ARG_RS2_RD { ARG_RS2, ARG_RD } + +#define OP_MASK 0xc0000000 +#define OP2_MASK 0x01c00000 +#define OP3_MASK 0x01f80000 +#define OPF_MASK 0x00003fe0 + +#define IMM 0x00002000 + +#define FMT1(op) ((op) << 30), OP_MASK +#define FMT2(op,op2) ((op) << 30)|((op2)<<22), (OP_MASK | OP2_MASK) +#define FMT3(op,op3) ((op) << 30)|((op3)<<19), (OP_MASK | OP3_MASK | IMM) +#define FMT3I(op,op3) ((op) << 30)|((op3)<<19)|IMM, (OP_MASK | OP3_MASK | IMM) +#define FMT3F(op,op3,opf) ((op) << 30)|((op3)<<19)|((opf)<<5), \ + (OP_MASK | OP3_MASK | OPF_MASK) + +#define BICC(A,COND) FMT2(0,((A<<7)|(COND<<3)|0x2)) +#define BFCC(A,COND) FMT2(0,((A<<7)|(COND<<3)|0x6)) +#define TICC(COND) FMT3I(0,((COND<<6)|0x3a)) + +enum sparc_iname { + CALL, NOP, SETHI, + + BA, BN, BNE, BE, BG, BLE, BGE, BL, BGU, BLEU, BCC, BCS, + BPOS, BNEG, BVC, BVS, + + ADDI, ADD, + ANDI, AND, + ORI, OR, + XORI, XOR, + SUBI, SUB, + ANDNI, ANDN, + ORNI, ORN, + XNORI, XNOR, + + UMULI, UMUL, + SMULI, SMUL, + UDIVI, UDIV, + SDIVI, SDIV, + + SUBCCI, SUBCC, + + SLLI, SLL, + SRLI, SRL, + SRAI, SRA, + + WRI, WR, + + SAVEI, SAVE, + RESTOREI, RESTORE, + + TA, + + JMPLI, JMPL, + + LDXI, LDX, + LDUWI, LDUW, + LDUHI, LDUH, + LDUBI, LDUB, + + STXI, STX, + STWI, STW, + STHI, STH, + STBI, STB, + + LDFI, LDF, + STFI, STF, + + FADD, FSUB, FCMP, FSTOI, FITOS, FNEG, FDIV, FMUL, + FBE, FBNE, FBL, FBGE, FBG, FBLE, +}; + +#define LDLI SL(LDUWI, LDXI) +#define LDL SL(LDUW, LDX) +#define STLI SL(STWI, STXI) +#define STL SL(STW, STX) + +#define SPARC_NOP 0x01000000 + +static const struct sparc_opcode sparc_opcodes[] = { + { "call", FMT1(1), { ARG_DISP30 }, }, + { "nop", SPARC_NOP, 0xffffffff, { ARG_NONE }, }, /* sethi %hi(0), %g0 */ + { "sethi", FMT2(0,4), { ARG_IMM22, ARG_RD }, }, + { "ba", BICC(0,8), { ARG_DISP22 }, }, + { "bn", BICC(0,0), { ARG_DISP22 }, }, + { "bne", BICC(0,9), { ARG_DISP22 }, }, + { "be", BICC(0,1), { ARG_DISP22 }, }, + { "bg", BICC(0,10), { ARG_DISP22 }, }, + { "ble", BICC(0,2), { ARG_DISP22 }, }, + { "bge", BICC(0,11), { ARG_DISP22 }, }, + { "bl", BICC(0,3), { ARG_DISP22 }, }, + { "bgu", BICC(0,12), { ARG_DISP22 }, }, + { "bleu", BICC(0,4), { ARG_DISP22 }, }, + { "bcc", BICC(0,13), { ARG_DISP22 }, }, + { "bcs", BICC(0,5), { ARG_DISP22 }, }, + { "bpos", BICC(0,14), { ARG_DISP22 }, }, + { "bneg", BICC(0,6), { ARG_DISP22 }, }, + { "bvc", BICC(0,15), { ARG_DISP22 }, }, + { "bvs", BICC(0,7), { ARG_DISP22 }, }, + + { "add", FMT3I(2, 0x00), ARG_RS1_SIMM13_RD, }, + { "add", FMT3 (2, 0x00), ARG_RS1_RS2_RD, }, + { "and", FMT3I(2, 0x01), ARG_RS1_SIMM13_RD, }, + { "and", FMT3 (2, 0x01), ARG_RS1_RS2_RD, }, + { "or", FMT3I(2, 0x02), ARG_RS1_SIMM13_RD, }, + { "or", FMT3 (2, 0x02), ARG_RS1_RS2_RD, }, + { "xor", FMT3I(2, 0x03), ARG_RS1_SIMM13_RD, }, + { "xor", FMT3 (2, 0x03), ARG_RS1_RS2_RD, }, + { "sub", FMT3I(2, 0x04), ARG_RS1_SIMM13_RD, }, + { "sub", FMT3 (2, 0x04), ARG_RS1_RS2_RD, }, + { "andn", FMT3I(2, 0x05), ARG_RS1_SIMM13_RD, }, + { "andn", FMT3 (2, 0x05), ARG_RS1_RS2_RD, }, + { "orn", FMT3I(2, 0x06), ARG_RS1_SIMM13_RD, }, + { "orn", FMT3 (2, 0x06), ARG_RS1_RS2_RD, }, + { "xnor", FMT3I(2, 0x07), ARG_RS1_SIMM13_RD, }, + { "xnor", FMT3 (2, 0x07), ARG_RS1_RS2_RD, }, + + { "umul", FMT3I(2, 0x0a), ARG_RS1_SIMM13_RD, }, + { "umul", FMT3 (2, 0x0a), ARG_RS1_RS2_RD, }, + { "smul", FMT3I(2, 0x0b), ARG_RS1_SIMM13_RD, }, + { "smul", FMT3 (2, 0x0b), ARG_RS1_RS2_RD, }, + { "udiv", FMT3I(2, 0x0e), ARG_RS1_SIMM13_RD, }, + { "udiv", FMT3 (2, 0x0e), ARG_RS1_RS2_RD, }, + { "sdiv", FMT3I(2, 0x0f), ARG_RS1_SIMM13_RD, }, + { "sdiv", FMT3 (2, 0x0f), ARG_RS1_RS2_RD, }, + + { "subcc", FMT3I(2, 0x14), ARG_RS1_SIMM13_RD, }, + { "subcc", FMT3 (2, 0x14), ARG_RS1_RS2_RD, }, + + { "sll", FMT3I(2, 0x25), ARG_RS1_SIMM13_RD, }, + { "sll", FMT3 (2, 0x25), ARG_RS1_RS2_RD, }, + { "srl", FMT3I(2, 0x26), ARG_RS1_SIMM13_RD, }, + { "srl", FMT3 (2, 0x26), ARG_RS1_RS2_RD, }, + { "sra", FMT3I(2, 0x27), ARG_RS1_SIMM13_RD, }, + { "sra", FMT3 (2, 0x27), ARG_RS1_RS2_RD, }, + + { "wr", FMT3I(2, 0x30), ARG_RS1_SIMM13_RD, }, + { "wr", FMT3 (2, 0x30), ARG_RS1_SIMM13_RD, }, + + { "save", FMT3I(2,0x3c), ARG_RS1_SIMM13_RD, }, + { "save", FMT3 (2,0x3c), ARG_RS1_RS2_RD, }, + { "restore", FMT3I(2,0x3d), ARG_RS1_SIMM13_RD, }, + { "restore", FMT3 (2,0x3d), ARG_RS1_RS2_RD, }, + { "ta", TICC(8), { ARG_SWTRAP, ARG_NONE }, }, + { "jmpl", FMT3I(2,0x38), ARG_RS1_SIMM13_RD, }, + { "jmpl", FMT3 (2,0x38), ARG_RS1_RS2_RD, }, + + { "ldx", FMT3I(3,0x0b), ARG_RS1_SIMM13_RD, }, + { "ldx", FMT3 (3,0x0b), ARG_RS1_RS2_RD, }, + { "lduw", FMT3I(3,0x00), ARG_RS1_SIMM13_RD, }, + { "lduw", FMT3 (3,0x00), ARG_RS1_RS2_RD, }, + { "lduh", FMT3I(3,0x02), ARG_RS1_SIMM13_RD, }, + { "lduh", FMT3 (3,0x02), ARG_RS1_RS2_RD, }, + { "ldub", FMT3I(3,0x01), ARG_RS1_SIMM13_RD, }, + { "ldub", FMT3 (3,0x01), ARG_RS1_RS2_RD, }, + + { "stx", FMT3I(3,0x0e), ARG_RS1_SIMM13_RD, }, + { "stx", FMT3 (3,0x0e), ARG_RS1_RS2_RD, }, + { "stw", FMT3I(3,0x04), ARG_RS1_SIMM13_RD, }, + { "stw", FMT3 (3,0x04), ARG_RS1_RS2_RD, }, + { "sth", FMT3I(3,0x06), ARG_RS1_SIMM13_RD, }, + { "sth", FMT3 (3,0x06), ARG_RS1_RS2_RD, }, + { "stb", FMT3I(3,0x05), ARG_RS1_SIMM13_RD, }, + { "stb", FMT3 (3,0x05), ARG_RS1_RS2_RD, }, + + { "ldf", FMT3I(3,0x20), ARG_RS1_SIMM13_RD, }, + { "ldf", FMT3 (3,0x20), ARG_RS1_RS2_RD, }, + { "stf", FMT3I(3,0x24), ARG_RS1_SIMM13_RD, }, + { "stf", FMT3 (3,0x24), ARG_RS1_RS2_RD, }, + + { "fadd", FMT3F(2,0x34,0x041), ARG_RS1_RS2_RD, }, + { "fsub", FMT3F(2,0x34,0x045), ARG_RS1_RS2_RD, }, + { "fcmp", FMT3F(2,0x35,0x051), ARG_RS1_RS2, }, + { "fstoi", FMT3F(2,0x34,0x0d1), ARG_RS2_RD, }, + { "fitos", FMT3F(2,0x34,0x0c4), ARG_RS2_RD, }, + + { "fneg", FMT3F(2,0x34,0x005), ARG_RS2_RD, }, + { "fdiv", FMT3F(2,0x34,0x04d), ARG_RS1_RS2_RD, }, + { "fmul", FMT3F(2,0x34,0x049), ARG_RS1_RS2_RD, }, + + { "fbe", BFCC(0,9), { ARG_DISP22 }, }, + { "fbne", BFCC(0,1), { ARG_DISP22 }, }, + { "fbl", BFCC(0,4), { ARG_DISP22 }, }, + { "fbge", BFCC(0,11), { ARG_DISP22 }, }, + { "fbg", BFCC(0,6), { ARG_DISP22 }, }, + { "fble", BFCC(0,13), { ARG_DISP22 }, }, +}; +#define SPARC_NUM_OPCODES (ARRAY_LEN(sparc_opcodes)) + +#define RS1(X) (((X) & 0x1f) << 14) +#define RS2(X) (((X) & 0x1f) << 0) +#define RD(X) (((X) & 0x1f) << 25) +#define SIMM13(X) (((X) & 0x1fff) << 0) +#define IMM22(X) (((X) & 0x3fffff) << 0) +#define DISP30(X) ((((X) >> 2) & 0x3fffffff) << 0) +#define DISP22(X) ((((X) >> 2) & 0x3fffff) << 0) +#define SWTRAP(X) (((X) & 0x7f) << 0) + +#define SIMM13_P(X) ((unsigned int) (X) + 0x1000 < 0x2000) + +static void vimm(unsigned int val, int bits, int shift, int sgned, int arg_index) +{ + unsigned int orig_val = val; + int orig_bits = bits; + + if (sgned) { + int x = (int) val; + if (x < 0) + x = -x; + val = (unsigned int) x; + bits--; + } + if (val & ~((1U << bits) - 1U)) { + Com_Printf("VM ERROR: immediate value 0x%08x out of %d bit range\n", + orig_val, orig_bits); + DIE("sparc VM bug"); + } +} + +static unsigned int sparc_assemble(enum sparc_iname iname, const int argc, const int *argv) +{ + const struct sparc_opcode *op = &sparc_opcodes[iname]; + unsigned int insn = op->opcode; + int i, flt, rd_flt; + + flt = (op->name[0] == 'f'); + rd_flt = flt || (op->name[2] == 'f'); + + for (i = 0; op->args[i] != ARG_NONE; i++) { + int val = argv[i]; + + switch (op->args[i]) { + case ARG_RS1: insn |= RS1(val); break; + case ARG_RS2: insn |= RS2(val); break; + case ARG_RD: insn |= RD(val); break; + case ARG_SIMM13: insn |= SIMM13(val); vimm(val,13,0,1,i); break; + case ARG_DISP30: insn |= DISP30(val); vimm(val,30,0,1,i); break; + case ARG_IMM22: insn |= IMM22(val); vimm(val,22,0,0,i); break; + case ARG_DISP22: insn |= DISP22(val); vimm(val,22,0,1,i); break; + case ARG_SWTRAP: insn |= SWTRAP(val); vimm(val,7,0,0,i); break; + } + } + + return insn; +} + +#define IN(inst, args...) \ +({ const int argv[] = { args }; \ + const int argc = ARRAY_LEN(argv); \ + sparc_assemble(inst, argc, argv); \ +}) + +#if 0 +static void pgreg(int reg_num, int arg_index, int flt) +{ + if (!flt) { + const char *fmt[] = { "%g", "%o", "%l", "%i" }; + + Com_Printf("%s%s%d", + (arg_index ? ", " : ""), + fmt[reg_num >> 3], reg_num & 7); + } else + Com_Printf("%s%%f%d", (arg_index ? ", " : ""), reg_num); +} + +static void pimm(unsigned int val, int bits, int shift, int sgned, int arg_index) + +{ + val >>= shift; + val &= ((1 << bits) - 1); + if (sgned) { + int sval = val << (32 - bits); + sval >>= (32 - bits); + Com_Printf("%s%d", + (arg_index ? ", " : ""), sval); + } else + Com_Printf("%s0x%08x", + (arg_index ? ", " : ""), val); +} + +static void sparc_disassemble(unsigned int insn) +{ + int op_idx; + + for (op_idx = 0; op_idx < SPARC_NUM_OPCODES; op_idx++) { + const struct sparc_opcode *op = &sparc_opcodes[op_idx]; + int i, flt, rd_flt; + + if ((insn & op->mask) != op->opcode) + continue; + + flt = (op->name[0] == 'f'); + rd_flt = flt || (op->name[2] == 'f'); + + Com_Printf("ASM: %7s\t", op->name); + for (i = 0; op->args[i] != ARG_NONE; i++) { + switch (op->args[i]) { + case ARG_RS1: pgreg((insn >> 14) & 0x1f, i, flt); break; + case ARG_RS2: pgreg((insn >> 0) & 0x1f, i, flt); break; + case ARG_RD: pgreg((insn >> 25) & 0x1f, i, rd_flt); break; + case ARG_SIMM13: pimm(insn, 13, 0, 1, i); break; + case ARG_DISP30: pimm(insn, 30, 0, 0, i); break; + case ARG_IMM22: pimm(insn, 22, 0, 0, i); break; + case ARG_DISP22: pimm(insn, 22, 0, 0, i); break; + case ARG_SWTRAP: pimm(insn, 7, 0, 0, i); break; + } + } + Com_Printf("\n"); + return; + } +} +#endif + +/* + * opcode information table: + * - length of immediate value + * - returned register type + * - required register(s) type + */ +#define opImm0 0x0000 /* no immediate */ +#define opImm1 0x0001 /* 1 byte immadiate value after opcode */ +#define opImm4 0x0002 /* 4 bytes immediate value after opcode */ + +#define opRet0 0x0000 /* returns nothing */ +#define opRetI 0x0004 /* returns integer */ +#define opRetF 0x0008 /* returns float */ +#define opRetIF (opRetI | opRetF) /* returns integer or float */ + +#define opArg0 0x0000 /* requires nothing */ +#define opArgI 0x0010 /* requires integer(s) */ +#define opArgF 0x0020 /* requires float(s) */ +#define opArgIF (opArgI | opArgF) /* requires integer or float */ + +#define opArg2I 0x0040 /* requires second argument, integer */ +#define opArg2F 0x0080 /* requires second argument, float */ +#define opArg2IF (opArg2I | opArg2F) /* requires second argument, integer or float */ + +static const unsigned char vm_opInfo[256] = +{ + [OP_UNDEF] = opImm0, + [OP_IGNORE] = opImm0, + [OP_BREAK] = opImm0, + [OP_ENTER] = opImm4, + /* OP_LEAVE has to accept floats, they will be converted to ints */ + [OP_LEAVE] = opImm4 | opRet0 | opArgIF, + /* only STORE4 and POP use values from OP_CALL, + * no need to convert floats back */ + [OP_CALL] = opImm0 | opRetI | opArgI, + [OP_PUSH] = opImm0 | opRetIF, + [OP_POP] = opImm0 | opRet0 | opArgIF, + [OP_CONST] = opImm4 | opRetIF, + [OP_LOCAL] = opImm4 | opRetI, + [OP_JUMP] = opImm0 | opRet0 | opArgI, + + [OP_EQ] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_NE] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_LTI] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_LEI] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_GTI] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_GEI] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_LTU] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_LEU] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_GTU] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_GEU] = opImm4 | opRet0 | opArgI | opArg2I, + [OP_EQF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_NEF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_LTF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_LEF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_GTF] = opImm4 | opRet0 | opArgF | opArg2F, + [OP_GEF] = opImm4 | opRet0 | opArgF | opArg2F, + + [OP_LOAD1] = opImm0 | opRetI | opArgI, + [OP_LOAD2] = opImm0 | opRetI | opArgI, + [OP_LOAD4] = opImm0 | opRetIF| opArgI, + [OP_STORE1] = opImm0 | opRet0 | opArgI | opArg2I, + [OP_STORE2] = opImm0 | opRet0 | opArgI | opArg2I, + [OP_STORE4] = opImm0 | opRet0 | opArgIF| opArg2I, + [OP_ARG] = opImm1 | opRet0 | opArgIF, + [OP_BLOCK_COPY] = opImm4 | opRet0 | opArgI | opArg2I, + + [OP_SEX8] = opImm0 | opRetI | opArgI, + [OP_SEX16] = opImm0 | opRetI | opArgI, + [OP_NEGI] = opImm0 | opRetI | opArgI, + [OP_ADD] = opImm0 | opRetI | opArgI | opArg2I, + [OP_SUB] = opImm0 | opRetI | opArgI | opArg2I, + [OP_DIVI] = opImm0 | opRetI | opArgI | opArg2I, + [OP_DIVU] = opImm0 | opRetI | opArgI | opArg2I, + [OP_MODI] = opImm0 | opRetI | opArgI | opArg2I, + [OP_MODU] = opImm0 | opRetI | opArgI | opArg2I, + [OP_MULI] = opImm0 | opRetI | opArgI | opArg2I, + [OP_MULU] = opImm0 | opRetI | opArgI | opArg2I, + [OP_BAND] = opImm0 | opRetI | opArgI | opArg2I, + [OP_BOR] = opImm0 | opRetI | opArgI | opArg2I, + [OP_BXOR] = opImm0 | opRetI | opArgI | opArg2I, + [OP_BCOM] = opImm0 | opRetI | opArgI, + [OP_LSH] = opImm0 | opRetI | opArgI | opArg2I, + [OP_RSHI] = opImm0 | opRetI | opArgI | opArg2I, + [OP_RSHU] = opImm0 | opRetI | opArgI | opArg2I, + [OP_NEGF] = opImm0 | opRetF | opArgF, + [OP_ADDF] = opImm0 | opRetF | opArgF | opArg2F, + [OP_SUBF] = opImm0 | opRetF | opArgF | opArg2F, + [OP_DIVF] = opImm0 | opRetF | opArgF | opArg2F, + [OP_MULF] = opImm0 | opRetF | opArgF | opArg2F, + [OP_CVIF] = opImm0 | opRetF | opArgI, + [OP_CVFI] = opImm0 | opRetI | opArgF, +}; + +static const char *opnames[256] = { + "OP_UNDEF", "OP_IGNORE", "OP_BREAK", "OP_ENTER", "OP_LEAVE", "OP_CALL", + "OP_PUSH", "OP_POP", "OP_CONST", "OP_LOCAL", "OP_JUMP", + "OP_EQ", "OP_NE", "OP_LTI", "OP_LEI", "OP_GTI", "OP_GEI", + "OP_LTU", "OP_LEU", "OP_GTU", "OP_GEU", "OP_EQF", "OP_NEF", + "OP_LTF", "OP_LEF", "OP_GTF", "OP_GEF", + "OP_LOAD1", "OP_LOAD2", "OP_LOAD4", "OP_STORE1", "OP_STORE2", + "OP_STORE4", "OP_ARG", "OP_BLOCK_COPY", + "OP_SEX8", "OP_SEX16", + "OP_NEGI", "OP_ADD", "OP_SUB", "OP_DIVI", "OP_DIVU", + "OP_MODI", "OP_MODU", "OP_MULI", "OP_MULU", "OP_BAND", + "OP_BOR", "OP_BXOR", "OP_BCOM", "OP_LSH", "OP_RSHI", "OP_RSHU", + "OP_NEGF", "OP_ADDF", "OP_SUBF", "OP_DIVF", "OP_MULF", + "OP_CVIF", "OP_CVFI", +}; + +static void VM_Destroy_Compiled(vm_t *vm) +{ + if (vm->codeBase) { + if (munmap(vm->codeBase, vm->codeLength)) + Com_Printf(S_COLOR_RED "Memory unmap failed, possible memory leak\n"); + } + vm->codeBase = NULL; +} + +typedef struct VM_Data { + unsigned int dataLength; + unsigned int codeLength; + unsigned int *CallThunk; + int (*AsmCall)(int, int); + void (*BlockCopy)(unsigned int, unsigned int, unsigned int); + unsigned int *iPointers; + unsigned int data[0]; +} vm_data_t; + +#ifdef offsetof +# define VM_Data_Offset(field) offsetof(vm_data_t, field) +#else +# define OFFSET(structName, field) \ + ((void *)&(((structName *)NULL)->field) - NULL) +# define VM_Data_Offset(field) OFFSET(vm_data_t, field) +#endif + +struct src_insn { + unsigned char op; + unsigned int i_count; + + union { + unsigned int i; + signed int si; + signed short ss[2]; + unsigned short us[2]; + unsigned char b; + } arg; + + unsigned char dst_reg_flags; + unsigned char src1_reg_flags; + unsigned char src2_reg_flags; +#define REG_FLAGS_FLOAT 0x1 + + struct src_insn *next; +}; + +struct dst_insn; +struct jump_insn { + enum sparc_iname jump_iname; + int jump_dest_insn; + struct dst_insn *parent; + struct jump_insn *next; +}; + +struct dst_insn { + struct dst_insn *next; + + unsigned int count; + unsigned int i_count; + + struct jump_insn *jump; + unsigned int length; + unsigned int code[0]; +}; + +#define HUNK_SIZE 29 +struct data_hunk { + struct data_hunk *next; + int count; + unsigned int data[HUNK_SIZE]; +}; + +struct func_info { + struct src_insn *first; + struct src_insn *last; + int has_call; + int need_float_tmp; + + struct src_insn *cached_const; + + int stack_space; + int gpr_pos; +#define rFIRST(fp) ((fp)->gpr_pos - 1) +#define rSECOND(fp) ((fp)->gpr_pos - 2) +#define POP_GPR(fp) ((fp)->gpr_pos--) +#define PUSH_GPR(fp) ((fp)->gpr_pos++) + + int fpr_pos; +#define fFIRST(fp) ((fp)->fpr_pos - 1) +#define fSECOND(fp) ((fp)->fpr_pos - 2) +#define POP_FPR(fp) ((fp)->fpr_pos--) +#define PUSH_FPR(fp) ((fp)->fpr_pos++) + +#define INSN_BUF_SIZE 50 + unsigned int insn_buf[INSN_BUF_SIZE]; + int insn_index; + + int saved_icount; + int force_emit; + + struct jump_insn *jump_first; + struct jump_insn *jump_last; + + struct dst_insn *dst_first; + struct dst_insn *dst_last; + int dst_count; + + struct dst_insn **dst_by_i_count; + + struct data_hunk *data_first; + int data_num; +}; + +#define THUNK_ICOUNT -1 + +static unsigned int sparc_push_data(struct func_info * const fp, unsigned int val) +{ + struct data_hunk *last, *dp = fp->data_first; + int off = 0; + + last = NULL; + while (dp) { + int i; + + for (i = 0; i < dp->count; i++) { + if (dp->data[i] == val) { + off += i; + return VM_Data_Offset(data[off]); + } + } + off += dp->count; + last = dp; + dp = dp->next; + } + + dp = last; + if (!dp || dp->count >= HUNK_SIZE) { + struct data_hunk *new = Z_Malloc(sizeof(*new)); + if (!dp) + fp->data_first = new; + else + dp->next = new; + dp = new; + dp->count = 0; + dp->next = NULL; + } + dp->data[dp->count++] = val; + fp->data_num = off + 1; + return VM_Data_Offset(data[off]); +} + +static void dst_insn_insert_tail(struct func_info * const fp, + struct dst_insn *dp) +{ + if (!fp->dst_first) { + fp->dst_first = fp->dst_last = dp; + } else { + fp->dst_last->next = dp; + fp->dst_last = dp; + } +} + +static void jump_insn_insert_tail(struct func_info * const fp, + struct jump_insn *jp) +{ + if (!fp->jump_first) { + fp->jump_first = fp->jump_last = jp; + } else { + fp->jump_last->next = jp; + fp->jump_last = jp; + } +} + +static struct dst_insn *dst_new(struct func_info * const fp, unsigned int length, + struct jump_insn *jp, int insns_size) +{ + struct dst_insn *dp = Z_Malloc(sizeof(struct dst_insn) + insns_size); + + dp->length = length; + dp->jump = jp; + dp->count = fp->dst_count++; + dp->i_count = fp->saved_icount; + dp->next = NULL; + if (fp->saved_icount != THUNK_ICOUNT) + fp->dst_by_i_count[fp->saved_icount] = dp; + + return dp; +} + +static void dst_insn_append(struct func_info * const fp) +{ + int insns_size = (sizeof(unsigned int) * fp->insn_index); + struct dst_insn *dp; + + dp = dst_new(fp, fp->insn_index, NULL, insns_size); + if (insns_size) + memcpy(&dp->code[0], fp->insn_buf, insns_size); + dst_insn_insert_tail(fp, dp); + + fp->insn_index = 0; +} + +static void jump_insn_append(struct func_info * const fp, enum sparc_iname iname, int dest) +{ + struct jump_insn *jp = Z_Malloc(sizeof(*jp)); + struct dst_insn *dp; + + dp = dst_new(fp, 2, jp, 0); + + jp->jump_iname = iname; + jp->jump_dest_insn = dest; + jp->parent = dp; + jp->next = NULL; + + jump_insn_insert_tail(fp, jp); + dst_insn_insert_tail(fp, dp); +} + +static void start_emit(struct func_info * const fp, int i_count) +{ + fp->saved_icount = i_count; + fp->insn_index = 0; + fp->force_emit = 0; +} + +static void __do_emit_one(struct func_info * const fp, unsigned int insn) +{ + fp->insn_buf[fp->insn_index++] = insn; +} + +#define in(inst, args...) __do_emit_one(fp, IN(inst, args)) + +static void end_emit(struct func_info * const fp) +{ + if (fp->insn_index || fp->force_emit) + dst_insn_append(fp); +} + +static void emit_jump(struct func_info * const fp, enum sparc_iname iname, int dest) +{ + end_emit(fp); + jump_insn_append(fp, iname, dest); +} + +static void analyze_function(struct func_info * const fp) +{ + struct src_insn *value_provider[20] = { NULL }; + struct src_insn *sp = fp->first; + int opstack_depth = 0; + + while ((sp = sp->next) != NULL) { + unsigned char opi, op = sp->op; + + opi = vm_opInfo[op]; + if (opi & opArgIF) { + struct src_insn *vp = value_provider[--opstack_depth]; + unsigned char vpopi = vm_opInfo[vp->op]; + + if ((opi & opArgI) && (vpopi & opRetI)) { + /* src1 and dst are integers */ + } else if ((opi & opArgF) && (vpopi & opRetF)) { + /* src1 and dst are floats */ + vp->dst_reg_flags |= REG_FLAGS_FLOAT; + sp->src1_reg_flags = REG_FLAGS_FLOAT; + } else { + /* illegal combination */ + DIE("unrecognized instruction combination"); + } + } + if (opi & opArg2IF) { + struct src_insn *vp = value_provider[--opstack_depth]; + unsigned char vpopi = vm_opInfo[vp->op]; + + if ((opi & opArg2I) && (vpopi & opRetI)) { + /* src2 and dst are integers */ + } else if ( (opi & opArg2F) && (vpopi & opRetF) ) { + /* src2 and dst are floats */ + vp->dst_reg_flags |= REG_FLAGS_FLOAT; + sp->src2_reg_flags = REG_FLAGS_FLOAT; + } else { + /* illegal combination */ + DIE("unrecognized instruction combination"); + } + } + if (opi & opRetIF) { + value_provider[opstack_depth] = sp; + opstack_depth++; + } + } +} + +static int asmcall(int call, int pstack) +{ + vm_t *savedVM = currentVM; + int i, ret; + + currentVM->programStack = pstack - 4; + if (sizeof(intptr_t) == sizeof(int)) { + intptr_t *argPosition = (intptr_t *)((byte *)currentVM->dataBase + pstack + 4); + argPosition[0] = -1 - call; + ret = currentVM->systemCall(argPosition); + } else { + intptr_t args[11]; + + args[0] = -1 - call; + int *argPosition = (int *)((byte *)currentVM->dataBase + pstack + 4); + for( i = 1; i < 11; i++ ) + args[i] = argPosition[i]; + + ret = currentVM->systemCall(args); + } + + currentVM = savedVM; + + return ret; +} + +static void blockcopy(unsigned int dest, unsigned int src, unsigned int count) +{ + unsigned int dataMask = currentVM->dataMask; + + if ((dest & dataMask) != dest || + (src & dataMask) != src || + ((dest+count) & dataMask) != dest + count || + ((src+count) & dataMask) != src + count) { + DIE("OP_BLOCK_COPY out of range!"); + } + + memcpy(currentVM->dataBase+dest, currentVM->dataBase+src, count); +} + +static void do_emit_const(struct func_info * const fp, struct src_insn *sp) +{ + start_emit(fp, sp->i_count); + if (sp->dst_reg_flags & REG_FLAGS_FLOAT) { + in(LDFI, rVMDATA, sparc_push_data(fp, sp->arg.i), fFIRST(fp)); + } else { + if ((sp->arg.i & ~0x3ff) == 0) { + in(ORI, G0, sp->arg.i & 0x3ff, rFIRST(fp)); + } else if ((sp->arg.i & 0x3ff) == 0) { + in(SETHI, sp->arg.i >> 10, rFIRST(fp)); + } else { + in(SETHI, sp->arg.i >> 10, rFIRST(fp)); + in(ORI, rFIRST(fp), sp->arg.i & 0x3ff, rFIRST(fp)); + } + } + end_emit(fp); +} + +#define MAYBE_EMIT_CONST(fp) \ +do { if ((fp)->cached_const) { \ + int saved_i_count = (fp)->saved_icount; \ + do_emit_const(fp, (fp)->cached_const); \ + (fp)->saved_icount = saved_i_count; \ + } \ +} while (0) + +#define EMIT_FALSE_CONST(fp) \ +do { int saved_i_count = (fp)->saved_icount; \ + (fp)->saved_icount = (fp)->cached_const->i_count; \ + dst_insn_append(fp); \ + (fp)->saved_icount = saved_i_count; \ +} while (0) + +static void compile_one_insn(struct func_info * const fp, struct src_insn *sp) +{ + start_emit(fp, sp->i_count); + + switch (sp->op) { + default: + Com_Printf("VM: Unhandled opcode 0x%02x[%s]\n", + sp->op, + opnames[sp->op] ? opnames[sp->op] : "UNKNOWN"); + DIE("Unsupported opcode"); + break; + + case OP_ENTER: { + int stack = SL(64, 128); + + if (fp->need_float_tmp) + stack += 16; + + in(SAVEI, O6, -stack, O6); + if (!SIMM13_P(sp->arg.si)) { + in(SETHI, sp->arg.i >> 10, rTMP); + in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP); + in(SUB, rPSTACK, rTMP, rPSTACK); + } else + in(SUBI, rPSTACK, sp->arg.si, rPSTACK); + break; + } + case OP_LEAVE: + if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) { + EMIT_FALSE_CONST(fp); + if (fp->cached_const->src1_reg_flags & REG_FLAGS_FLOAT) + DIE("constant float in OP_LEAVE"); + + if (!SIMM13_P(sp->arg.si)) { + in(SETHI, sp->arg.i >> 10, rTMP); + in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP); + in(ADD, rPSTACK, rTMP, rPSTACK); + } else + in(ADDI, rPSTACK, sp->arg.si, rPSTACK); + in(JMPLI, I7, 8, G0); + in(RESTOREI, G0, fp->cached_const->arg.si, O0); + POP_GPR(fp); + } else { + MAYBE_EMIT_CONST(fp); + if (!SIMM13_P(sp->arg.si)) { + in(SETHI, sp->arg.i >> 10, rTMP); + in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP); + in(ADD, rPSTACK, rTMP, rPSTACK); + } else + in(ADDI, rPSTACK, sp->arg.si, rPSTACK); + if (sp->src1_reg_flags & REG_FLAGS_FLOAT) { + in(STFI, O6, SL(64, 128), fFIRST(fp)); + in(LDUWI, O6, SL(64, 128), O0); + in(JMPLI, I7, 8, G0); + in(RESTORE, O0, G0, O0); + POP_FPR(fp); + } else { + in(JMPLI, I7, 8, G0); + in(RESTORE, rFIRST(fp), G0, O0); + POP_GPR(fp); + } + } + assert(fp->gpr_pos == L0); + assert(fp->fpr_pos == F0); + break; + case OP_JUMP: + if (fp->cached_const) { + EMIT_FALSE_CONST(fp); + emit_jump(fp, BA, fp->cached_const->arg.i); + } else { + MAYBE_EMIT_CONST(fp); + in(LDLI, rVMDATA, VM_Data_Offset(iPointers), rTMP); + in(SLLI, rFIRST(fp), 2, rFIRST(fp)); + in(LDL, rTMP, rFIRST(fp), rTMP); + in(JMPL, rTMP, G0, G0); + in(NOP); + } + POP_GPR(fp); + break; + case OP_CALL: + if (fp->cached_const) { + EMIT_FALSE_CONST(fp); + if (fp->cached_const->arg.si >= 0) { + emit_jump(fp, CALL, fp->cached_const->arg.i); + } else { + in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP); + in(LDLI, rVMDATA, VM_Data_Offset(AsmCall), O3); + in(ORI, G0, fp->cached_const->arg.si, O0); + in(JMPL, rTMP, G0, O7); + in(OR, G0, rPSTACK, O1); + } + in(OR, G0, O0, rFIRST(fp)); + } else { + MAYBE_EMIT_CONST(fp); + in(SUBCCI, rFIRST(fp), 0, G0); + in(BL, +4*7); + in(NOP); + + /* normal call */ + in(LDLI, rVMDATA, VM_Data_Offset(iPointers), O5); + in(SLLI, rFIRST(fp), 2, rFIRST(fp)); + in(LDL, O5, rFIRST(fp), rTMP); + in(BA, +4*4); + in(NOP); + + /* syscall */ + in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP); + in(LDLI, rVMDATA, VM_Data_Offset(AsmCall), O3); + + in(OR, G0, rFIRST(fp), O0); + in(JMPL, rTMP, G0, O7); + in(OR, G0, rPSTACK, O1); + + /* return value */ + in(OR, G0, O0, rFIRST(fp)); + } + break; + case OP_BLOCK_COPY: + MAYBE_EMIT_CONST(fp); + in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP); + in(LDLI, rVMDATA, VM_Data_Offset(BlockCopy), O3); + in(OR, G0, rSECOND(fp), O0); + in(OR, G0, rFIRST(fp), O1); + if ((sp->arg.i & ~0x3ff) == 0) { + in(ORI, G0, sp->arg.i & 0x3ff, O2); + } else if ((sp->arg.i & 0x3ff) == 0) { + in(SETHI, sp->arg.i >> 10, O2); + } else { + in(SETHI, sp->arg.i >> 10, O2); + in(ORI, O2, sp->arg.i & 0x3ff, O2); + } + in(JMPL, rTMP, G0, O7); + in(NOP); + POP_GPR(fp); + POP_GPR(fp); + break; + + case OP_PUSH: + MAYBE_EMIT_CONST(fp); + if (sp->dst_reg_flags & REG_FLAGS_FLOAT) + PUSH_FPR(fp); + else + PUSH_GPR(fp); + fp->force_emit = 1; + break; + case OP_POP: + MAYBE_EMIT_CONST(fp); + if (sp->src1_reg_flags & REG_FLAGS_FLOAT) + POP_FPR(fp); + else + POP_GPR(fp); + fp->force_emit = 1; + break; + case OP_ARG: + MAYBE_EMIT_CONST(fp); + in(ADDI, rPSTACK, sp->arg.b, rTMP); + if (sp->src1_reg_flags & REG_FLAGS_FLOAT) { + in(STF, rDATABASE, rTMP, fFIRST(fp)); + POP_FPR(fp); + } else { + in(STW, rDATABASE, rTMP, rFIRST(fp)); + POP_GPR(fp); + } + break; + case OP_IGNORE: + MAYBE_EMIT_CONST(fp); + in(NOP); + break; + case OP_BREAK: + MAYBE_EMIT_CONST(fp); + in(TA, 0x5); + break; + case OP_LOCAL: + MAYBE_EMIT_CONST(fp); + PUSH_GPR(fp); + if (!SIMM13_P(sp->arg.i)) { + in(SETHI, sp->arg.i >> 10, rTMP); + in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP); + in(ADD, rPSTACK, rTMP, rFIRST(fp)); + } else + in(ADDI, rPSTACK, sp->arg.i, rFIRST(fp)); + break; + case OP_CONST: + MAYBE_EMIT_CONST(fp); + break; + case OP_LOAD4: + MAYBE_EMIT_CONST(fp); + in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp)); + if (sp->dst_reg_flags & REG_FLAGS_FLOAT) { + PUSH_FPR(fp); + in(LDF, rFIRST(fp), rDATABASE, fFIRST(fp)); + POP_GPR(fp); + } else { + in(LDUW, rFIRST(fp), rDATABASE, rFIRST(fp)); + } + break; + case OP_LOAD2: + MAYBE_EMIT_CONST(fp); + in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp)); + in(LDUH, rFIRST(fp), rDATABASE, rFIRST(fp)); + break; + case OP_LOAD1: + MAYBE_EMIT_CONST(fp); + in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp)); + in(LDUB, rFIRST(fp), rDATABASE, rFIRST(fp)); + break; + case OP_STORE4: + MAYBE_EMIT_CONST(fp); + if (sp->src1_reg_flags & REG_FLAGS_FLOAT) { + in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp)); + in(STF, rFIRST(fp), rDATABASE, fFIRST(fp)); + POP_FPR(fp); + } else { + in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp)); + in(STW, rSECOND(fp), rDATABASE, rFIRST(fp)); + POP_GPR(fp); + } + POP_GPR(fp); + break; + case OP_STORE2: + MAYBE_EMIT_CONST(fp); + in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp)); + in(STH, rSECOND(fp), rDATABASE, rFIRST(fp)); + POP_GPR(fp); + POP_GPR(fp); + break; + case OP_STORE1: + MAYBE_EMIT_CONST(fp); + in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp)); + in(STB, rSECOND(fp), rDATABASE, rFIRST(fp)); + POP_GPR(fp); + POP_GPR(fp); + break; + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_GEI: + case OP_GTI: + case OP_LEI: + case OP_LTU: + case OP_GEU: + case OP_GTU: + case OP_LEU: { + enum sparc_iname iname = BA; + + if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) { + EMIT_FALSE_CONST(fp); + in(SUBCCI, rSECOND(fp), fp->cached_const->arg.si, G0); + } else { + MAYBE_EMIT_CONST(fp); + in(SUBCC, rSECOND(fp), rFIRST(fp), G0); + } + switch(sp->op) { + case OP_EQ: iname = BE; break; + case OP_NE: iname = BNE; break; + case OP_LTI: iname = BL; break; + case OP_GEI: iname = BGE; break; + case OP_GTI: iname = BG; break; + case OP_LEI: iname = BLE; break; + case OP_LTU: iname = BCS; break; + case OP_GEU: iname = BCC; break; + case OP_GTU: iname = BGU; break; + case OP_LEU: iname = BLEU; break; + } + emit_jump(fp, iname, sp->arg.i); + POP_GPR(fp); + POP_GPR(fp); + break; + } + + case OP_SEX8: + MAYBE_EMIT_CONST(fp); + in(SLLI, rFIRST(fp), 24, rFIRST(fp)); + in(SRAI, rFIRST(fp), 24, rFIRST(fp)); + break; + case OP_SEX16: + MAYBE_EMIT_CONST(fp); + in(SLLI, rFIRST(fp), 16, rFIRST(fp)); + in(SRAI, rFIRST(fp), 16, rFIRST(fp)); + break; + case OP_NEGI: + MAYBE_EMIT_CONST(fp); + in(SUB, G0, rFIRST(fp), rFIRST(fp)); + break; + case OP_ADD: + if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) { + EMIT_FALSE_CONST(fp); + in(ADDI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); + } else { + MAYBE_EMIT_CONST(fp); + in(ADD, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + } + POP_GPR(fp); + break; + case OP_SUB: + if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) { + EMIT_FALSE_CONST(fp); + in(SUBI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); + } else { + MAYBE_EMIT_CONST(fp); + in(SUB, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + } + POP_GPR(fp); + break; + case OP_DIVI: + MAYBE_EMIT_CONST(fp); + in(SRAI, rSECOND(fp), 31, rTMP); + in(WRI, rTMP, 0, Y_REG); + in(SDIV, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + POP_GPR(fp); + break; + case OP_DIVU: + MAYBE_EMIT_CONST(fp); + in(WRI, G0, 0, Y_REG); + in(UDIV, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + POP_GPR(fp); + break; + case OP_MODI: + MAYBE_EMIT_CONST(fp); + in(SRAI, rSECOND(fp), 31, rTMP); + in(WRI, rTMP, 0, Y_REG); + in(SDIV, rSECOND(fp), rFIRST(fp), rTMP); + in(SMUL, rTMP, rFIRST(fp), rTMP); + in(SUB, rSECOND(fp), rTMP, rSECOND(fp)); + POP_GPR(fp); + break; + case OP_MODU: + MAYBE_EMIT_CONST(fp); + in(WRI, G0, 0, Y_REG); + in(UDIV, rSECOND(fp), rFIRST(fp), rTMP); + in(SMUL, rTMP, rFIRST(fp), rTMP); + in(SUB, rSECOND(fp), rTMP, rSECOND(fp)); + POP_GPR(fp); + break; + case OP_MULI: + MAYBE_EMIT_CONST(fp); + in(SMUL, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + POP_GPR(fp); + break; + case OP_MULU: + MAYBE_EMIT_CONST(fp); + in(UMUL, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + POP_GPR(fp); + break; + case OP_BAND: + MAYBE_EMIT_CONST(fp); + in(AND, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + POP_GPR(fp); + break; + case OP_BOR: + MAYBE_EMIT_CONST(fp); + in(OR, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + POP_GPR(fp); + break; + case OP_BXOR: + MAYBE_EMIT_CONST(fp); + in(XOR, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + POP_GPR(fp); + break; + case OP_BCOM: + MAYBE_EMIT_CONST(fp); + in(XNOR, rFIRST(fp), G0, rFIRST(fp)); + break; + case OP_LSH: + if (fp->cached_const) { + EMIT_FALSE_CONST(fp); + in(SLLI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); + } else { + MAYBE_EMIT_CONST(fp); + in(SLL, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + } + POP_GPR(fp); + break; + case OP_RSHI: + if (fp->cached_const) { + EMIT_FALSE_CONST(fp); + in(SRAI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); + } else { + MAYBE_EMIT_CONST(fp); + in(SRA, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + } + POP_GPR(fp); + break; + case OP_RSHU: + if (fp->cached_const) { + EMIT_FALSE_CONST(fp); + in(SRLI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp)); + } else { + MAYBE_EMIT_CONST(fp); + in(SRL, rSECOND(fp), rFIRST(fp), rSECOND(fp)); + } + POP_GPR(fp); + break; + + case OP_NEGF: + MAYBE_EMIT_CONST(fp); + in(FNEG, fFIRST(fp), fFIRST(fp)); + break; + case OP_ADDF: + MAYBE_EMIT_CONST(fp); + in(FADD, fSECOND(fp), fFIRST(fp), fSECOND(fp)); + POP_FPR(fp); + break; + case OP_SUBF: + MAYBE_EMIT_CONST(fp); + in(FSUB, fSECOND(fp), fFIRST(fp), fSECOND(fp)); + POP_FPR(fp); + break; + case OP_DIVF: + MAYBE_EMIT_CONST(fp); + in(FDIV, fSECOND(fp), fFIRST(fp), fSECOND(fp)); + POP_FPR(fp); + break; + case OP_MULF: + MAYBE_EMIT_CONST(fp); + in(FMUL, fSECOND(fp), fFIRST(fp), fSECOND(fp)); + POP_FPR(fp); + break; + + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_GEF: + case OP_GTF: + case OP_LEF: { + enum sparc_iname iname = FBE; + + MAYBE_EMIT_CONST(fp); + in(FCMP, fSECOND(fp), fFIRST(fp)); + switch(sp->op) { + case OP_EQF: iname = FBE; break; + case OP_NEF: iname = FBNE; break; + case OP_LTF: iname = FBL; break; + case OP_GEF: iname = FBGE; break; + case OP_GTF: iname = FBG; break; + case OP_LEF: iname = FBLE; break; + } + emit_jump(fp, iname, sp->arg.i); + POP_FPR(fp); + POP_FPR(fp); + break; + } + case OP_CVIF: + MAYBE_EMIT_CONST(fp); + PUSH_FPR(fp); + in(STWI, O6, SL(64, 128), rFIRST(fp)); + in(LDFI, O6, SL(64, 128), fFIRST(fp)); + in(FITOS, fFIRST(fp), fFIRST(fp)); + POP_GPR(fp); + break; + case OP_CVFI: + MAYBE_EMIT_CONST(fp); + PUSH_GPR(fp); + in(FSTOI, fFIRST(fp), fFIRST(fp)); + in(STFI, O6, SL(64, 128), fFIRST(fp)); + in(LDUWI, O6, SL(64, 128), rFIRST(fp)); + POP_FPR(fp); + break; + } + if (sp->op != OP_CONST) { + fp->cached_const = NULL; + end_emit(fp); + } else { + fp->cached_const = sp; + if (sp->dst_reg_flags & REG_FLAGS_FLOAT) { + PUSH_FPR(fp); + } else { + PUSH_GPR(fp); + } + } + end_emit(fp); +} + +static void free_source_insns(struct func_info * const fp) +{ + struct src_insn *sp = fp->first->next; + + while (sp) { + struct src_insn *next = sp->next; + Z_Free(sp); + sp = next; + } +} + +static void compile_function(struct func_info * const fp) +{ + struct src_insn *sp; + + analyze_function(fp); + + fp->gpr_pos = L0; + fp->fpr_pos = F0; + fp->insn_index = 0; + + fp->stack_space = SL(64, 128); + fp->cached_const = NULL; + + sp = fp->first; + while ((sp = sp->next) != NULL) + compile_one_insn(fp, sp); + + free_source_insns(fp); +} + +/* We have two thunks for sparc. The first is for the entry into + * the VM, where setup the fixed global registers. The second is + * for calling out to C code from the VM, where we need to preserve + * those fixed globals across the call. + */ +static void emit_vm_thunk(struct func_info * const fp) +{ + /* int vm_thunk(void *vmdata, int programstack, void *database, int datamask) */ + start_emit(fp, THUNK_ICOUNT); + + in(OR, G0, O0, rVMDATA); + in(OR, G0, O1, rPSTACK); + in(OR, G0, O2, rDATABASE); + in(BA, +4*17); + in(OR, G0, O3, rDATAMASK); + + /* int call_thunk(int arg0, int arg1, int arg2, int (*func)(int int int)) */ +#define CALL_THUNK_INSN_OFFSET 5 + in(SAVEI, O6, -SL(64, 128), O6); + + in(OR, G0, rVMDATA, L0); + in(OR, G0, rPSTACK, L1); + in(OR, G0, rDATABASE, L2); + in(OR, G0, rDATAMASK, L3); + + in(OR, G0, I0, O0); + in(OR, G0, I1, O1); + in(JMPL, I3, G0, O7); + in(OR, G0, I2, O2); + + in(OR, G0, L0, rVMDATA); + in(OR, G0, L1, rPSTACK); + in(OR, G0, L2, rDATABASE); + in(OR, G0, L3, rDATAMASK); + + in(JMPLI, I7, 8, G0); + in(RESTORE, O0, G0, O0); + + end_emit(fp); +} + +static void sparc_compute_code(vm_t *vm, struct func_info * const fp) +{ + struct dst_insn *dp = fp->dst_first; + unsigned int *code_now, *code_begin; + unsigned char *data_and_code; + unsigned int code_length; + int code_insns = 0, off; + struct data_hunk *dhp; + struct jump_insn *jp; + vm_data_t *data; + + while (dp) { + code_insns += dp->length; + dp = dp->next; + } + + code_length = (sizeof(vm_data_t) + + (fp->data_num * sizeof(unsigned int)) + + (code_insns * sizeof(unsigned int))); + + data_and_code = mmap(NULL, code_length, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (data_and_code == MAP_FAILED) + DIE("Not enough memory"); + + code_now = code_begin = (unsigned int *) + (data_and_code + VM_Data_Offset(data[fp->data_num])); + + dp = fp->dst_first; + while (dp) { + int i_count = dp->i_count; + + if (i_count != THUNK_ICOUNT) { + if (!fp->dst_by_i_count[i_count]) + fp->dst_by_i_count[i_count] = (void *) code_now; + } + if (!dp->jump) { + memcpy(code_now, &dp->code[0], dp->length * sizeof(unsigned int)); + code_now += dp->length; + } else { + int i; + + dp->jump->parent = (void *) code_now; + + for (i = 0; i < dp->length; i++) + code_now[i] = SPARC_NOP; + code_now += dp->length; + } + + dp = dp->next; + } + + jp = fp->jump_first; + while (jp) { + unsigned int *from = (void *) jp->parent; + unsigned int *to = (void *) fp->dst_by_i_count[jp->jump_dest_insn]; + signed int disp = (to - from); + + *from = IN(jp->jump_iname, disp << 2); + + jp = jp->next; + } + + vm->codeBase = data_and_code; + vm->codeLength = code_length; + + data = (vm_data_t *) data_and_code; + data->CallThunk = code_begin + CALL_THUNK_INSN_OFFSET; + data->AsmCall = asmcall; + data->BlockCopy = blockcopy; + data->iPointers = (unsigned int *) vm->instructionPointers; + data->dataLength = VM_Data_Offset(data[fp->data_num]); + data->codeLength = (code_now - code_begin) * sizeof(unsigned int); + +#if 0 + { + unsigned int *insn = code_begin; + int i; + + Com_Printf("INSN DUMP\n"); + for (i = 0; i < data->codeLength / 4; i+= 8) { + Com_Printf("\t.word\t0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", + insn[i + 0], insn[i + 1], + insn[i + 2], insn[i + 3], + insn[i + 4], insn[i + 5], + insn[i + 6], insn[i + 7]); + } + } +#endif + + dhp = fp->data_first; + off = 0; + while (dhp) { + struct data_hunk *next = dhp->next; + int i; + + for (i = 0; i < dhp->count; i++) + data->data[off + i] = dhp->data[i]; + + off += dhp->count; + + Z_Free(dhp); + + dhp = next; + } + fp->data_first = NULL; + fp->data_num = 0; + + dp = fp->dst_first; + while (dp) { + struct dst_insn *next = dp->next; + if (dp->jump) + Z_Free(dp->jump); + Z_Free(dp); + dp = next; + } + fp->dst_first = fp->dst_last = NULL; +} + +void VM_Compile(vm_t *vm, vmHeader_t *header) +{ + struct func_info fi; + unsigned char *code; + int i_count, pc, i; + + memset(&fi, 0, sizeof(fi)); + + fi.first = Z_Malloc(sizeof(struct src_insn)); + fi.first->next = NULL; + +#ifdef __arch64__ + Z_Free(vm->instructionPointers); + vm->instructionPointers = Z_Malloc(header->instructionCount * + sizeof(void *)); +#endif + + fi.dst_by_i_count = (struct dst_insn **) vm->instructionPointers; + memset(fi.dst_by_i_count, 0, header->instructionCount * sizeof(void *)); + + vm->compiled = qfalse; + + emit_vm_thunk(&fi); + + code = (unsigned char *) header + header->codeOffset; + pc = 0; + + for (i_count = 0; i_count < header->instructionCount; i_count++) { + unsigned char opi, op = code[pc++]; + struct src_insn *sp; + + if (op == OP_CALL || op == OP_BLOCK_COPY) + fi.has_call = 1; + opi = vm_opInfo[op]; + if (op == OP_CVIF || op == OP_CVFI || + (op == OP_LEAVE && (opi & opArgF))) + fi.need_float_tmp = 1; + + if (op == OP_ENTER) { + if (fi.first->next) + compile_function(&fi); + fi.first->next = NULL; + fi.last = fi.first; + fi.has_call = fi.need_float_tmp = 0; + } + + sp = Z_Malloc(sizeof(*sp)); + sp->op = op; + sp->i_count = i_count; + sp->arg.i = 0; + sp->next = NULL; + + if (vm_opInfo[op] & opImm4) { + union { + unsigned char b[4]; + unsigned int i; + } c = { { code[ pc + 3 ], code[ pc + 2 ], + code[ pc + 1 ], code[ pc + 0 ] }, }; + + sp->arg.i = c.i; + pc += 4; + } else if (vm_opInfo[op] & opImm1) { + sp->arg.b = code[pc++]; + } + + fi.last->next = sp; + fi.last = sp; + } + compile_function(&fi); + + Z_Free(fi.first); + + memset(fi.dst_by_i_count, 0, header->instructionCount * sizeof(void *)); + sparc_compute_code(vm, &fi); + + for (i = 0; i < header->instructionCount; i++) { + if (!fi.dst_by_i_count[i]) { + Com_Printf(S_COLOR_RED "Pointer %d not initialized !\n", i); + DIE("sparc JIT bug"); + } + } + + if (mprotect(vm->codeBase, vm->codeLength, PROT_READ|PROT_EXEC)) { + VM_Destroy_Compiled(vm); + DIE("mprotect failed"); + } + + vm->destroy = VM_Destroy_Compiled; + vm->compiled = qtrue; +} + +int VM_CallCompiled(vm_t *vm, int *args) +{ + vm_data_t *vm_dataAndCode = (void *) vm->codeBase; + int programStack = vm->programStack; + int stackOnEntry = programStack; + byte *image = vm->dataBase; + int *argPointer; + int retVal; + + currentVM = vm; + + vm->currentlyInterpreting = qtrue; + + programStack -= 48; + argPointer = (int *)&image[ programStack + 8 ]; + memcpy( argPointer, args, 4 * 9 ); + argPointer[-1] = 0; + argPointer[-2] = -1; + + /* call generated code */ + { + int (*entry)(void *, int, void *, int); + entry = (void *)(vm->codeBase + vm_dataAndCode->dataLength); + retVal = entry(vm->codeBase, programStack, vm->dataBase, vm->dataMask); + } + + vm->programStack = stackOnEntry; + vm->currentlyInterpreting = qfalse; + + return retVal; +} diff --git a/src/qcommon/vm_sparc.h b/src/qcommon/vm_sparc.h new file mode 100644 index 0000000..dbed627 --- /dev/null +++ b/src/qcommon/vm_sparc.h @@ -0,0 +1,78 @@ +#ifndef VM_SPARC_H +#define VM_SPARC_H + +/* integer regs */ +#define G0 0 +#define G1 1 +#define G2 2 +#define G3 3 +#define G4 4 +#define G5 5 +#define G6 6 +#define G7 7 +#define O0 8 +#define O1 9 +#define O2 10 +#define O3 11 +#define O4 12 +#define O5 13 +#define O6 14 +#define O7 15 +#define L0 16 +#define L1 17 +#define L2 18 +#define L3 19 +#define L4 20 +#define L5 21 +#define L6 22 +#define L7 23 +#define I0 24 +#define I1 25 +#define I2 26 +#define I3 27 +#define I4 28 +#define I5 29 +#define I6 30 +#define I7 31 + +/* float regs */ +#define F0 0 +#define F1 1 +#define F2 2 +#define F3 3 +#define F4 4 +#define F5 5 +#define F6 6 +#define F7 7 +#define F8 8 +#define F9 9 +#define F10 10 +#define F11 11 +#define F12 12 +#define F13 13 +#define F14 14 +#define F15 15 +#define F16 16 +#define F17 17 +#define F18 18 +#define F19 19 +#define F20 20 +#define F21 21 +#define F22 22 +#define F23 23 +#define F24 24 +#define F25 25 +#define F26 26 +#define F27 27 +#define F28 28 +#define F29 29 +#define F30 30 +#define F31 31 + +/* state registers */ +#define Y_REG 0 +#define CCR_REG 2 +#define ASI_REG 3 +#define FPRS_REG 6 + +#endif diff --git a/src/qcommon/vm_x86.c b/src/qcommon/vm_x86.c new file mode 100644 index 0000000..f25be99 --- /dev/null +++ b/src/qcommon/vm_x86.c @@ -0,0 +1,1245 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// vm_x86.c -- load time compiler and execution environment for x86 + +#include "vm_local.h" +#ifdef _WIN32 +#include +#endif + +#ifdef __FreeBSD__ +#include +#endif + +#ifndef _WIN32 +#include // for PROT_ stuff +#endif + +/* need this on NX enabled systems (i386 with PAE kernel or + * noexec32=on x86_64) */ +#if defined(__linux__) || defined(__FreeBSD__) +#define VM_X86_MMAP +#endif + +static void VM_Destroy_Compiled(vm_t* self); + +/* + + eax scratch + ebx scratch + ecx scratch (required for shifts) + edx scratch (required for divisions) + esi program stack + edi opstack + +*/ + +#define VMFREE_BUFFERS() do {Z_Free(buf); Z_Free(jused);} while(0) +static byte *buf = NULL; +static byte *jused = NULL; +static int compiledOfs = 0; +static byte *code = NULL; +static int pc = 0; + +static int *instructionPointers = NULL; + +#define FTOL_PTR + +#ifdef _MSC_VER + +#if defined( FTOL_PTR ) +int _ftol( float ); +static int ftolPtr = (int)_ftol; +#endif + +#else // _MSC_VER + +#if defined( FTOL_PTR ) + +int qftol( void ); +int qftol027F( void ); +int qftol037F( void ); +int qftol0E7F( void ); +int qftol0F7F( void ); + + +static int ftolPtr = (int)qftol0F7F; +#endif // FTOL_PTR + +#endif + +void AsmCall(void); +static void (*const asmCallPtr)(void) = AsmCall; + + +static int callMask = 0; + +static int instruction, pass; +static int lastConst = 0; +static int oc0, oc1, pop0, pop1; + +typedef enum +{ + LAST_COMMAND_NONE = 0, + LAST_COMMAND_MOV_EDI_EAX, + LAST_COMMAND_SUB_DI_4, + LAST_COMMAND_SUB_DI_8, +} ELastCommand; + +static ELastCommand LastCommand; + +/* +================= +AsmCall +================= +*/ +#ifdef _MSC_VER +__declspec( naked ) void AsmCall( void ) { +int programStack; +int *opStack; +int syscallNum; +vm_t* savedVM; + +__asm { + mov eax, dword ptr [edi] + sub edi, 4 + test eax,eax + jl systemCall + // calling another vm function + shl eax,2 + add eax, dword ptr [instructionPointers] + call dword ptr [eax] + mov eax, dword ptr [edi] + and eax, [callMask] + ret +systemCall: + + // convert negative num to system call number + // and store right before the first arg + not eax + + push ebp + mov ebp, esp + sub esp, __LOCAL_SIZE + + mov dword ptr syscallNum, eax // so C code can get at it + mov dword ptr programStack, esi // so C code can get at it + mov dword ptr opStack, edi + + push ecx + push esi // we may call recursively, so the + push edi // statics aren't guaranteed to be around +} + + savedVM = currentVM; + + // save the stack to allow recursive VM entry + currentVM->programStack = programStack - 4; + *(int *)((byte *)currentVM->dataBase + programStack + 4) = syscallNum; +//VM_LogSyscalls( (int *)((byte *)currentVM->dataBase + programStack + 4) ); + *(opStack+1) = currentVM->systemCall( (int *)((byte *)currentVM->dataBase + programStack + 4) ); + + currentVM = savedVM; + +_asm { + pop edi + pop esi + pop ecx + add edi, 4 // we added the return value + + mov esp, ebp + pop ebp + + ret +} + +} + +#else //!_MSC_VER + +#if defined(__MINGW32__) || defined(MACOS_X) // _ is prepended to compiled symbols +#define CMANGVAR(sym) "_"#sym +#define CMANGFUNC(sym) "_"#sym +#elif defined(__ICC) && (__ICC >= 1000) +#define CMANGVAR(sym) #sym".0" +#define CMANGFUNC(sym) #sym +#else +#define CMANGVAR(sym) #sym +#define CMANGFUNC(sym) #sym +#endif + +static void __attribute__((cdecl, used)) CallAsmCall(int const syscallNum, + int const programStack, int* const opStack) +{ + vm_t *const vm = currentVM; + intptr_t *const data = (intptr_t*)(vm->dataBase + programStack + 4); + + // save the stack to allow recursive VM entry + vm->programStack = programStack - 4; + *data = syscallNum; + opStack[1] = vm->systemCall(data); + + currentVM = vm; +} + +__asm__( + ".text\n\t" + ".p2align 4,,15\n\t" +#if defined __ELF__ + ".type " CMANGFUNC(AsmCall) ", @function\n" +#endif + CMANGFUNC(AsmCall) ":\n\t" + "movl (%edi), %eax\n\t" + "subl $4, %edi\n\t" + "testl %eax, %eax\n\t" + "jl 0f\n\t" + "shll $2, %eax\n\t" + "addl " CMANGVAR(instructionPointers) ", %eax\n\t" + "call *(%eax)\n\t" + "movl (%edi), %eax\n\t" + "andl " CMANGVAR(callMask) ", %eax\n\t" + "ret\n" + "0:\n\t" // system call + "notl %eax\n\t" + "pushl %ebp\n\t" + "movl %esp, %ebp\n\t" + "andl $-16, %esp\n\t" // align the stack so engine can use sse + "pushl %ecx\n\t" + "pushl %edi\n\t" // opStack + "pushl %esi\n\t" // programStack + "pushl %eax\n\t" // syscallNum + "call " CMANGFUNC(CallAsmCall) "\n\t" + "addl $12, %esp\n\t" + "popl %ecx\n\t" + "movl %ebp, %esp\n\t" + "popl %ebp\n\t" + "addl $4, %edi\n\t" + "ret\n\t" +#if defined __ELF__ + ".size " CMANGFUNC(AsmCall)", .-" CMANGFUNC(AsmCall) +#endif +); + +#endif + +static int Constant4( void ) { + int v; + + v = code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24); + pc += 4; + return v; +} + +static int Constant1( void ) { + int v; + + v = code[pc]; + pc += 1; + return v; +} + +static void Emit1( int v ) +{ + buf[ compiledOfs ] = v; + compiledOfs++; + + LastCommand = LAST_COMMAND_NONE; +} + +#if 0 +static void Emit2( int v ) { + Emit1( v & 255 ); + Emit1( ( v >> 8 ) & 255 ); +} +#endif + +static void Emit4( int v ) { + Emit1( v & 255 ); + Emit1( ( v >> 8 ) & 255 ); + Emit1( ( v >> 16 ) & 255 ); + Emit1( ( v >> 24 ) & 255 ); +} + +static int Hex( int c ) { + if ( c >= 'a' && c <= 'f' ) { + return 10 + c - 'a'; + } + if ( c >= 'A' && c <= 'F' ) { + return 10 + c - 'A'; + } + if ( c >= '0' && c <= '9' ) { + return c - '0'; + } + + VMFREE_BUFFERS(); + Com_Error( ERR_DROP, "Hex: bad char '%c'", c ); + + return 0; +} +static void EmitString( const char *string ) { + int c1, c2; + int v; + + while ( 1 ) { + c1 = string[0]; + c2 = string[1]; + + v = ( Hex( c1 ) << 4 ) | Hex( c2 ); + Emit1( v ); + + if ( !string[2] ) { + break; + } + string += 3; + } +} + + + +static void EmitCommand(ELastCommand command) +{ + switch(command) + { + case LAST_COMMAND_MOV_EDI_EAX: + EmitString( "89 07" ); // mov dword ptr [edi], eax + break; + + case LAST_COMMAND_SUB_DI_4: + EmitString( "83 EF 04" ); // sub edi, 4 + break; + + case LAST_COMMAND_SUB_DI_8: + EmitString( "83 EF 08" ); // sub edi, 8 + break; + default: + break; + } + LastCommand = command; +} + +static void EmitAddEDI4(vm_t *vm) { + if (LastCommand == LAST_COMMAND_SUB_DI_4 && jused[instruction-1] == 0) + { // sub di,4 + compiledOfs -= 3; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + return; + } + if (LastCommand == LAST_COMMAND_SUB_DI_8 && jused[instruction-1] == 0) + { // sub di,8 + compiledOfs -= 3; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "83 EF 04" ); // sub edi,4 + return; + } + EmitString( "83 C7 04" ); // add edi,4 +} + +static void EmitMovEAXEDI(vm_t *vm) { + if (LastCommand == LAST_COMMAND_MOV_EDI_EAX) + { // mov [edi], eax + compiledOfs -= 2; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + return; + } + if (pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || + pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1 ) + { + return; + } + if (pop1 == OP_CONST && buf[compiledOfs-6] == 0xC7 && buf[compiledOfs-5] == 0x07 ) + { // mov edi, 0x123456 + compiledOfs -= 6; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "B8" ); // mov eax, 0x12345678 + Emit4( lastConst ); + return; + } + EmitString( "8B 07" ); // mov eax, dword ptr [edi] +} + +qboolean EmitMovEBXEDI(vm_t *vm, int andit) { + if (LastCommand == LAST_COMMAND_MOV_EDI_EAX) + { // mov [edi], eax + compiledOfs -= 2; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "8B D8"); // mov bx, eax + return qfalse; + } + if (pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || + pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1 ) + { + EmitString( "8B D8"); // mov bx, eax + return qfalse; + } + if (pop1 == OP_CONST && buf[compiledOfs-6] == 0xC7 && buf[compiledOfs-5] == 0x07 ) + { // mov edi, 0x123456 + compiledOfs -= 6; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "BB" ); // mov ebx, 0x12345678 + if (andit) { + Emit4( lastConst & andit ); + } else { + Emit4( lastConst ); + } + return qtrue; + } + + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + return qfalse; +} + +#define JUSED(x) \ + do { \ + if (x < 0 || x >= jusedSize) { \ + VMFREE_BUFFERS(); \ + Com_Error( ERR_DROP, \ + "VM_CompileX86: jump target out of range at offset %d", pc ); \ + } \ + jused[x] = 1; \ + } while(0) + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { + int op; + int maxLength; + int v; + int i; + qboolean opt; + int jusedSize = header->instructionCount + 2; + + // allocate a very large temp buffer, we will shrink it later + maxLength = header->codeLength * 8; + buf = Z_Malloc(maxLength); + jused = Z_Malloc(jusedSize); + + Com_Memset(jused, 0, jusedSize); + + // ensure that the optimisation pass knows about all the jump + // table targets + for( i = 0; i < vm->numJumpTableTargets; i++ ) { + jused[ *(int *)(vm->jumpTableTargets + ( i * sizeof( int ) ) ) ] = 1; + } + + for(pass=0;pass<2;pass++) { + oc0 = -23423; + oc1 = -234354; + pop0 = -43435; + pop1 = -545455; + + // translate all instructions + pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + compiledOfs = 0; + + LastCommand = LAST_COMMAND_NONE; + + while(instruction < header->instructionCount) + { + if(compiledOfs > maxLength - 16) + { + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: maxLength exceeded"); + } + + vm->instructionPointers[ instruction ] = compiledOfs; + instruction++; + + if(pc > header->codeLength) + { + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: pc > header->codeLength"); + } + + op = code[ pc ]; + pc++; + switch ( op ) { + case 0: + break; + case OP_BREAK: + EmitString( "CC" ); // int 3 + break; + case OP_ENTER: + EmitString( "81 EE" ); // sub esi, 0x12345678 + Emit4( Constant4() ); + break; + case OP_CONST: + if (code[pc+4] == OP_LOAD4) { + EmitAddEDI4(vm); + EmitString( "BB" ); // mov ebx, 0x12345678 + Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); + EmitString( "8B 03" ); // mov eax, dword ptr [ebx] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + pc++; // OP_LOAD4 + instruction += 1; + break; + } + if (code[pc+4] == OP_LOAD2) { + EmitAddEDI4(vm); + EmitString( "BB" ); // mov ebx, 0x12345678 + Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); + EmitString( "0F B7 03" ); // movzx eax, word ptr [ebx] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + pc++; // OP_LOAD4 + instruction += 1; + break; + } + if (code[pc+4] == OP_LOAD1) { + EmitAddEDI4(vm); + EmitString( "BB" ); // mov ebx, 0x12345678 + Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); + EmitString( "0F B6 03" ); // movzx eax, byte ptr [ebx] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + pc++; // OP_LOAD4 + instruction += 1; + break; + } + if (code[pc+4] == OP_STORE4) { + opt = EmitMovEBXEDI(vm, (vm->dataMask & ~3)); + EmitString( "B8" ); // mov eax, 0x12345678 + Emit4( Constant4() ); +// if (!opt) { +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask & ~3 ); +// } + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_STORE4 + instruction += 1; + break; + } + if (code[pc+4] == OP_STORE2) { + opt = EmitMovEBXEDI(vm, (vm->dataMask & ~1)); + EmitString( "B8" ); // mov eax, 0x12345678 + Emit4( Constant4() ); +// if (!opt) { +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask & ~1 ); +// } + EmitString( "66 89 83" ); // mov word ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_STORE4 + instruction += 1; + break; + } + if (code[pc+4] == OP_STORE1) { + opt = EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "B8" ); // mov eax, 0x12345678 + Emit4( Constant4() ); +// if (!opt) { +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask ); +// } + EmitString( "88 83" ); // mov byte ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_STORE4 + instruction += 1; + break; + } + if (code[pc+4] == OP_ADD) { + EmitString( "81 07" ); // add dword ptr [edi], 0x1234567 + Emit4( Constant4() ); + pc++; // OP_ADD + instruction += 1; + break; + } + if (code[pc+4] == OP_SUB) { + EmitString( "81 2F" ); // sub dword ptr [edi], 0x1234567 + Emit4( Constant4() ); + pc++; // OP_ADD + instruction += 1; + break; + } + EmitAddEDI4(vm); + EmitString( "C7 07" ); // mov dword ptr [edi], 0x12345678 + lastConst = Constant4(); + Emit4( lastConst ); + if (code[pc] == OP_JUMP) { + JUSED(lastConst); + } + break; + case OP_LOCAL: + EmitAddEDI4(vm); + EmitString( "8D 86" ); // lea eax, [0x12345678 + esi] + oc0 = oc1; + oc1 = Constant4(); + Emit4( oc1 ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_ARG: + EmitMovEAXEDI(vm); // mov eax,dword ptr [edi] + EmitString( "89 86" ); // mov dword ptr [esi+database],eax + // FIXME: range check + Emit4( Constant1() + (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_CALL: + EmitString( "C7 86" ); // mov dword ptr [esi+database],0x12345678 + Emit4( (int)vm->dataBase ); + Emit4( pc ); + EmitString( "FF 15" ); // call asmCallPtr + Emit4( (int)&asmCallPtr ); + break; + case OP_PUSH: + EmitAddEDI4(vm); + break; + case OP_POP: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_LEAVE: + v = Constant4(); + EmitString( "81 C6" ); // add esi, 0x12345678 + Emit4( v ); + EmitString( "C3" ); // ret + break; + case OP_LOAD4: + if (code[pc] == OP_CONST && code[pc+5] == OP_ADD && code[pc+6] == OP_STORE4) { + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + compiledOfs -= 11; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + } + pc++; // OP_CONST + v = Constant4(); + EmitMovEBXEDI(vm, vm->dataMask); + if (v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + EmitString( "FF 83"); // inc dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + } else { + EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitString( "05" ); // add eax, const + Emit4( v ); + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + } else { + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + } + } + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_ADD + pc++; // OP_STORE + instruction += 3; + break; + } + + if (code[pc] == OP_CONST && code[pc+5] == OP_SUB && code[pc+6] == OP_STORE4) { + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + compiledOfs -= 11; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + } + EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + pc++; // OP_CONST + v = Constant4(); + if (v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + EmitString( "FF 8B"); // dec dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + } else { + EmitString( "2D" ); // sub eax, const + Emit4( v ); + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + } else { + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + } + } + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_SUB + pc++; // OP_STORE + instruction += 3; + break; + } + + if (buf[compiledOfs-2] == 0x89 && buf[compiledOfs-1] == 0x07) { + compiledOfs -= 2; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "8B 80"); // mov eax, dword ptr [eax + 0x1234567] + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + } + EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_LOAD2: + EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "0F B7 83" ); // movzx eax, word ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_LOAD1: + EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "0F B6 83" ); // movzx eax, byte ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_STORE4: + EmitMovEAXEDI(vm); + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] +// if (pop1 != OP_CALL) { +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask & ~3 ); +// } + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + break; + case OP_STORE2: + EmitMovEAXEDI(vm); + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask & ~1 ); + EmitString( "66 89 83" ); // mov word ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + break; + case OP_STORE1: + EmitMovEAXEDI(vm); + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask ); + EmitString( "88 83" ); // mov byte ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + break; + + case OP_EQ: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_NE: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LTI: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7D 06" ); // jnl +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LEI: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7F 06" ); // jnle +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GTI: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7E 06" ); // jng +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GEI: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7C 06" ); // jnge +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LTU: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "73 06" ); // jnb +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LEU: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "77 06" ); // jnbe +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GTU: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "76 06" ); // jna +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GEU: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "72 06" ); // jnae +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_EQF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 40" ); // test ah,0x40 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_NEF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 40" ); // test ah,0x40 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LTF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 01" ); // test ah,0x01 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LEF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 41" ); // test ah,0x41 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GTF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 41" ); // test ah,0x41 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GEF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 01" ); // test ah,0x01 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + JUSED(v); + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_NEGI: + EmitString( "F7 1F" ); // neg dword ptr [edi] + break; + case OP_ADD: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "01 47 FC" ); // add dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_SUB: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "29 47 FC" ); // sub dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_DIVI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "99" ); // cdq + EmitString( "F7 3F" ); // idiv dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_DIVU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "33 D2" ); // xor edx, edx + EmitString( "F7 37" ); // div dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_MODI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "99" ); // cdq + EmitString( "F7 3F" ); // idiv dword ptr [edi] + EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_MODU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "33 D2" ); // xor edx, edx + EmitString( "F7 37" ); // div dword ptr [edi] + EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_MULI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "F7 2F" ); // imul dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_MULU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "F7 27" ); // mul dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_BAND: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "21 47 FC" ); // and dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_BOR: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "09 47 FC" ); // or dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_BXOR: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "31 47 FC" ); // xor dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_BCOM: + EmitString( "F7 17" ); // not dword ptr [edi] + break; + case OP_LSH: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 67 FC" ); // shl dword ptr [edi-4], cl + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_RSHI: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 7F FC" ); // sar dword ptr [edi-4], cl + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_RSHU: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 6F FC" ); // shr dword ptr [edi-4], cl + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_NEGF: + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D9 E0" ); // fchs + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_ADDF: + EmitString( "D9 47 FC" ); // fld dword ptr [edi-4] + EmitString( "D8 07" ); // fadd dword ptr [edi] + EmitString( "D9 5F FC" ); // fstp dword ptr [edi-4] + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_SUBF: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 67 04" ); // fsub dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_DIVF: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 77 04" ); // fdiv dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_MULF: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 4f 04" ); // fmul dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_CVIF: + EmitString( "DB 07" ); // fild dword ptr [edi] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_CVFI: +#ifndef FTOL_PTR // WHENHELLISFROZENOVER + // not IEEE complient, but simple and fast + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "DB 1F" ); // fistp dword ptr [edi] +#else // FTOL_PTR + // call the library conversion function + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "FF 15" ); // call ftolPtr + Emit4( (int)&ftolPtr ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax +#endif + break; + case OP_SEX8: + EmitString( "0F BE 07" ); // movsx eax, byte ptr [edi] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_SEX16: + EmitString( "0F BF 07" ); // movsx eax, word ptr [edi] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + + case OP_BLOCK_COPY: + // FIXME: range check + EmitString( "56" ); // push esi + EmitString( "57" ); // push edi + EmitString( "8B 37" ); // mov esi,[edi] + EmitString( "8B 7F FC" ); // mov edi,[edi-4] + EmitString( "B9" ); // mov ecx,0x12345678 + Emit4( Constant4() >> 2 ); + EmitString( "B8" ); // mov eax, datamask + Emit4( vm->dataMask ); + EmitString( "BB" ); // mov ebx, database + Emit4( (int)vm->dataBase ); + EmitString( "23 F0" ); // and esi, eax + EmitString( "03 F3" ); // add esi, ebx + EmitString( "23 F8" ); // and edi, eax + EmitString( "03 FB" ); // add edi, ebx + EmitString( "F3 A5" ); // rep movsd + EmitString( "5F" ); // pop edi + EmitString( "5E" ); // pop esi + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + break; + + case OP_JUMP: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "8B 47 04" ); // mov eax,dword ptr [edi+4] + // FIXME: range check + EmitString( "FF 24 85" ); // jmp dword ptr [instructionPointers + eax * 4] + Emit4( (int)vm->instructionPointers ); + break; + default: + VMFREE_BUFFERS(); + Com_Error(ERR_DROP, "VM_CompileX86: bad opcode %i at offset %i", op, pc); + } + pop0 = pop1; + pop1 = op; + } + } + + // copy to an exact size buffer on the hunk + vm->codeLength = compiledOfs; +#ifdef VM_X86_MMAP + vm->codeBase = mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); + if(vm->codeBase == MAP_FAILED) + Com_Error(ERR_FATAL, "VM_CompileX86: can't mmap memory"); +#elif _WIN32 + // allocate memory with EXECUTE permissions under windows. + vm->codeBase = VirtualAlloc(NULL, compiledOfs, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if(!vm->codeBase) + Com_Error(ERR_FATAL, "VM_CompileX86: VirtualAlloc failed"); +#else + vm->codeBase = malloc(compiledOfs); +#endif + + Com_Memcpy( vm->codeBase, buf, compiledOfs ); + +#ifdef VM_X86_MMAP + if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC)) + Com_Error(ERR_FATAL, "VM_CompileX86: mprotect failed"); +#elif _WIN32 + { + DWORD oldProtect = 0; + + // remove write permissions. + if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect)) + Com_Error(ERR_FATAL, "VM_CompileX86: VirtualProtect failed"); + } +#endif + + Z_Free( buf ); + Z_Free( jused ); + Com_Printf( "VM file %s compiled to %i bytes of code\n", vm->name, compiledOfs ); + + vm->destroy = VM_Destroy_Compiled; + + // offset all the instruction pointers for the new location + for ( i = 0 ; i < header->instructionCount ; i++ ) { + vm->instructionPointers[i] += (int)vm->codeBase; + } +} + +void VM_Destroy_Compiled(vm_t* self) +{ +#ifdef VM_X86_MMAP + munmap(self->codeBase, self->codeLength); +#elif _WIN32 + VirtualFree(self->codeBase, 0, MEM_RELEASE); +#else + free(self->codeBase); +#endif +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ +int VM_CallCompiled( vm_t *vm, int *args ) { + int stack[1024]; + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + void *opStack; + int *oldInstructionPointers; + + oldInstructionPointers = instructionPointers; + + currentVM = vm; + instructionPointers = vm->instructionPointers; + + // interpret the code + vm->currentlyInterpreting = qtrue; + + callMask = vm->dataMask; + + // we might be called recursively, so this might not be the very top + programStack = vm->programStack; + stackOnEntry = programStack; + + // set up the stack frame + image = vm->dataBase; + + programCounter = 0; + + programStack -= 48; + + *(int *)&image[ programStack + 44] = args[9]; + *(int *)&image[ programStack + 40] = args[8]; + *(int *)&image[ programStack + 36] = args[7]; + *(int *)&image[ programStack + 32] = args[6]; + *(int *)&image[ programStack + 28] = args[5]; + *(int *)&image[ programStack + 24] = args[4]; + *(int *)&image[ programStack + 20] = args[3]; + *(int *)&image[ programStack + 16] = args[2]; + *(int *)&image[ programStack + 12] = args[1]; + *(int *)&image[ programStack + 8 ] = args[0]; + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + // off we go into generated code... + opStack = &stack; + + { +#ifdef _MSC_VER + void *entryPoint = vm->codeBase; + + __asm { + pushad + mov esi, programStack + mov edi, opStack + call entryPoint + mov programStack, esi + mov opStack, edi + popad + } +#else + /* These registers are used as scratch registers and are destroyed after the + * call. Do not use clobber, so they can be used as input for the asm. */ + unsigned eax; + unsigned ebx; + unsigned ecx; + unsigned edx; + + __asm__ volatile( + "call *%6" + : "+S" (programStack), "+D" (opStack), + "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) + : "mr" (vm->codeBase) + : "cc", "memory" + ); +#endif + } + + if ( opStack != &stack[1] ) { + Com_Error( ERR_DROP, "opStack corrupted in compiled code" ); + } + if ( programStack != stackOnEntry - 48 ) { + Com_Error( ERR_DROP, "programStack corrupted in compiled code" ); + } + + vm->programStack = stackOnEntry; + + // in case we were recursively called by another vm + instructionPointers = oldInstructionPointers; + + return *(int *)opStack; +} diff --git a/src/qcommon/vm_x86_64.c b/src/qcommon/vm_x86_64.c new file mode 100644 index 0000000..6b33544 --- /dev/null +++ b/src/qcommon/vm_x86_64.c @@ -0,0 +1,1099 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2005 Ludwig Nussel + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// vm_x86_64.c -- load time compiler and execution environment for x86-64 + +#include "vm_local.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __WIN64__ + #include + #define CROSSCALL __attribute__ ((sysv_abi))//fool the vm we're SYSV ABI + //#define __USE_MINGW_ANSI_STDIO 1 //very slow - avoid if possible +#else + #include + #include + #define VM_X86_64_MMAP + #define CROSSCALL +#endif + +//#define DEBUG_VM + +#ifdef DEBUG_VM +#define Dfprintf(fd, args...) fprintf(fd, ##args) +static FILE* qdasmout; +#else +#define Dfprintf(args...) +#endif + +#define VM_FREEBUFFERS(vm) do {assembler_init(0); VM_Destroy_Compiled(vm);} while(0) + +void assembler_set_output(char* buf); +size_t assembler_get_code_size(void); +void assembler_init(int pass); +void assemble_line(const char* input, size_t len); + +static void VM_Destroy_Compiled(vm_t* self); + +/* + + |=====================| + ^ dataMask ^- programStack rdi + | + +- r8 + + eax scratch + ebx scratch + ecx scratch (required for shifts) + edx scratch (required for divisions) + rsi stack pointer (opStack) + rdi program frame pointer (programStack) + r8 pointer data (vm->dataBase) + r10 start of generated code +*/ + + +static int64_t CROSSCALL callAsmCall(int64_t callProgramStack, int64_t callSyscallNum) +{ + vm_t *savedVM; + int64_t ret = 0x77; + int64_t args[11]; +// int iargs[11]; + int i; + +// Dfprintf(stderr, "callAsmCall(%ld, %ld)\n", callProgramStack, callSyscallNum); +// Com_Printf("-> callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum); + + savedVM = currentVM; + + // save the stack to allow recursive VM entry + currentVM->programStack = callProgramStack - 4; + + args[0] = callSyscallNum; +// iargs[0] = callSyscallNum; + for(i = 0; i < 10; ++i) + { +// iargs[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i); + args[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i); + } + ret = currentVM->systemCall(args); + + currentVM = savedVM; +// Com_Printf("<- callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum); + + return ret; +} + +#ifdef DEBUG_VM +static char *opnames[256] = { + "OP_UNDEF", + + "OP_IGNORE", + + "OP_BREAK", + + "OP_ENTER", + "OP_LEAVE", + "OP_CALL", + "OP_PUSH", + "OP_POP", + + "OP_CONST", + + "OP_LOCAL", + + "OP_JUMP", + + //------------------- + + "OP_EQ", + "OP_NE", + + "OP_LTI", + "OP_LEI", + "OP_GTI", + "OP_GEI", + + "OP_LTU", + "OP_LEU", + "OP_GTU", + "OP_GEU", + + "OP_EQF", + "OP_NEF", + + "OP_LTF", + "OP_LEF", + "OP_GTF", + "OP_GEF", + + //------------------- + + "OP_LOAD1", + "OP_LOAD2", + "OP_LOAD4", + "OP_STORE1", + "OP_STORE2", + "OP_STORE4", + "OP_ARG", + + "OP_BLOCK_COPY", + + //------------------- + + "OP_SEX8", + "OP_SEX16", + + "OP_NEGI", + "OP_ADD", + "OP_SUB", + "OP_DIVI", + "OP_DIVU", + "OP_MODI", + "OP_MODU", + "OP_MULI", + "OP_MULU", + + "OP_BAND", + "OP_BOR", + "OP_BXOR", + "OP_BCOM", + + "OP_LSH", + "OP_RSHI", + "OP_RSHU", + + "OP_NEGF", + "OP_ADDF", + "OP_SUBF", + "OP_DIVF", + "OP_MULF", + + "OP_CVIF", + "OP_CVFI" +}; +#endif // DEBUG_VM + +static unsigned char op_argsize[256] = +{ + [OP_ENTER] = 4, + [OP_LEAVE] = 4, + [OP_CONST] = 4, + [OP_LOCAL] = 4, + [OP_EQ] = 4, + [OP_NE] = 4, + [OP_LTI] = 4, + [OP_LEI] = 4, + [OP_GTI] = 4, + [OP_GEI] = 4, + [OP_LTU] = 4, + [OP_LEU] = 4, + [OP_GTU] = 4, + [OP_GEU] = 4, + [OP_EQF] = 4, + [OP_NEF] = 4, + [OP_LTF] = 4, + [OP_LEF] = 4, + [OP_GTF] = 4, + [OP_GEF] = 4, + [OP_ARG] = 1, + [OP_BLOCK_COPY] = 4, +}; + +void emit(const char* fmt, ...) +{ + va_list ap; + char line[4096]; + va_start(ap, fmt); + Q_vsnprintf(line, sizeof(line), fmt, ap); + va_end(ap); + assemble_line(line, strlen(line)); +} + +#define CHECK_INSTR_REG(reg) \ + emit("cmpl $%u, %%"#reg, header->instructionCount); \ + emit("jb jmp_ok_i_%08x", instruction); \ + emit("movq $%"PRIu64", %%rax", (uint64_t)jmpviolation); \ + emit("callq *%%rax"); \ + emit("jmp_ok_i_%08x:", instruction); + +#define PREPARE_JMP(reg) \ + CHECK_INSTR_REG(reg) \ + emit("movq $%"PRIu64", %%rbx", (uint64_t)vm->instructionPointers); \ + emit("movl (%%rbx, %%rax, 4), %%eax"); \ + emit("addq %%r10, %%rax"); + +#define CHECK_INSTR(nr) \ + do { if(nr < 0 || nr >= header->instructionCount) { \ + VM_FREEBUFFERS(vm); \ + Com_Error( ERR_DROP, \ + "%s: jump target 0x%x out of range at offset %d", __func__, nr, pc ); \ + } } while(0) + +#define JMPIARG \ + CHECK_INSTR(iarg); \ + emit("movq $%"PRIu64", %%rax", vm->codeBase+vm->instructionPointers[iarg]); \ + emit("jmpq *%%rax"); + +#define CONST_OPTIMIZE +#ifdef CONST_OPTIMIZE +#define MAYBE_EMIT_CONST() \ + if (got_const) \ + { \ + got_const = 0; \ + vm->instructionPointers[instruction-1] = assembler_get_code_size(); \ + emit("addq $4, %%rsi"); \ + emit("movl $%d, 0(%%rsi)", const_value); \ + } +#else +#define MAYBE_EMIT_CONST() +#endif + +// integer compare and jump +#define IJ(op) \ + MAYBE_EMIT_CONST(); \ + emit("subq $8, %%rsi"); \ + emit("movl 4(%%rsi), %%eax"); \ + emit("cmpl 8(%%rsi), %%eax"); \ + emit(op " i_%08x", instruction+1); \ + JMPIARG \ + neednilabel = 1; + +#ifdef USE_X87 +#define FJ(bits, op) \ + MAYBE_EMIT_CONST(); \ + emit("subq $8, %%rsi");\ + emit("flds 4(%%rsi)");\ + emit("fcomps 8(%%rsi)");\ + emit("fnstsw %%ax");\ + emit("testb $" #bits ", %%ah");\ + emit(op " i_%08x", instruction+1);\ + JMPIARG \ + neednilabel = 1; +#define XJ(x) +#else +#define FJ(x, y) +#define XJ(op) \ + MAYBE_EMIT_CONST(); \ + emit("subq $8, %%rsi");\ + emit("movss 4(%%rsi), %%xmm0");\ + emit("ucomiss 8(%%rsi), %%xmm0");\ + emit("jp i_%08x", instruction+1);\ + emit(op " i_%08x", instruction+1);\ + JMPIARG \ + neednilabel = 1; +#endif + +#define SIMPLE(op) \ + MAYBE_EMIT_CONST(); \ + emit("subq $4, %%rsi"); \ + emit("movl 4(%%rsi), %%eax"); \ + emit(op " %%eax, 0(%%rsi)"); + +#ifdef USE_X87 +#define FSIMPLE(op) \ + MAYBE_EMIT_CONST(); \ + emit("subq $4, %%rsi"); \ + emit("flds 0(%%rsi)"); \ + emit(op " 4(%%rsi)"); \ + emit("fstps 0(%%rsi)"); +#define XSIMPLE(op) +#else +#define FSIMPLE(op) +#define XSIMPLE(op) \ + MAYBE_EMIT_CONST(); \ + emit("subq $4, %%rsi"); \ + emit("movss 0(%%rsi), %%xmm0"); \ + emit(op " 4(%%rsi), %%xmm0"); \ + emit("movss %%xmm0, 0(%%rsi)"); +#endif + +#define SHIFT(op) \ + MAYBE_EMIT_CONST(); \ + emit("subq $4, %%rsi"); \ + emit("movl 4(%%rsi), %%ecx"); \ + emit("movl 0(%%rsi), %%eax"); \ + emit(op " %%cl, %%eax"); \ + emit("movl %%eax, 0(%%rsi)"); + +#ifdef DEBUG_VM +#define RANGECHECK(reg, bytes) \ + emit("movl %%" #reg ", %%ecx"); \ + emit("andl $0x%x, %%ecx", vm->dataMask &~(bytes-1)); \ + emit("cmpl %%" #reg ", %%ecx"); \ + emit("jz rc_ok_i_%08x", instruction); \ + emit("movq $%"PRIu64", %%rax", (uint64_t)memviolation); \ + emit("callq *%%rax"); \ + emit("rc_ok_i_%08x:", instruction); +#elif 1 +// check is too expensive, so just confine memory access +#define RANGECHECK(reg, bytes) \ + emit("andl $0x%x, %%" #reg, vm->dataMask &~(bytes-1)); +#else +#define RANGECHECK(reg, bytes) +#endif + +#ifdef DEBUG_VM +#define NOTIMPL(x) \ + do { Com_Error(ERR_DROP, "instruction not implemented: %s\n", opnames[x]); } while(0) +#else +#define NOTIMPL(x) \ + do { Com_Printf(S_COLOR_RED "instruction not implemented: %x\n", x); vm->compiled = qfalse; return; } while(0) +#endif + +static void* getentrypoint(vm_t* vm) +{ + return vm->codeBase; +} + +static void CROSSCALL block_copy_vm(unsigned dest, unsigned src, unsigned count) +{ + unsigned dataMask = currentVM->dataMask; + + if ((dest & dataMask) != dest + || (src & dataMask) != src + || ((dest+count) & dataMask) != dest + count + || ((src+count) & dataMask) != src + count) + { + Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!\n"); + } + + memcpy(currentVM->dataBase+dest, currentVM->dataBase+src, count); +} + +static void CROSSCALL eop(void) +{ + Com_Error(ERR_DROP, "end of program reached without return!\n"); + exit(1); +} + +static void CROSSCALL jmpviolation(void) +{ + Com_Error(ERR_DROP, "program tried to execute code outside VM\n"); + exit(1); +} + +#ifdef DEBUG_VM +static void CROSSCALL memviolation(void) +{ + Com_Error(ERR_DROP, "program tried to access memory outside VM\n"); + exit(1); +} +#endif + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { + unsigned char op; + int pc; + unsigned instruction; + char* code; + unsigned iarg = 0; + unsigned char barg = 0; + int neednilabel = 0; + struct timeval tvstart = {0, 0}; +#ifdef DEBUG_VM + char fn_d[MAX_QPATH]; // disassembled +#endif + + int pass; + size_t compiledOfs = 0; + + // const optimization + unsigned got_const = 0, const_value = 0; + + vm->codeBase = NULL; + + gettimeofday(&tvstart, NULL); + + for (pass = 0; pass < 2; ++pass) { + + if(pass) + { + compiledOfs = assembler_get_code_size(); + vm->codeLength = compiledOfs; + + #ifdef VM_X86_64_MMAP + vm->codeBase = mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); + if(vm->codeBase == MAP_FAILED) + Com_Error(ERR_FATAL, "VM_CompileX86_64: can't mmap memory"); + #elif __WIN64__ + // allocate memory with write permissions under windows. + vm->codeBase = VirtualAlloc(NULL, compiledOfs, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + if(!vm->codeBase) + Com_Error(ERR_FATAL, "VM_CompileX86_64: VirtualAlloc failed"); + #else + vm->codeBase = malloc(compiledOfs); + if(!vm_codeBase) + Com_Error(ERR_FATAL, "VM_CompileX86_64: Failed to allocate memory"); + #endif + + assembler_set_output((char*)vm->codeBase); + } + + assembler_init(pass); + +#ifdef DEBUG_VM + strcpy(fn_d,vm->name); + strcat(fn_d, ".qdasm"); + + qdasmout = fopen(fn_d, "w"); +#endif + + // translate all instructions + pc = 0; + code = (char *)header + header->codeOffset; + + for ( instruction = 0; instruction < header->instructionCount; ++instruction ) + { + op = code[ pc ]; + ++pc; + + vm->instructionPointers[instruction] = assembler_get_code_size(); + + /* store current instruction number in r15 for debugging */ +#if DEBUG_VM0 + emit("nop"); + emit("movq $%d, %%r15", instruction); + emit("nop"); +#endif + + if(op_argsize[op] == 4) + { + iarg = *(int*)(code+pc); + pc += 4; + Dfprintf(qdasmout, "%s %8u\n", opnames[op], iarg); + } + else if(op_argsize[op] == 1) + { + barg = code[pc++]; + Dfprintf(qdasmout, "%s %8hu\n", opnames[op], barg); + } + else + { + Dfprintf(qdasmout, "%s\n", opnames[op]); + } + + if(neednilabel) + { + emit("i_%08x:", instruction); + neednilabel = 0; + } + + switch ( op ) + { + case OP_UNDEF: + NOTIMPL(op); + break; + case OP_IGNORE: + MAYBE_EMIT_CONST(); + emit("nop"); + break; + case OP_BREAK: + MAYBE_EMIT_CONST(); + emit("int3"); + break; + case OP_ENTER: + MAYBE_EMIT_CONST(); + emit("subl $%d, %%edi", iarg); + break; + case OP_LEAVE: + MAYBE_EMIT_CONST(); + emit("addl $%d, %%edi", iarg); // get rid of stack frame + emit("ret"); + break; + case OP_CALL: + RANGECHECK(edi, 4); + emit("movl $%d, 0(%%r8, %%rdi, 1)", instruction+1); // save next instruction + if(got_const) + { + if ((int)const_value < 0) + goto emit_do_syscall; + + CHECK_INSTR(const_value); + emit("movq $%"PRIu64", %%rax", vm->codeBase+vm->instructionPointers[const_value]); + emit("callq *%%rax"); + got_const = 0; + break; + } + else + { + MAYBE_EMIT_CONST(); + emit("movl 0(%%rsi), %%eax"); // get instr from stack + emit("subq $4, %%rsi"); + + emit("orl %%eax, %%eax"); + emit("jl callSyscall%d", instruction); + + PREPARE_JMP(eax); + emit("callq *%%rax"); + + emit("jmp i_%08x", instruction+1); + emit("callSyscall%d:", instruction); + } +emit_do_syscall: +// emit("fnsave 4(%%rsi)"); + emit("push %%rsi"); + emit("push %%rdi"); + emit("push %%r8"); + emit("push %%r9"); + emit("push %%r10"); + emit("movq %%rsp, %%rbx"); // we need to align the stack pointer + emit("subq $8, %%rbx"); // | + emit("andq $127, %%rbx"); // | + emit("subq %%rbx, %%rsp"); // <-+ + emit("push %%rbx"); + if(got_const) { + got_const = 0; + emit("movq $%u, %%rsi", -1-const_value); // second argument in rsi + } else { + emit("negl %%eax"); // convert to actual number + emit("decl %%eax"); + // first argument already in rdi + emit("movq %%rax, %%rsi"); // second argument in rsi + } + emit("movq $%"PRIu64", %%rax", (uint64_t)callAsmCall); + emit("callq *%%rax"); + emit("pop %%rbx"); + emit("addq %%rbx, %%rsp"); + emit("pop %%r10"); + emit("pop %%r9"); + emit("pop %%r8"); + emit("pop %%rdi"); + emit("pop %%rsi"); +// emit("frstor 4(%%rsi)"); + emit("addq $4, %%rsi"); + emit("movl %%eax, (%%rsi)"); // store return value + neednilabel = 1; + break; + case OP_PUSH: + MAYBE_EMIT_CONST(); + emit("addq $4, %%rsi"); + break; + case OP_POP: + MAYBE_EMIT_CONST(); + emit("subq $4, %%rsi"); + break; + case OP_CONST: + MAYBE_EMIT_CONST(); +#ifdef CONST_OPTIMIZE + got_const = 1; + const_value = iarg; +#else + emit("addq $4, %%rsi"); + emit("movl $%d, 0(%%rsi)", iarg); +#endif + break; + case OP_LOCAL: + MAYBE_EMIT_CONST(); + emit("movl %%edi, %%ebx"); + emit("addl $%d,%%ebx", iarg); + emit("addq $4, %%rsi"); + emit("movl %%ebx, 0(%%rsi)"); + break; + case OP_JUMP: + if(got_const) { + iarg = const_value; + got_const = 0; + JMPIARG; + } else { + emit("movl 0(%%rsi), %%eax"); // get instr from stack + emit("subq $4, %%rsi"); + + PREPARE_JMP(eax); + emit("jmp *%%rax"); + } + break; + case OP_EQ: + IJ("jne"); + break; + case OP_NE: + IJ("je"); + break; + case OP_LTI: + IJ("jnl"); + break; + case OP_LEI: + IJ("jnle"); + break; + case OP_GTI: + IJ("jng"); + break; + case OP_GEI: + IJ("jnge"); + break; + case OP_LTU: + IJ("jnb"); + break; + case OP_LEU: + IJ("jnbe"); + break; + case OP_GTU: + IJ("jna"); + break; + case OP_GEU: + IJ("jnae"); + break; + case OP_EQF: + FJ(0x40, "jz"); + XJ("jnz"); + break; + case OP_NEF: + FJ(0x40, "jnz"); +#ifndef USE_X87 + MAYBE_EMIT_CONST(); + emit("subq $8, %%rsi"); + emit("movss 4(%%rsi), %%xmm0"); + emit("ucomiss 8(%%rsi), %%xmm0"); + emit("jp dojump_i_%08x", instruction); + emit("jz i_%08x", instruction+1); + emit("dojump_i_%08x:", instruction); + JMPIARG + neednilabel = 1; +#endif + break; + case OP_LTF: + FJ(0x01, "jz"); + XJ("jnc"); + break; + case OP_LEF: + FJ(0x41, "jz"); + XJ("ja"); + break; + case OP_GTF: + FJ(0x41, "jnz"); + XJ("jbe"); + break; + case OP_GEF: + FJ(0x01, "jnz"); + XJ("jb"); + break; + case OP_LOAD1: + MAYBE_EMIT_CONST(); + emit("movl 0(%%rsi), %%eax"); // get value from stack + RANGECHECK(eax, 1); + emit("movb 0(%%r8, %%rax, 1), %%al"); // deref into eax + emit("andq $255, %%rax"); + emit("movl %%eax, 0(%%rsi)"); // store on stack + break; + case OP_LOAD2: + MAYBE_EMIT_CONST(); + emit("movl 0(%%rsi), %%eax"); // get value from stack + RANGECHECK(eax, 2); + emit("movw 0(%%r8, %%rax, 1), %%ax"); // deref into eax + emit("movl %%eax, 0(%%rsi)"); // store on stack + break; + case OP_LOAD4: + MAYBE_EMIT_CONST(); + emit("movl 0(%%rsi), %%eax"); // get value from stack + RANGECHECK(eax, 4); // not a pointer!? + emit("movl 0(%%r8, %%rax, 1), %%eax"); // deref into eax + emit("movl %%eax, 0(%%rsi)"); // store on stack + break; + case OP_STORE1: + MAYBE_EMIT_CONST(); + emit("movl 0(%%rsi), %%eax"); // get value from stack + emit("andq $255, %%rax"); + emit("movl -4(%%rsi), %%ebx"); // get pointer from stack + RANGECHECK(ebx, 1); + emit("movb %%al, 0(%%r8, %%rbx, 1)"); // store in memory + emit("subq $8, %%rsi"); + break; + case OP_STORE2: + MAYBE_EMIT_CONST(); + emit("movl 0(%%rsi), %%eax"); // get value from stack + emit("movl -4(%%rsi), %%ebx"); // get pointer from stack + RANGECHECK(ebx, 2); + emit("movw %%ax, 0(%%r8, %%rbx, 1)"); // store in memory + emit("subq $8, %%rsi"); + break; + case OP_STORE4: + MAYBE_EMIT_CONST(); + emit("movl -4(%%rsi), %%ebx"); // get pointer from stack + RANGECHECK(ebx, 4); + emit("movl 0(%%rsi), %%ecx"); // get value from stack + emit("movl %%ecx, 0(%%r8, %%rbx, 1)"); // store in memory + emit("subq $8, %%rsi"); + break; + case OP_ARG: + MAYBE_EMIT_CONST(); + emit("subq $4, %%rsi"); + emit("movl 4(%%rsi), %%eax"); // get value from stack + emit("movl $0x%hx, %%ebx", barg); + emit("addl %%edi, %%ebx"); + RANGECHECK(ebx, 4); + emit("movl %%eax, 0(%%r8,%%rbx, 1)"); // store in args space + break; + case OP_BLOCK_COPY: + + MAYBE_EMIT_CONST(); + emit("subq $8, %%rsi"); + emit("push %%rsi"); + emit("push %%rdi"); + emit("push %%r8"); + emit("push %%r9"); + emit("push %%r10"); + emit("movq %%rsp, %%rbx"); // we need to align the stack pointer + emit("subq $8, %%rbx"); // | + emit("andq $127, %%rbx"); // | + emit("subq %%rbx, %%rsp"); // <-+ + emit("push %%rbx"); + emit("movl 4(%%rsi), %%edi"); // 1st argument dest + emit("movl 8(%%rsi), %%esi"); // 2nd argument src + emit("movl $%d, %%edx", iarg); // 3rd argument count + emit("movq $%"PRIu64", %%rax", (uint64_t)block_copy_vm); + emit("callq *%%rax"); + emit("pop %%rbx"); + emit("addq %%rbx, %%rsp"); + emit("pop %%r10"); + emit("pop %%r9"); + emit("pop %%r8"); + emit("pop %%rdi"); + emit("pop %%rsi"); + + break; + case OP_SEX8: + MAYBE_EMIT_CONST(); + emit("movw 0(%%rsi), %%ax"); + emit("andq $255, %%rax"); + emit("cbw"); + emit("cwde"); + emit("movl %%eax, 0(%%rsi)"); + break; + case OP_SEX16: + MAYBE_EMIT_CONST(); + emit("movw 0(%%rsi), %%ax"); + emit("cwde"); + emit("movl %%eax, 0(%%rsi)"); + break; + case OP_NEGI: + MAYBE_EMIT_CONST(); + emit("negl 0(%%rsi)"); + break; + case OP_ADD: + SIMPLE("addl"); + break; + case OP_SUB: + SIMPLE("subl"); + break; + case OP_DIVI: + MAYBE_EMIT_CONST(); + emit("subq $4, %%rsi"); + emit("movl 0(%%rsi), %%eax"); + emit("cdq"); + emit("idivl 4(%%rsi)"); + emit("movl %%eax, 0(%%rsi)"); + break; + case OP_DIVU: + MAYBE_EMIT_CONST(); + emit("subq $4, %%rsi"); + emit("movl 0(%%rsi), %%eax"); + emit("xorq %%rdx, %%rdx"); + emit("divl 4(%%rsi)"); + emit("movl %%eax, 0(%%rsi)"); + break; + case OP_MODI: + MAYBE_EMIT_CONST(); + emit("subq $4, %%rsi"); + emit("movl 0(%%rsi), %%eax"); + emit("xorl %%edx, %%edx"); + emit("cdq"); + emit("idivl 4(%%rsi)"); + emit("movl %%edx, 0(%%rsi)"); + break; + case OP_MODU: + MAYBE_EMIT_CONST(); + emit("subq $4, %%rsi"); + emit("movl 0(%%rsi), %%eax"); + emit("xorl %%edx, %%edx"); + emit("divl 4(%%rsi)"); + emit("movl %%edx, 0(%%rsi)"); + break; + case OP_MULI: + MAYBE_EMIT_CONST(); + emit("subq $4, %%rsi"); + emit("movl 0(%%rsi), %%eax"); + emit("imull 4(%%rsi)"); + emit("movl %%eax, 0(%%rsi)"); + break; + case OP_MULU: + MAYBE_EMIT_CONST(); + emit("subq $4, %%rsi"); + emit("movl 0(%%rsi), %%eax"); + emit("mull 4(%%rsi)"); + emit("movl %%eax, 0(%%rsi)"); + break; + case OP_BAND: + SIMPLE("andl"); + break; + case OP_BOR: + SIMPLE("orl"); + break; + case OP_BXOR: + SIMPLE("xorl"); + break; + case OP_BCOM: + MAYBE_EMIT_CONST(); + emit("notl 0(%%rsi)"); + break; + case OP_LSH: + SHIFT("shl"); + break; + case OP_RSHI: + SHIFT("sarl"); + break; + case OP_RSHU: + SHIFT("shrl"); + break; + case OP_NEGF: + MAYBE_EMIT_CONST(); +#ifdef USE_X87 + emit("flds 0(%%rsi)"); + emit("fchs"); + emit("fstps 0(%%rsi)"); +#else + emit("movl $0x80000000, %%eax"); + emit("xorl %%eax, 0(%%rsi)"); +#endif + break; + case OP_ADDF: + FSIMPLE("fadds"); + XSIMPLE("addss"); + break; + case OP_SUBF: + FSIMPLE("fsubs"); + XSIMPLE("subss"); + break; + case OP_DIVF: + FSIMPLE("fdivs"); + XSIMPLE("divss"); + break; + case OP_MULF: + FSIMPLE("fmuls"); + XSIMPLE("mulss"); + break; + case OP_CVIF: + MAYBE_EMIT_CONST(); +#ifdef USE_X87 + emit("filds 0(%%rsi)"); + emit("fstps 0(%%rsi)"); +#else + emit("movl 0(%%rsi), %%eax"); + emit("cvtsi2ss %%eax, %%xmm0"); + emit("movss %%xmm0, 0(%%rsi)"); +#endif + break; + case OP_CVFI: + MAYBE_EMIT_CONST(); +#ifdef USE_X87 + emit("flds 0(%%rsi)"); + emit("fnstcw 4(%%rsi)"); + emit("movw $0x0F7F, 8(%%rsi)"); // round toward zero + emit("fldcw 8(%%rsi)"); + emit("fistpl 0(%%rsi)"); + emit("fldcw 4(%%rsi)"); +#else + emit("movss 0(%%rsi), %%xmm0"); + emit("cvttss2si %%xmm0, %%eax"); + emit("movl %%eax, 0(%%rsi)"); +#endif + break; + default: + NOTIMPL(op); + break; + } + + + } + + if(got_const) + { + VM_FREEBUFFERS(vm); + Com_Error(ERR_DROP, "leftover const\n"); + } + + emit("movq $%"PRIu64", %%rax", (uint64_t)eop); + emit("callq *%%rax"); + + } // pass loop + + assembler_init(0); + + #ifdef VM_X86_64_MMAP + if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC)) + Com_Error(ERR_FATAL, "VM_CompileX86_64: mprotect failed"); + #elif __WIN64__ + { + DWORD oldProtect = 0; + + // remove write permissions; give exec permision + if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect)) + Com_Error(ERR_FATAL, "VM_CompileX86_64: VirtualProtect failed"); + } + #endif + + vm->destroy = VM_Destroy_Compiled; + +#ifdef DEBUG_VM + fflush(qdasmout); + fclose(qdasmout); + +#if 0 + strcpy(fn_d,vm->name); + strcat(fn_d, ".bin"); + qdasmout = fopen(fn_d, "w"); + fwrite(vm->codeBase, compiledOfs, 1, qdasmout); + fflush(qdasmout); + fclose(qdasmout); +#endif +#endif + + #ifndef __WIN64__ //timersub and gettimeofday + if(vm->compiled) + { + struct timeval tvdone = {0, 0}; + struct timeval dur = {0, 0}; + Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n", vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength ); + + gettimeofday(&tvdone, NULL); + timersub(&tvdone, &tvstart, &dur); + Com_Printf( "compilation took %"PRIu64".%06"PRIu64" seconds\n", dur.tv_sec, dur.tv_usec ); + } + #endif +} + + +void VM_Destroy_Compiled(vm_t* self) +{ + if(self && self->codeBase) + { +#ifdef VM_X86_64_MMAP + munmap(self->codeBase, self->codeLength); +#elif __WIN64__ + VirtualFree(self->codeBase, 0, MEM_RELEASE); +#else + free(self->codeBase); +#endif + } +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ + +#ifdef DEBUG_VM +static char* memData; +#endif + +int VM_CallCompiled( vm_t *vm, int *args ) { + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + void *entryPoint; + void *opStack; + int stack[1024] = { 0xDEADBEEF }; + + currentVM = vm; + +// Com_Printf("entering %s level %d, call %d, arg1 = 0x%x\n", vm->name, vm->callLevel, args[0], args[1]); + + // interpret the code + vm->currentlyInterpreting = qtrue; + +// callMask = vm->dataMask; + + // we might be called recursively, so this might not be the very top + programStack = vm->programStack; + stackOnEntry = programStack; + + // set up the stack frame + image = vm->dataBase; +#ifdef DEBUG_VM + memData = (char*)image; +#endif + + programCounter = 0; + + programStack -= 48; + + *(int *)&image[ programStack + 44] = args[9]; + *(int *)&image[ programStack + 40] = args[8]; + *(int *)&image[ programStack + 36] = args[7]; + *(int *)&image[ programStack + 32] = args[6]; + *(int *)&image[ programStack + 28] = args[5]; + *(int *)&image[ programStack + 24] = args[4]; + *(int *)&image[ programStack + 20] = args[3]; + *(int *)&image[ programStack + 16] = args[2]; + *(int *)&image[ programStack + 12] = args[1]; + *(int *)&image[ programStack + 8 ] = args[0]; + *(int *)&image[ programStack + 4 ] = 0x77777777; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + // off we go into generated code... + entryPoint = getentrypoint(vm); + opStack = &stack; + + __asm__ __volatile__ ( + " movq %5,%%rsi \r\n" \ + " movl %4,%%edi \r\n" \ + " movq %2,%%r10 \r\n" \ + " movq %3,%%r8 \r\n" \ + " subq $24, %%rsp # fix alignment as call pushes one value \r\n" \ + " callq *%%r10 \r\n" \ + " addq $24, %%rsp \r\n" \ + " movl %%edi, %0 \r\n" \ + " movq %%rsi, %1 \r\n" \ + : "=m" (programStack), "=m" (opStack) + : "m" (entryPoint), "m" (vm->dataBase), "m" (programStack), "m" (opStack) + : "%rsi", "%rdi", "%rax", "%rbx", "%rcx", "%rdx", "%r8", "%r10", "%r15", "%xmm0" + ); + + if ( opStack != &stack[1] ) { + Com_Error( ERR_DROP, "opStack corrupted in compiled code (offset %"PRId64")\n", (int64_t) ((void *) &stack[1] - opStack)); + } + if ( programStack != stackOnEntry - 48 ) { + Com_Error( ERR_DROP, "programStack corrupted in compiled code\n" ); + } + +// Com_Printf("exiting %s level %d\n", vm->name, vm->callLevel); + vm->programStack = stackOnEntry; + + return *(int *)opStack; +} diff --git a/src/qcommon/vm_x86_64_assembler.c b/src/qcommon/vm_x86_64_assembler.c new file mode 100644 index 0000000..06695d8 --- /dev/null +++ b/src/qcommon/vm_x86_64_assembler.c @@ -0,0 +1,1447 @@ +/* +=========================================================================== +vm_x86_64_assembler.c -- assembler for x86-64 + +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development +Copyright (C) 2007 Ludwig Nussel , Novell inc. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#define _ISOC99_SOURCE + +#include "vm_local.h" +#include +#include +#include +#include + +#include + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +static char* out; +static unsigned compiledOfs; +static unsigned assembler_pass; + +static const char* cur_line; + +static FILE* fout; + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +#define crap(fmt, args...) do { \ + _crap(__FUNCTION__, fmt, ##args); \ +} while(0) + +#define CRAP_INVALID_ARGS crap("invalid arguments %s, %s", argtype2str(arg1.type),argtype2str(arg2.type)); + +#ifdef DEBUG +#define debug(fmt, args...) printf(fmt, ##args) +#else +#define debug(fmt, args...) +#endif + +static void _crap(const char* func, const char* fmt, ...) +{ + va_list ap; + fprintf(stderr, "%s() - ", func); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + if(cur_line && cur_line[0]) + fprintf(stderr, "-> %s\n", cur_line); + exit(1); +} + +static void emit1(unsigned char v) +{ + int writecnt; + + if(assembler_pass) + { + out[compiledOfs++] = v; + + if(fout) + writecnt = fwrite(&v, 1, 1, fout); + + debug("%02hx ", v); + } + else + { + ++compiledOfs; + } +} + +static inline void emit2(u16 v) +{ + emit1(v&0xFF); + emit1((v>>8)&0xFF); +} + +static inline void emit4(u32 v) +{ + emit1(v&0xFF); + emit1((v>>8)&0xFF); + emit1((v>>16)&0xFF); + emit1((v>>24)&0xFF); +} + +static inline void emit8(u64 v) +{ + emit4(v&0xFFFFFFFF); + emit4((v>>32)&0xFFFFFFFF); +} + +enum { + REX_W = 0x08, + REX_R = 0x04, + REX_X = 0x02, + REX_B = 0x01, +}; + +enum { + MODRM_MOD_00 = 0x00, + MODRM_MOD_01 = 0x01 << 6, + MODRM_MOD_10 = 0x02 << 6, + MODRM_MOD_11 = 0x03 << 6, + MODRM_RM_SIB = 0x04, +}; + +typedef enum +{ + T_NONE = 0x00, + T_REGISTER = 0x01, + T_IMMEDIATE = 0x02, + T_MEMORY = 0x04, + T_LABEL = 0x08, + T_ABSOLUTE = 0x80 +} argtype_t; + +typedef enum { + R_8 = 0x100, + R_16 = 0x200, + R_64 = 0x800, + R_MSZ = 0xF00, // size mask + R_XMM = 0x2000, // xmm register. year, sucks + R_EAX = 0x00, + R_EBX = 0x03, + R_ECX = 0x01, + R_EDX = 0x02, + R_ESI = 0x06, + R_EDI = 0x07, + R_ESP = 0x04, + R_RAX = R_EAX | R_64, + R_RBX = R_EBX | R_64, + R_RCX = R_ECX | R_64, + R_RDX = R_EDX | R_64, + R_RSI = R_ESI | R_64, + R_RDI = R_EDI | R_64, + R_RSP = R_ESP | R_64, + R_R8 = 0x08 | R_64, + R_R9 = 0x09 | R_64, + R_R10 = 0x0A | R_64, + R_R15 = 0x0F | R_64, + R_AL = R_EAX | R_8, + R_AX = R_EAX | R_16, + R_CL = R_ECX | R_8, + R_XMM0 = 0x00 | R_XMM, + R_MGP = 0x0F, // mask for general purpose registers +} reg_t; + +typedef enum { + MODRM_SIB = 0, + MODRM_NOSIB = 0x3, +} modrm_sib_t; + +typedef struct { + unsigned disp; + argtype_t basetype; + union { + u64 imm; + reg_t reg; + } base; + argtype_t indextype; + union { + u64 imm; + reg_t reg; + } index; + unsigned scale; +} memref_t; + +#define LABELLEN 32 + +typedef struct { + argtype_t type; + union { + u64 imm; + reg_t reg; + memref_t mem; + char label[LABELLEN]; + } v; + int absolute:1; +} arg_t; + +typedef void (*emitfunc)(const char* op, arg_t arg1, arg_t arg2, void* data); + +typedef struct { + char* mnemonic; + emitfunc func; + void* data; +} op_t; + +typedef struct { + u8 xmmprefix; + u8 subcode; // in modrm + u8 rmcode; // opcode for reg/mem, reg + u8 mrcode; // opcode for reg, reg/mem + u8 rcode8; // opcode for reg8/mem8 + u8 rcode; // opcode for reg/mem +} opparam_t; + +/* ************************* */ + +static unsigned hashkey(const char *string, unsigned len) { + unsigned register hash, i; + + hash = 0; + for (i = 0; i < len && string[i] != '\0'; ++i) { + hash += string[i] * (119 + i); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + return hash; +} + +struct hashentry { + char* label; + unsigned address; + struct hashentry* next; +}; +static struct hashentry* labelhash[1021]; + +// no dup check! +static void hash_add_label(const char* label, unsigned address) +{ + struct hashentry* h; + unsigned i = hashkey(label, -1U); + int labellen; + + i %= ARRAY_LEN(labelhash); + h = Z_Malloc(sizeof(struct hashentry)); + + labellen = strlen(label) + 1; + h->label = Z_Malloc(labellen); + memcpy(h->label, label, labellen); + + h->address = address; + h->next = labelhash[i]; + labelhash[i] = h; +} + +static unsigned lookup_label(const char* label) +{ + struct hashentry* h; + unsigned i = hashkey(label, -1U); + i %= ARRAY_LEN(labelhash); + for(h = labelhash[i]; h; h = h->next ) + { + if(!strcmp(h->label, label)) + return h->address; + } + if(assembler_pass) + crap("label %s undefined", label); + return 0; +} + +static void labelhash_free(void) +{ + struct hashentry* h; + unsigned i; + unsigned z = 0, min = -1U, max = 0, t = 0; + for ( i = 0; i < ARRAY_LEN(labelhash); ++i) + { + unsigned n = 0; + h = labelhash[i]; + while(h) + { + struct hashentry* next = h->next; + Z_Free(h->label); + Z_Free(h); + h = next; + ++n; + } + t+=n; + if(!n) ++z; + //else printf("%u\n", n); + min = MIN(min, n); + max = MAX(max, n); + } + printf("total %u, hsize %"PRIu64", zero %u, min %u, max %u\n", t, ARRAY_LEN(labelhash), z, min, max); + memset(labelhash, 0, sizeof(labelhash)); +} + +/* ************************* */ + + +static const char* argtype2str(argtype_t t) +{ + switch(t) + { + case T_NONE: return "none"; + case T_REGISTER: return "register"; + case T_IMMEDIATE: return "immediate"; + case T_MEMORY: return "memory"; + case T_LABEL: return "label"; + default: crap("invalid type"); + } + /* not reached */ + return T_NONE; +} + +/* ************************* */ + +static inline int iss8(u64 v) +{ + return (llabs(v) <= 0x80); //llabs instead of labs required for __WIN64 +} + +static inline int isu8(u64 v) +{ + return (v <= 0xff); +} + +static inline int iss16(u64 v) +{ + return (llabs(v) <= 0x8000); +} + +static inline int isu16(u64 v) +{ + return (v <= 0xffff); +} + +static inline int iss32(u64 v) +{ + return (llabs(v) <= 0x80000000); +} + +static inline int isu32(u64 v) +{ + return (v <= 0xffffffff); +} + +static void emit_opsingle(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + u8 op = (u8)((uint64_t) data); + + if(arg1.type != T_NONE || arg2.type != T_NONE) + CRAP_INVALID_ARGS; + + emit1(op); +} + +static void emit_opsingle16(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + emit1(0x66); + emit_opsingle(mnemonic, arg1, arg2, data); +} + +static void compute_rexmodrmsib(u8* rex_r, u8* modrm_r, u8* sib_r, arg_t* arg1, arg_t* arg2) +{ + u8 rex = 0; + u8 modrm = 0; + u8 sib = 0; + + if((arg1->type == T_REGISTER && arg2->type == T_REGISTER) + && ((arg1->v.reg & R_MSZ) != (arg2->v.reg & R_MSZ)) + && !((arg1->v.reg & R_XMM) || (arg2->v.reg & R_XMM))) + crap("both registers must be of same width"); + + if((arg1->type == T_REGISTER && arg1->v.reg & R_64) + || (arg2->type == T_REGISTER && arg2->v.reg & R_64)) + { + rex |= REX_W; + } + + if(arg1->type == T_REGISTER) + { + if((arg1->v.reg & R_MGP) > 0x07) + rex |= REX_R; + + modrm |= (arg1->v.reg & 0x07) << 3; + } + + if(arg2->type == T_REGISTER) + { + if((arg2->v.reg & R_MGP) > 0x07) + rex |= REX_B; + + modrm |= (arg2->v.reg & 0x07); + } + + if(arg2->type == T_MEMORY) + { + if((arg2->v.mem.basetype == T_REGISTER && !(arg2->v.mem.base.reg & R_64)) + || (arg2->v.mem.indextype == T_REGISTER && !(arg2->v.mem.index.reg & R_64))) + { + crap("only 64bit base/index registers are %x %x", arg2->v.mem.base.reg, arg2->v.mem.index.reg); + } + + if(arg2->v.mem.indextype == T_REGISTER) + { + modrm |= MODRM_RM_SIB; + if(!arg2->v.mem.disp) + { + modrm |= MODRM_MOD_00; + } + else if(iss8(arg2->v.mem.disp)) + { + modrm |= MODRM_MOD_01; + } + else if(isu32(arg2->v.mem.disp)) + { + modrm |= MODRM_MOD_10; + } + else + { + crap("invalid displacement"); + } + + if((arg2->v.mem.index.reg & R_MGP) > 0x07) + rex |= REX_X; + + if((arg2->v.mem.base.reg & R_MGP) > 0x07) + rex |= REX_B; + + if(arg2->v.mem.basetype != T_REGISTER) + crap("base must be register"); + switch(arg2->v.mem.scale) + { + case 1: break; + case 2: sib |= 1 << 6; break; + case 4: sib |= 2 << 6; break; + case 8: sib |= 3 << 6; break; + } + sib |= (arg2->v.mem.index.reg & 0x07) << 3; + sib |= (arg2->v.mem.base.reg & 0x07); + } + else if(arg2->v.mem.indextype == T_NONE) + { + if(!arg2->v.mem.disp) + { + modrm |= MODRM_MOD_00; + } + else if(iss8(arg2->v.mem.disp)) + { + modrm |= MODRM_MOD_01; + } + else if(isu32(arg2->v.mem.disp)) + { + modrm |= MODRM_MOD_10; + } + else + { + crap("invalid displacement"); + } + + if(arg2->v.mem.basetype != T_REGISTER) + crap("todo: base != register"); + + if((arg2->v.mem.base.reg & R_MGP) > 0x07) + rex |= REX_B; + + modrm |= arg2->v.mem.base.reg & 0x07; + } + else + { + crap("invalid indextype"); + } + } + else + { + modrm |= MODRM_MOD_11; + } + + if(rex) + rex |= 0x40; // XXX + + *rex_r = rex; + *modrm_r = modrm; + *sib_r = sib; +} + +static void maybe_emit_displacement(arg_t* arg) +{ + if(arg->type != T_MEMORY) + return; + + if(arg->v.mem.disp) + { + if(iss8(arg->v.mem.disp)) + { + emit1((u8)arg->v.mem.disp); + } + else if(isu32(arg->v.mem.disp)) + { + emit4(arg->v.mem.disp); + } + else + { + crap("invalid displacement"); + } + } +} + +/* one byte operator with register added to operator */ +static void emit_opreg(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + u8 op = (u8)((uint64_t) data); + + if(arg1.type != T_REGISTER || arg2.type != T_NONE) + CRAP_INVALID_ARGS; + + if((arg1.v.reg & R_MGP) > 0x07) + emit1(0x40 | REX_B); + + op |= (arg1.v.reg & 0x07); + + emit1(op); +} + +/* operator which operates on reg/mem */ +static void emit_op_rm(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + u8 rex, modrm, sib; + opparam_t* params = data; + + if((arg1.type != T_REGISTER && arg1.type != T_MEMORY) || arg2.type != T_NONE) + CRAP_INVALID_ARGS; + + compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + + modrm |= params->subcode << 3; + + if(arg1.v.reg & R_16) + emit1(0x66); + + if(rex) emit1(rex); + if(arg1.v.reg & R_8) + emit1(params->rcode8); // op reg8/mem8, + else + emit1(params->rcode); // op reg/mem, + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + maybe_emit_displacement(&arg1); +} + +/* operator which operates on reg/mem with cl */ +static void emit_op_rm_cl(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + u8 rex, modrm, sib; + opparam_t* params = data; + + if(arg2.type != T_REGISTER || arg1.type != T_REGISTER) + CRAP_INVALID_ARGS; + + if((arg1.v.reg & R_MGP) != R_ECX && !(arg1.v.reg & R_8)) + crap("only cl register is valid"); + + arg1.type = T_NONE; // don't complain, we know it's cl anyways + + compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + + modrm |= params->subcode << 3; + + if(arg2.v.reg & R_16) + emit1(0x66); + + if(rex) emit1(rex); + if(arg2.v.reg & R_8) + emit1(params->rcode8); // op reg8/mem8, + else + emit1(params->rcode); // op reg/mem, + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + maybe_emit_displacement(&arg2); +} + +static void emit_mov(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + u8 rex = 0; + u8 modrm = 0; + u8 sib = 0; + + if(arg1.type == T_IMMEDIATE && arg2.type == T_REGISTER) + { + u8 op = 0xb8; + + if(arg2.v.reg & R_8) + { + if(!isu8(arg1.v.imm)) + crap("value too large for 8bit register"); + + op = 0xb0; + } + else if(arg2.v.reg & R_16) + { + if(!isu16(arg1.v.imm)) + crap("value too large for 16bit register"); + emit1(0x66); + } + else if(!(arg2.v.reg & R_64)) + { + if(!isu32(arg1.v.imm)) + crap("value too large for 32bit register"); + } + + compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + + if(rex) emit1(rex); + + op |= (arg2.v.reg & 0x07); + + emit1(op); + + if(arg2.v.reg & R_8) emit1(arg1.v.imm); + else if(arg2.v.reg & R_16) emit2(arg1.v.imm); + else if(arg2.v.reg & R_64) emit8(arg1.v.imm); + else emit4(arg1.v.imm); + } + else if(arg1.type == T_IMMEDIATE && arg2.type == T_MEMORY) + { + if(!iss32(arg1.v.imm)) + { + crap("only 32bit immediates supported"); + } + + compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + if(rex) emit1(rex); + emit1(0xc7); // mov reg/mem, imm + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + emit4(arg1.v.imm); + } + else if(arg1.type == T_REGISTER && arg2.type == T_REGISTER) // XXX: same as next + { + if(arg1.type != T_REGISTER || arg2.type != T_REGISTER) + crap("both args must be registers"); + + if((arg1.v.reg & R_MSZ) != (arg2.v.reg & R_MSZ)) + crap("both registers must be same width"); + + compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + + if(rex) emit1(rex); + emit1(0x89); // mov reg reg/mem, + emit1(modrm); + } + else if(arg1.type == T_REGISTER && arg2.type == T_MEMORY) + { + compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + + if(arg1.v.reg & R_16) + emit1(0x66); + + if(rex) emit1(rex); + if(arg1.v.reg & R_8) + emit1(0x88); // mov reg reg/mem, + else + emit1(0x89); // mov reg reg/mem, + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + maybe_emit_displacement(&arg2); + } + else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER) + { + compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + + if(arg2.v.reg & R_16) + emit1(0x66); + + if(rex) emit1(rex); + if(arg2.v.reg & R_8) + emit1(0x8a); // mov reg/mem, reg + else + emit1(0x8b); // mov reg/mem, reg + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + maybe_emit_displacement(&arg1); + } + else + CRAP_INVALID_ARGS; +} + +static void emit_subaddand(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + u8 rex = 0; + u8 modrm = 0; + u8 sib = 0; + + opparam_t* params = data; + + if(arg1.type == T_IMMEDIATE && arg2.type == T_REGISTER) + { + if(!iss32(arg1.v.imm)) + { + crap("only 8 and 32 bit immediates supported"); + } + + compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + + modrm |= params->subcode << 3; + + if(rex) emit1(rex); +#if 0 + if(isu8(arg1.v.imm)) + { + emit1(0x83); // sub reg/mem, imm8 + emit1(modrm); + emit1(arg1.v.imm&0xFF); + } + else +#endif + { + emit1(0x81); // sub reg/mem, imm32 + emit1(modrm); + emit4(arg1.v.imm); + } + } + else if(arg1.type == T_REGISTER && (arg2.type == T_MEMORY || arg2.type == T_REGISTER)) + { + compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + + if(rex) emit1(rex); + emit1(params->rmcode); // sub reg/mem, reg + emit1(modrm); + if(arg2.type == T_MEMORY && (modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + maybe_emit_displacement(&arg2); + } + else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER && params->mrcode) + { + compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + + if(rex) emit1(rex); + emit1(params->mrcode); // sub reg, reg/mem + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + maybe_emit_displacement(&arg1); + } + else + CRAP_INVALID_ARGS; +} + +static void emit_condjump(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + unsigned off; + int disp; + unsigned char opcode = (unsigned char)(((uint64_t)data)&0xFF); + + if(arg1.type != T_LABEL || arg2.type != T_NONE) + crap("%s: argument must be label", mnemonic); + + emit1(opcode); + + off = lookup_label(arg1.v.label); + disp = off-(compiledOfs+1); + if(assembler_pass && abs(disp) > 127) + crap("cannot jump that far (%x -> %x = %x)", compiledOfs, off, disp); + + emit1(disp); +} + +static void emit_jmp(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + if((arg1.type != T_LABEL && arg1.type != T_REGISTER && arg1.type != T_MEMORY) || arg2.type != T_NONE) + CRAP_INVALID_ARGS; + + if(arg1.type == T_LABEL) + { + unsigned off; + int disp; + + off = lookup_label(arg1.v.label); + disp = off-(compiledOfs+5); + emit1(0xe9); + emit4(disp); + } + else + { + u8 rex, modrm, sib; + + if(arg1.type == T_REGISTER) + { + if(!arg1.absolute) + crap("jmp must be absolute"); + + if((arg1.v.reg & R_64) != R_64) + crap("register must be 64bit"); + + arg1.v.reg ^= R_64; // no rex required for call + } + + compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + + modrm |= 0x4 << 3; + + if(rex) emit1(rex); + emit1(0xff); + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + maybe_emit_displacement(&arg1); + } +} + +static void emit_call(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + u8 rex, modrm, sib; + + if((arg1.type != T_REGISTER && arg1.type != T_IMMEDIATE) || arg2.type != T_NONE) + CRAP_INVALID_ARGS; + + if(arg1.type == T_REGISTER) + { + if(!arg1.absolute) + crap("call must be absolute"); + + if((arg1.v.reg & R_64) != R_64) + crap("register must be 64bit"); + + arg1.v.reg ^= R_64; // no rex required for call + + compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + + modrm |= 0x2 << 3; + + if(rex) emit1(rex); + emit1(0xff); + emit1(modrm); + } + else + { + if(!isu32(arg1.v.imm)) + crap("must be 32bit argument"); + emit1(0xe8); + emit4(arg1.v.imm); + } +} + + +static void emit_twobyte(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) +{ + u8 rex, modrm, sib; + + opparam_t* params = data; + + if(arg1.type == T_REGISTER && (arg2.type == T_MEMORY || arg2.type == T_REGISTER)) + { + compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); + + if(params->xmmprefix) emit1(params->xmmprefix); + if(rex) emit1(rex); + emit1(0x0f); + emit1(params->rmcode); // sub reg/mem, reg + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + maybe_emit_displacement(&arg2); + } + else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER && params->mrcode) + { + compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); + + if(params->xmmprefix) emit1(params->xmmprefix); + if(rex) emit1(rex); + emit1(0x0f); + emit1(params->mrcode); // sub reg, reg/mem + emit1(modrm); + if((modrm & 0x07) == MODRM_RM_SIB) + emit1(sib); + + maybe_emit_displacement(&arg1); + } + else + CRAP_INVALID_ARGS; +} + +static opparam_t params_add = { subcode: 0, rmcode: 0x01, }; +static opparam_t params_or = { subcode: 1, rmcode: 0x09, }; +static opparam_t params_and = { subcode: 4, rmcode: 0x21, }; +static opparam_t params_sub = { subcode: 5, rmcode: 0x29, }; +static opparam_t params_xor = { subcode: 6, rmcode: 0x31, }; +static opparam_t params_cmp = { subcode: 7, rmcode: 0x39, mrcode: 0x3b, }; +static opparam_t params_dec = { subcode: 1, rcode: 0xff, rcode8: 0xfe, }; +static opparam_t params_sar = { subcode: 7, rcode: 0xd3, rcode8: 0xd2, }; +static opparam_t params_shl = { subcode: 4, rcode: 0xd3, rcode8: 0xd2, }; +static opparam_t params_shr = { subcode: 5, rcode: 0xd3, rcode8: 0xd2, }; +static opparam_t params_idiv = { subcode: 7, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_div = { subcode: 6, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_imul = { subcode: 5, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_mul = { subcode: 4, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_neg = { subcode: 3, rcode: 0xf7, rcode8: 0xf6, }; +static opparam_t params_not = { subcode: 2, rcode: 0xf7, rcode8: 0xf6, }; + +static opparam_t params_cvtsi2ss = { xmmprefix: 0xf3, rmcode: 0x2a }; +static opparam_t params_cvttss2si = { xmmprefix: 0xf3, rmcode: 0x2c }; +static opparam_t params_addss = { xmmprefix: 0xf3, mrcode: 0x58 }; +static opparam_t params_divss = { xmmprefix: 0xf3, mrcode: 0x5e }; +static opparam_t params_movss = { xmmprefix: 0xf3, mrcode: 0x10, rmcode: 0x11 }; +static opparam_t params_mulss = { xmmprefix: 0xf3, mrcode: 0x59 }; +static opparam_t params_subss = { xmmprefix: 0xf3, mrcode: 0x5c }; +static opparam_t params_ucomiss = { mrcode: 0x2e }; + +static int ops_sorted = 0; +static op_t ops[] = { + { "addl", emit_subaddand, ¶ms_add }, + { "addq", emit_subaddand, ¶ms_add }, + { "addss", emit_twobyte, ¶ms_addss }, + { "andl", emit_subaddand, ¶ms_and }, + { "andq", emit_subaddand, ¶ms_and }, + { "callq", emit_call, NULL }, + { "cbw", emit_opsingle16, (void*)0x98 }, + { "cdq", emit_opsingle, (void*)0x99 }, + { "cmpl", emit_subaddand, ¶ms_cmp }, + { "cmpq", emit_subaddand, ¶ms_cmp }, + { "cvtsi2ss", emit_twobyte, ¶ms_cvtsi2ss }, + { "cvttss2si", emit_twobyte, ¶ms_cvttss2si }, + { "cwde", emit_opsingle, (void*)0x98 }, + { "decl", emit_op_rm, ¶ms_dec }, + { "decq", emit_op_rm, ¶ms_dec }, + { "divl", emit_op_rm, ¶ms_div }, + { "divq", emit_op_rm, ¶ms_div }, + { "divss", emit_twobyte, ¶ms_divss }, + { "idivl", emit_op_rm, ¶ms_idiv }, + { "imull", emit_op_rm, ¶ms_imul }, + { "int3", emit_opsingle, (void*)0xcc }, + { "ja", emit_condjump, (void*)0x77 }, + { "jbe", emit_condjump, (void*)0x76 }, + { "jb", emit_condjump, (void*)0x72 }, + { "je", emit_condjump, (void*)0x74 }, + { "jl", emit_condjump, (void*)0x7c }, + { "jmp", emit_jmp, NULL }, + { "jmpq", emit_jmp, NULL }, + { "jnae", emit_condjump, (void*)0x72 }, + { "jna", emit_condjump, (void*)0x76 }, + { "jnbe", emit_condjump, (void*)0x77 }, + { "jnb", emit_condjump, (void*)0x73 }, + { "jnc", emit_condjump, (void*)0x73 }, + { "jne", emit_condjump, (void*)0x75 }, + { "jnge", emit_condjump, (void*)0x7c }, + { "jng", emit_condjump, (void*)0x7e }, + { "jnle", emit_condjump, (void*)0x7f }, + { "jnl", emit_condjump, (void*)0x7d }, + { "jnz", emit_condjump, (void*)0x75 }, + { "jp", emit_condjump, (void*)0x7a }, + { "jz", emit_condjump, (void*)0x74 }, + { "movb", emit_mov, NULL }, + { "movl", emit_mov, NULL }, + { "movq", emit_mov, NULL }, + { "movss", emit_twobyte, ¶ms_movss }, + { "movw", emit_mov, NULL }, + { "mull", emit_op_rm, ¶ms_mul }, + { "mulss", emit_twobyte, ¶ms_mulss }, + { "negl", emit_op_rm, ¶ms_neg }, + { "negq", emit_op_rm, ¶ms_neg }, + { "nop", emit_opsingle, (void*)0x90 }, + { "notl", emit_op_rm, ¶ms_not }, + { "notq", emit_op_rm, ¶ms_not }, + { "or", emit_subaddand, ¶ms_or }, + { "orl", emit_subaddand, ¶ms_or }, + { "pop", emit_opreg, (void*)0x58 }, + { "push", emit_opreg, (void*)0x50 }, + { "ret", emit_opsingle, (void*)0xc3 }, + { "sarl", emit_op_rm_cl, ¶ms_sar }, + { "shl", emit_op_rm_cl, ¶ms_shl }, + { "shrl", emit_op_rm_cl, ¶ms_shr }, + { "subl", emit_subaddand, ¶ms_sub }, + { "subq", emit_subaddand, ¶ms_sub }, + { "subss", emit_twobyte, ¶ms_subss }, + { "ucomiss", emit_twobyte, ¶ms_ucomiss }, + { "xorl", emit_subaddand, ¶ms_xor }, + { "xorq", emit_subaddand, ¶ms_xor }, + { NULL, NULL, NULL } +}; + +static int opsort(const void* A, const void* B) +{ + const op_t* a = A; + const op_t* b = B; + return strcmp(a->mnemonic, b->mnemonic); +} + +static op_t* getop(const char* n) +{ +#if 0 + op_t* o = ops; + while(o->mnemonic) + { + if(!strcmp(o->mnemonic, n)) + return o; + ++o; + } + +#else + unsigned m, t, b; + int r; + t = ARRAY_LEN(ops)-1; + b = 0; + + while(b <= t) + { + m = ((t-b)>>1) + b; + if((r = strcmp(ops[m].mnemonic, n)) == 0) + { + return &ops[m]; + } + else if(r < 0) + { + b = m + 1; + } + else + { + t = m - 1; + } + } +#endif + + return NULL; +} + +static reg_t parsereg(const char* str) +{ + const char* s = str; + if(*s == 'a' && s[1] == 'l' && !s[2]) + { + return R_AL; + } + else if(*s == 'a' && s[1] == 'x' && !s[2]) + { + return R_AX; + } + if(*s == 'c' && s[1] == 'l' && !s[2]) + { + return R_CL; + } + if(*s == 'x') + { + if(!strcmp(s, "xmm0")) + return R_XMM0; + } + else if(*s == 'r' && s[1]) + { + ++s; + if(s[1] == 'x') + { + switch(*s++) + { + case 'a': return R_RAX; + case 'b': return R_RBX; + case 'c': return R_RCX; + case 'd': return R_RDX; + } + } + else if(s[1] == 'i') + { + switch(*s++) + { + case 's': return R_RSI; + case 'd': return R_RDI; + } + } + else if(s[0] == 's' && s[1] == 'p' && !s[2]) + { + return R_RSP; + } + else if(*s == '8' && !s[1]) + return R_R8; + else if(*s == '9' && !s[1]) + return R_R9; + else if(*s == '1' && s[1] == '0') + return R_R10; + else if(*s == '1' && s[1] == '5') + return R_R15; + } + else if(*s == 'e' && s[1]) + { + ++s; + if(s[1] == 'x') + { + switch(*s++) + { + case 'a': return R_EAX; + case 'b': return R_EBX; + case 'c': return R_ECX; + case 'd': return R_EDX; + } + } + else if(s[1] == 'i') + { + switch(*s++) + { + case 's': return R_ESI; + case 'd': return R_EDI; + } + } + } + + crap("invalid register %s", str); + + return 0; +} + +typedef enum { + TOK_LABEL = 0x80, + TOK_INT = 0x81, + TOK_END = 0x82, + TOK_INVALID = 0x83, +} token_t; + +static unsigned char nexttok(const char** str, char* label, u64* val) +{ + const char* s = *str; + + if(label) *label = 0; + if(val) *val = 0; + + while(*s && *s == ' ') ++s; + + if(!*s) + { + return TOK_END; + } + else if(*s == '$' || *s == '*' || *s == '%' || *s == '-' || *s == ')' || *s == '(' || *s == ',') + { + *str = s+1; + return *s; + } + else if(*s >= 'a' && *s <= 'z') + { + size_t a = strspn(s+1, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); + if(a+1 >= LABELLEN) + crap("label %s too long", s); + if(label) + { + strncpy(label, s, a+1); + label[a+1] = 0; + } + *str = s+a+1; + return TOK_LABEL; + } + else if(*s >= '0' && *s <= '9') + { + char* endptr = NULL; + u64 v = strtoull(s, &endptr, 0); + if(endptr && (endptr-s == 0)) + crap("invalid integer %s", s); + if(val) *val = v; + *str = endptr; + return TOK_INT; + } + crap("can't parse '%s'", *str); + return TOK_INVALID; +} + +static arg_t parsearg(const char** str) +{ + arg_t arg; + const char* s = *str; + char label[20]; + u64 val; + int negative = 1; + unsigned ttype; + + arg.type = T_NONE; + arg.absolute = 0; + + while(*s && *s == ' ') ++s; + + switch(nexttok(&s, label, &val)) + { + case '$' : + ttype = nexttok(&s, NULL, &val); + if(ttype == '-') + { + negative = -1; + ttype = nexttok(&s, NULL, &val); + } + if(ttype != TOK_INT) + crap("expected integer"); + arg.type = T_IMMEDIATE; + arg.v.imm = negative * val; + break; + case '*' : + if((ttype = nexttok(&s, NULL, NULL)) != '%') + { + if(ttype == '(') + goto tok_memory; + crap("expected '%%'"); + } + arg.absolute = 1; + /* fall through */ + case '%' : + if(nexttok(&s, label, &val) != TOK_LABEL) + crap("expected label"); + arg.type = T_REGISTER; + arg.v.reg = parsereg(label); + break; + case TOK_LABEL: + arg.type = T_LABEL; + strncpy(arg.v.label, label, LABELLEN); + break; + case '-': + negative = -1; + if(nexttok(&s, NULL, &val) != TOK_INT) + crap("expected integer"); + /* fall through */ + case TOK_INT: + if(nexttok(&s, label, NULL) != '(') + crap("expected '('"); // mov to/from fixed address not supported + /* fall through */ + case '(': +tok_memory: + arg.type = T_MEMORY; + arg.v.mem.indextype = T_NONE; + arg.v.mem.disp = negative * val; + ttype = nexttok(&s, label, &val); + if(ttype == '%' && nexttok(&s, label, &val) != TOK_LABEL) + { + crap("expected register"); + } + if (ttype == '%') + { + arg.v.mem.basetype = T_REGISTER; + arg.v.mem.base.reg = parsereg(label); + } + else if (ttype == TOK_INT) + { + arg.v.mem.basetype = T_IMMEDIATE; + arg.v.mem.base.imm = val; + } + if((ttype = nexttok(&s, NULL, NULL)) == ',') + { + ttype = nexttok(&s, label, &val); + if(ttype == '%' && nexttok(&s, label, &val) != TOK_LABEL) + { + crap("expected register"); + } + if (ttype == '%') + { + arg.v.mem.indextype = T_REGISTER; + arg.v.mem.index.reg = parsereg(label); + } + else if (ttype == TOK_INT) + { + crap("index must be register"); + arg.v.mem.indextype = T_IMMEDIATE; + arg.v.mem.index.imm = val; + } + if(nexttok(&s, NULL, NULL) != ',') + crap("expected ','"); + if(nexttok(&s, NULL, &val) != TOK_INT) + crap("expected integer"); + if(val != 1 && val != 2 && val != 4 && val != 8) + crap("scale must 1, 2, 4 or 8"); + arg.v.mem.scale = val; + + ttype = nexttok(&s, NULL, NULL); + } + if(ttype != ')') + { + crap("expected ')' or ','"); + } + break; + default: + crap("invalid token %hu in %s", *(unsigned char*)s, *str); + break; + } + + *str = s; + + return arg; +} + +/* ************************* */ + +void assembler_init(int pass) +{ + compiledOfs = 0; + assembler_pass = pass; + if(!pass) + { + labelhash_free(); + cur_line = NULL; + } + if(!ops_sorted) + { + ops_sorted = 1; + qsort(ops, ARRAY_LEN(ops)-1, sizeof(ops[0]), opsort); + } +} + +size_t assembler_get_code_size(void) +{ + return compiledOfs; +} + +void assembler_set_output(char* buf) +{ + out = buf; +} + +void assemble_line(const char* input, size_t len) +{ + char line[4096]; + char* s; + op_t* o; + char* opn; + arg_t arg1, arg2; + + arg1.type = T_NONE; + arg2.type = T_NONE; + opn = NULL; + o = NULL; + + if(len < 1) + return; + + if(len >= sizeof(line)) + crap("line too long"); + + memcpy(line, input, sizeof(line)); + cur_line = input; + + if(line[len-1] == '\n') line[--len] = 0; + if(line[len-1] == ':') + { + line[--len] = 0; + if(assembler_pass) + debug("%s: 0x%x\n", line, compiledOfs); + else + hash_add_label(line, compiledOfs); + } + else + { + opn = line; + s = strchr(line, ' '); + if(s) + { + *s++ = 0; + arg1 = parsearg((const char**)&s); + if(*s) + { + if(*s != ',') + crap("expected ',', got '%c'", *s); + ++s; + arg2 = parsearg((const char**)&s); + } + } + + if(!opn) + { + crap("no operator in %s", line); + } + + o = getop(opn); + if(!o) + { + crap("cannot handle op %s", opn); + } + o->func(opn, arg1, arg2, o->data); + if(assembler_pass) + debug(" - %s%s", cur_line, cur_line[strlen(cur_line)-1]=='\n'?"":"\n"); + } +} + +#ifdef SA_STANDALONE +int main(int argc, char* argv[]) +{ + char line[4096]; + size_t len; + int pass; + FILE* file = NULL; + + if(argc < 2) + { + crap("specify file"); + } + + file = fopen(argv[1], "r"); + if(!file) + { + crap("can't open file"); + } + + if(argc > 2) + { + fout = fopen(argv[2], "w"); + if(!fout) + { + crap("can't open %s for writing", argv[2]); + } + } + + for(pass = 0; pass < 2; ++pass) + { + if(fseek(file, 0, SEEK_SET)) + crap("can't rewind file"); + + if(pass) + { + char* b = malloc(assembler_get_code_size()); + if(!b) + crap("cannot allocate memory"); + assembler_set_output(b); + } + + assembler_init(pass); + + while(fgets(line, sizeof(line), file)) + { + len = strlen(line); + if(!len) continue; + + assemble_line(line, len); + } + } + + assembler_init(0); + + fclose(file); + + return 0; +} +#endif diff --git a/src/renderer/qgl.h b/src/renderer/qgl.h new file mode 100644 index 0000000..2dbe27d --- /dev/null +++ b/src/renderer/qgl.h @@ -0,0 +1,381 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#ifdef USE_LOCAL_HEADERS +# include "SDL_opengl.h" +#else +# include +#endif + +extern void (APIENTRYP qglActiveTextureARB) (GLenum texture); +extern void (APIENTRYP qglClientActiveTextureARB) (GLenum texture); +extern void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t); + +extern void (APIENTRYP qglLockArraysEXT) (GLint first, GLsizei count); +extern void (APIENTRYP qglUnlockArraysEXT) (void); + + +//=========================================================================== + +#define qglAccum glAccum +#define qglAlphaFunc glAlphaFunc +#define qglAreTexturesResident glAreTexturesResident +#define qglArrayElement glArrayElement +#define qglBegin glBegin +#define qglBindTexture glBindTexture +#define qglBitmap glBitmap +#define qglBlendFunc glBlendFunc +#define qglCallList glCallList +#define qglCallLists glCallLists +#define qglClear glClear +#define qglClearAccum glClearAccum +#define qglClearColor glClearColor +#define qglClearDepth glClearDepth +#define qglClearIndex glClearIndex +#define qglClearStencil glClearStencil +#define qglClipPlane glClipPlane +#define qglColor3b glColor3b +#define qglColor3bv glColor3bv +#define qglColor3d glColor3d +#define qglColor3dv glColor3dv +#define qglColor3f glColor3f +#define qglColor3fv glColor3fv +#define qglColor3i glColor3i +#define qglColor3iv glColor3iv +#define qglColor3s glColor3s +#define qglColor3sv glColor3sv +#define qglColor3ub glColor3ub +#define qglColor3ubv glColor3ubv +#define qglColor3ui glColor3ui +#define qglColor3uiv glColor3uiv +#define qglColor3us glColor3us +#define qglColor3usv glColor3usv +#define qglColor4b glColor4b +#define qglColor4bv glColor4bv +#define qglColor4d glColor4d +#define qglColor4dv glColor4dv +#define qglColor4f glColor4f +#define qglColor4fv glColor4fv +#define qglColor4i glColor4i +#define qglColor4iv glColor4iv +#define qglColor4s glColor4s +#define qglColor4sv glColor4sv +#define qglColor4ub glColor4ub +#define qglColor4ubv glColor4ubv +#define qglColor4ui glColor4ui +#define qglColor4uiv glColor4uiv +#define qglColor4us glColor4us +#define qglColor4usv glColor4usv +#define qglColorMask glColorMask +#define qglColorMaterial glColorMaterial +#define qglColorPointer glColorPointer +#define qglCopyPixels glCopyPixels +#define qglCopyTexImage1D glCopyTexImage1D +#define qglCopyTexImage2D glCopyTexImage2D +#define qglCopyTexSubImage1D glCopyTexSubImage1D +#define qglCopyTexSubImage2D glCopyTexSubImage2D +#define qglCullFace glCullFace +#define qglDeleteLists glDeleteLists +#define qglDeleteTextures glDeleteTextures +#define qglDepthFunc glDepthFunc +#define qglDepthMask glDepthMask +#define qglDepthRange glDepthRange +#define qglDisable glDisable +#define qglDisableClientState glDisableClientState +#define qglDrawArrays glDrawArrays +#define qglDrawBuffer glDrawBuffer +#define qglDrawElements glDrawElements +#define qglDrawPixels glDrawPixels +#define qglEdgeFlag glEdgeFlag +#define qglEdgeFlagPointer glEdgeFlagPointer +#define qglEdgeFlagv glEdgeFlagv +#define qglEnable glEnable +#define qglEnableClientState glEnableClientState +#define qglEnd glEnd +#define qglEndList glEndList +#define qglEvalCoord1d glEvalCoord1d +#define qglEvalCoord1dv glEvalCoord1dv +#define qglEvalCoord1f glEvalCoord1f +#define qglEvalCoord1fv glEvalCoord1fv +#define qglEvalCoord2d glEvalCoord2d +#define qglEvalCoord2dv glEvalCoord2dv +#define qglEvalCoord2f glEvalCoord2f +#define qglEvalCoord2fv glEvalCoord2fv +#define qglEvalMesh1 glEvalMesh1 +#define qglEvalMesh2 glEvalMesh2 +#define qglEvalPoint1 glEvalPoint1 +#define qglEvalPoint2 glEvalPoint2 +#define qglFeedbackBuffer glFeedbackBuffer +#define qglFinish glFinish +#define qglFlush glFlush +#define qglFogf glFogf +#define qglFogfv glFogfv +#define qglFogi glFogi +#define qglFogiv glFogiv +#define qglFrontFace glFrontFace +#define qglFrustum glFrustum +#define qglGenLists glGenLists +#define qglGenTextures glGenTextures +#define qglGetBooleanv glGetBooleanv +#define qglGetClipPlane glGetClipPlane +#define qglGetDoublev glGetDoublev +#define qglGetError glGetError +#define qglGetFloatv glGetFloatv +#define qglGetIntegerv glGetIntegerv +#define qglGetLightfv glGetLightfv +#define qglGetLightiv glGetLightiv +#define qglGetMapdv glGetMapdv +#define qglGetMapfv glGetMapfv +#define qglGetMapiv glGetMapiv +#define qglGetMaterialfv glGetMaterialfv +#define qglGetMaterialiv glGetMaterialiv +#define qglGetPixelMapfv glGetPixelMapfv +#define qglGetPixelMapuiv glGetPixelMapuiv +#define qglGetPixelMapusv glGetPixelMapusv +#define qglGetPointerv glGetPointerv +#define qglGetPolygonStipple glGetPolygonStipple +#define qglGetString glGetString +#define qglGetTexGendv glGetTexGendv +#define qglGetTexGenfv glGetTexGenfv +#define qglGetTexGeniv glGetTexGeniv +#define qglGetTexImage glGetTexImage +#define qglGetTexLevelParameterfv glGetTexLevelParameterfv +#define qglGetTexLevelParameteriv glGetTexLevelParameteriv +#define qglGetTexParameterfv glGetTexParameterfv +#define qglGetTexParameteriv glGetTexParameteriv +#define qglHint glHint +#define qglIndexMask glIndexMask +#define qglIndexPointer glIndexPointer +#define qglIndexd glIndexd +#define qglIndexdv glIndexdv +#define qglIndexf glIndexf +#define qglIndexfv glIndexfv +#define qglIndexi glIndexi +#define qglIndexiv glIndexiv +#define qglIndexs glIndexs +#define qglIndexsv glIndexsv +#define qglIndexub glIndexub +#define qglIndexubv glIndexubv +#define qglInitNames glInitNames +#define qglInterleavedArrays glInterleavedArrays +#define qglIsEnabled glIsEnabled +#define qglIsList glIsList +#define qglIsTexture glIsTexture +#define qglLightModelf glLightModelf +#define qglLightModelfv glLightModelfv +#define qglLightModeli glLightModeli +#define qglLightModeliv glLightModeliv +#define qglLightf glLightf +#define qglLightfv glLightfv +#define qglLighti glLighti +#define qglLightiv glLightiv +#define qglLineStipple glLineStipple +#define qglLineWidth glLineWidth +#define qglListBase glListBase +#define qglLoadIdentity glLoadIdentity +#define qglLoadMatrixd glLoadMatrixd +#define qglLoadMatrixf glLoadMatrixf +#define qglLoadName glLoadName +#define qglLogicOp glLogicOp +#define qglMap1d glMap1d +#define qglMap1f glMap1f +#define qglMap2d glMap2d +#define qglMap2f glMap2f +#define qglMapGrid1d glMapGrid1d +#define qglMapGrid1f glMapGrid1f +#define qglMapGrid2d glMapGrid2d +#define qglMapGrid2f glMapGrid2f +#define qglMaterialf glMaterialf +#define qglMaterialfv glMaterialfv +#define qglMateriali glMateriali +#define qglMaterialiv glMaterialiv +#define qglMatrixMode glMatrixMode +#define qglMultMatrixd glMultMatrixd +#define qglMultMatrixf glMultMatrixf +#define qglNewList glNewList +#define qglNormal3b glNormal3b +#define qglNormal3bv glNormal3bv +#define qglNormal3d glNormal3d +#define qglNormal3dv glNormal3dv +#define qglNormal3f glNormal3f +#define qglNormal3fv glNormal3fv +#define qglNormal3i glNormal3i +#define qglNormal3iv glNormal3iv +#define qglNormal3s glNormal3s +#define qglNormal3sv glNormal3sv +#define qglNormalPointer glNormalPointer +#define qglOrtho glOrtho +#define qglPassThrough glPassThrough +#define qglPixelMapfv glPixelMapfv +#define qglPixelMapuiv glPixelMapuiv +#define qglPixelMapusv glPixelMapusv +#define qglPixelStoref glPixelStoref +#define qglPixelStorei glPixelStorei +#define qglPixelTransferf glPixelTransferf +#define qglPixelTransferi glPixelTransferi +#define qglPixelZoom glPixelZoom +#define qglPointSize glPointSize +#define qglPolygonMode glPolygonMode +#define qglPolygonOffset glPolygonOffset +#define qglPolygonStipple glPolygonStipple +#define qglPopAttrib glPopAttrib +#define qglPopClientAttrib glPopClientAttrib +#define qglPopMatrix glPopMatrix +#define qglPopName glPopName +#define qglPrioritizeTextures glPrioritizeTextures +#define qglPushAttrib glPushAttrib +#define qglPushClientAttrib glPushClientAttrib +#define qglPushMatrix glPushMatrix +#define qglPushName glPushName +#define qglRasterPos2d glRasterPos2d +#define qglRasterPos2dv glRasterPos2dv +#define qglRasterPos2f glRasterPos2f +#define qglRasterPos2fv glRasterPos2fv +#define qglRasterPos2i glRasterPos2i +#define qglRasterPos2iv glRasterPos2iv +#define qglRasterPos2s glRasterPos2s +#define qglRasterPos2sv glRasterPos2sv +#define qglRasterPos3d glRasterPos3d +#define qglRasterPos3dv glRasterPos3dv +#define qglRasterPos3f glRasterPos3f +#define qglRasterPos3fv glRasterPos3fv +#define qglRasterPos3i glRasterPos3i +#define qglRasterPos3iv glRasterPos3iv +#define qglRasterPos3s glRasterPos3s +#define qglRasterPos3sv glRasterPos3sv +#define qglRasterPos4d glRasterPos4d +#define qglRasterPos4dv glRasterPos4dv +#define qglRasterPos4f glRasterPos4f +#define qglRasterPos4fv glRasterPos4fv +#define qglRasterPos4i glRasterPos4i +#define qglRasterPos4iv glRasterPos4iv +#define qglRasterPos4s glRasterPos4s +#define qglRasterPos4sv glRasterPos4sv +#define qglReadBuffer glReadBuffer +#define qglReadPixels glReadPixels +#define qglRectd glRectd +#define qglRectdv glRectdv +#define qglRectf glRectf +#define qglRectfv glRectfv +#define qglRecti glRecti +#define qglRectiv glRectiv +#define qglRects glRects +#define qglRectsv glRectsv +#define qglRenderMode glRenderMode +#define qglRotated glRotated +#define qglRotatef glRotatef +#define qglScaled glScaled +#define qglScalef glScalef +#define qglScissor glScissor +#define qglSelectBuffer glSelectBuffer +#define qglShadeModel glShadeModel +#define qglStencilFunc glStencilFunc +#define qglStencilMask glStencilMask +#define qglStencilOp glStencilOp +#define qglTexCoord1d glTexCoord1d +#define qglTexCoord1dv glTexCoord1dv +#define qglTexCoord1f glTexCoord1f +#define qglTexCoord1fv glTexCoord1fv +#define qglTexCoord1i glTexCoord1i +#define qglTexCoord1iv glTexCoord1iv +#define qglTexCoord1s glTexCoord1s +#define qglTexCoord1sv glTexCoord1sv +#define qglTexCoord2d glTexCoord2d +#define qglTexCoord2dv glTexCoord2dv +#define qglTexCoord2f glTexCoord2f +#define qglTexCoord2fv glTexCoord2fv +#define qglTexCoord2i glTexCoord2i +#define qglTexCoord2iv glTexCoord2iv +#define qglTexCoord2s glTexCoord2s +#define qglTexCoord2sv glTexCoord2sv +#define qglTexCoord3d glTexCoord3d +#define qglTexCoord3dv glTexCoord3dv +#define qglTexCoord3f glTexCoord3f +#define qglTexCoord3fv glTexCoord3fv +#define qglTexCoord3i glTexCoord3i +#define qglTexCoord3iv glTexCoord3iv +#define qglTexCoord3s glTexCoord3s +#define qglTexCoord3sv glTexCoord3sv +#define qglTexCoord4d glTexCoord4d +#define qglTexCoord4dv glTexCoord4dv +#define qglTexCoord4f glTexCoord4f +#define qglTexCoord4fv glTexCoord4fv +#define qglTexCoord4i glTexCoord4i +#define qglTexCoord4iv glTexCoord4iv +#define qglTexCoord4s glTexCoord4s +#define qglTexCoord4sv glTexCoord4sv +#define qglTexCoordPointer glTexCoordPointer +#define qglTexEnvf glTexEnvf +#define qglTexEnvfv glTexEnvfv +#define qglTexEnvi glTexEnvi +#define qglTexEnviv glTexEnviv +#define qglTexGend glTexGend +#define qglTexGendv glTexGendv +#define qglTexGenf glTexGenf +#define qglTexGenfv glTexGenfv +#define qglTexGeni glTexGeni +#define qglTexGeniv glTexGeniv +#define qglTexImage1D glTexImage1D +#define qglTexImage2D glTexImage2D +#define qglTexParameterf glTexParameterf +#define qglTexParameterfv glTexParameterfv +#define qglTexParameteri glTexParameteri +#define qglTexParameteriv glTexParameteriv +#define qglTexSubImage1D glTexSubImage1D +#define qglTexSubImage2D glTexSubImage2D +#define qglTranslated glTranslated +#define qglTranslatef glTranslatef +#define qglVertex2d glVertex2d +#define qglVertex2dv glVertex2dv +#define qglVertex2f glVertex2f +#define qglVertex2fv glVertex2fv +#define qglVertex2i glVertex2i +#define qglVertex2iv glVertex2iv +#define qglVertex2s glVertex2s +#define qglVertex2sv glVertex2sv +#define qglVertex3d glVertex3d +#define qglVertex3dv glVertex3dv +#define qglVertex3f glVertex3f +#define qglVertex3fv glVertex3fv +#define qglVertex3i glVertex3i +#define qglVertex3iv glVertex3iv +#define qglVertex3s glVertex3s +#define qglVertex3sv glVertex3sv +#define qglVertex4d glVertex4d +#define qglVertex4dv glVertex4dv +#define qglVertex4f glVertex4f +#define qglVertex4fv glVertex4fv +#define qglVertex4i glVertex4i +#define qglVertex4iv glVertex4iv +#define qglVertex4s glVertex4s +#define qglVertex4sv glVertex4sv +#define qglVertexPointer glVertexPointer +#define qglViewport glViewport + +#endif diff --git a/src/renderer/tr_animation.c b/src/renderer/tr_animation.c new file mode 100644 index 0000000..0aa628b --- /dev/null +++ b/src/renderer/tr_animation.c @@ -0,0 +1,659 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +/* + +All bones should be an identity orientation to display the mesh exactly +as it is specified. + +For all other frames, the bones represent the transformation from the +orientation of the bone in the base frame to the orientation in this +frame. + +*/ + +/* +============== +R_AddAnimSurfaces +============== +*/ +void R_AddAnimSurfaces( trRefEntity_t *ent ) { + md4Header_t *header; + md4Surface_t *surface; + md4LOD_t *lod; + shader_t *shader; + int i; + + header = (md4Header_t *) tr.currentModel->md4; + lod = (md4LOD_t *)( (byte *)header + header->ofsLODs ); + + surface = (md4Surface_t *)( (byte *)lod + lod->ofsSurfaces ); + for ( i = 0 ; i < lod->numSurfaces ; i++ ) { + shader = R_GetShaderByHandle( surface->shaderIndex ); + R_AddDrawSurf( (void *)surface, shader, 0 /*fogNum*/, qfalse ); + surface = (md4Surface_t *)( (byte *)surface + surface->ofsEnd ); + } +} + +/* +============== +RB_SurfaceAnim +============== +*/ +void RB_SurfaceAnim( md4Surface_t *surface ) { + int i, j, k; + float frontlerp, backlerp; + int *triangles; + int indexes; + int baseIndex, baseVertex; + int numVerts; + md4Vertex_t *v; + md4Bone_t bones[MD4_MAX_BONES]; + md4Bone_t *bonePtr, *bone; + md4Header_t *header; + md4Frame_t *frame; + md4Frame_t *oldFrame; + int frameSize; + + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + frontlerp = 1; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + frontlerp = 1.0f - backlerp; + } + header = (md4Header_t *)((byte *)surface + surface->ofsHeader); + + frameSize = (size_t)( &((md4Frame_t *)0)->bones[ header->numBones ] ); + + frame = (md4Frame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.frame * frameSize ); + oldFrame = (md4Frame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.oldframe * frameSize ); + + RB_CheckOverflow( surface->numVerts, surface->numTriangles * 3 ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + for (j = 0 ; j < indexes ; j++) { + tess.indexes[baseIndex + j] = baseIndex + triangles[j]; + } + tess.numIndexes += indexes; + + // + // lerp all the needed bones + // + if ( !backlerp ) { + // no lerping needed + bonePtr = frame->bones; + } else { + bonePtr = bones; + for ( i = 0 ; i < header->numBones*12 ; i++ ) { + ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + + backlerp * ((float *)oldFrame->bones)[i]; + } + } + + // + // deform the vertexes by the lerped bones + // + numVerts = surface->numVerts; + // FIXME + // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left + // in for reference. + //v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts + 12); + v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts); + for ( j = 0; j < numVerts; j++ ) { + vec3_t tempVert, tempNormal; + md4Weight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); + w = v->weights; + for ( k = 0 ; k < v->numWeights ; k++, w++ ) { + bone = bonePtr + w->boneIndex; + + tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); + tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); + tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); + + tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); + tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); + tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); + } + + tess.xyz[baseVertex + j][0] = tempVert[0]; + tess.xyz[baseVertex + j][1] = tempVert[1]; + tess.xyz[baseVertex + j][2] = tempVert[2]; + + tess.normal[baseVertex + j][0] = tempNormal[0]; + tess.normal[baseVertex + j][1] = tempNormal[1]; + tess.normal[baseVertex + j][2] = tempNormal[2]; + + tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; + tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; + + // FIXME + // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left + // in for reference. + //v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 ); + v = (md4Vertex_t *)&v->weights[v->numWeights]; + } + + tess.numVertexes += surface->numVerts; +} + + +#ifdef RAVENMD4 + +// copied and adapted from tr_mesh.c + +/* +============= +R_MDRCullModel +============= +*/ + +static int R_MDRCullModel( mdrHeader_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + mdrFrame_t *oldFrame, *newFrame; + int i, frameSize; + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + // compute frame pointers + newFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + oldFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.oldframe); + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + // Ummm... yeah yeah I know we don't really have an md3 here.. but we pretend + // we do. After all, the purpose of md4s are not that different, are they? + + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +R_MDRComputeFogNum + +================= +*/ + +int R_MDRComputeFogNum( mdrHeader_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + mdrFrame_t *mdrFrame; + vec3_t localOrigin; + int frameSize; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + // FIXME: non-normalized axis issues + mdrFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + VectorAdd( ent->e.origin, mdrFrame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - mdrFrame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + mdrFrame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + + +/* +============== +R_MDRAddAnimSurfaces +============== +*/ + +// much stuff in there is just copied from R_AddMd3Surfaces in tr_mesh.c + +void R_MDRAddAnimSurfaces( trRefEntity_t *ent ) { + mdrHeader_t *header; + mdrSurface_t *surface; + mdrLOD_t *lod; + shader_t *shader; + skin_t *skin; + int i, j; + int lodnum = 0; + int fogNum = 0; + int cull; + qboolean personalModel; + + header = (mdrHeader_t *) tr.currentModel->md4; + + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) + { + ent->e.frame %= header->numFrames; + ent->e.oldframe %= header->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ((ent->e.frame >= header->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= header->numFrames) + || (ent->e.oldframe < 0) ) + { + ri.Printf( PRINT_DEVELOPER, "R_MDRAddAnimSurfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_MDRCullModel (header, ent); + if ( cull == CULL_OUT ) { + return; + } + + // figure out the current LOD of the model we're rendering, and set the lod pointer respectively. + lodnum = R_ComputeLOD(ent); + // check whether this model has as that many LODs at all. If not, try the closest thing we got. + if(header->numLODs <= 0) + return; + if(header->numLODs <= lodnum) + lodnum = header->numLODs - 1; + + lod = (mdrLOD_t *)( (byte *)header + header->ofsLODs); + for(i = 0; i < lodnum; i++) + { + lod = (mdrLOD_t *) ((byte *) lod + lod->ofsEnd); + } + + // set up lighting + if ( !personalModel || r_shadows->integer > 1 ) + { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // fogNum? + fogNum = R_MDRComputeFogNum( header, ent ); + + surface = (mdrSurface_t *)( (byte *)lod + lod->ofsSurfaces ); + + for ( i = 0 ; i < lod->numSurfaces ; i++ ) + { + + if(ent->e.customShader) + shader = R_GetShaderByHandle(ent->e.customShader); + else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) + { + skin = R_GetSkinByHandle(ent->e.customSkin); + shader = tr.defaultShader; + + for(j = 0; j < skin->numSurfaces; j++) + { + if (!strcmp(skin->surfaces[j]->name, surface->name)) + { + shader = skin->surfaces[j]->shader; + break; + } + } + } + else if(surface->shaderIndex > 0) + shader = R_GetShaderByHandle( surface->shaderIndex ); + else + shader = tr.defaultShader; + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse ); + } + + if (!personalModel) + R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse ); + + surface = (mdrSurface_t *)( (byte *)surface + surface->ofsEnd ); + } +} + +/* +============== +RB_MDRSurfaceAnim +============== +*/ +void RB_MDRSurfaceAnim( md4Surface_t *surface ) +{ + int i, j, k; + float frontlerp, backlerp; + int *triangles; + int indexes; + int baseIndex, baseVertex; + int numVerts; + mdrVertex_t *v; + mdrHeader_t *header; + mdrFrame_t *frame; + mdrFrame_t *oldFrame; + mdrBone_t bones[MD4_MAX_BONES], *bonePtr, *bone; + + int frameSize; + + // don't lerp if lerping off, or this is the only frame, or the last frame... + // + if (backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame) + { + backlerp = 0; // if backlerp is 0, lerping is off and frontlerp is never used + frontlerp = 1; + } + else + { + backlerp = backEnd.currentEntity->e.backlerp; + frontlerp = 1.0f - backlerp; + } + + header = (mdrHeader_t *)((byte *)surface + surface->ofsHeader); + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + frame = (mdrFrame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.frame * frameSize ); + oldFrame = (mdrFrame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.oldframe * frameSize ); + + RB_CheckOverflow( surface->numVerts, surface->numTriangles ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + + // Set up all triangles. + for (j = 0 ; j < indexes ; j++) + { + tess.indexes[baseIndex + j] = baseVertex + triangles[j]; + } + tess.numIndexes += indexes; + + // + // lerp all the needed bones + // + if ( !backlerp ) + { + // no lerping needed + bonePtr = frame->bones; + } + else + { + bonePtr = bones; + + for ( i = 0 ; i < header->numBones*12 ; i++ ) + { + ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + backlerp * ((float *)oldFrame->bones)[i]; + } + } + + // + // deform the vertexes by the lerped bones + // + numVerts = surface->numVerts; + v = (mdrVertex_t *) ((byte *)surface + surface->ofsVerts); + for ( j = 0; j < numVerts; j++ ) + { + vec3_t tempVert, tempNormal; + mdrWeight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); + w = v->weights; + for ( k = 0 ; k < v->numWeights ; k++, w++ ) + { + bone = bonePtr + w->boneIndex; + + tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); + tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); + tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); + + tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); + tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); + tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); + } + + tess.xyz[baseVertex + j][0] = tempVert[0]; + tess.xyz[baseVertex + j][1] = tempVert[1]; + tess.xyz[baseVertex + j][2] = tempVert[2]; + + tess.normal[baseVertex + j][0] = tempNormal[0]; + tess.normal[baseVertex + j][1] = tempNormal[1]; + tess.normal[baseVertex + j][2] = tempNormal[2]; + + tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; + tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; + + v = (mdrVertex_t *)&v->weights[v->numWeights]; + } + + tess.numVertexes += surface->numVerts; +} + + +#define MC_MASK_X ((1<<(MC_BITS_X))-1) +#define MC_MASK_Y ((1<<(MC_BITS_Y))-1) +#define MC_MASK_Z ((1<<(MC_BITS_Z))-1) +#define MC_MASK_VECT ((1<<(MC_BITS_VECT))-1) + +#define MC_SCALE_VECT (1.0f/(float)((1<<(MC_BITS_VECT-1))-2)) + +#define MC_POS_X (0) +#define MC_SHIFT_X (0) + +#define MC_POS_Y ((((MC_BITS_X))/8)) +#define MC_SHIFT_Y ((((MC_BITS_X)%8))) + +#define MC_POS_Z ((((MC_BITS_X+MC_BITS_Y))/8)) +#define MC_SHIFT_Z ((((MC_BITS_X+MC_BITS_Y)%8))) + +#define MC_POS_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z))/8)) +#define MC_SHIFT_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z)%8))) + +#define MC_POS_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT))/8)) +#define MC_SHIFT_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT)%8))) + +#define MC_POS_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2))/8)) +#define MC_SHIFT_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2)%8))) + +#define MC_POS_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3))/8)) +#define MC_SHIFT_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3)%8))) + +#define MC_POS_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4))/8)) +#define MC_SHIFT_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4)%8))) + +#define MC_POS_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5))/8)) +#define MC_SHIFT_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5)%8))) + +#define MC_POS_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6))/8)) +#define MC_SHIFT_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6)%8))) + +#define MC_POS_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7))/8)) +#define MC_SHIFT_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7)%8))) + +#define MC_POS_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8))/8)) +#define MC_SHIFT_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8)%8))) + +void MC_UnCompress(float mat[3][4],const unsigned char * comp) +{ + int val; + + val=(int)((unsigned short *)(comp))[0]; + val-=1<<(MC_BITS_X-1); + mat[0][3]=((float)(val))*MC_SCALE_X; + + val=(int)((unsigned short *)(comp))[1]; + val-=1<<(MC_BITS_Y-1); + mat[1][3]=((float)(val))*MC_SCALE_Y; + + val=(int)((unsigned short *)(comp))[2]; + val-=1<<(MC_BITS_Z-1); + mat[2][3]=((float)(val))*MC_SCALE_Z; + + val=(int)((unsigned short *)(comp))[3]; + val-=1<<(MC_BITS_VECT-1); + mat[0][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[4]; + val-=1<<(MC_BITS_VECT-1); + mat[0][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[5]; + val-=1<<(MC_BITS_VECT-1); + mat[0][2]=((float)(val))*MC_SCALE_VECT; + + + val=(int)((unsigned short *)(comp))[6]; + val-=1<<(MC_BITS_VECT-1); + mat[1][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[7]; + val-=1<<(MC_BITS_VECT-1); + mat[1][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[8]; + val-=1<<(MC_BITS_VECT-1); + mat[1][2]=((float)(val))*MC_SCALE_VECT; + + + val=(int)((unsigned short *)(comp))[9]; + val-=1<<(MC_BITS_VECT-1); + mat[2][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[10]; + val-=1<<(MC_BITS_VECT-1); + mat[2][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[11]; + val-=1<<(MC_BITS_VECT-1); + mat[2][2]=((float)(val))*MC_SCALE_VECT; +} +#endif diff --git a/src/renderer/tr_backend.c b/src/renderer/tr_backend.c new file mode 100644 index 0000000..54431ab --- /dev/null +++ b/src/renderer/tr_backend.c @@ -0,0 +1,1210 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + +backEndData_t *backEndData[SMP_FRAMES]; +backEndState_t backEnd; + + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +/* +** GL_Bind +*/ +void GL_Bind( image_t *image ) { + int texnum; + + if ( !image ) { + ri.Printf( PRINT_WARNING, "GL_Bind: NULL image\n" ); + texnum = tr.defaultImage->texnum; + } else { + texnum = image->texnum; + } + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[glState.currenttmu] != texnum ) { + image->frameUsed = tr.frameCount; + glState.currenttextures[glState.currenttmu] = texnum; + qglBindTexture (GL_TEXTURE_2D, texnum); + } +} + +/* +** GL_SelectTexture +*/ +void GL_SelectTexture( int unit ) +{ + if ( glState.currenttmu == unit ) + { + return; + } + + if ( unit == 0 ) + { + qglActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + } + else if ( unit == 1 ) + { + qglActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + } else { + ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit ); + } + + glState.currenttmu = unit; +} + + +/* +** GL_BindMultitexture +*/ +void GL_BindMultitexture( image_t *image0, GLuint env0, image_t *image1, GLuint env1 ) { + int texnum0, texnum1; + + texnum0 = image0->texnum; + texnum1 = image1->texnum; + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum0 = texnum1 = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[1] != texnum1 ) { + GL_SelectTexture( 1 ); + image1->frameUsed = tr.frameCount; + glState.currenttextures[1] = texnum1; + qglBindTexture( GL_TEXTURE_2D, texnum1 ); + } + if ( glState.currenttextures[0] != texnum0 ) { + GL_SelectTexture( 0 ); + image0->frameUsed = tr.frameCount; + glState.currenttextures[0] = texnum0; + qglBindTexture( GL_TEXTURE_2D, texnum0 ); + } +} + + +/* +** GL_Cull +*/ +void GL_Cull( int cullType ) { + if ( glState.faceCulling == cullType ) { + return; + } + + glState.faceCulling = cullType; + + if ( cullType == CT_TWO_SIDED ) + { + qglDisable( GL_CULL_FACE ); + } + else + { + qglEnable( GL_CULL_FACE ); + + if ( cullType == CT_BACK_SIDED ) + { + if ( backEnd.viewParms.isMirror ) + { + qglCullFace( GL_FRONT ); + } + else + { + qglCullFace( GL_BACK ); + } + } + else + { + if ( backEnd.viewParms.isMirror ) + { + qglCullFace( GL_BACK ); + } + else + { + qglCullFace( GL_FRONT ); + } + } + } +} + +/* +** GL_TexEnv +*/ +void GL_TexEnv( int env ) +{ + if ( env == glState.texEnv[glState.currenttmu] ) + { + return; + } + + glState.texEnv[glState.currenttmu] = env; + + + switch ( env ) + { + case GL_MODULATE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + break; + case GL_REPLACE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + break; + case GL_DECAL: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + break; + case GL_ADD: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + break; + default: + ri.Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed\n", env ); + break; + } +} + +/* +** GL_State +** +** This routine is responsible for setting the most commonly changed state +** in Q3. +*/ +void GL_State( unsigned long stateBits ) +{ + unsigned long diff = stateBits ^ glState.glStateBits; + + if ( !diff ) + { + return; + } + + // + // check depthFunc bits + // + if ( diff & GLS_DEPTHFUNC_EQUAL ) + { + if ( stateBits & GLS_DEPTHFUNC_EQUAL ) + { + qglDepthFunc( GL_EQUAL ); + } + else + { + qglDepthFunc( GL_LEQUAL ); + } + } + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + GLenum srcFactor, dstFactor; + + if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + switch ( stateBits & GLS_SRCBLEND_BITS ) + { + case GLS_SRCBLEND_ZERO: + srcFactor = GL_ZERO; + break; + case GLS_SRCBLEND_ONE: + srcFactor = GL_ONE; + break; + case GLS_SRCBLEND_DST_COLOR: + srcFactor = GL_DST_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: + srcFactor = GL_ONE_MINUS_DST_COLOR; + break; + case GLS_SRCBLEND_SRC_ALPHA: + srcFactor = GL_SRC_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: + srcFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_SRCBLEND_DST_ALPHA: + srcFactor = GL_DST_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: + srcFactor = GL_ONE_MINUS_DST_ALPHA; + break; + case GLS_SRCBLEND_ALPHA_SATURATE: + srcFactor = GL_SRC_ALPHA_SATURATE; + break; + default: + srcFactor = GL_ONE; // to get warning to shut up + ri.Error( ERR_DROP, "GL_State: invalid src blend state bits\n" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) + { + case GLS_DSTBLEND_ZERO: + dstFactor = GL_ZERO; + break; + case GLS_DSTBLEND_ONE: + dstFactor = GL_ONE; + break; + case GLS_DSTBLEND_SRC_COLOR: + dstFactor = GL_SRC_COLOR; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: + dstFactor = GL_ONE_MINUS_SRC_COLOR; + break; + case GLS_DSTBLEND_SRC_ALPHA: + dstFactor = GL_SRC_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_DSTBLEND_DST_ALPHA: + dstFactor = GL_DST_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: + dstFactor = GL_ONE_MINUS_DST_ALPHA; + break; + default: + dstFactor = GL_ONE; // to get warning to shut up + ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits\n" ); + break; + } + + qglEnable( GL_BLEND ); + qglBlendFunc( srcFactor, dstFactor ); + } + else + { + qglDisable( GL_BLEND ); + } + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK_TRUE ) + { + if ( stateBits & GLS_DEPTHMASK_TRUE ) + { + qglDepthMask( GL_TRUE ); + } + else + { + qglDepthMask( GL_FALSE ); + } + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) + { + if ( stateBits & GLS_POLYMODE_LINE ) + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } + else + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // depthtest + // + if ( diff & GLS_DEPTHTEST_DISABLE ) + { + if ( stateBits & GLS_DEPTHTEST_DISABLE ) + { + qglDisable( GL_DEPTH_TEST ); + } + else + { + qglEnable( GL_DEPTH_TEST ); + } + } + + // + // alpha test + // + if ( diff & GLS_ATEST_BITS ) + { + switch ( stateBits & GLS_ATEST_BITS ) + { + case 0: + qglDisable( GL_ALPHA_TEST ); + break; + case GLS_ATEST_GT_0: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GREATER, 0.0f ); + break; + case GLS_ATEST_LT_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_LESS, 0.5f ); + break; + case GLS_ATEST_GE_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.5f ); + break; + default: + assert( 0 ); + break; + } + } + + glState.glStateBits = stateBits; +} + + + +/* +================ +RB_Hyperspace + +A player has predicted a teleport, but hasn't arrived yet +================ +*/ +static void RB_Hyperspace( void ) { + float c; + + if ( !backEnd.isHyperspace ) { + // do initialization shit + } + + c = ( backEnd.refdef.time & 255 ) / 255.0f; + qglClearColor( c, c, c, 1 ); + qglClear( GL_COLOR_BUFFER_BIT ); + + backEnd.isHyperspace = qtrue; +} + + +static void SetViewportAndScissor( void ) { + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf( backEnd.viewParms.projectionMatrix ); + qglMatrixMode(GL_MODELVIEW); + + // set the window clipping + qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); +} + +/* +================= +RB_BeginDrawingView + +Any mirrored or portaled views have already been drawn, so prepare +to actually render the visible surfaces for this view +================= +*/ +void RB_BeginDrawingView (void) { + int clearBits = 0; + + // sync with gl if needed + if ( r_finish->integer == 1 && !glState.finishCalled ) { + qglFinish (); + glState.finishCalled = qtrue; + } + if ( r_finish->integer == 0 ) { + glState.finishCalled = qtrue; + } + + // we will need to change the projection matrix before drawing + // 2D images again + backEnd.projection2D = qfalse; + + // + // set the modelview matrix for the viewer + // + SetViewportAndScissor(); + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + // clear relevant buffers + clearBits = GL_DEPTH_BUFFER_BIT; + + if ( r_measureOverdraw->integer || r_shadows->integer == 2 ) + { + clearBits |= GL_STENCIL_BUFFER_BIT; + } + if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) ) + { + clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used +#ifdef _DEBUG + qglClearColor( 0.8f, 0.7f, 0.4f, 1.0f ); // FIXME: get color of sky +#else + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // FIXME: get color of sky +#endif + } + qglClear( clearBits ); + + if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) ) + { + RB_Hyperspace(); + return; + } + else + { + backEnd.isHyperspace = qfalse; + } + + glState.faceCulling = -1; // force face culling to set next time + + // we will only draw a sun if there was sky rendered in this view + backEnd.skyRenderedThisView = qfalse; + + // clip to the plane of the portal + if ( backEnd.viewParms.isPortal ) { + float plane[4]; + double plane2[4]; + + plane[0] = backEnd.viewParms.portalPlane.normal[0]; + plane[1] = backEnd.viewParms.portalPlane.normal[1]; + plane[2] = backEnd.viewParms.portalPlane.normal[2]; + plane[3] = backEnd.viewParms.portalPlane.dist; + + plane2[0] = DotProduct (backEnd.viewParms.or.axis[0], plane); + plane2[1] = DotProduct (backEnd.viewParms.or.axis[1], plane); + plane2[2] = DotProduct (backEnd.viewParms.or.axis[2], plane); + plane2[3] = DotProduct (plane, backEnd.viewParms.or.origin) - plane[3]; + + qglLoadMatrixf( s_flipMatrix ); + qglClipPlane (GL_CLIP_PLANE0, plane2); + qglEnable (GL_CLIP_PLANE0); + } else { + qglDisable (GL_CLIP_PLANE0); + } +} + + +#define MAC_EVENT_PUMP_MSEC 5 + +/* +================== +RB_RenderDrawSurfList +================== +*/ +void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader, *oldShader; + int fogNum, oldFogNum; + int entityNum, oldEntityNum; + int dlighted, oldDlighted; + qboolean depthRange, oldDepthRange, isCrosshair, wasCrosshair; + int i; + drawSurf_t *drawSurf; + int oldSort; + float originalTime; + + // save original time for entity shader offsets + originalTime = backEnd.refdef.floatTime; + + // clear the z buffer, set the modelview, etc + RB_BeginDrawingView (); + + // draw everything + oldEntityNum = -1; + backEnd.currentEntity = &tr.worldEntity; + oldShader = NULL; + oldFogNum = -1; + oldDepthRange = qfalse; + wasCrosshair = qfalse; + oldDlighted = qfalse; + oldSort = -1; + depthRange = qfalse; + + backEnd.pc.c_surfaces += numDrawSurfs; + + for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) { + if ( drawSurf->sort == oldSort ) { + // fast path, same as previous sort + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + continue; + } + oldSort = drawSurf->sort; + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + + // + // change the tess parameters if needed + // a "entityMergable" shader is a shader that can have surfaces from seperate + // entities merged into a single batch, like smoke and blood puff sprites + if (shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted + || ( entityNum != oldEntityNum && !shader->entityMergable ) ) { + if (oldShader != NULL) { + RB_EndSurface(); + } + RB_BeginSurface( shader, fogNum ); + oldShader = shader; + oldFogNum = fogNum; + oldDlighted = dlighted; + } + + // + // change the modelview matrix if needed + // + if ( entityNum != oldEntityNum ) { + depthRange = isCrosshair = qfalse; + + if ( entityNum != ENTITYNUM_WORLD ) { + backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; + backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime; + // we have to reset the shaderTime as well otherwise image animations start + // from the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.or ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) { + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or ); + } + + if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) + { + // hack the depth range to prevent view model from poking into walls + depthRange = qtrue; + + if(backEnd.currentEntity->e.renderfx & RF_CROSSHAIR) + isCrosshair = qtrue; + } + } else { + backEnd.currentEntity = &tr.worldEntity; + backEnd.refdef.floatTime = originalTime; + backEnd.or = backEnd.viewParms.world; + // we have to reset the shaderTime as well otherwise image animations on + // the world (like water) continue with the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or ); + } + + qglLoadMatrixf( backEnd.or.modelMatrix ); + + // + // change depthrange. Also change projection matrix so first person weapon does not look like coming + // out of the screen. + // + if (oldDepthRange != depthRange || wasCrosshair != isCrosshair) + { + if (depthRange) + { + if(backEnd.viewParms.stereoFrame != STEREO_CENTER) + { + if(isCrosshair) + { + if(oldDepthRange) + { + // was not a crosshair but now is, change back proj matrix + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf(backEnd.viewParms.projectionMatrix); + qglMatrixMode(GL_MODELVIEW); + } + } + else + { + viewParms_t temp = backEnd.viewParms; + + R_SetupProjection(&temp, r_znear->value, qfalse); + + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf(temp.projectionMatrix); + qglMatrixMode(GL_MODELVIEW); + } + } + + if(!oldDepthRange) + qglDepthRange (0, 0.3); + } + else + { + if(!wasCrosshair && backEnd.viewParms.stereoFrame != STEREO_CENTER) + { + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf(backEnd.viewParms.projectionMatrix); + qglMatrixMode(GL_MODELVIEW); + } + + qglDepthRange (0, 1); + } + + oldDepthRange = depthRange; + wasCrosshair = isCrosshair; + } + + oldEntityNum = entityNum; + } + + // add the triangles for this surface + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + } + + backEnd.refdef.floatTime = originalTime; + + // draw the contents of the last shader batch + if (oldShader != NULL) { + RB_EndSurface(); + } + + // go back to the world modelview matrix + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + if ( depthRange ) { + qglDepthRange (0, 1); + } + +#if 0 + RB_DrawSun(); +#endif + // darken down any stencil shadows + RB_ShadowFinish(); + + // add light flares on lights that aren't obscured + RB_RenderFlares(); +} + + +/* +============================================================================ + +RENDER BACK END THREAD FUNCTIONS + +============================================================================ +*/ + +/* +================ +RB_SetGL2D + +================ +*/ +void RB_SetGL2D (void) { + backEnd.projection2D = qtrue; + + // set 2D virtual screen size + qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglMatrixMode(GL_PROJECTION); + qglLoadIdentity (); + qglOrtho (0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1); + qglMatrixMode(GL_MODELVIEW); + qglLoadIdentity (); + + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglDisable( GL_CULL_FACE ); + qglDisable( GL_CLIP_PLANE0 ); + + // set time for 2D shaders + backEnd.refdef.time = ri.Milliseconds(); + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; +} + + +/* +============= +RE_StretchRaw + +FIXME: not exactly backend +Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. +Used for cinematics. +============= +*/ +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { + int i, j; + int start, end; + + if ( !tr.registered ) { + return; + } + R_SyncRenderThread(); + + // we definately want to sync every frame for the cinematics + qglFinish(); + + start = end = 0; + if ( r_speeds->integer ) { + start = ri.Milliseconds(); + } + + // make sure rows and cols are powers of 2 + for ( i = 0 ; ( 1 << i ) < cols ; i++ ) { + } + for ( j = 0 ; ( 1 << j ) < rows ; j++ ) { + } + if ( ( 1 << i ) != cols || ( 1 << j ) != rows) { + ri.Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); + } + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; + tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + } else { + if (dirty) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } + + if ( r_speeds->integer ) { + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); + } + + RB_SetGL2D(); + + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglBegin (GL_QUADS); + qglTexCoord2f ( 0.5f / cols, 0.5f / rows ); + qglVertex2f (x, y); + qglTexCoord2f ( ( cols - 0.5f ) / cols , 0.5f / rows ); + qglVertex2f (x+w, y); + qglTexCoord2f ( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows ); + qglVertex2f (x+w, y+h); + qglTexCoord2f ( 0.5f / cols, ( rows - 0.5f ) / rows ); + qglVertex2f (x, y+h); + qglEnd (); +} + +void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; + tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + } else { + if (dirty) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } +} + + +/* +============= +RB_SetColor + +============= +*/ +const void *RB_SetColor( const void *data ) { + const setColorCommand_t *cmd; + + cmd = (const setColorCommand_t *)data; + + backEnd.color2D[0] = cmd->color[0] * 255; + backEnd.color2D[1] = cmd->color[1] * 255; + backEnd.color2D[2] = cmd->color[2] * 255; + backEnd.color2D[3] = cmd->color[3] * 255; + + return (const void *)(cmd + 1); +} + +/* +============= +RB_StretchPic +============= +*/ +const void *RB_StretchPic ( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + + cmd = (const stretchPicCommand_t *)data; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0 ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + + *(int *)tess.vertexColors[ numVerts ] = + *(int *)tess.vertexColors[ numVerts + 1 ] = + *(int *)tess.vertexColors[ numVerts + 2 ] = + *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D; + + tess.xyz[ numVerts ][0] = cmd->x; + tess.xyz[ numVerts ][1] = cmd->y; + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0][0] = cmd->s1; + tess.texCoords[ numVerts ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 1 ][1] = cmd->y; + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2; + + tess.xyz[ numVerts + 3 ][0] = cmd->x; + tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2; + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawSurfs + +============= +*/ +const void *RB_DrawSurfs( const void *data ) { + const drawSurfsCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + cmd = (const drawSurfsCommand_t *)data; + + backEnd.refdef = cmd->refdef; + backEnd.viewParms = cmd->viewParms; + + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawBuffer + +============= +*/ +const void *RB_DrawBuffer( const void *data ) { + const drawBufferCommand_t *cmd; + + cmd = (const drawBufferCommand_t *)data; + + qglDrawBuffer( cmd->buffer ); + + // clear screen for debugging + if ( r_clear->integer ) { + qglClearColor( 1, 0, 0.5, 1 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + + return (const void *)(cmd + 1); +} + +/* +=============== +RB_ShowImages + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. + +Also called by RE_EndRegistration +=============== +*/ +void RB_ShowImages( void ) { + int i; + image_t *image; + float x, y, w, h; + int start, end; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + qglClear( GL_COLOR_BUFFER_BIT ); + + qglFinish(); + + start = ri.Milliseconds(); + + for ( i=0 ; iinteger == 2 ) { + w *= image->uploadWidth / 512.0f; + h *= image->uploadHeight / 512.0f; + } + + GL_Bind( image ); + qglBegin (GL_QUADS); + qglTexCoord2f( 0, 0 ); + qglVertex2f( x, y ); + qglTexCoord2f( 1, 0 ); + qglVertex2f( x + w, y ); + qglTexCoord2f( 1, 1 ); + qglVertex2f( x + w, y + h ); + qglTexCoord2f( 0, 1 ); + qglVertex2f( x, y + h ); + qglEnd(); + } + + qglFinish(); + + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start ); + +} + +/* +============= +RB_ColorMask + +============= +*/ +const void *RB_ColorMask(const void *data) +{ + const colorMaskCommand_t *cmd = data; + + qglColorMask(cmd->rgba[0], cmd->rgba[1], cmd->rgba[2], cmd->rgba[3]); + + return (const void *)(cmd + 1); +} + +/* +============= +RB_ClearDepth + +============= +*/ +const void *RB_ClearDepth(const void *data) +{ + const clearDepthCommand_t *cmd = data; + + if(tess.numIndexes) + RB_EndSurface(); + + // texture swapping test + if (r_showImages->integer) + RB_ShowImages(); + + qglClear(GL_DEPTH_BUFFER_BIT); + + return (const void *)(cmd + 1); +} + +/* +============= +RB_SwapBuffers + +============= +*/ +const void *RB_SwapBuffers( const void *data ) { + const swapBuffersCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // texture swapping test + if ( r_showImages->integer ) { + RB_ShowImages(); + } + + cmd = (const swapBuffersCommand_t *)data; + + // we measure overdraw by reading back the stencil buffer and + // counting up the number of increments that have happened + if ( r_measureOverdraw->integer ) { + int i; + long sum = 0; + unsigned char *stencilReadback; + + stencilReadback = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + sum += stencilReadback[i]; + } + + backEnd.pc.c_overDraw += sum; + ri.Hunk_FreeTempMemory( stencilReadback ); + } + + + if ( !glState.finishCalled ) { + qglFinish(); + } + + GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" ); + + GLimp_EndFrame(); + + backEnd.projection2D = qfalse; + + return (const void *)(cmd + 1); +} + +/* +==================== +RB_ExecuteRenderCommands + +This function will be called synchronously if running without +smp extensions, or asynchronously by another thread. +==================== +*/ +void RB_ExecuteRenderCommands( const void *data ) { + int t1, t2; + + t1 = ri.Milliseconds (); + + if ( !r_smp->integer || data == backEndData[0]->commands.cmds ) { + backEnd.smpFrame = 0; + } else { + backEnd.smpFrame = 1; + } + + while ( 1 ) { + switch ( *(const int *)data ) { + case RC_SET_COLOR: + data = RB_SetColor( data ); + break; + case RC_STRETCH_PIC: + data = RB_StretchPic( data ); + break; + case RC_DRAW_SURFS: + data = RB_DrawSurfs( data ); + break; + case RC_DRAW_BUFFER: + data = RB_DrawBuffer( data ); + break; + case RC_SWAP_BUFFERS: + data = RB_SwapBuffers( data ); + break; + case RC_SCREENSHOT: + data = RB_TakeScreenshotCmd( data ); + break; + case RC_VIDEOFRAME: + data = RB_TakeVideoFrameCmd( data ); + break; + case RC_COLORMASK: + data = RB_ColorMask(data); + break; + case RC_CLEARDEPTH: + data = RB_ClearDepth(data); + break; + case RC_END_OF_LIST: + default: + // stop rendering on this thread + t2 = ri.Milliseconds (); + backEnd.pc.msec = t2 - t1; + return; + } + } + +} + + +/* +================ +RB_RenderThread +================ +*/ +void RB_RenderThread( void ) { + const void *data; + + // wait for either a rendering command or a quit command + while ( 1 ) { + // sleep until we have work to do + data = GLimp_RendererSleep(); + + if ( !data ) { + return; // all done, renderer is shutting down + } + + renderThreadActive = qtrue; + + RB_ExecuteRenderCommands( data ); + + renderThreadActive = qfalse; + } +} + diff --git a/src/renderer/tr_bsp.c b/src/renderer/tr_bsp.c new file mode 100644 index 0000000..563bb36 --- /dev/null +++ b/src/renderer/tr_bsp.c @@ -0,0 +1,1870 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_map.c + +#include "tr_local.h" + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +static world_t s_worldData; +static byte *fileBase; + +int c_subdivisions; +int c_gridVerts; + +//=============================================================================== + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) +{ + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { + int shift, r, g, b; + + // shift the color data based on overbright range + shift = r_mapOverBrightBits->integer - tr.overbrightBits; + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define LIGHTMAP_SIZE 128 +static void R_LoadLightmaps( lump_t *l ) { + byte *buf, *buf_p; + int len; + byte image[LIGHTMAP_SIZE*LIGHTMAP_SIZE*4]; + int i, j; + float maxIntensity = 0; + double sumIntensity = 0; + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + // we are about to upload textures + R_SyncRenderThread(); + + // create all the lightmaps + tr.numLightmaps = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3); + if ( tr.numLightmaps == 1 ) { + //FIXME: HACK: maps with only one lightmap turn up fullbright for some reason. + //this avoids this, but isn't the correct solution. + tr.numLightmaps++; + } + + // if we are in r_vertexLight mode, we don't need the lightmaps at all + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + return; + } + + tr.lightmaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low ); + for ( i = 0 ; i < tr.numLightmaps ; i++ ) { + // expand the 24 bit on-disk to 32 bit + buf_p = buf + i * LIGHTMAP_SIZE*LIGHTMAP_SIZE * 3; + + if ( r_lightmap->integer == 2 ) + { // color code by intensity as development tool (FIXME: check range) + for ( j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) + { + float r = buf_p[j*3+0]; + float g = buf_p[j*3+1]; + float b = buf_p[j*3+2]; + float intensity; + float out[3] = {0.0, 0.0, 0.0}; + + intensity = 0.33f * r + 0.685f * g + 0.063f * b; + + if ( intensity > 255 ) + intensity = 1.0f; + else + intensity /= 255.0f; + + if ( intensity > maxIntensity ) + maxIntensity = intensity; + + HSVtoRGB( intensity, 1.00, 0.50, out ); + + image[j*4+0] = out[0] * 255; + image[j*4+1] = out[1] * 255; + image[j*4+2] = out[2] * 255; + image[j*4+3] = 255; + + sumIntensity += intensity; + } + } else { + for ( j = 0 ; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) { + R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] ); + image[j*4+3] = 255; + } + } + tr.lightmaps[i] = R_CreateImage( va("*lightmap%d",i), image, + LIGHTMAP_SIZE, LIGHTMAP_SIZE, qfalse, qfalse, GL_CLAMP_TO_EDGE ); + } + + if ( r_lightmap->integer == 2 ) { + ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) ); + } +} + + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( const byte *vis ) { + tr.externalVisData = vis; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( lump_t *l ) { + int len; + byte *buf; + + len = ( s_worldData.numClusters + 63 ) & ~63; + s_worldData.novis = ri.Hunk_Alloc( len, h_low ); + Com_Memset( s_worldData.novis, 0xff, len ); + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + s_worldData.numClusters = LittleLong( ((int *)buf)[0] ); + s_worldData.clusterBytes = LittleLong( ((int *)buf)[1] ); + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + if ( tr.externalVisData ) { + s_worldData.vis = tr.externalVisData; + } else { + byte *dest; + + dest = ri.Hunk_Alloc( len - 8, h_low ); + Com_Memcpy( dest, buf + 8, len - 8 ); + s_worldData.vis = dest; + } +} + +//=============================================================================== + + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) { + shader_t *shader; + dshader_t *dsh; + + shaderNum = LittleLong( shaderNum ); + if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders ) { + ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum ); + } + dsh = &s_worldData.shaders[ shaderNum ]; + + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + lightmapNum = LIGHTMAP_BY_VERTEX; + } + + if ( r_fullbright->integer ) { + lightmapNum = LIGHTMAP_WHITEIMAGE; + } + + shader = R_FindShader( dsh->shader, lightmapNum, qtrue ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + int i, j; + srfSurfaceFace_t *cv; + int numPoints, numIndexes; + int lightmapNum; + int sfaceSize, ofsIndexes; + + lightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numPoints = LittleLong( ds->numVerts ); + if (numPoints > MAX_FACE_POINTS) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numPoints); + numPoints = MAX_FACE_POINTS; + surf->shader = tr.defaultShader; + } + + numIndexes = LittleLong( ds->numIndexes ); + + // create the srfSurfaceFace_t + sfaceSize = ( size_t ) &((srfSurfaceFace_t *)0)->points[numPoints]; + ofsIndexes = sfaceSize; + sfaceSize += sizeof( int ) * numIndexes; + + cv = ri.Hunk_Alloc( sfaceSize, h_low ); + cv->surfaceType = SF_FACE; + cv->numPoints = numPoints; + cv->numIndices = numIndexes; + cv->ofsIndices = ofsIndexes; + + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + cv->points[i][j] = LittleFloat( verts[i].xyz[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + cv->points[i][3+j] = LittleFloat( verts[i].st[j] ); + cv->points[i][5+j] = LittleFloat( verts[i].lightmap[j] ); + } + R_ColorShiftLightingBytes( verts[i].color, (byte *)&cv->points[i][7] ); + } + + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + ((int *)((byte *)cv + cv->ofsIndices ))[i] = LittleLong( indexes[ i ] ); + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } + cv->plane.dist = DotProduct( cv->points[0], cv->plane.normal ); + SetPlaneSignbits( &cv->plane ); + cv->plane.type = PlaneTypeForNormal( cv->plane.normal ); + + surf->data = (surfaceType_t *)cv; +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, msurface_t *surf ) { + srfGridMesh_t *grid; + int i, j; + int width, height, numPoints; + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE]; + int lightmapNum; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + + lightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = LittleLong( ds->patchWidth ); + height = LittleLong( ds->patchHeight ); + + verts += LittleLong( ds->firstVert ); + numPoints = width * height; + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + points[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + points[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + points[i].st[j] = LittleFloat( verts[i].st[j] ); + points[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] ); + } + R_ColorShiftLightingBytes( verts[i].color, points[i].color ); + } + + // pre-tesseleate + grid = R_SubdividePatchToGrid( width, height, points ); + surf->data = (surfaceType_t *)grid; + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] ); + bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] ); + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + srfTriangles_t *tri; + int i, j; + int numVerts, numIndexes; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong( ds->numVerts ); + numIndexes = LittleLong( ds->numIndexes ); + + tri = ri.Hunk_Alloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) + + numIndexes * sizeof( tri->indexes[0] ), h_low ); + tri->surfaceType = SF_TRIANGLES; + tri->numVerts = numVerts; + tri->numIndexes = numIndexes; + tri->verts = (drawVert_t *)(tri + 1); + tri->indexes = (int *)(tri->verts + tri->numVerts ); + + surf->data = (surfaceType_t *)tri; + + // copy vertexes + ClearBounds( tri->bounds[0], tri->bounds[1] ); + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numVerts ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tri->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + tri->verts[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] ); + for ( j = 0 ; j < 2 ; j++ ) { + tri->verts[i].st[j] = LittleFloat( verts[i].st[j] ); + tri->verts[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] ); + } + + R_ColorShiftLightingBytes( verts[i].color, tri->verts[i].color ); + } + + // copy indexes + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + tri->indexes[i] = LittleLong( indexes[i] ); + if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) { + ri.Error( ERR_DROP, "Bad index in triangle surface" ); + } + } +} + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + srfFlare_t *flare; + int i; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + flare = ri.Hunk_Alloc( sizeof( *flare ), h_low ); + flare->surfaceType = SF_FLARE; + + surf->data = (surfaceType_t *)flare; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] ); + flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] ); + flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } +} + + +/* +================= +R_MergedWidthPoints + +returns true if there are grid points merged on a width edge +================= +*/ +int R_MergedWidthPoints(srfGridMesh_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->width-1; i++) { + for (j = i + 1; j < grid->width-1; j++) { + if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue; + return qtrue; + } + } + return qfalse; +} + +/* +================= +R_MergedHeightPoints + +returns true if there are grid points merged on a height edge +================= +*/ +int R_MergedHeightPoints(srfGridMesh_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->height-1; i++) { + for (j = i + 1; j < grid->height-1; j++) { + if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue; + return qtrue; + } + } + return qfalse; +} + +/* +================= +R_FixSharedVertexLodError_r + +NOTE: never sync LoD through grid edges with merged points! + +FIXME: write generalized version that also avoids cracks between a patch and one that meets half way? +================= +*/ +void R_FixSharedVertexLodError_r( int start, srfGridMesh_t *grid1 ) { + int j, k, l, m, n, offset1, offset2, touch; + srfGridMesh_t *grid2; + + for ( j = start; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // if the LOD errors are already fixed for this patch + if ( grid2->lodFixed == 2 ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + touch = qfalse; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->width-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->widthLodError[k]; + touch = qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->widthLodError[k]; + touch = qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->height-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->heightLodError[k]; + touch = qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->heightLodError[k]; + touch = qtrue; + } + } + } + } + if (touch) { + grid2->lodFixed = 2; + R_FixSharedVertexLodError_r ( start, grid2 ); + //NOTE: this would be correct but makes things really slow + //grid2->lodFixed = 1; + } + } +} + +/* +================= +R_FixSharedVertexLodError + +This function assumes that all patches in one group are nicely stitched together for the highest LoD. +If this is not the case this function will still do its job but won't fix the highest LoD cracks. +================= +*/ +void R_FixSharedVertexLodError( void ) { + int i; + srfGridMesh_t *grid1; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodFixed ) + continue; + // + grid1->lodFixed = 2; + // recursively fix other patches in the same LOD group + R_FixSharedVertexLodError_r( i + 1, grid1); + } +} + + +/* +=============== +R_StitchPatches +=============== +*/ +int R_StitchPatches( int grid1num, int grid2num ) { + float *v1, *v2; + srfGridMesh_t *grid1, *grid2; + int k, l, m, n, offset1, offset2, row, column; + + grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data; + grid2 = (srfGridMesh_t *) s_worldData.surfaces[grid2num].data; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->width-2; k += 2) { + + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->height-2; k += 2) { + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = grid1->width-1; k > 1; k -= 2) { + + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + if (!grid2) + break; + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = grid1->height-1; k > 1; k -= 2) { + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + return qfalse; +} + +/* +=============== +R_TryStitchPatch + +This function will try to stitch patches in the same LoD group together for the highest LoD. + +Only single missing vertice cracks will be fixed. + +Vertices will be joined at the patch side a crack is first found, at the other side +of the patch (on the same row or column) the vertices will not be joined and cracks +might still appear at that side. +=============== +*/ +int R_TryStitchingPatch( int grid1num ) { + int j, numstitches; + srfGridMesh_t *grid1, *grid2; + + numstitches = 0; + grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data; + for ( j = 0; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + while (R_StitchPatches(grid1num, j)) + { + numstitches++; + } + } + return numstitches; +} + +/* +=============== +R_StitchAllPatches +=============== +*/ +void R_StitchAllPatches( void ) { + int i, stitched, numstitches; + srfGridMesh_t *grid1; + + numstitches = 0; + do + { + stitched = qfalse; + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodStitched ) + continue; + // + grid1->lodStitched = qtrue; + stitched = qtrue; + // + numstitches += R_TryStitchingPatch( i ); + } + } + while (stitched); + ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches ); +} + +/* +=============== +R_MovePatchSurfacesToHunk +=============== +*/ +void R_MovePatchSurfacesToHunk(void) { + int i, size; + srfGridMesh_t *grid, *hunkgrid; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid->surfaceType != SF_GRID ) + continue; + // + size = (grid->width * grid->height - 1) * sizeof( drawVert_t ) + sizeof( *grid ); + hunkgrid = ri.Hunk_Alloc( size, h_low ); + Com_Memcpy(hunkgrid, grid, size); + + hunkgrid->widthLodError = ri.Hunk_Alloc( grid->width * 4, h_low ); + Com_Memcpy( hunkgrid->widthLodError, grid->widthLodError, grid->width * 4 ); + + hunkgrid->heightLodError = ri.Hunk_Alloc( grid->height * 4, h_low ); + Com_Memcpy( hunkgrid->heightLodError, grid->heightLodError, grid->height * 4 ); + + R_FreeSurfaceGridMesh( grid ); + + s_worldData.surfaces[i].data = (void *) hunkgrid; + } +} + +/* +=============== +R_LoadSurfaces +=============== +*/ +static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) { + dsurface_t *in; + msurface_t *out; + drawVert_t *dv; + int *indexes; + int count; + int numFaces, numMeshes, numTriSurfs, numFlares; + int i; + + numFaces = 0; + numMeshes = 0; + numTriSurfs = 0; + numFlares = 0; + + in = (void *)(fileBase + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = surfs->filelen / sizeof(*in); + + dv = (void *)(fileBase + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + indexes = (void *)(fileBase + indexLump->fileofs); + if ( indexLump->filelen % sizeof(*indexes)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + out = ri.Hunk_Alloc ( count * sizeof(*out), h_low ); + + s_worldData.surfaces = out; + s_worldData.numsurfaces = count; + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + ParseMesh ( in, dv, out ); + numMeshes++; + break; + case MST_TRIANGLE_SOUP: + ParseTriSurf( in, dv, out, indexes ); + numTriSurfs++; + break; + case MST_PLANAR: + ParseFace( in, dv, out, indexes ); + numFaces++; + break; + case MST_FLARE: + ParseFlare( in, dv, out, indexes ); + numFlares++; + break; + default: + ri.Error( ERR_DROP, "Bad surfaceType" ); + } + } + +#ifdef PATCH_STITCHING + R_StitchAllPatches(); +#endif + + R_FixSharedVertexLodError(); + +#ifdef PATCH_STITCHING + R_MovePatchSurfacesToHunk(); +#endif + + ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n", + numFaces, numMeshes, numTriSurfs, numFlares ); +} + + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( lump_t *l ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = (void *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + + s_worldData.bmodels = out = ri.Hunk_Alloc( count * sizeof(*out), h_low ); + + for ( i=0 ; itype = MOD_BRUSH; + model->bmodel = out; + Com_sprintf( model->name, sizeof( model->name ), "*%d", i ); + + for (j=0 ; j<3 ; j++) { + out->bounds[0][j] = LittleFloat (in->mins[j]); + out->bounds[1][j] = LittleFloat (in->maxs[j]); + } + + out->firstSurface = s_worldData.surfaces + LittleLong( in->firstSurface ); + out->numSurfaces = LittleLong( in->numSurfaces ); + } +} + + + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents != -1) + return; + R_SetParent (node->children[0], node); + R_SetParent (node->children[1], node); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *out; + int numNodes, numLeafs; + + in = (void *)(fileBase + nodeLump->fileofs); + if (nodeLump->filelen % sizeof(dnode_t) || + leafLump->filelen % sizeof(dleaf_t) ) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + numNodes = nodeLump->filelen / sizeof(dnode_t); + numLeafs = leafLump->filelen / sizeof(dleaf_t); + + out = ri.Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), h_low); + + s_worldData.nodes = out; + s_worldData.numnodes = numNodes + numLeafs; + s_worldData.numDecisionNodes = numNodes; + + // load nodes + for ( i=0 ; imins[j] = LittleLong (in->mins[j]); + out->maxs[j] = LittleLong (in->maxs[j]); + } + + p = LittleLong(in->planeNum); + out->plane = s_worldData.planes + p; + + out->contents = CONTENTS_NODE; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = LittleLong (in->children[j]); + if (p >= 0) + out->children[j] = s_worldData.nodes + p; + else + out->children[j] = s_worldData.nodes + numNodes + (-1 - p); + } + } + + // load leafs + inLeaf = (void *)(fileBase + leafLump->fileofs); + for ( i=0 ; imins[j] = LittleLong (inLeaf->mins[j]); + out->maxs[j] = LittleLong (inLeaf->maxs[j]); + } + + out->cluster = LittleLong(inLeaf->cluster); + out->area = LittleLong(inLeaf->area); + + if ( out->cluster >= s_worldData.numClusters ) { + s_worldData.numClusters = out->cluster + 1; + } + + out->firstmarksurface = s_worldData.marksurfaces + + LittleLong(inLeaf->firstLeafSurface); + out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces); + } + + // chain decendants + R_SetParent (s_worldData.nodes, NULL); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +static void R_LoadShaders( lump_t *l ) { + int i, count; + dshader_t *in, *out; + + in = (void *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = ri.Hunk_Alloc ( count*sizeof(*out), h_low ); + + s_worldData.shaders = out; + s_worldData.numShaders = count; + + Com_Memcpy( out, in, count*sizeof(*out) ); + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = ri.Hunk_Alloc ( count*sizeof(*out), h_low); + + s_worldData.marksurfaces = out; + s_worldData.nummarksurfaces = count; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = ri.Hunk_Alloc ( count*2*sizeof(*out), h_low); + + s_worldData.planes = out; + s_worldData.numplanes = count; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) { + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide; + + fogs = (void *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*fogs)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + count = l->filelen / sizeof(*fogs); + + // create fog strucutres for them + s_worldData.numfogs = count + 1; + s_worldData.fogs = ri.Hunk_Alloc ( s_worldData.numfogs*sizeof(*out), h_low); + out = s_worldData.fogs + 1; + + if ( !count ) { + return; + } + + brushes = (void *)(fileBase + brushesLump->fileofs); + if (brushesLump->filelen % sizeof(*brushes)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + brushesCount = brushesLump->filelen / sizeof(*brushes); + + sides = (void *)(fileBase + sidesLump->fileofs); + if (sidesLump->filelen % sizeof(*sides)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + sidesCount = sidesLump->filelen / sizeof(*sides); + + for ( i=0 ; ioriginalBrushNumber = LittleLong( fogs->brushNum ); + + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + ri.Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = LittleLong( brush->firstSide ); + + if ( (unsigned)firstSide > sidesCount - 6 ) { + ri.Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][0] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][1] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][2] = s_worldData.planes[ planeNum ].dist; + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, qtrue ); + + out->parms = shader->fogParms; + + out->colorInt = ColorBytes4 ( shader->fogParms.color[0] * tr.identityLight, + shader->fogParms.color[1] * tr.identityLight, + shader->fogParms.color[2] * tr.identityLight, 1.0 ); + + d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque; + out->tcScale = 1.0f / ( d * 8 ); + + // set the gradient vector + sideNum = LittleLong( fogs->visibleSide ); + + if ( sideNum == -1 ) { + out->hasSurface = qfalse; + } else { + out->hasSurface = qtrue; + planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum ); + VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -s_worldData.planes[ planeNum ].dist; + } + + out++; + } + +} + + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( lump_t *l ) { + int i; + vec3_t maxs; + int numGridPoints; + world_t *w; + float *wMins, *wMaxs; + + w = &s_worldData; + + w->lightGridInverseSize[0] = 1.0f / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0f / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0f / w->lightGridSize[2]; + + wMins = w->bmodels[0].bounds[0]; + wMaxs = w->bmodels[0].bounds[1]; + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1; + } + + numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( l->filelen != numGridPoints * 8 ) { + ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridData = ri.Hunk_Alloc( l->filelen, h_low ); + Com_Memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen ); + + // deal with overbright bits + for ( i = 0 ; i < numGridPoints ; i++ ) { + R_ColorShiftLightingBytes( &w->lightGridData[i*8], &w->lightGridData[i*8] ); + R_ColorShiftLightingBytes( &w->lightGridData[i*8+3], &w->lightGridData[i*8+3] ); + } +} + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( lump_t *l ) { + char *p, *token, *s; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + + w = &s_worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + p = (char *)(fileBase + l->fileofs); + + // store for reference by the cgame + w->entityString = ri.Hunk_Alloc( l->filelen + 1, h_low ); + strcpy( w->entityString, p ); + w->entityParsePoint = w->entityString; + + token = COM_ParseExt( &p, qtrue ); + if (!*token || *token != '{') { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(keyname, token, sizeof(keyname)); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(value, token, sizeof(value)); + + // check for remapping of shaders for vertex lighting + s = "vertexremapshader"; + if (!Q_strncmp(keyname, s, strlen(s)) ) { + s = strchr(value, ';'); + if (!s) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value ); + break; + } + *s++ = 0; + if (r_vertexLight->integer) { + R_RemapShader(value, s, "0"); + } + continue; + } + // check for remapping of shaders + s = "remapshader"; + if (!Q_strncmp(keyname, s, strlen(s)) ) { + s = strchr(value, ';'); + if (!s) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in shaderremap '%s'\n", value ); + break; + } + *s++ = 0; + R_RemapShader(value, s, "0"); + continue; + } + // check for a different grid size + if (!Q_stricmp(keyname, "gridsize")) { + sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + } +} + +/* +================= +R_GetEntityToken +================= +*/ +qboolean R_GetEntityToken( char *buffer, int size ) { + const char *s; + + s = COM_Parse( &s_worldData.entityParsePoint ); + Q_strncpyz( buffer, s, size ); + if ( !s_worldData.entityParsePoint || !s[0] ) { + s_worldData.entityParsePoint = s_worldData.entityString; + return qfalse; + } else { + return qtrue; + } +} + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap( const char *name ) { + int i; + dheader_t *header; + union { + byte *b; + void *v; + } buffer; + byte *startMarker; + + if ( tr.worldMapLoaded ) { + ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" ); + } + + // set default sun direction to be used if it isn't + // overridden by a shader + tr.sunDirection[0] = 0.45f; + tr.sunDirection[1] = 0.3f; + tr.sunDirection[2] = 0.9f; + + VectorNormalize( tr.sunDirection ); + + tr.worldMapLoaded = qtrue; + + // load it + ri.FS_ReadFile( name, &buffer.v ); + if ( !buffer.b ) { + ri.Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name); + } + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + + Com_Memset( &s_worldData, 0, sizeof( s_worldData ) ); + Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); + + Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) ); + COM_StripExtension(s_worldData.baseName, s_worldData.baseName, sizeof(s_worldData.baseName)); + + startMarker = ri.Hunk_Alloc(0, h_low); + c_gridVerts = 0; + + header = (dheader_t *)buffer.b; + fileBase = (byte *)header; + + i = LittleLong (header->version); + if ( i != BSP_VERSION ) { + ri.Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", + name, i, BSP_VERSION); + } + + // swap all the lumps + for (i=0 ; ilumps[LUMP_SHADERS] ); + R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS] ); + R_LoadPlanes (&header->lumps[LUMP_PLANES]); + R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] ); + R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] ); + R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES]); + R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]); + R_LoadSubmodels (&header->lumps[LUMP_MODELS]); + R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] ); + R_LoadEntities( &header->lumps[LUMP_ENTITIES] ); + R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] ); + + s_worldData.dataSize = (byte *)ri.Hunk_Alloc(0, h_low) - startMarker; + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &s_worldData; + + ri.FS_FreeFile( buffer.v ); +} + diff --git a/src/renderer/tr_cmds.c b/src/renderer/tr_cmds.c new file mode 100644 index 0000000..833c0f0 --- /dev/null +++ b/src/renderer/tr_cmds.c @@ -0,0 +1,651 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + +volatile renderCommandList_t *renderCommandList; + +volatile qboolean renderThreadActive; + + +/* +===================== +R_PerformanceCounters +===================== +*/ +void R_PerformanceCounters( void ) { + if ( !r_speeds->integer ) { + // clear the counters even if we aren't printing + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); + return; + } + + if (r_speeds->integer == 1) { + ri.Printf (PRINT_ALL, "%i/%i shaders/surfs %i leafs %i verts %i/%i tris %.2f mtex %.2f dc\n", + backEnd.pc.c_shaders, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes, + backEnd.pc.c_indexes/3, backEnd.pc.c_totalIndexes/3, + R_SumOfUsedImages()/(1000000.0f), backEnd.pc.c_overDraw / (float)(glConfig.vidWidth * glConfig.vidHeight) ); + } else if (r_speeds->integer == 2) { + ri.Printf (PRINT_ALL, "(patch) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out, + tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out ); + ri.Printf (PRINT_ALL, "(md3) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out, + tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out ); + } else if (r_speeds->integer == 3) { + ri.Printf (PRINT_ALL, "viewcluster: %i\n", tr.viewCluster ); + } else if (r_speeds->integer == 4) { + if ( backEnd.pc.c_dlightVertexes ) { + ri.Printf (PRINT_ALL, "dlight srf:%i culled:%i verts:%i tris:%i\n", + tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled, + backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 ); + } + } + else if (r_speeds->integer == 5 ) + { + ri.Printf( PRINT_ALL, "zFar: %.0f\n", tr.viewParms.zFar ); + } + else if (r_speeds->integer == 6 ) + { + ri.Printf( PRINT_ALL, "flare adds:%i tests:%i renders:%i\n", + backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders ); + } + + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + + +/* +==================== +R_InitCommandBuffers +==================== +*/ +void R_InitCommandBuffers( void ) { + glConfig.smpActive = qfalse; + if ( r_smp->integer ) { + ri.Printf( PRINT_ALL, "Trying SMP acceleration...\n" ); + if ( GLimp_SpawnRenderThread( RB_RenderThread ) ) { + ri.Printf( PRINT_ALL, "...succeeded.\n" ); + glConfig.smpActive = qtrue; + } else { + ri.Printf( PRINT_ALL, "...failed.\n" ); + } + } +} + +/* +==================== +R_ShutdownCommandBuffers +==================== +*/ +void R_ShutdownCommandBuffers( void ) { + // kill the rendering thread + if ( glConfig.smpActive ) { + GLimp_WakeRenderer( NULL ); + glConfig.smpActive = qfalse; + } +} + +/* +==================== +R_IssueRenderCommands +==================== +*/ +int c_blockedOnRender; +int c_blockedOnMain; + +void R_IssueRenderCommands( qboolean runPerformanceCounters ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData[tr.smpFrame]->commands; + assert(cmdList); + // add an end-of-list command + *(int *)(cmdList->cmds + cmdList->used) = RC_END_OF_LIST; + + // clear it out, in case this is a sync and not a buffer flip + cmdList->used = 0; + + if ( glConfig.smpActive ) { + // if the render thread is not idle, wait for it + if ( renderThreadActive ) { + c_blockedOnRender++; + if ( r_showSmp->integer ) { + ri.Printf( PRINT_ALL, "R" ); + } + } else { + c_blockedOnMain++; + if ( r_showSmp->integer ) { + ri.Printf( PRINT_ALL, "." ); + } + } + + // sleep until the renderer has completed + GLimp_FrontEndSleep(); + } + + // at this point, the back end thread is idle, so it is ok + // to look at its performance counters + if ( runPerformanceCounters ) { + R_PerformanceCounters(); + } + + // actually start the commands going + if ( !r_skipBackEnd->integer ) { + // let it start on the new batch + if ( !glConfig.smpActive ) { + RB_ExecuteRenderCommands( cmdList->cmds ); + } else { + GLimp_WakeRenderer( cmdList ); + } + } +} + + +/* +==================== +R_SyncRenderThread + +Issue any pending commands and wait for them to complete. +After exiting, the render thread will have completed its work +and will remain idle and the main thread is free to issue +OpenGL calls until R_IssueRenderCommands is called. +==================== +*/ +void R_SyncRenderThread( void ) { + if ( !tr.registered ) { + return; + } + R_IssueRenderCommands( qfalse ); + + if ( !glConfig.smpActive ) { + return; + } + GLimp_FrontEndSleep(); +} + +/* +============ +R_GetCommandBuffer + +make sure there is enough command space, waiting on the +render thread if needed. +============ +*/ +void *R_GetCommandBuffer( int bytes ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData[tr.smpFrame]->commands; + + // always leave room for the end of list command + if ( cmdList->used + bytes + 4 > MAX_RENDER_COMMANDS ) { + if ( bytes > MAX_RENDER_COMMANDS - 4 ) { + ri.Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes ); + } + // if we run out of room, just start dropping commands + return NULL; + } + + cmdList->used += bytes; + + return cmdList->cmds + cmdList->used - bytes; +} + + +/* +============= +R_AddDrawSurfCmd + +============= +*/ +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) { + drawSurfsCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_SURFS; + + cmd->drawSurfs = drawSurfs; + cmd->numDrawSurfs = numDrawSurfs; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; +} + + +/* +============= +RE_SetColor + +Passing NULL will set the color to white +============= +*/ +void RE_SetColor( const float *rgba ) { + setColorCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SET_COLOR; + if ( !rgba ) { + static float colorWhite[4] = { 1, 1, 1, 1 }; + + rgba = colorWhite; + } + + cmd->color[0] = rgba[0]; + cmd->color[1] = rgba[1]; + cmd->color[2] = rgba[2]; + cmd->color[3] = rgba[3]; +} + +/* +============= +R_ClipRegion +============= +*/ +static qboolean R_ClipRegion ( float *x, float *y, float *w, float *h, + float *s1, float *t1, float *s2, float *t2 ) { + float left, top, right, bottom; + float _s1, _t1, _s2, _t2; + float clipLeft, clipTop, clipRight, clipBottom; + + if (tr.clipRegion[2] <= tr.clipRegion[0] || + tr.clipRegion[3] <= tr.clipRegion[1] ) { + return qfalse; + } + + left = *x; + top = *y; + right = *x + *w; + bottom = *y + *h; + + _s1 = *s1; + _t1 = *t1; + _s2 = *s2; + _t2 = *t2; + + clipLeft = tr.clipRegion[0]; + clipTop = tr.clipRegion[1]; + clipRight = tr.clipRegion[2]; + clipBottom = tr.clipRegion[3]; + + // Completely clipped away + if ( right <= clipLeft || left >= clipRight || + bottom <= clipTop || top >= clipBottom ) { + return qtrue; + } + + // Clip left edge + if ( left < clipLeft ) { + float f = ( clipLeft - left ) / ( right - left ); + *s1 = ( f * ( _s2 - _s1 ) ) + _s1; + *x = clipLeft; + *w -= ( clipLeft - left ); + } + + // Clip right edge + if ( right > clipRight ) { + float f = ( clipRight - right ) / ( left - right ); + *s2 = ( f * ( _s1 - _s2 ) ) + _s2; + *w = clipRight - *x; + } + + // Clip top edge + if ( top < clipTop ) { + float f = ( clipTop - top ) / ( bottom - top ); + *t1 = ( f * ( _t2 - _t1 ) ) + _t1; + *y = clipTop; + *h -= ( clipTop - top ); + } + + // Clip bottom edge + if ( bottom > clipBottom ) { + float f = ( clipBottom - bottom ) / ( top - bottom ); + *t2 = ( f * ( _t1 - _t2 ) ) + _t2; + *h = clipBottom - *y; + } + + return qfalse; +} + +/* +============= +RE_SetClipRegion +============= +*/ +void RE_SetClipRegion( const float *region ) { + if ( region == NULL ) { + Com_Memset( tr.clipRegion, 0, sizeof( vec4_t ) ); + } else { + Vector4Copy( region, tr.clipRegion ); + } +} + +/* +============= +RE_StretchPic +============= +*/ +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + stretchPicCommand_t *cmd; + + if (!tr.registered) { + return; + } + if (R_ClipRegion(&x, &y, &w, &h, &s1, &t1, &s2, &t2)) { + return; + } + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_STRETCH_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; +} + +#define MODE_RED_CYAN 1 +#define MODE_RED_BLUE 2 +#define MODE_RED_GREEN 3 +#define MODE_MAX MODE_RED_GREEN + +void R_SetColorMode(GLboolean *rgba, stereoFrame_t stereoFrame, int colormode) +{ + rgba[0] = rgba[1] = rgba[2] = rgba[3] = GL_TRUE; + + if(colormode > MODE_MAX) + { + if(stereoFrame == STEREO_LEFT) + stereoFrame = STEREO_RIGHT; + else if(stereoFrame == STEREO_RIGHT) + stereoFrame = STEREO_LEFT; + + colormode -= MODE_MAX; + } + + if(stereoFrame == STEREO_LEFT) + rgba[1] = rgba[2] = GL_FALSE; + else if(stereoFrame == STEREO_RIGHT) + { + rgba[0] = GL_FALSE; + + if(colormode == MODE_RED_BLUE) + rgba[1] = GL_FALSE; + else if(colormode == MODE_RED_GREEN) + rgba[2] = GL_FALSE; + } +} + + +/* +==================== +RE_BeginFrame + +If running in stereo, RE_BeginFrame will be called twice +for each RE_EndFrame +==================== +*/ +void RE_BeginFrame( stereoFrame_t stereoFrame ) { + drawBufferCommand_t *cmd = NULL; + colorMaskCommand_t *colcmd = NULL; + + if ( !tr.registered ) { + return; + } + glState.finishCalled = qfalse; + + tr.frameCount++; + tr.frameSceneNum = 0; + + // + // do overdraw measurement + // + if ( r_measureOverdraw->integer ) + { + if ( glConfig.stencilBits < 4 ) + { + ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else if ( r_shadows->integer == 2 ) + { + ri.Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else + { + R_SyncRenderThread(); + qglEnable( GL_STENCIL_TEST ); + qglStencilMask( ~0U ); + qglClearStencil( 0U ); + qglStencilFunc( GL_ALWAYS, 0U, ~0U ); + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + r_measureOverdraw->modified = qfalse; + } + else + { + // this is only reached if it was on and is now off + if ( r_measureOverdraw->modified ) { + R_SyncRenderThread(); + qglDisable( GL_STENCIL_TEST ); + } + r_measureOverdraw->modified = qfalse; + } + + // + // texturemode stuff + // + if ( r_textureMode->modified ) { + R_SyncRenderThread(); + GL_TextureMode( r_textureMode->string ); + r_textureMode->modified = qfalse; + } + + // + // gamma stuff + // + if ( r_gamma->modified ) { + r_gamma->modified = qfalse; + + R_SyncRenderThread(); + R_SetColorMappings(); + } + + // check for errors + if ( !r_ignoreGLErrors->integer ) + { + int err; + + R_SyncRenderThread(); + if ((err = qglGetError()) != GL_NO_ERROR) + ri.Error(ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!\n", err); + } + + if (glConfig.stereoEnabled) { + if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) + return; + + cmd->commandId = RC_DRAW_BUFFER; + + if ( stereoFrame == STEREO_LEFT ) { + cmd->buffer = (int)GL_BACK_LEFT; + } else if ( stereoFrame == STEREO_RIGHT ) { + cmd->buffer = (int)GL_BACK_RIGHT; + } else { + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + } + } + else + { + if(r_anaglyphMode->integer) + { + if(r_anaglyphMode->modified) + { + // clear both, front and backbuffer. + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + qglClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + qglDrawBuffer(GL_FRONT); + qglClear(GL_COLOR_BUFFER_BIT); + qglDrawBuffer(GL_BACK); + qglClear(GL_COLOR_BUFFER_BIT); + + r_anaglyphMode->modified = qfalse; + } + + if(stereoFrame == STEREO_LEFT) + { + if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) + return; + + if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) ) + return; + } + else if(stereoFrame == STEREO_RIGHT) + { + clearDepthCommand_t *cldcmd; + + if( !(cldcmd = R_GetCommandBuffer(sizeof(*cldcmd))) ) + return; + + cldcmd->commandId = RC_CLEARDEPTH; + + if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) ) + return; + } + else + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + + R_SetColorMode(colcmd->rgba, stereoFrame, r_anaglyphMode->integer); + colcmd->commandId = RC_COLORMASK; + } + else + { + if(stereoFrame != STEREO_CENTER) + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); + + if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) + return; + } + + if(cmd) + { + cmd->commandId = RC_DRAW_BUFFER; + + if(r_anaglyphMode->modified) + { + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + r_anaglyphMode->modified = qfalse; + } + + if (!Q_stricmp(r_drawBuffer->string, "GL_FRONT")) + cmd->buffer = (int)GL_FRONT; + else + cmd->buffer = (int)GL_BACK; + } + } + + tr.refdef.stereoFrame = stereoFrame; +} + + +/* +============= +RE_EndFrame + +Returns the number of msec spent in the back end +============= +*/ +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) { + swapBuffersCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SWAP_BUFFERS; + + R_IssueRenderCommands( qtrue ); + + // use the other buffers next frame, because another CPU + // may still be rendering into the current ones + R_ToggleSmpFrame(); + + if ( frontEndMsec ) { + *frontEndMsec = tr.frontEndMsec; + } + tr.frontEndMsec = 0; + if ( backEndMsec ) { + *backEndMsec = backEnd.pc.msec; + } + backEnd.pc.msec = 0; +} + +/* +============= +RE_TakeVideoFrame +============= +*/ +void RE_TakeVideoFrame( int width, int height, + byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg ) +{ + videoFrameCommand_t *cmd; + + if( !tr.registered ) { + return; + } + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if( !cmd ) { + return; + } + + cmd->commandId = RC_VIDEOFRAME; + + cmd->width = width; + cmd->height = height; + cmd->captureBuffer = captureBuffer; + cmd->encodeBuffer = encodeBuffer; + cmd->motionJpeg = motionJpeg; +} diff --git a/src/renderer/tr_curve.c b/src/renderer/tr_curve.c new file mode 100644 index 0000000..00cb2ed --- /dev/null +++ b/src/renderer/tr_curve.c @@ -0,0 +1,627 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +/* + +This file does all of the processing necessary to turn a raw grid of points +read from the map file into a srfGridMesh_t ready for rendering. + +The level of detail solution is direction independent, based only on subdivided +distance from the true curve. + +Only a single entry point: + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + +*/ + + +/* +============ +LerpDrawVert +============ +*/ +static void LerpDrawVert( drawVert_t *a, drawVert_t *b, drawVert_t *out ) { + out->xyz[0] = 0.5f * (a->xyz[0] + b->xyz[0]); + out->xyz[1] = 0.5f * (a->xyz[1] + b->xyz[1]); + out->xyz[2] = 0.5f * (a->xyz[2] + b->xyz[2]); + + out->st[0] = 0.5f * (a->st[0] + b->st[0]); + out->st[1] = 0.5f * (a->st[1] + b->st[1]); + + out->lightmap[0] = 0.5f * (a->lightmap[0] + b->lightmap[0]); + out->lightmap[1] = 0.5f * (a->lightmap[1] + b->lightmap[1]); + + out->color[0] = (a->color[0] + b->color[0]) >> 1; + out->color[1] = (a->color[1] + b->color[1]) >> 1; + out->color[2] = (a->color[2] + b->color[2]) >> 1; + out->color[3] = (a->color[3] + b->color[3]) >> 1; +} + +/* +============ +Transpose +============ +*/ +static void Transpose( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j][i]; + ctrl[j][i] = ctrl[i][j]; + ctrl[i][j] = temp; + } else { + // just copy + ctrl[j][i] = ctrl[i][j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[j][i]; + ctrl[j][i] = temp; + } else { + // just copy + ctrl[i][j] = ctrl[j][i]; + } + } + } + } + +} + + +/* +================= +MakeMeshNormals + +Handles all the complicated wrapping and degenerate cases +================= +*/ +static void MakeMeshNormals( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count = 0; + vec3_t base; + vec3_t delta; + int x, y; + drawVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; +static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = qfalse; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j][i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y][x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + if ( count == 0 ) { +//printf("bad normal\n"); + count = 1; + } + VectorNormalize2( sum, dv->normal ); + } + } +} + + +/* +============ +InvertCtrl +============ +*/ +static void InvertCtrl( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width/2 ; j++ ) { + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[i][width-1-j]; + ctrl[i][width-1-j] = temp; + } + } +} + + +/* +================= +InvertErrorTable +================= +*/ +static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + Com_Memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1][i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0][i] = copy[1][height-1-i]; + } + +} + +/* +================== +PutPointsOnCurve +================== +*/ +static void PutPointsOnCurve( drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], + int width, int height ) { + int i, j; + drawVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } +} + +/* +================= +R_CreateSurfaceGridMesh +================= +*/ +srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height, + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE] ) { + int i, j, size; + drawVert_t *vert; + vec3_t tmpVec; + srfGridMesh_t *grid; + + // copy the results out to a grid + size = (width * height - 1) * sizeof( drawVert_t ) + sizeof( *grid ); + +#ifdef PATCH_STITCHING + grid = /*ri.Hunk_Alloc*/ ri.Malloc( size ); + Com_Memset(grid, 0, size); + + grid->widthLodError = /*ri.Hunk_Alloc*/ ri.Malloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = /*ri.Hunk_Alloc*/ ri.Malloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#else + grid = ri.Hunk_Alloc( size ); + Com_Memset(grid, 0, size); + + grid->widthLodError = ri.Hunk_Alloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = ri.Hunk_Alloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#endif + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j][i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + // + return grid; +} + +/* +================= +R_FreeSurfaceGridMesh +================= +*/ +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) { + ri.Free(grid->widthLodError); + ri.Free(grid->heightLodError); + ri.Free(grid); +} + +/* +================= +R_SubdividePatchToGrid +================= +*/ +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + int i, j, k, l; + drawVert_t_cleared( prev ); + drawVert_t_cleared( next ); + drawVert_t_cleared( mid ); + float len, maxLen; + int dir; + int t; + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j][i] = points[j*width+i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir][j] = 0; + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < width ; j += 2 ) { + // check subdivided midpoints against control points + + // FIXME: also check midpoints of adjacent patches against the control points + // this would basically stitch all patches in the same LOD group together. + + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t midxyz2; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2 + + ctrl[i][j+2].xyz[l] ) * 0.25f; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz ); + VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz2); + len = VectorLengthSquared( midxyz2 ); // we will do the sqrt later + if ( len > maxLen ) { + maxLen = len; + } + } + + maxLen = sqrt(maxLen); + + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1f ) { + errorTable[dir][j+1] = 999; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir][j+1] = 1.0f/maxLen; + continue; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir][j+1] = 1.0f/maxLen; + continue; // didn't need subdivision + } + + errorTable[dir][j+2] = 1.0f/maxLen; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev ); + LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i][k] = ctrl[i][k-2]; + } + ctrl[i][j + 1] = prev; + ctrl[i][j + 2] = mid; + ctrl[i][j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width-1 ; i++ ) { + if ( errorTable[0][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k][j-1] = ctrl[k][j]; + } + errorTable[0][j-1] = errorTable[0][j]; + } + width--; + } + + for ( i = 1 ; i < height-1 ; i++ ) { + if ( errorTable[1][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[j-1][k] = ctrl[j][k]; + } + errorTable[1][j-1] = errorTable[1][j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + return R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); +} + +/* +=============== +R_GridInsertColumn +=============== +*/ +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ) { + int i, j; + int width, height, oldwidth; + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + + oldwidth = 0; + width = grid->width + 1; + if (width > MAX_GRID_SIZE) + return NULL; + height = grid->height; + for (i = 0; i < width; i++) { + if (i == column) { + //insert new column + for (j = 0; j < grid->height; j++) { + LerpDrawVert( &grid->verts[j * grid->width + i-1], &grid->verts[j * grid->width + i], &ctrl[j][i] ); + if (j == row) + VectorCopy(point, ctrl[j][i].xyz); + } + errorTable[0][i] = loderror; + continue; + } + errorTable[0][i] = grid->widthLodError[oldwidth]; + for (j = 0; j < grid->height; j++) { + ctrl[j][i] = grid->verts[j * grid->width + oldwidth]; + } + oldwidth++; + } + for (j = 0; j < grid->height; j++) { + errorTable[1][j] = grid->heightLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh(grid); + // create a new grid + grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); + return grid; +} + +/* +=============== +R_GridInsertRow +=============== +*/ +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ) { + int i, j; + int width, height, oldheight; + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + + oldheight = 0; + width = grid->width; + height = grid->height + 1; + if (height > MAX_GRID_SIZE) + return NULL; + for (i = 0; i < height; i++) { + if (i == row) { + //insert new row + for (j = 0; j < grid->width; j++) { + LerpDrawVert( &grid->verts[(i-1) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] ); + if (j == column) + VectorCopy(point, ctrl[i][j].xyz); + } + errorTable[1][i] = loderror; + continue; + } + errorTable[1][i] = grid->heightLodError[oldheight]; + for (j = 0; j < grid->width; j++) { + ctrl[i][j] = grid->verts[oldheight * grid->width + j]; + } + oldheight++; + } + for (j = 0; j < grid->width; j++) { + errorTable[0][j] = grid->widthLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh(grid); + // create a new grid + grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); + return grid; +} diff --git a/src/renderer/tr_flares.c b/src/renderer/tr_flares.c new file mode 100644 index 0000000..afef22a --- /dev/null +++ b/src/renderer/tr_flares.c @@ -0,0 +1,530 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_flares.c + +#include "tr_local.h" + +/* +============================================================================= + +LIGHT FLARES + +A light flare is an effect that takes place inside the eye when bright light +sources are visible. The size of the flare reletive to the screen is nearly +constant, irrespective of distance, but the intensity should be proportional to the +projected area of the light source. + +A surface that has been flagged as having a light flare will calculate the depth +buffer value that its midpoint should have when the surface is added. + +After all opaque surfaces have been rendered, the depth buffer is read back for +each flare in view. If the point has not been obscured by a closer surface, the +flare should be drawn. + +Surfaces that have a repeated texture should never be flagged as flaring, because +there will only be a single flare added at the midpoint of the polygon. + +To prevent abrupt popping, the intensity of the flare is interpolated up and +down as it changes visibility. This involves scene to scene state, unlike almost +all other aspects of the renderer, and is complicated by the fact that a single +frame may have multiple scenes. + +RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially +up to five or more times in a frame with 3D status bar icons). + +============================================================================= +*/ + + +// flare states maintain visibility over multiple frames for fading +// layers: view, mirror, menu +typedef struct flare_s { + struct flare_s *next; // for active chain + + int addedFrame; + + qboolean inPortal; // true if in a portal view of the scene + int frameSceneNum; + void *surface; + int fogNum; + + int fadeTime; + + qboolean visible; // state of last test + float drawIntensity; // may be non 0 even if !visible due to fading + + int windowX, windowY; + float eyeZ; + + vec3_t origin; + vec3_t color; +} flare_t; + +#define MAX_FLARES 128 + +flare_t r_flareStructs[MAX_FLARES]; +flare_t *r_activeFlares, *r_inactiveFlares; + +int flareCoeff; + +/* +================== +R_ClearFlares +================== +*/ +void R_ClearFlares( void ) { + int i; + + Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) ); + r_activeFlares = NULL; + r_inactiveFlares = NULL; + + for ( i = 0 ; i < MAX_FLARES ; i++ ) { + r_flareStructs[i].next = r_inactiveFlares; + r_inactiveFlares = &r_flareStructs[i]; + } +} + + +/* +================== +RB_AddFlare + +This is called at surface tesselation time +================== +*/ +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) { + int i; + flare_t *f, *oldest; + vec3_t local; + float d = 1; + vec4_t eye, clip, normalized, window; + + backEnd.pc.c_flareAdds++; + + if(normal && (normal[0] || normal[1] || normal[2])) + { + VectorSubtract( backEnd.viewParms.or.origin, point, local ); + VectorNormalizeFast(local); + d = DotProduct(local, normal); + + // If the viewer is behind the flare don't add it. + if(d < 0) + return; + } + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.or.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + + // see if a flare with a matching surface, scene, and view exists + oldest = r_flareStructs; + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + break; + } + } + + // allocate a new one + if (!f ) { + if ( !r_inactiveFlares ) { + // the list is completely full + return; + } + f = r_inactiveFlares; + r_inactiveFlares = r_inactiveFlares->next; + f->next = r_activeFlares; + r_activeFlares = f; + + f->surface = surface; + f->frameSceneNum = backEnd.viewParms.frameSceneNum; + f->inPortal = backEnd.viewParms.isPortal; + f->addedFrame = -1; + } + + if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 2000; + } + + f->addedFrame = backEnd.viewParms.frameCount; + f->fogNum = fogNum; + + VectorCopy(point, f->origin); + VectorCopy( color, f->color ); + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + VectorScale( f->color, d, f->color ); + + // save info needed to test + f->windowX = backEnd.viewParms.viewportX + window[0]; + f->windowY = backEnd.viewParms.viewportY + window[1]; + + f->eyeZ = eye[2]; +} + +/* +================== +RB_AddDlightFlares +================== +*/ +void RB_AddDlightFlares( void ) { + dlight_t *l; + int i, j, k; + fog_t *fog = NULL; + + if ( !r_flares->integer ) { + return; + } + + l = backEnd.refdef.dlights; + + if(tr.world) + fog = tr.world->fogs; + + for (i=0 ; inumfogs ; j++ ) { + fog = &tr.world->fogs[j]; + for ( k = 0 ; k < 3 ; k++ ) { + if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j == tr.world->numfogs ) { + j = 0; + } + } + else + j = 0; + + RB_AddFlare( (void *)l, j, l->origin, l->color, NULL ); + } +} + +/* +=============================================================================== + +FLARE BACK END + +=============================================================================== +*/ + +/* +================== +RB_TestFlare +================== +*/ +void RB_TestFlare( flare_t *f ) { + float depth; + qboolean visible; + float fade; + float screenZ; + + backEnd.pc.c_flareTests++; + + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync + glState.finishCalled = qfalse; + + // read back the z buffer contents + qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -f->eyeZ - -screenZ ) < 24; + + if ( visible ) { + if ( !f->visible ) { + f->visible = qtrue; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value; + } else { + if ( f->visible ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value; + } + + if ( fade < 0 ) { + fade = 0; + } + if ( fade > 1 ) { + fade = 1; + } + + f->drawIntensity = fade; +} + + +/* +================== +RB_RenderFlare +================== +*/ +void RB_RenderFlare( flare_t *f ) { + float size; + vec3_t color; + int iColor[3]; + float distance, intensity, factor; + byte fogFactors[3] = {255, 255, 255}; + + backEnd.pc.c_flareRenders++; + + // We don't want too big values anyways when dividing by distance. + if(f->eyeZ > -1.0f) + distance = 1.0f; + else + distance = -f->eyeZ; + + // calculate the flare size.. + size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / distance ); + +/* + * This is an alternative to intensity scaling. It changes the size of the flare on screen instead + * with growing distance. See in the description at the top why this is not the way to go. + // size will change ~ 1/r. + size = backEnd.viewParms.viewportWidth * (r_flareSize->value / (distance * -2.0f)); +*/ + +/* + * As flare sizes stay nearly constant with increasing distance we must decrease the intensity + * to achieve a reasonable visual result. The intensity is ~ (size^2 / distance^2) which can be + * got by considering the ratio of + * (flaresurface on screen) : (Surface of sphere defined by flare origin and distance from flare) + * An important requirement is: + * intensity <= 1 for all distances. + * + * The formula used here to compute the intensity is as follows: + * intensity = flareCoeff * size^2 / (distance + size*sqrt(flareCoeff))^2 + * As you can see, the intensity will have a max. of 1 when the distance is 0. + * The coefficient flareCoeff will determine the falloff speed with increasing distance. + */ + + factor = distance + size * sqrt(flareCoeff); + + intensity = flareCoeff * size * size / (factor * factor); + + VectorScale(f->color, f->drawIntensity * intensity, color); + +// Calculations for fogging + if(tr.world && f->fogNum < tr.world->numfogs) + { + tess.numVertexes = 1; + VectorCopy(f->origin, tess.xyz[0]); + tess.fogNum = f->fogNum; + + RB_CalcModulateColorsByFog(fogFactors); + + // We don't need to render the flare if colors are 0 anyways. + if(!(fogFactors[0] || fogFactors[1] || fogFactors[2])) + return; + } + + iColor[0] = color[0] * fogFactors[0]; + iColor[1] = color[1] * fogFactors[1]; + iColor[2] = color[2] * fogFactors[2]; + + RB_BeginSurface( tr.flareShader, f->fogNum ); + + // FIXME: use quadstamp? + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); +} + +/* +================== +RB_RenderFlares + +Because flares are simulating an occular effect, they should be drawn after +everything (all views) in the entire frame has been drawn. + +Because of the way portals use the depth buffer to mark off areas, the +needed information would be lost after each view, so we are forced to draw +flares after each view. + +The resulting artifact is that flares in mirrors or portals don't dim properly +when occluded by something in the main view, and portal flares that should +extend past the portal edge will be overwritten. +================== +*/ +void RB_RenderFlares (void) { + flare_t *f; + flare_t **prev; + qboolean draw; + + if ( !r_flares->integer ) { + return; + } + + if(r_flareCoeff->modified) + { + if(r_flareCoeff->value == 0.0f) + flareCoeff = atof(FLARE_STDCOEFF); + else + flareCoeff = r_flareCoeff->value; + + r_flareCoeff->modified = qfalse; + } + + // Reset currentEntity to world so that any previously referenced entities + // don't have influence on the rendering of these flares (i.e. RF_ renderer flags). + backEnd.currentEntity = &tr.worldEntity; + backEnd.or = backEnd.viewParms.world; + +// RB_AddDlightFlares(); + + // perform z buffer readback on each flare in this view + draw = qfalse; + prev = &r_activeFlares; + while ( ( f = *prev ) != NULL ) { + // throw out any flares that weren't added last frame + if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) { + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + + // don't draw any here that aren't from this scene / portal + f->drawIntensity = 0; + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + RB_TestFlare( f ); + if ( f->drawIntensity ) { + draw = qtrue; + } else { + // this flare has completely faded out, so remove it from the chain + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + } + + prev = &f->next; + } + + if ( !draw ) { + return; // none visible + } + + if ( backEnd.viewParms.isPortal ) { + qglDisable (GL_CLIP_PLANE0); + } + + qglPushMatrix(); + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, + backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, + -99999, 99999 ); + + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal + && f->drawIntensity ) { + RB_RenderFlare( f ); + } + } + + qglPopMatrix(); + qglMatrixMode( GL_MODELVIEW ); + qglPopMatrix(); +} + diff --git a/src/renderer/tr_font.c b/src/renderer/tr_font.c new file mode 100644 index 0000000..3fe80d0 --- /dev/null +++ b/src/renderer/tr_font.c @@ -0,0 +1,550 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_font.c +// +// +// The font system uses FreeType 2.x to render TrueType fonts for use within the game. +// As of this writing ( Nov, 2000 ) Team Arena uses these fonts for all of the ui and +// about 90% of the cgame presentation. A few areas of the CGAME were left uses the old +// fonts since the code is shared with standard Q3A. +// +// If you include this font rendering code in a commercial product you MUST include the +// following somewhere with your product, see www.freetype.org for specifics or changes. +// The Freetype code also uses some hinting techniques that MIGHT infringe on patents +// held by apple so be aware of that also. +// +// As of Q3A 1.25+ and Team Arena, we are shipping the game with the font rendering code +// disabled. This removes any potential patent issues and it keeps us from having to +// distribute an actual TrueTrype font which is 1. expensive to do and 2. seems to require +// an act of god to accomplish. +// +// What we did was pre-render the fonts using FreeType ( which is why we leave the FreeType +// credit in the credits ) and then saved off the glyph data and then hand touched up the +// font bitmaps so they scale a bit better in GL. +// +// There are limitations in the way fonts are saved and reloaded in that it is based on +// point size and not name. So if you pre-render Helvetica in 18 point and Impact in 18 point +// you will end up with a single 18 point data file and image set. Typically you will want to +// choose 3 sizes to best approximate the scaling you will be doing in the ui scripting system +// +// In the UI Scripting code, a scale of 1.0 is equal to a 48 point font. In Team Arena, we +// use three or four scales, most of them exactly equaling the specific rendered size. We +// rendered three sizes in Team Arena, 12, 16, and 20. +// +// To generate new font data you need to go through the following steps. +// 1. delete the fontImage_x_xx.tga files and fontImage_xx.dat files from the fonts path. +// 2. in a ui script, specificy a font, smallFont, and bigFont keyword with font name and +// point size. the original TrueType fonts must exist in fonts at this point. +// 3. run the game, you should see things normally. +// 4. Exit the game and there will be three dat files and at least three tga files. The +// tga's are in 256x256 pages so if it takes three images to render a 24 point font you +// will end up with fontImage_0_24.tga through fontImage_2_24.tga +// 5. You will need to flip the tga's in Photoshop as the tga output code writes them upside +// down. +// 6. In future runs of the game, the system looks for these images and data files when a s +// specific point sized font is rendered and loads them for use. +// 7. Because of the original beta nature of the FreeType code you will probably want to hand +// touch the font bitmaps. +// +// Currently a define in the project turns on or off the FreeType code which is currently +// defined out. To pre-render new fonts you need enable the define ( BUILD_FREETYPE ) and +// uncheck the exclude from build check box in the FreeType2 area of the Renderer project. + + +#include "tr_local.h" +#include "../qcommon/qcommon.h" + +#ifdef BUILD_FREETYPE +#include +#include +#include +#include +#include +#include + +#define _FLOOR(x) ((x) & -64) +#define _CEIL(x) (((x)+63) & -64) +#define _TRUNC(x) ((x) >> 6) + +FT_Library ftLibrary = NULL; +#endif + +#define MAX_FONTS 6 +static int registeredFontCount = 0; +static fontInfo_t registeredFont[MAX_FONTS]; + +#ifdef BUILD_FREETYPE +void R_GetGlyphInfo(FT_GlyphSlot glyph, int *left, int *right, int *width, int *top, int *bottom, int *height, int *pitch) { + + *left = _FLOOR( glyph->metrics.horiBearingX ); + *right = _CEIL( glyph->metrics.horiBearingX + glyph->metrics.width ); + *width = _TRUNC(*right - *left); + + *top = _CEIL( glyph->metrics.horiBearingY ); + *bottom = _FLOOR( glyph->metrics.horiBearingY - glyph->metrics.height ); + *height = _TRUNC( *top - *bottom ); + *pitch = ( qtrue ? (*width+3) & -4 : (*width+7) >> 3 ); +} + + +FT_Bitmap *R_RenderGlyph(FT_GlyphSlot glyph, glyphInfo_t* glyphOut) { + + FT_Bitmap *bit2; + int left, right, width, top, bottom, height, pitch, size; + + R_GetGlyphInfo(glyph, &left, &right, &width, &top, &bottom, &height, &pitch); + + if ( glyph->format == ft_glyph_format_outline ) { + size = pitch*height; + + bit2 = Z_Malloc(sizeof(FT_Bitmap)); + + bit2->width = width; + bit2->rows = height; + bit2->pitch = pitch; + bit2->pixel_mode = ft_pixel_mode_grays; + //bit2->pixel_mode = ft_pixel_mode_mono; + bit2->buffer = Z_Malloc(pitch*height); + bit2->num_grays = 256; + + Com_Memset( bit2->buffer, 0, size ); + + FT_Outline_Translate( &glyph->outline, -left, -bottom ); + + FT_Outline_Get_Bitmap( ftLibrary, &glyph->outline, bit2 ); + + glyphOut->height = height; + glyphOut->pitch = pitch; + glyphOut->top = (glyph->metrics.horiBearingY >> 6) + 1; + glyphOut->bottom = bottom; + + return bit2; + } + else { + ri.Printf(PRINT_ALL, "Non-outline fonts are not supported\n"); + } + return NULL; +} + +void WriteTGA (char *filename, byte *data, int width, int height) { + byte *buffer; + int i, c; + + buffer = Z_Malloc(width*height*4 + 18); + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width&255; + buffer[13] = width>>8; + buffer[14] = height&255; + buffer[15] = height>>8; + buffer[16] = 32; // pixel size + + // swap rgb to bgr + c = 18 + width * height * 4; + for (i=18 ; iglyph, &glyph); + if (bitmap) { + glyph.xSkip = (face->glyph->metrics.horiAdvance >> 6) + 1; + } else { + return &glyph; + } + + if (glyph.height > *maxHeight) { + *maxHeight = glyph.height; + } + + if (calcHeight) { + Z_Free(bitmap->buffer); + Z_Free(bitmap); + return &glyph; + } + +/* + // need to convert to power of 2 sizes so we do not get + // any scaling from the gl upload + for (scaled_width = 1 ; scaled_width < glyph.pitch ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < glyph.height ; scaled_height<<=1) + ; +*/ + + scaled_width = glyph.pitch; + scaled_height = glyph.height; + + // we need to make sure we fit + if (*xOut + scaled_width + 1 >= 255) { + if (*yOut + *maxHeight + 1 >= 255) { + *yOut = -1; + *xOut = -1; + Z_Free(bitmap->buffer); + Z_Free(bitmap); + return &glyph; + } else { + *xOut = 0; + *yOut += *maxHeight + 1; + } + } else if (*yOut + *maxHeight + 1 >= 255) { + *yOut = -1; + *xOut = -1; + Z_Free(bitmap->buffer); + Z_Free(bitmap); + return &glyph; + } + + + src = bitmap->buffer; + dst = imageOut + (*yOut * 256) + *xOut; + + if (bitmap->pixel_mode == ft_pixel_mode_mono) { + for (i = 0; i < glyph.height; i++) { + int j; + unsigned char *_src = src; + unsigned char *_dst = dst; + unsigned char mask = 0x80; + unsigned char val = *_src; + for (j = 0; j < glyph.pitch; j++) { + if (mask == 0x80) { + val = *_src++; + } + if (val & mask) { + *_dst = 0xff; + } + mask >>= 1; + + if ( mask == 0 ) { + mask = 0x80; + } + _dst++; + } + + src += glyph.pitch; + dst += 256; + + } + } else { + for (i = 0; i < glyph.height; i++) { + Com_Memcpy(dst, src, glyph.pitch); + src += glyph.pitch; + dst += 256; + } + } + + // we now have an 8 bit per pixel grey scale bitmap + // that is width wide and pf->ftSize->metrics.y_ppem tall + + glyph.imageHeight = scaled_height; + glyph.imageWidth = scaled_width; + glyph.s = (float)*xOut / 256; + glyph.t = (float)*yOut / 256; + glyph.s2 = glyph.s + (float)scaled_width / 256; + glyph.t2 = glyph.t + (float)scaled_height / 256; + + *xOut += scaled_width + 1; + } + + Z_Free(bitmap->buffer); + Z_Free(bitmap); + + return &glyph; +} +#endif + +static int fdOffset; +static byte *fdFile; + +int readInt( void ) { + int i = fdFile[fdOffset]+(fdFile[fdOffset+1]<<8)+(fdFile[fdOffset+2]<<16)+(fdFile[fdOffset+3]<<24); + fdOffset += 4; + return i; +} + +typedef union { + byte fred[4]; + float ffred; +} poor; + +float readFloat( void ) { + poor me; +#if defined Q3_BIG_ENDIAN + me.fred[0] = fdFile[fdOffset+3]; + me.fred[1] = fdFile[fdOffset+2]; + me.fred[2] = fdFile[fdOffset+1]; + me.fred[3] = fdFile[fdOffset+0]; +#elif defined Q3_LITTLE_ENDIAN + me.fred[0] = fdFile[fdOffset+0]; + me.fred[1] = fdFile[fdOffset+1]; + me.fred[2] = fdFile[fdOffset+2]; + me.fred[3] = fdFile[fdOffset+3]; +#endif + fdOffset += 4; + return me.ffred; +} + +void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { +#ifdef BUILD_FREETYPE + FT_Face face; + int j, k, xOut, yOut, lastStart, imageNumber; + int scaledSize, newSize, maxHeight, left, satLevels; + unsigned char *out, *imageBuff; + glyphInfo_t *glyph; + image_t *image; + qhandle_t h; + float max; +#endif + void *faceData; + int i, len; + char name[1024]; + float dpi = 72; // + float glyphScale = 72.0f / dpi; // change the scale to be relative to 1 based on 72 dpi ( so dpi of 144 means a scale of .5 ) + + + if (!fontName) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: called with empty name\n"); + return; + } + + if (pointSize <= 0) { + pointSize = 12; + } + // we also need to adjust the scale based on point size relative to 48 points as the ui scaling is based on a 48 point font + glyphScale *= 48.0f / pointSize; + + // make sure the render thread is stopped + R_SyncRenderThread(); + + if (registeredFontCount >= MAX_FONTS) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: Too many fonts registered already.\n"); + return; + } + + Com_sprintf(name, sizeof(name), "fonts/fontImage_%i.dat",pointSize); + for (i = 0; i < registeredFontCount; i++) { + if (Q_stricmp(name, registeredFont[i].name) == 0) { + Com_Memcpy(font, ®isteredFont[i], sizeof(fontInfo_t)); + return; + } + } + + len = ri.FS_ReadFile(name, NULL); + if (len == sizeof(fontInfo_t)) { + ri.FS_ReadFile(name, &faceData); + fdOffset = 0; + fdFile = faceData; + for(i=0; iglyphs[i].height = readInt(); + font->glyphs[i].top = readInt(); + font->glyphs[i].bottom = readInt(); + font->glyphs[i].pitch = readInt(); + font->glyphs[i].xSkip = readInt(); + font->glyphs[i].imageWidth = readInt(); + font->glyphs[i].imageHeight = readInt(); + font->glyphs[i].s = readFloat(); + font->glyphs[i].t = readFloat(); + font->glyphs[i].s2 = readFloat(); + font->glyphs[i].t2 = readFloat(); + font->glyphs[i].glyph = readInt(); + Com_Memcpy(font->glyphs[i].shaderName, &fdFile[fdOffset], 32); + fdOffset += 32; + } + font->glyphScale = readFloat(); + Com_Memcpy(font->name, &fdFile[fdOffset], MAX_QPATH); + +// Com_Memcpy(font, faceData, sizeof(fontInfo_t)); + Q_strncpyz(font->name, name, sizeof(font->name)); + for (i = GLYPH_START; i < GLYPH_END; i++) { + font->glyphs[i].glyph = RE_RegisterShaderNoMip(font->glyphs[i].shaderName); + } + Com_Memcpy(®isteredFont[registeredFontCount++], font, sizeof(fontInfo_t)); + return; + } + +#ifndef BUILD_FREETYPE + ri.Printf(PRINT_ALL, "RE_RegisterFont: FreeType code not available\n"); +#else + if (ftLibrary == NULL) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: FreeType not initialized.\n"); + return; + } + + len = ri.FS_ReadFile(fontName, &faceData); + if (len <= 0) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: Unable to read font file\n"); + return; + } + + // allocate on the stack first in case we fail + if (FT_New_Memory_Face( ftLibrary, faceData, len, 0, &face )) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: FreeType2, unable to allocate new face.\n"); + return; + } + + + if (FT_Set_Char_Size( face, pointSize << 6, pointSize << 6, dpi, dpi)) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: FreeType2, Unable to set face char size.\n"); + return; + } + + //*font = ®isteredFonts[registeredFontCount++]; + + // make a 256x256 image buffer, once it is full, register it, clean it and keep going + // until all glyphs are rendered + + out = Z_Malloc(1024*1024); + if (out == NULL) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: Z_Malloc failure during output image creation.\n"); + return; + } + Com_Memset(out, 0, 1024*1024); + + maxHeight = 0; + + for (i = GLYPH_START; i < GLYPH_END; i++) { + glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qtrue); + } + + xOut = 0; + yOut = 0; + i = GLYPH_START; + lastStart = i; + imageNumber = 0; + + while ( i <= GLYPH_END ) { + + glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qfalse); + + if (xOut == -1 || yOut == -1 || i == GLYPH_END) { + // ran out of room + // we need to create an image from the bitmap, set all the handles in the glyphs to this point + // + + scaledSize = 256*256; + newSize = scaledSize * 4; + imageBuff = Z_Malloc(newSize); + left = 0; + max = 0; + satLevels = 255; + for ( k = 0; k < (scaledSize) ; k++ ) { + if (max < out[k]) { + max = out[k]; + } + } + + if (max > 0) { + max = 255/max; + } + + for ( k = 0; k < (scaledSize) ; k++ ) { + imageBuff[left++] = 255; + imageBuff[left++] = 255; + imageBuff[left++] = 255; + + imageBuff[left++] = ((float)out[k] * max); + } + + Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i.tga", imageNumber++, pointSize); + if (r_saveFontData->integer) { + WriteTGA(name, imageBuff, 256, 256); + } + + //Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i", imageNumber++, pointSize); + image = R_CreateImage(name, imageBuff, 256, 256, qfalse, qfalse, GL_CLAMP_TO_EDGE); + h = RE_RegisterShaderFromImage(name, LIGHTMAP_2D, image, qfalse); + for (j = lastStart; j < i; j++) { + font->glyphs[j].glyph = h; + Q_strncpyz(font->glyphs[j].shaderName, name, sizeof(font->glyphs[j].shaderName)); + } + lastStart = i; + Com_Memset(out, 0, 1024*1024); + xOut = 0; + yOut = 0; + Z_Free(imageBuff); + i++; + } else { + Com_Memcpy(&font->glyphs[i], glyph, sizeof(glyphInfo_t)); + i++; + } + } + + registeredFont[registeredFontCount].glyphScale = glyphScale; + font->glyphScale = glyphScale; + Com_Memcpy(®isteredFont[registeredFontCount++], font, sizeof(fontInfo_t)); + + if (r_saveFontData->integer) { + ri.FS_WriteFile(va("fonts/fontImage_%i.dat", pointSize), font, sizeof(fontInfo_t)); + } + + Z_Free(out); + + ri.FS_FreeFile(faceData); +#endif +} + + + +void R_InitFreeType(void) { +#ifdef BUILD_FREETYPE + if (FT_Init_FreeType( &ftLibrary )) { + ri.Printf(PRINT_ALL, "R_InitFreeType: Unable to initialize FreeType.\n"); + } +#endif + registeredFontCount = 0; +} + + +void R_DoneFreeType(void) { +#ifdef BUILD_FREETYPE + if (ftLibrary) { + FT_Done_FreeType( ftLibrary ); + ftLibrary = NULL; + } +#endif + registeredFontCount = 0; +} + diff --git a/src/renderer/tr_image.c b/src/renderer/tr_image.c new file mode 100644 index 0000000..1fa8042 --- /dev/null +++ b/src/renderer/tr_image.c @@ -0,0 +1,1607 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_image.c +#include "tr_local.h" + +static byte s_intensitytable[256]; +static unsigned char s_gammatable[256]; + +int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; +int gl_filter_max = GL_LINEAR; + +#define FILE_HASH_SIZE 1024 +static image_t* hashTable[FILE_HASH_SIZE]; + +/* +** R_GammaCorrect +*/ +void R_GammaCorrect( byte *buffer, int bufSize ) { + int i; + + for ( i = 0; i < bufSize; i++ ) { + buffer[i] = s_gammatable[buffer[i]]; + } +} + +typedef struct { + char *name; + int minimize, maximize; +} textureMode_t; + +textureMode_t modes[] = { + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} +}; + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +=============== +GL_TextureMode +=============== +*/ +void GL_TextureMode( const char *string ) { + int i; + image_t *glt; + + for ( i=0 ; i< 6 ; i++ ) { + if ( !Q_stricmp( modes[i].name, string ) ) { + break; + } + } + + // hack to prevent trilinear from being set on voodoo, + // because their driver freaks... + if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D ) { + ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" ); + i = 3; + } + + + if ( i == 6 ) { + ri.Printf (PRINT_ALL, "bad filter name\n"); + return; + } + + gl_filter_min = modes[i].minimize; + gl_filter_max = modes[i].maximize; + + // change all the existing mipmap texture objects + for ( i = 0 ; i < tr.numImages ; i++ ) { + glt = tr.images[ i ]; + if ( glt->mipmap ) { + GL_Bind (glt); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + } + } +} + +/* +=============== +R_SumOfUsedImages +=============== +*/ +int R_SumOfUsedImages( void ) { + int total; + int i; + + total = 0; + for ( i = 0; i < tr.numImages; i++ ) { + if ( tr.images[i]->frameUsed == tr.frameCount ) { + total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight; + } + } + + return total; +} + +/* +=============== +R_ImageList_f +=============== +*/ +void R_ImageList_f( void ) { + int i; + image_t *image; + int texels; + const char *yesno[] = { + "no ", "yes" + }; + + ri.Printf (PRINT_ALL, "\n -w-- -h-- -mm- -TMU- -if-- wrap --name-------\n"); + texels = 0; + + for ( i = 0 ; i < tr.numImages ; i++ ) { + image = tr.images[ i ]; + + texels += image->uploadWidth*image->uploadHeight; + ri.Printf (PRINT_ALL, "%4i: %4i %4i %s %d ", + i, image->uploadWidth, image->uploadHeight, yesno[image->mipmap], image->TMU ); + switch ( image->internalFormat ) { + case 1: + ri.Printf( PRINT_ALL, "I " ); + break; + case 2: + ri.Printf( PRINT_ALL, "IA " ); + break; + case 3: + ri.Printf( PRINT_ALL, "RGB " ); + break; + case 4: + ri.Printf( PRINT_ALL, "RGBA " ); + break; + case GL_RGBA8: + ri.Printf( PRINT_ALL, "RGBA8" ); + break; + case GL_RGB8: + ri.Printf( PRINT_ALL, "RGB8" ); + break; + case GL_RGB4_S3TC: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + ri.Printf( PRINT_ALL, "S3TC " ); + break; + case GL_RGBA4: + ri.Printf( PRINT_ALL, "RGBA4" ); + break; + case GL_RGB5: + ri.Printf( PRINT_ALL, "RGB5 " ); + break; + default: + ri.Printf( PRINT_ALL, "???? " ); + } + + switch ( image->wrapClampMode ) { + case GL_REPEAT: + ri.Printf( PRINT_ALL, "rept " ); + break; + case GL_CLAMP_TO_EDGE: + ri.Printf( PRINT_ALL, "clmp " ); + break; + default: + ri.Printf( PRINT_ALL, "%4i ", image->wrapClampMode ); + break; + } + + ri.Printf( PRINT_ALL, " %s\n", image->imgName ); + } + ri.Printf (PRINT_ALL, " ---------\n"); + ri.Printf (PRINT_ALL, " %i total texels (not including mipmaps)\n", texels); + ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages ); +} + +//======================================================================= + +/* +================ +ResampleTexture + +Used to resample images in a more general than quartering fashion. + +This will only be filtered properly if the resampled size +is greater than half the original size. + +If a larger shrinking is needed, use the mipmap function +before or after. +================ +*/ +static void ResampleTexture( unsigned *in, int inwidth, int inheight, unsigned *out, + int outwidth, int outheight ) { + int i, j; + unsigned *inrow, *inrow2; + unsigned frac, fracstep; + unsigned p1[2048], p2[2048]; + byte *pix1, *pix2, *pix3, *pix4; + + if (outwidth>2048) + ri.Error(ERR_DROP, "ResampleTexture: max width"); + + fracstep = inwidth*0x10000/outwidth; + + frac = fracstep>>2; + for ( i=0 ; i>16); + frac += fracstep; + } + frac = 3*(fracstep>>2); + for ( i=0 ; i>16); + frac += fracstep; + } + + for (i=0 ; i> 1; + for (j=0 ; j>2; + ((byte *)(out+j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + ((byte *)(out+j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + ((byte *)(out+j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } +} + +/* +================ +R_LightScaleTexture + +Scale up the pixel values in a texture to increase the +lighting range +================ +*/ +void R_LightScaleTexture (unsigned *in, int inwidth, int inheight, qboolean only_gamma ) +{ + if ( only_gamma ) + { + if ( !glConfig.deviceSupportsGamma ) + { + int i, c; + byte *p; + + p = (byte *)in; + + c = inwidth*inheight; + for (i=0 ; i> 1; + outHeight = inHeight >> 1; + temp = ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 ); + + inWidthMask = inWidth - 1; + inHeightMask = inHeight - 1; + + for ( i = 0 ; i < outHeight ; i++ ) { + for ( j = 0 ; j < outWidth ; j++ ) { + outpix = (byte *) ( temp + i * outWidth + j ); + for ( k = 0 ; k < 4 ; k++ ) { + total = + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k]; + outpix[k] = total / 36; + } + } + } + + Com_Memcpy( in, temp, outWidth * outHeight * 4 ); + ri.Hunk_FreeTempMemory( temp ); +} + +/* +================ +R_MipMap + +Operates in place, quartering the size of the texture +================ +*/ +static void R_MipMap (byte *in, int width, int height) { + int i, j; + byte *out; + int row; + + if ( !r_simpleMipMaps->integer ) { + R_MipMap2( (unsigned *)in, width, height ); + return; + } + + if ( width == 1 && height == 1 ) { + return; + } + + row = width * 4; + out = in; + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i>1; + out[1] = ( in[1] + in[5] )>>1; + out[2] = ( in[2] + in[6] )>>1; + out[3] = ( in[3] + in[7] )>>1; + } + return; + } + + for (i=0 ; i>2; + out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2; + out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2; + out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2; + } + } +} + + +/* +================== +R_BlendOverTexture + +Apply a color blend over a set of pixels +================== +*/ +static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) { + int i; + int inverseAlpha; + int premult[3]; + + inverseAlpha = 255 - blend[3]; + premult[0] = blend[0] * blend[3]; + premult[1] = blend[1] * blend[3]; + premult[2] = blend[2] * blend[3]; + + for ( i = 0 ; i < pixelCount ; i++, data+=4 ) { + data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + +byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + + +/* +=============== +Upload32 + +=============== +*/ +extern qboolean charSet; +static void Upload32( unsigned *data, + int width, int height, + qboolean mipmap, + qboolean picmip, + qboolean lightMap, + int *format, + int *pUploadWidth, int *pUploadHeight ) +{ + int samples; + unsigned *scaledBuffer = NULL; + unsigned *resampledBuffer = NULL; + int scaled_width, scaled_height; + int i, c; + byte *scan; + GLenum internalFormat = GL_RGB; + float rMax = 0, gMax = 0, bMax = 0; + + // + // convert to exact power of 2 sizes + // + for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1) + ; + if ( r_roundImagesDown->integer && scaled_width > width ) + scaled_width >>= 1; + if ( r_roundImagesDown->integer && scaled_height > height ) + scaled_height >>= 1; + + if ( scaled_width != width || scaled_height != height ) { + resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 ); + ResampleTexture (data, width, height, resampledBuffer, scaled_width, scaled_height); + data = resampledBuffer; + width = scaled_width; + height = scaled_height; + } + + // + // perform optional picmip operation + // + if ( picmip ) { + scaled_width >>= r_picmip->integer; + scaled_height >>= r_picmip->integer; + } + + // + // clamp to minimum size + // + if (scaled_width < 1) { + scaled_width = 1; + } + if (scaled_height < 1) { + scaled_height = 1; + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( scaled_width > glConfig.maxTextureSize + || scaled_height > glConfig.maxTextureSize ) { + scaled_width >>= 1; + scaled_height >>= 1; + } + + scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height ); + + // + // scan the texture for each channel's max values + // and verify if the alpha channel is being used or not + // + c = width*height; + scan = ((byte *)data); + samples = 3; + + if( r_greyscale->integer ) + { + for ( i = 0; i < c; i++ ) + { + byte luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); + scan[i*4] = luma; + scan[i*4 + 1] = luma; + scan[i*4 + 2] = luma; + } + } + else if( r_greyscale->value ) + { + for ( i = 0; i < c; i++ ) + { + float luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); + scan[i*4] = LERP(scan[i*4], luma, r_greyscale->value); + scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, r_greyscale->value); + scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, r_greyscale->value); + } + } + + if(lightMap) + { + if(r_greyscale->integer) + internalFormat = GL_LUMINANCE; + else + internalFormat = GL_RGB; + } + else + { + for ( i = 0; i < c; i++ ) + { + if ( scan[i*4+0] > rMax ) + { + rMax = scan[i*4+0]; + } + if ( scan[i*4+1] > gMax ) + { + gMax = scan[i*4+1]; + } + if ( scan[i*4+2] > bMax ) + { + bMax = scan[i*4+2]; + } + if ( scan[i*4 + 3] != 255 ) + { + samples = 4; + break; + } + } + // select proper internal format + if ( samples == 3 ) + { + if(r_greyscale->integer) + { + if(r_texturebits->integer == 16) + internalFormat = GL_LUMINANCE8; + else if(r_texturebits->integer == 32) + internalFormat = GL_LUMINANCE16; + else + internalFormat = GL_LUMINANCE; + } + else + { + if ( glConfig.textureCompression == TC_S3TC_ARB ) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + } + else if ( glConfig.textureCompression == TC_S3TC ) + { + internalFormat = GL_RGB4_S3TC; + } + else if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGB5; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGB8; + } + else + { + internalFormat = GL_RGB; + } + } + } + else if ( samples == 4 ) + { + if(r_greyscale->integer) + { + if(r_texturebits->integer == 16) + internalFormat = GL_LUMINANCE8_ALPHA8; + else if(r_texturebits->integer == 32) + internalFormat = GL_LUMINANCE16_ALPHA16; + else + internalFormat = GL_LUMINANCE_ALPHA; + } + else + { + if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGBA8; + } + else + { + internalFormat = GL_RGBA; + } + } + } + } + + // copy or resample data as appropriate for first MIP level + if ( ( scaled_width == width ) && + ( scaled_height == height ) ) { + if (!mipmap) + { + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + *format = internalFormat; + + goto done; + } + Com_Memcpy (scaledBuffer, data, width*height*4); + } + else + { + // use the normal mip-mapping function to go down from here + while ( width > scaled_width || height > scaled_height ) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if ( width < 1 ) { + width = 1; + } + if ( height < 1 ) { + height = 1; + } + } + Com_Memcpy( scaledBuffer, data, width * height * 4 ); + } + + R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !mipmap ); + + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + *format = internalFormat; + + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + + if (mipmap) + { + int miplevel; + + miplevel = 0; + while (scaled_width > 1 || scaled_height > 1) + { + R_MipMap( (byte *)scaledBuffer, scaled_width, scaled_height ); + scaled_width >>= 1; + scaled_height >>= 1; + if (scaled_width < 1) + scaled_width = 1; + if (scaled_height < 1) + scaled_height = 1; + miplevel++; + + if ( r_colorMipLevels->integer ) { + R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] ); + } + + qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + } + } +done: + + if (mipmap) + { + if ( glConfig.textureFilterAnisotropic ) + qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, + (GLint)Com_Clamp( 1, glConfig.maxAnisotropy, r_ext_max_anisotropy->integer ) ); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + } + else + { + if ( glConfig.textureFilterAnisotropic ) + qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); + + if ( scaledBuffer != 0 ) + ri.Hunk_FreeTempMemory( scaledBuffer ); + if ( resampledBuffer != 0 ) + ri.Hunk_FreeTempMemory( resampledBuffer ); +} + + +/* +================ +R_CreateImage + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, + qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ) { + image_t *image; + qboolean isLightmap = qfalse; + long hash; + + if (strlen(name) >= MAX_QPATH ) { + ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long\n", name); + } + if ( !strncmp( name, "*lightmap", 9 ) ) { + isLightmap = qtrue; + } + + if ( tr.numImages == MAX_DRAWIMAGES ) { + ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit\n"); + } + + image = tr.images[tr.numImages] = ri.Hunk_Alloc( sizeof( image_t ), h_low ); + image->texnum = 1024 + tr.numImages; + tr.numImages++; + + image->mipmap = mipmap; + image->allowPicmip = allowPicmip; + + strcpy (image->imgName, name); + + image->width = width; + image->height = height; + image->wrapClampMode = glWrapClampMode; + + // lightmaps are always allocated on TMU 1 + if ( qglActiveTextureARB && isLightmap ) { + image->TMU = 1; + } else { + image->TMU = 0; + } + + if ( qglActiveTextureARB ) { + GL_SelectTexture( image->TMU ); + } + + GL_Bind(image); + + Upload32( (unsigned *)pic, image->width, image->height, + image->mipmap, + allowPicmip, + isLightmap, + &image->internalFormat, + &image->uploadWidth, + &image->uploadHeight ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode ); + + qglBindTexture( GL_TEXTURE_2D, 0 ); + + if ( image->TMU == 1 ) { + GL_SelectTexture( 0 ); + } + + hash = generateHashValue(name); + image->next = hashTable[hash]; + hashTable[hash] = image; + + return image; +} + +//=================================================================== + +typedef struct +{ + char *ext; + void (*ImageLoader)( const char *, unsigned char **, int *, int * ); +} imageExtToLoaderMap_t; + +// Note that the ordering indicates the order of preference used +// when there are multiple images of different formats available +static imageExtToLoaderMap_t imageLoaders[ ] = +{ + { "tga", R_LoadTGA }, + { "jpg", R_LoadJPG }, + { "jpeg", R_LoadJPG }, + { "png", R_LoadPNG }, + { "pcx", R_LoadPCX }, + { "bmp", R_LoadBMP } +}; + +static int numImageLoaders = sizeof( imageLoaders ) / + sizeof( imageLoaders[ 0 ] ); + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +void R_LoadImage( const char *name, byte **pic, int *width, int *height ) +{ + qboolean orgNameFailed = qfalse; + int i; + char localName[ MAX_QPATH ]; + const char *ext; + + *pic = NULL; + *width = 0; + *height = 0; + + Q_strncpyz( localName, name, MAX_QPATH ); + + ext = COM_GetExtension( localName ); + + if( *ext ) + { + // Look for the correct loader and use it + for( i = 0; i < numImageLoaders; i++ ) + { + if( !Q_stricmp( ext, imageLoaders[ i ].ext ) ) + { + // Load + imageLoaders[ i ].ImageLoader( localName, pic, width, height ); + break; + } + } + + // A loader was found + if( i < numImageLoaders ) + { + if( *pic == NULL ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = qtrue; + COM_StripExtension( name, localName, MAX_QPATH ); + } + else + { + // Something loaded + return; + } + } + } + + // Try and find a suitable match using all + // the image formats supported + for( i = 0; i < numImageLoaders; i++ ) + { + char *altName = va( "%s.%s", localName, imageLoaders[ i ].ext ); + + // Load + imageLoaders[ i ].ImageLoader( altName, pic, width, height ); + + if( *pic ) + { + if( orgNameFailed ) + { + ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", + name, altName ); + } + + break; + } + } +} + + +/* +=============== +R_FindImageFile + +Finds or loads the given image. +Returns NULL if it fails, not a default image. +============== +*/ +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ) { + image_t *image; + int width, height; + byte *pic; + long hash; + + if (!name) { + return NULL; + } + + hash = generateHashValue(name); + + // + // see if the image is already loaded + // + for (image=hashTable[hash]; image; image=image->next) { + if ( !strcmp( name, image->imgName ) ) { + // the white image can be used with any set of parms, but other mismatches are errors + if ( strcmp( name, "*white" ) ) { + if ( image->mipmap != mipmap ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed mipmap parm\n", name ); + } + if ( image->allowPicmip != allowPicmip ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed allowPicmip parm\n", name ); + } + if ( image->wrapClampMode != glWrapClampMode ) { + ri.Printf( PRINT_ALL, "WARNING: reused image %s with mixed glWrapClampMode parm\n", name ); + } + } + return image; + } + } + + // + // load the pic from disk + // + R_LoadImage( name, &pic, &width, &height ); + if ( pic == NULL ) { + return NULL; + } + + image = R_CreateImage( ( char * ) name, pic, width, height, mipmap, allowPicmip, glWrapClampMode ); + ri.Free( pic ); + return image; +} + + +/* +================ +R_CreateDlightImage +================ +*/ +#define DLIGHT_SIZE 16 +static void R_CreateDlightImage( void ) { + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int b; + + // make a centered inverse-square falloff blob for dynamic lighting + for (x=0 ; x 255) { + b = 255; + } else if ( b < 75 ) { + b = 0; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, qfalse, qfalse, GL_CLAMP_TO_EDGE ); +} + + +/* +================= +R_InitFogTable +================= +*/ +void R_InitFogTable( void ) { + int i; + float d; + float exp; + + exp = 0.5; + + for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) { + d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp ); + + tr.fogTable[i] = d; + } +} + +/* +================ +R_FogFactor + +Returns a 0.0 to 1.0 fog density value +This is called for each texel of the fog texture on startup +and for each vertex of transparent shaders in fog dynamically +================ +*/ +float R_FogFactor( float s, float t ) { + float d; + + s -= 1.0/512; + if ( s < 0 ) { + return 0; + } + if ( t < 1.0/32 ) { + return 0; + } + if ( t < 31.0/32 ) { + s *= (t - 1.0f/32.0f) / (30.0f/32.0f); + } + + // we need to leave a lot of clamp range + s *= 8; + + if ( s > 1.0 ) { + s = 1.0; + } + + d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ]; + + return d; +} + +/* +================ +R_CreateFogImage +================ +*/ +#define FOG_S 256 +#define FOG_T 32 +static void R_CreateFogImage( void ) { + int x,y; + byte *data; + float g; + float d; + float borderColor[4]; + + data = ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 ); + + g = 2.0; + + // S is distance, T is depth + for (x=0 ; xinteger; + if ( !glConfig.deviceSupportsGamma ) { + tr.overbrightBits = 0; // need hardware gamma for overbright + } + + // never overbright in windowed mode + if ( !glConfig.isFullscreen ) + { + tr.overbrightBits = 0; + } + + // allow 2 overbright bits in 24 bit, but only 1 in 16 bit + if ( glConfig.colorBits > 16 ) { + if ( tr.overbrightBits > 2 ) { + tr.overbrightBits = 2; + } + } else { + if ( tr.overbrightBits > 1 ) { + tr.overbrightBits = 1; + } + } + if ( tr.overbrightBits < 0 ) { + tr.overbrightBits = 0; + } + + tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + tr.identityLightByte = 255 * tr.identityLight; + + + if ( r_intensity->value <= 1 ) { + ri.Cvar_Set( "r_intensity", "1" ); + } + + if ( r_gamma->value < 0.5f ) { + ri.Cvar_Set( "r_gamma", "0.5" ); + } else if ( r_gamma->value > 3.0f ) { + ri.Cvar_Set( "r_gamma", "3.0" ); + } + + g = r_gamma->value; + + shift = tr.overbrightBits; + + for ( i = 0; i < 256; i++ ) { + if ( g == 1 ) { + inf = i; + } else { + inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f; + } + inf <<= shift; + if (inf < 0) { + inf = 0; + } + if (inf > 255) { + inf = 255; + } + s_gammatable[i] = inf; + } + + for (i=0 ; i<256 ; i++) { + j = i * r_intensity->value; + if (j > 255) { + j = 255; + } + s_intensitytable[i] = j; + } + + if ( glConfig.deviceSupportsGamma ) + { + GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); + } +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + Com_Memset(hashTable, 0, sizeof(hashTable)); + // build brightness translation tables + R_SetColorMappings(); + + // create default texture and white texture + R_CreateBuiltinImages(); +} + +/* +=============== +R_DeleteTextures +=============== +*/ +void R_DeleteTextures( void ) { + int i; + + for ( i=0; itexnum ); + } + Com_Memset( tr.images, 0, sizeof( tr.images ) ); + + tr.numImages = 0; + + Com_Memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } else { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } +} + +/* +============================================================================ + +SKINS + +============================================================================ +*/ + +/* +================== +CommaParse + +This is unfortunate, but the skin files aren't +compatable with our normal parsing rules. +================== +*/ +static char *CommaParse( char **data_p ) { + int c = 0, len; + char *data; + static char com_token[MAX_TOKEN_CHARS]; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + while ( 1 ) { + // skip whitespace + while( (c = *data) <= ' ') { + if( !c ) { + break; + } + data++; + } + + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + while (*data && *data != '\n') + data++; + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + if ( c == 0 ) { + return ""; + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32 && c != ',' ); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + + +/* +=============== +RE_RegisterSkin + +=============== +*/ +qhandle_t RE_RegisterSkin( const char *name ) { + qhandle_t hSkin; + skin_t *skin; + skinSurface_t *surf; + union { + char *c; + void *v; + } text; + char *text_p; + char *token; + char surfName[MAX_QPATH]; + + if ( !name || !name[0] ) { + Com_Printf( "Empty name passed to RE_RegisterSkin\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Skin name exceeds MAX_QPATH\n" ); + return 0; + } + + + // see if the skin is already loaded + for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { + skin = tr.skins[hSkin]; + if ( !Q_stricmp( skin->name, name ) ) { + if( skin->numSurfaces == 0 ) { + return 0; // default skin + } + return hSkin; + } + } + + // allocate a new skin + if ( tr.numSkins == MAX_SKINS ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); + return 0; + } + tr.numSkins++; + skin = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); + skin->numSurfaces = 0; + + // make sure the render thread is stopped + R_SyncRenderThread(); + + // If not a .skin file, load as a single shader + if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { + skin->numSurfaces = 1; + skin->surfaces[0] = ri.Hunk_Alloc( sizeof(skin->surfaces[0]), h_low ); + skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue ); + return hSkin; + } + + // load and parse the skin file + ri.FS_ReadFile( name, &text.v ); + if ( !text.c ) { + return 0; + } + + text_p = text.c; + while ( text_p && *text_p ) { + // get surface name + token = CommaParse( &text_p ); + Q_strncpyz( surfName, token, sizeof( surfName ) ); + + if ( !token[0] ) { + break; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surfName ); + + if ( *text_p == ',' ) { + text_p++; + } + + if ( strstr( token, "tag_" ) ) { + continue; + } + + // parse the shader name + token = CommaParse( &text_p ); + + surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue ); + skin->numSurfaces++; + } + + ri.FS_FreeFile( text.v ); + + + // never let a skin have 0 shaders + if ( skin->numSurfaces == 0 ) { + return 0; // use default skin + } + + return hSkin; +} + + +/* +=============== +R_InitSkins +=============== +*/ +void R_InitSkins( void ) { + skin_t *skin; + + tr.numSkins = 1; + + // make the default skin have all default shaders + skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + Q_strncpyz( skin->name, "", sizeof( skin->name ) ); + skin->numSurfaces = 1; + skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces ), h_low ); + skin->surfaces[0]->shader = tr.defaultShader; +} + +/* +=============== +R_GetSkinByHandle +=============== +*/ +skin_t *R_GetSkinByHandle( qhandle_t hSkin ) { + if ( hSkin < 1 || hSkin >= tr.numSkins ) { + return tr.skins[0]; + } + return tr.skins[ hSkin ]; +} + +/* +=============== +R_SkinList_f +=============== +*/ +void R_SkinList_f( void ) { + int i, j; + skin_t *skin; + + ri.Printf (PRINT_ALL, "------------------\n"); + + for ( i = 0 ; i < tr.numSkins ; i++ ) { + skin = tr.skins[i]; + + ri.Printf( PRINT_ALL, "%3i:%s\n", i, skin->name ); + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + ri.Printf( PRINT_ALL, " %s = %s\n", + skin->surfaces[j]->name, skin->surfaces[j]->shader->name ); + } + } + ri.Printf (PRINT_ALL, "------------------\n"); +} + diff --git a/src/renderer/tr_image_bmp.c b/src/renderer/tr_image_bmp.c new file mode 100644 index 0000000..7537312 --- /dev/null +++ b/src/renderer/tr_image_bmp.c @@ -0,0 +1,240 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +typedef struct +{ + char id[2]; + unsigned fileSize; + unsigned reserved0; + unsigned bitmapDataOffset; + unsigned bitmapHeaderSize; + unsigned width; + unsigned height; + unsigned short planes; + unsigned short bitsPerPixel; + unsigned compression; + unsigned bitmapDataSize; + unsigned hRes; + unsigned vRes; + unsigned colors; + unsigned importantColors; + unsigned char palette[256][4]; +} BMPHeader_t; + +void R_LoadBMP( const char *name, byte **pic, int *width, int *height ) +{ + int columns, rows; + unsigned numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *end; + union { + byte *b; + void *v; + } buffer; + int length; + BMPHeader_t bmpHeader; + byte *bmpRGBA; + + *pic = NULL; + + if(width) + *width = 0; + + if(height) + *height = 0; + + // + // load the file + // + length = ri.FS_ReadFile( ( char * ) name, &buffer.v); + if (!buffer.b || length < 0) { + return; + } + + if (length < 54) + { + ri.Error( ERR_DROP, "LoadBMP: header too short (%s)\n", name ); + } + + buf_p = buffer.b; + end = buffer.b + length; + + bmpHeader.id[0] = *buf_p++; + bmpHeader.id[1] = *buf_p++; + bmpHeader.fileSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.reserved0 = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataOffset = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapHeaderSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.width = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.height = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.planes = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.compression = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.hRes = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.vRes = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.colors = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.importantColors = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + + if ( bmpHeader.bitsPerPixel == 8 ) + { + if (buf_p + sizeof(bmpHeader.palette) > end) + ri.Error( ERR_DROP, "LoadBMP: header too short (%s)\n", name ); + + Com_Memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); + buf_p += sizeof(bmpHeader.palette); + } + + if (buffer.b + bmpHeader.bitmapDataOffset > end) + { + ri.Error( ERR_DROP, "LoadBMP: invalid offset value in header (%s)\n", name ); + } + + buf_p = buffer.b + bmpHeader.bitmapDataOffset; + + if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) + { + ri.Error( ERR_DROP, "LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + } + if ( bmpHeader.fileSize != length ) + { + ri.Error( ERR_DROP, "LoadBMP: header size does not match file size (%u vs. %u) (%s)\n", bmpHeader.fileSize, length, name ); + } + if ( bmpHeader.compression != 0 ) + { + ri.Error( ERR_DROP, "LoadBMP: only uncompressed BMP files supported (%s)\n", name ); + } + if ( bmpHeader.bitsPerPixel < 8 ) + { + ri.Error( ERR_DROP, "LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name ); + } + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + case 16: + case 24: + case 32: + break; + default: + ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%hu' in file '%s'\n", bmpHeader.bitsPerPixel, name ); + break; + } + + columns = bmpHeader.width; + rows = bmpHeader.height; + if ( rows < 0 ) + rows = -rows; + numPixels = columns * rows; + + if(columns <= 0 || !rows || numPixels > 0x1FFFFFFF // 4*1FFFFFFF == 0x7FFFFFFC < 0x7FFFFFFF + || ((numPixels * 4) / columns) / 4 != rows) + { + ri.Error (ERR_DROP, "LoadBMP: %s has an invalid image size\n", name); + } + if(buf_p + numPixels*bmpHeader.bitsPerPixel/8 > end) + { + ri.Error (ERR_DROP, "LoadBMP: file truncated (%s)\n", name); + } + + if ( width ) + *width = columns; + if ( height ) + *height = rows; + + bmpRGBA = ri.Malloc( numPixels * 4 ); + *pic = bmpRGBA; + + + for ( row = rows-1; row >= 0; row-- ) + { + pixbuf = bmpRGBA + row*columns*4; + + for ( column = 0; column < columns; column++ ) + { + unsigned char red, green, blue, alpha; + int palIndex; + unsigned short shortPixel; + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + palIndex = *buf_p++; + *pixbuf++ = bmpHeader.palette[palIndex][2]; + *pixbuf++ = bmpHeader.palette[palIndex][1]; + *pixbuf++ = bmpHeader.palette[palIndex][0]; + *pixbuf++ = 0xff; + break; + case 16: + shortPixel = * ( unsigned short * ) pixbuf; + pixbuf += 2; + *pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7; + *pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2; + *pixbuf++ = ( shortPixel & ( 31 ) ) << 3; + *pixbuf++ = 0xff; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + } + } + } + + ri.FS_FreeFile( buffer.v ); + +} diff --git a/src/renderer/tr_image_jpg.c b/src/renderer/tr_image_jpg.c new file mode 100644 index 0000000..7390e14 --- /dev/null +++ b/src/renderer/tr_image_jpg.c @@ -0,0 +1,438 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +#ifdef USE_INTERNAL_JPEG +# define JPEG_INTERNALS +#endif + +#include + +#ifndef USE_INTERNAL_JPEG +# if JPEG_LIB_VERSION < 80 +# error Need system libjpeg >= 80 +# endif +#endif + +static void R_JPGErrorExit(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + (*cinfo->err->format_message) (cinfo, buffer); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + ri.Error(ERR_FATAL, "%s\n", buffer); +} + +static void R_JPGOutputMessage(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Send it to stderr, adding a newline */ + ri.Printf(PRINT_ALL, "%s\n", buffer); +} + +void R_LoadJPG(const char *filename, unsigned char **pic, int *width, int *height) +{ + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo = {NULL}; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + unsigned int row_stride; /* physical row width in output buffer */ + unsigned int pixelcount, memcount; + unsigned int sindex, dindex; + byte *out; + int len; + union { + byte *b; + void *v; + } fbuffer; + byte *buf; + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + len = ri.FS_ReadFile ( ( char * ) filename, &fbuffer.v); + if (!fbuffer.b || len < 0) { + return; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = R_JPGErrorExit; + cinfo.err->output_message = R_JPGOutputMessage; + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_mem_src(&cinfo, fbuffer.b, len); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* + * Make sure it always converts images to RGB color space. This will + * automatically convert 8-bit greyscale images to RGB as well. + */ + cinfo.out_color_space = JCS_RGB; + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + + pixelcount = cinfo.output_width * cinfo.output_height; + + if(!cinfo.output_width || !cinfo.output_height + || ((pixelcount * 4) / cinfo.output_width) / 4 != cinfo.output_height + || pixelcount > 0x1FFFFFFF || cinfo.output_components != 3 + ) + { + // Free the memory to make sure we don't leak memory + ri.FS_FreeFile (fbuffer.v); + jpeg_destroy_decompress(&cinfo); + + ri.Error(ERR_DROP, "LoadJPG: %s has an invalid image format: %dx%d*4=%d, components: %d\n", filename, + cinfo.output_width, cinfo.output_height, pixelcount * 4, cinfo.output_components); + } + + memcount = pixelcount * 4; + row_stride = cinfo.output_width * cinfo.output_components; + + out = ri.Malloc(memcount); + + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + buf = ((out+(row_stride*cinfo.output_scanline))); + buffer = &buf; + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + } + + buf = out; + + // Expand from RGB to RGBA + sindex = pixelcount * cinfo.output_components; + dindex = memcount; + + do + { + buf[--dindex] = 255; + buf[--dindex] = buf[--sindex]; + buf[--dindex] = buf[--sindex]; + buf[--dindex] = buf[--sindex]; + } while(sindex); + + *pic = out; + + /* Step 7: Finish decompression */ + + jpeg_finish_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + ri.FS_FreeFile (fbuffer.v); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ +} + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + byte* outfile; /* target stream */ + int size; +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +static void +init_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + dest->pub.next_output_byte = dest->outfile; + dest->pub.free_in_buffer = dest->size; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +static boolean +empty_output_buffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + jpeg_destroy_compress(cinfo); + + // Make crash fatal or we would probably leak memory. + ri.Error(ERR_FATAL, "Output buffer for encoded JPEG image has insufficient size of %d bytes\n", + dest->size); + + return FALSE; +} + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +static void term_destination(j_compress_ptr cinfo) +{ +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +static void +jpegDest (j_compress_ptr cinfo, byte* outfile, int size) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; + dest->size = size; +} + +/* +================= +SaveJPGToBuffer + +Encodes JPEG from image in image_buffer and writes to buffer. +Expects RGB input data +================= +*/ +size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality, + int image_width, int image_height, byte *image_buffer, int padding) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + my_dest_ptr dest; + int row_stride; /* physical row width in image buffer */ + size_t outcount; + + /* Step 1: allocate and initialize JPEG compression object */ + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = R_JPGErrorExit; + cinfo.err->output_message = R_JPGOutputMessage; + + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + jpegDest(&cinfo, buffer, bufSize); + + /* Step 3: set parameters for compression */ + cinfo.image_width = image_width; /* image width and height, in pixels */ + cinfo.image_height = image_height; + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + /* If quality is set high, disable chroma subsampling */ + if (quality >= 85) { + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + } + + /* Step 4: Start compressor */ + jpeg_start_compress(&cinfo, TRUE); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + row_stride = image_width * cinfo.input_components + padding; /* JSAMPLEs per row in image_buffer */ + + while (cinfo.next_scanline < cinfo.image_height) { + /* jpeg_write_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could pass + * more than one scanline at a time if that's more convenient. + */ + row_pointer[0] = &image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride]; + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + /* Step 6: Finish compression */ + jpeg_finish_compress(&cinfo); + + dest = (my_dest_ptr) cinfo.dest; + outcount = dest->size - dest->pub.free_in_buffer; + + /* Step 7: release JPEG compression object */ + jpeg_destroy_compress(&cinfo); + + /* And we're done! */ + return outcount; +} + +void RE_SaveJPG(char * filename, int quality, int image_width, int image_height, byte *image_buffer, int padding) +{ + byte *out; + size_t bufSize; + + bufSize = image_width * image_height * 3; + out = ri.Hunk_AllocateTempMemory(bufSize); + + bufSize = RE_SaveJPGToBuffer(out, bufSize, quality, image_width, image_height, image_buffer, padding); + ri.FS_WriteFile(filename, out, bufSize); + + ri.Hunk_FreeTempMemory(out); +} diff --git a/src/renderer/tr_image_pcx.c b/src/renderer/tr_image_pcx.c new file mode 100644 index 0000000..8837d5b --- /dev/null +++ b/src/renderer/tr_image_pcx.c @@ -0,0 +1,176 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + 2008 Ludwig Nussel +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + unsigned short hscreensize, vscreensize; + char filler[54]; + unsigned char data[]; +} pcx_t; + +void R_LoadPCX ( const char *filename, byte **pic, int *width, int *height) +{ + union { + byte *b; + void *v; + } raw; + byte *end; + pcx_t *pcx; + int len; + unsigned char dataByte = 0, runLength = 0; + byte *out, *pix; + unsigned short w, h; + byte *pic8; + byte *palette; + int i; + unsigned size = 0; + + if (width) + *width = 0; + if (height) + *height = 0; + *pic = NULL; + + // + // load the file + // + len = ri.FS_ReadFile( ( char * ) filename, &raw.v); + if (!raw.b || len < 0) { + return; + } + + if((unsigned)len < sizeof(pcx_t)) + { + ri.Printf (PRINT_ALL, "PCX truncated: %s\n", filename); + ri.FS_FreeFile (raw.v); + return; + } + + // + // parse the PCX file + // + pcx = (pcx_t *)raw.b; + end = raw.b+len; + + w = LittleShort(pcx->xmax)+1; + h = LittleShort(pcx->ymax)+1; + size = w*h; + + if (pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->color_planes != 1 + || pcx->bits_per_pixel != 8 + || w >= 1024 + || h >= 1024) + { + ri.Printf (PRINT_ALL, "Bad or unsupported pcx file %s (%dx%d@%d)\n", filename, w, h, pcx->bits_per_pixel); + return; + } + + pix = pic8 = ri.Malloc ( size ); + + raw.b = pcx->data; + // FIXME: should use bytes_per_line but original q3 didn't do that either + while(pix < pic8+size) + { + if(runLength > 0) { + *pix++ = dataByte; + --runLength; + continue; + } + + if(raw.b+1 > end) + break; + dataByte = *raw.b++; + + if((dataByte & 0xC0) == 0xC0) + { + if(raw.b+1 > end) + break; + runLength = dataByte & 0x3F; + dataByte = *raw.b++; + } + else + runLength = 1; + } + + if(pix < pic8+size) + { + ri.Printf (PRINT_ALL, "PCX file truncated: %s\n", filename); + ri.FS_FreeFile (pcx); + ri.Free (pic8); + } + + if (raw.b-(byte*)pcx >= end - (byte*)769 || end[-769] != 0x0c) + { + ri.Printf (PRINT_ALL, "PCX missing palette: %s\n", filename); + ri.FS_FreeFile (pcx); + ri.Free (pic8); + return; + } + + palette = end-768; + + pix = out = ri.Malloc(4 * size ); + for (i = 0 ; i < size ; i++) + { + unsigned char p = pic8[i]; + pix[0] = palette[p*3]; + pix[1] = palette[p*3 + 1]; + pix[2] = palette[p*3 + 2]; + pix[3] = 255; + pix += 4; + } + + if (width) + *width = w; + if (height) + *height = h; + + *pic = out; + + ri.FS_FreeFile (pcx); + ri.Free (pic8); +} diff --git a/src/renderer/tr_image_png.c b/src/renderer/tr_image_png.c new file mode 100644 index 0000000..ef44c16 --- /dev/null +++ b/src/renderer/tr_image_png.c @@ -0,0 +1,2488 @@ +/* +=========================================================================== +ioquake3 png decoder +Copyright (C) 2007,2008 Joerg Dietrich + +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 "tr_local.h" + +#include "../qcommon/puff.h" + +// we could limit the png size to a lower value here +#ifndef INT_MAX +#define INT_MAX 0x1fffffff +#endif + +/* +================= +PNG LOADING +================= +*/ + +/* + * Quake 3 image format : RGBA + */ + +#define Q3IMAGE_BYTESPERPIXEL (4) + +/* + * PNG specifications + */ + +/* + * The first 8 Bytes of every PNG-File are a fixed signature + * to identify the file as a PNG. + */ + +#define PNG_Signature "\x89\x50\x4E\x47\xD\xA\x1A\xA" +#define PNG_Signature_Size (8) + +/* + * After the signature diverse chunks follow. + * A chunk consists of a header and if Length + * is bigger than 0 a body and a CRC of the body follow. + */ + +struct PNG_ChunkHeader +{ + uint32_t Length; + uint32_t Type; +}; + +#define PNG_ChunkHeader_Size (8) + +typedef uint32_t PNG_ChunkCRC; + +#define PNG_ChunkCRC_Size (4) + +/* + * We use the following ChunkTypes. + * All others are ignored. + */ + +#define MAKE_CHUNKTYPE(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | ((d))) + +#define PNG_ChunkType_IHDR MAKE_CHUNKTYPE('I', 'H', 'D', 'R') +#define PNG_ChunkType_PLTE MAKE_CHUNKTYPE('P', 'L', 'T', 'E') +#define PNG_ChunkType_IDAT MAKE_CHUNKTYPE('I', 'D', 'A', 'T') +#define PNG_ChunkType_IEND MAKE_CHUNKTYPE('I', 'E', 'N', 'D') +#define PNG_ChunkType_tRNS MAKE_CHUNKTYPE('t', 'R', 'N', 'S') + +/* + * Per specification the first chunk after the signature SHALL be IHDR. + */ + +struct PNG_Chunk_IHDR +{ + uint32_t Width; + uint32_t Height; + uint8_t BitDepth; + uint8_t ColourType; + uint8_t CompressionMethod; + uint8_t FilterMethod; + uint8_t InterlaceMethod; +}; + +#define PNG_Chunk_IHDR_Size (13) + +/* + * ColourTypes + */ + +#define PNG_ColourType_Grey (0) +#define PNG_ColourType_True (2) +#define PNG_ColourType_Indexed (3) +#define PNG_ColourType_GreyAlpha (4) +#define PNG_ColourType_TrueAlpha (6) + +/* + * number of colour components + * + * Grey : 1 grey + * True : 1 R, 1 G, 1 B + * Indexed : 1 index + * GreyAlpha : 1 grey, 1 alpha + * TrueAlpha : 1 R, 1 G, 1 B, 1 alpha + */ + +#define PNG_NumColourComponents_Grey (1) +#define PNG_NumColourComponents_True (3) +#define PNG_NumColourComponents_Indexed (1) +#define PNG_NumColourComponents_GreyAlpha (2) +#define PNG_NumColourComponents_TrueAlpha (4) + +/* + * For the different ColourTypes + * different BitDepths are specified. + */ + +#define PNG_BitDepth_1 ( 1) +#define PNG_BitDepth_2 ( 2) +#define PNG_BitDepth_4 ( 4) +#define PNG_BitDepth_8 ( 8) +#define PNG_BitDepth_16 (16) + +/* + * Only one valid CompressionMethod is standardized. + */ + +#define PNG_CompressionMethod_0 (0) + +/* + * Only one valid FilterMethod is currently standardized. + */ + +#define PNG_FilterMethod_0 (0) + +/* + * This FilterMethod defines 5 FilterTypes + */ + +#define PNG_FilterType_None (0) +#define PNG_FilterType_Sub (1) +#define PNG_FilterType_Up (2) +#define PNG_FilterType_Average (3) +#define PNG_FilterType_Paeth (4) + +/* + * Two InterlaceMethods are standardized : + * 0 - NonInterlaced + * 1 - Interlaced + */ + +#define PNG_InterlaceMethod_NonInterlaced (0) +#define PNG_InterlaceMethod_Interlaced (1) + +/* + * The Adam7 interlace method uses 7 passes. + */ + +#define PNG_Adam7_NumPasses (7) + +/* + * The compressed data starts with a header ... + */ + +struct PNG_ZlibHeader +{ + uint8_t CompressionMethod; + uint8_t Flags; +}; + +#define PNG_ZlibHeader_Size (2) + +/* + * ... and is followed by a check value + */ + +#define PNG_ZlibCheckValue_Size (4) + +/* + * Some support functions for buffered files follow. + */ + +/* + * buffered file representation + */ + +struct BufferedFile +{ + byte *Buffer; + int Length; + byte *Ptr; + int BytesLeft; +}; + +/* + * Read a file into a buffer. + */ + +static struct BufferedFile *ReadBufferedFile(const char *name) +{ + struct BufferedFile *BF; + union { + byte *b; + void *v; + } buffer; + + /* + * input verification + */ + + if(!name) + { + return(NULL); + } + + /* + * Allocate control struct. + */ + + BF = ri.Malloc(sizeof(struct BufferedFile)); + if(!BF) + { + return(NULL); + } + + /* + * Initialize the structs components. + */ + + BF->Length = 0; + BF->Buffer = NULL; + BF->Ptr = NULL; + BF->BytesLeft = 0; + + /* + * Read the file. + */ + + BF->Length = ri.FS_ReadFile((char *) name, &buffer.v); + BF->Buffer = buffer.b; + + /* + * Did we get it? Is it big enough? + */ + + if(!(BF->Buffer && (BF->Length > 0))) + { + ri.Free(BF); + + return(NULL); + } + + /* + * Set the pointers and counters. + */ + + BF->Ptr = BF->Buffer; + BF->BytesLeft = BF->Length; + + return(BF); +} + +/* + * Close a buffered file. + */ + +static void CloseBufferedFile(struct BufferedFile *BF) +{ + if(BF) + { + if(BF->Buffer) + { + ri.FS_FreeFile(BF->Buffer); + } + + ri.Free(BF); + } +} + +/* + * Get a pointer to the requested bytes. + */ + +static void *BufferedFileRead(struct BufferedFile *BF, unsigned Length) +{ + void *RetVal; + + /* + * input verification + */ + + if(!(BF && Length)) + { + return(NULL); + } + + /* + * not enough bytes left + */ + + if(Length > BF->BytesLeft) + { + return(NULL); + } + + /* + * the pointer to the requested data + */ + + RetVal = BF->Ptr; + + /* + * Raise the pointer and counter. + */ + + BF->Ptr += Length; + BF->BytesLeft -= Length; + + return(RetVal); +} + +/* + * Rewind the buffer. + */ + +static qboolean BufferedFileRewind(struct BufferedFile *BF, unsigned Offset) +{ + unsigned BytesRead; + + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * special trick to rewind to the beginning of the buffer + */ + + if(Offset == (unsigned)-1) + { + BF->Ptr = BF->Buffer; + BF->BytesLeft = BF->Length; + + return(qtrue); + } + + /* + * How many bytes do we have already read? + */ + + BytesRead = BF->Ptr - BF->Buffer; + + /* + * We can only rewind to the beginning of the BufferedFile. + */ + + if(Offset > BytesRead) + { + return(qfalse); + } + + /* + * lower the pointer and counter. + */ + + BF->Ptr -= Offset; + BF->BytesLeft += Offset; + + return(qtrue); +} + +/* + * Skip some bytes. + */ + +static qboolean BufferedFileSkip(struct BufferedFile *BF, unsigned Offset) +{ + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * We can only skip to the end of the BufferedFile. + */ + + if(Offset > BF->BytesLeft) + { + return(qfalse); + } + + /* + * lower the pointer and counter. + */ + + BF->Ptr += Offset; + BF->BytesLeft -= Offset; + + return(qtrue); +} + +/* + * Find a chunk + */ + +static qboolean FindChunk(struct BufferedFile *BF, uint32_t ChunkType) +{ + struct PNG_ChunkHeader *CH; + + uint32_t Length; + uint32_t Type; + + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * cycle trough the chunks + */ + + while(qtrue) + { + /* + * Read the chunk-header. + */ + + CH = BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + return(qfalse); + } + + /* + * Do not swap the original types + * they might be needed later. + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We found it! + */ + + if(Type == ChunkType) + { + /* + * Rewind to the start of the chunk. + */ + + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + else + { + /* + * Skip the rest of the chunk. + */ + + if(Length) + { + if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size)) + { + return(qfalse); + } + } + } + } + + return(qtrue); +} + +/* + * Decompress all IDATs + */ + +static uint32_t DecompressIDATs(struct BufferedFile *BF, uint8_t **Buffer) +{ + uint8_t *DecompressedData; + uint32_t DecompressedDataLength; + + uint8_t *CompressedData; + uint8_t *CompressedDataPtr; + uint32_t CompressedDataLength; + + struct PNG_ChunkHeader *CH; + + uint32_t Length; + uint32_t Type; + + int BytesToRewind; + + int32_t puffResult; + uint8_t *puffDest; + uint32_t puffDestLen; + uint8_t *puffSrc; + uint32_t puffSrcLen; + + /* + * input verification + */ + + if(!(BF && Buffer)) + { + return(-1); + } + + /* + * some zeroing + */ + + DecompressedData = NULL; + DecompressedDataLength = 0; + *Buffer = DecompressedData; + + CompressedData = NULL; + CompressedDataLength = 0; + + BytesToRewind = 0; + + /* + * Find the first IDAT chunk. + */ + + if(!FindChunk(BF, PNG_ChunkType_IDAT)) + { + return(-1); + } + + /* + * Count the size of the uncompressed data + */ + + while(qtrue) + { + /* + * Read chunk header + */ + + CH = BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + /* + * Rewind to the start of this adventure + * and return unsuccessfull + */ + + BufferedFileRewind(BF, BytesToRewind); + + return(-1); + } + + /* + * Length and Type of chunk + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We have reached the end of the IDAT chunks + */ + + if(!(Type == PNG_ChunkType_IDAT)) + { + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + + /* + * Add chunk header to count. + */ + + BytesToRewind += PNG_ChunkHeader_Size; + + /* + * Skip to next chunk + */ + + if(Length) + { + if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size)) + { + BufferedFileRewind(BF, BytesToRewind); + + return(-1); + } + + BytesToRewind += Length + PNG_ChunkCRC_Size; + CompressedDataLength += Length; + } + } + + BufferedFileRewind(BF, BytesToRewind); + + CompressedData = ri.Malloc(CompressedDataLength); + if(!CompressedData) + { + return(-1); + } + + CompressedDataPtr = CompressedData; + + /* + * Collect the compressed Data + */ + + while(qtrue) + { + /* + * Read chunk header + */ + + CH = BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Length and Type of chunk + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We have reached the end of the IDAT chunks + */ + + if(!(Type == PNG_ChunkType_IDAT)) + { + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + + /* + * Copy the Data + */ + + if(Length) + { + uint8_t *OrigCompressedData; + + OrigCompressedData = BufferedFileRead(BF, Length); + if(!OrigCompressedData) + { + ri.Free(CompressedData); + + return(-1); + } + + if(!BufferedFileSkip(BF, PNG_ChunkCRC_Size)) + { + ri.Free(CompressedData); + + return(-1); + } + + memcpy(CompressedDataPtr, OrigCompressedData, Length); + CompressedDataPtr += Length; + } + } + + /* + * Let puff() calculate the decompressed data length. + */ + + puffDest = NULL; + puffDestLen = 0; + + /* + * The zlib header and checkvalue don't belong to the compressed data. + */ + + puffSrc = CompressedData + PNG_ZlibHeader_Size; + puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size; + + /* + * first puff() to calculate the size of the uncompressed data + */ + + puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen); + if(!((puffResult == 0) && (puffDestLen > 0))) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Allocate the buffer for the uncompressed data. + */ + + DecompressedData = ri.Malloc(puffDestLen); + if(!DecompressedData) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Set the input again in case something was changed by the last puff() . + */ + + puffDest = DecompressedData; + puffSrc = CompressedData + PNG_ZlibHeader_Size; + puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size; + + /* + * decompression puff() + */ + + puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen); + + /* + * The compressed data is not needed anymore. + */ + + ri.Free(CompressedData); + + /* + * Check if the last puff() was successfull. + */ + + if(!((puffResult == 0) && (puffDestLen > 0))) + { + ri.Free(DecompressedData); + + return(-1); + } + + /* + * Set the output of this function. + */ + + DecompressedDataLength = puffDestLen; + *Buffer = DecompressedData; + + return(DecompressedDataLength); +} + +/* + * the Paeth predictor + */ + +static uint8_t PredictPaeth(uint8_t a, uint8_t b, uint8_t c) +{ + /* + * a == Left + * b == Up + * c == UpLeft + */ + + uint8_t Pr; + int p; + int pa, pb, pc; + + Pr = 0; + + p = ((int) a) + ((int) b) - ((int) c); + pa = abs(p - ((int) a)); + pb = abs(p - ((int) b)); + pc = abs(p - ((int) c)); + + if((pa <= pb) && (pa <= pc)) + { + Pr = a; + } + else if(pb <= pc) + { + Pr = b; + } + else + { + Pr = c; + } + + return(Pr); + +} + +/* + * Reverse the filters. + */ + +static qboolean UnfilterImage(uint8_t *DecompressedData, + uint32_t ImageHeight, + uint32_t BytesPerScanline, + uint32_t BytesPerPixel) +{ + uint8_t *DecompPtr; + uint8_t FilterType; + uint8_t *PixelLeft, *PixelUp, *PixelUpLeft; + uint32_t w, h, p; + + /* + * some zeros for the filters + */ + + uint8_t Zeros[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + /* + * input verification + */ + + if(!(DecompressedData && BytesPerPixel)) + { + return(qfalse); + } + + /* + * ImageHeight and BytesPerScanline can be zero in small interlaced images. + */ + + if((!ImageHeight) || (!BytesPerScanline)) + { + return(qtrue); + } + + /* + * Set the pointer to the start of the decompressed Data. + */ + + DecompPtr = DecompressedData; + + /* + * Un-filtering is done in place. + */ + + /* + * Go trough all scanlines. + */ + + for(h = 0; h < ImageHeight; h++) + { + /* + * Every scanline starts with a FilterType byte. + */ + + FilterType = *DecompPtr; + DecompPtr++; + + /* + * Left pixel of the first byte in a scanline is zero. + */ + + PixelLeft = Zeros; + + /* + * Set PixelUp to previous line only if we are on the second line or above. + * + * Plus one byte for the FilterType + */ + + if(h > 0) + { + PixelUp = DecompPtr - (BytesPerScanline + 1); + } + else + { + PixelUp = Zeros; + } + + /* + * The pixel left to the first pixel of the previous scanline is zero too. + */ + + PixelUpLeft = Zeros; + + /* + * Cycle trough all pixels of the scanline. + */ + + for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++) + { + /* + * Cycle trough the bytes of the pixel. + */ + + for(p = 0; p < BytesPerPixel; p++) + { + switch(FilterType) + { + case PNG_FilterType_None : + { + /* + * The byte is unfiltered. + */ + + break; + } + + case PNG_FilterType_Sub : + { + DecompPtr[p] += PixelLeft[p]; + + break; + } + + case PNG_FilterType_Up : + { + DecompPtr[p] += PixelUp[p]; + + break; + } + + case PNG_FilterType_Average : + { + DecompPtr[p] += ((uint8_t) ((((uint16_t) PixelLeft[p]) + ((uint16_t) PixelUp[p])) / 2)); + + break; + } + + case PNG_FilterType_Paeth : + { + DecompPtr[p] += PredictPaeth(PixelLeft[p], PixelUp[p], PixelUpLeft[p]); + + break; + } + + default : + { + return(qfalse); + } + } + } + + PixelLeft = DecompPtr; + + /* + * We only have a upleft pixel if we are on the second line or above. + */ + + if(h > 0) + { + PixelUpLeft = DecompPtr - (BytesPerScanline + 1); + } + + /* + * Skip to the next pixel. + */ + + DecompPtr += BytesPerPixel; + + /* + * We only have a previous line if we are on the second line and above. + */ + + if(h > 0) + { + PixelUp = DecompPtr - (BytesPerScanline + 1); + } + } + } + + return(qtrue); +} + +/* + * Convert a raw input pixel to Quake 3 RGA format. + */ + +static qboolean ConvertPixel(struct PNG_Chunk_IHDR *IHDR, + byte *OutPtr, + uint8_t *DecompPtr, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + /* + * input verification + */ + + if(!(IHDR && OutPtr && DecompPtr && TransparentColour && OutPal)) + { + return(qfalse); + } + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + uint8_t Step; + uint8_t GreyValue; + + Step = 0xFF / ((1 << IHDR->BitDepth) - 1); + + GreyValue = DecompPtr[0] * Step; + + OutPtr[0] = GreyValue; + OutPtr[1] = GreyValue; + OutPtr[2] = GreyValue; + OutPtr[3] = 0xFF; + + /* + * Grey supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if(TransparentColour[1] == DecompPtr[0]) + { + OutPtr[3] = 0x00; + } + } + + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = 0xFF; + + /* + * Grey supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if(IHDR->BitDepth == PNG_BitDepth_8) + { + if(TransparentColour[1] == DecompPtr[0]) + { + OutPtr[3] = 0x00; + } + } + else + { + if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1])) + { + OutPtr[3] = 0x00; + } + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[1]; + OutPtr[2] = DecompPtr[2]; + OutPtr[3] = 0xFF; + + /* + * True supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if((TransparentColour[1] == DecompPtr[0]) && + (TransparentColour[3] == DecompPtr[1]) && + (TransparentColour[5] == DecompPtr[2])) + { + OutPtr[3] = 0x00; + } + } + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[2]; + OutPtr[2] = DecompPtr[4]; + OutPtr[3] = 0xFF; + + /* + * True supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]) && + (TransparentColour[2] == DecompPtr[2]) && (TransparentColour[3] == DecompPtr[3]) && + (TransparentColour[4] == DecompPtr[4]) && (TransparentColour[5] == DecompPtr[5])) + { + OutPtr[3] = 0x00; + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + OutPtr[0] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 0]; + OutPtr[1] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 1]; + OutPtr[2] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 2]; + OutPtr[3] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 3]; + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = DecompPtr[1]; + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = DecompPtr[2]; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[1]; + OutPtr[2] = DecompPtr[2]; + OutPtr[3] = DecompPtr[3]; + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[2]; + OutPtr[2] = DecompPtr[4]; + OutPtr[3] = DecompPtr[6]; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + return(qtrue); +} + + +/* + * Decode a non-interlaced image. + */ + +static qboolean DecodeImageNonInterlaced(struct PNG_Chunk_IHDR *IHDR, + byte *OutBuffer, + uint8_t *DecompressedData, + uint32_t DecompressedDataLength, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + uint32_t IHDR_Width; + uint32_t IHDR_Height; + uint32_t BytesPerScanline, BytesPerPixel, PixelsPerByte; + uint32_t w, h, p; + byte *OutPtr; + uint8_t *DecompPtr; + + /* + * input verification + */ + + if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal)) + { + return(qfalse); + } + + /* + * byte swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * information for un-filtering + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + { + BytesPerPixel = PNG_NumColourComponents_Indexed; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + /* + * Calculate the size of one scanline + */ + + BytesPerScanline = (IHDR_Width * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte; + + /* + * Check if we have enough data for the whole image. + */ + + if(!(DecompressedDataLength == ((BytesPerScanline + 1) * IHDR_Height))) + { + return(qfalse); + } + + /* + * Unfilter the image. + */ + + if(!UnfilterImage(DecompressedData, IHDR_Height, BytesPerScanline, BytesPerPixel)) + { + return(qfalse); + } + + /* + * Set the working pointers to the beginning of the buffers. + */ + + OutPtr = OutBuffer; + DecompPtr = DecompressedData; + + /* + * Create the output image. + */ + + for(h = 0; h < IHDR_Height; h++) + { + /* + * Count the pixels on the scanline for those multipixel bytes + */ + + uint32_t CurrPixel; + + /* + * skip FilterType + */ + + DecompPtr++; + + /* + * Reset the pixel count. + */ + + CurrPixel = 0; + + for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++) + { + if(PixelsPerByte > 1) + { + uint8_t Mask; + uint32_t Shift; + uint8_t SinglePixel; + + for(p = 0; p < PixelsPerByte; p++) + { + if(CurrPixel < IHDR_Width) + { + Mask = (1 << IHDR->BitDepth) - 1; + Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth; + + SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift); + + if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + OutPtr += Q3IMAGE_BYTESPERPIXEL; + CurrPixel++; + } + } + + } + else + { + if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + + OutPtr += Q3IMAGE_BYTESPERPIXEL; + } + + DecompPtr += BytesPerPixel; + } + } + + return(qtrue); +} + +/* + * Decode an interlaced image. + */ + +static qboolean DecodeImageInterlaced(struct PNG_Chunk_IHDR *IHDR, + byte *OutBuffer, + uint8_t *DecompressedData, + uint32_t DecompressedDataLength, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + uint32_t IHDR_Width; + uint32_t IHDR_Height; + uint32_t BytesPerScanline[PNG_Adam7_NumPasses], BytesPerPixel, PixelsPerByte; + uint32_t PassWidth[PNG_Adam7_NumPasses], PassHeight[PNG_Adam7_NumPasses]; + uint32_t WSkip[PNG_Adam7_NumPasses], WOffset[PNG_Adam7_NumPasses], HSkip[PNG_Adam7_NumPasses], HOffset[PNG_Adam7_NumPasses]; + uint32_t w, h, p, a; + byte *OutPtr; + uint8_t *DecompPtr; + uint32_t TargetLength; + + /* + * input verification + */ + + if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal)) + { + return(qfalse); + } + + /* + * byte swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * Skip and Offset for the passes. + */ + + WSkip[0] = 8; + WOffset[0] = 0; + HSkip[0] = 8; + HOffset[0] = 0; + + WSkip[1] = 8; + WOffset[1] = 4; + HSkip[1] = 8; + HOffset[1] = 0; + + WSkip[2] = 4; + WOffset[2] = 0; + HSkip[2] = 8; + HOffset[2] = 4; + + WSkip[3] = 4; + WOffset[3] = 2; + HSkip[3] = 4; + HOffset[3] = 0; + + WSkip[4] = 2; + WOffset[4] = 0; + HSkip[4] = 4; + HOffset[4] = 2; + + WSkip[5] = 2; + WOffset[5] = 1; + HSkip[5] = 2; + HOffset[5] = 0; + + WSkip[6] = 1; + WOffset[6] = 0; + HSkip[6] = 2; + HOffset[6] = 1; + + /* + * Calculate the sizes of the passes. + */ + + PassWidth[0] = (IHDR_Width + 7) / 8; + PassHeight[0] = (IHDR_Height + 7) / 8; + + PassWidth[1] = (IHDR_Width + 3) / 8; + PassHeight[1] = (IHDR_Height + 7) / 8; + + PassWidth[2] = (IHDR_Width + 3) / 4; + PassHeight[2] = (IHDR_Height + 3) / 8; + + PassWidth[3] = (IHDR_Width + 1) / 4; + PassHeight[3] = (IHDR_Height + 3) / 4; + + PassWidth[4] = (IHDR_Width + 1) / 2; + PassHeight[4] = (IHDR_Height + 1) / 4; + + PassWidth[5] = (IHDR_Width + 0) / 2; + PassHeight[5] = (IHDR_Height + 1) / 2; + + PassWidth[6] = (IHDR_Width + 0) / 1; + PassHeight[6] = (IHDR_Height + 0) / 2; + + /* + * information for un-filtering + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + { + BytesPerPixel = PNG_NumColourComponents_Indexed; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + /* + * Calculate the size of the scanlines per pass + */ + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + BytesPerScanline[a] = (PassWidth[a] * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte; + } + + /* + * Calculate the size of all passes + */ + + TargetLength = 0; + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + TargetLength += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]); + } + + /* + * Check if we have enough data for the whole image. + */ + + if(!(DecompressedDataLength == TargetLength)) + { + return(qfalse); + } + + /* + * Unfilter the image. + */ + + DecompPtr = DecompressedData; + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + if(!UnfilterImage(DecompPtr, PassHeight[a], BytesPerScanline[a], BytesPerPixel)) + { + return(qfalse); + } + + DecompPtr += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]); + } + + /* + * Set the working pointers to the beginning of the buffers. + */ + + DecompPtr = DecompressedData; + + /* + * Create the output image. + */ + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + for(h = 0; h < PassHeight[a]; h++) + { + /* + * Count the pixels on the scanline for those multipixel bytes + */ + + uint32_t CurrPixel; + + /* + * skip FilterType + * but only when the pass has a width bigger than zero + */ + + if(BytesPerScanline[a]) + { + DecompPtr++; + } + + /* + * Reset the pixel count. + */ + + CurrPixel = 0; + + for(w = 0; w < (BytesPerScanline[a] / BytesPerPixel); w++) + { + if(PixelsPerByte > 1) + { + uint8_t Mask; + uint32_t Shift; + uint8_t SinglePixel; + + for(p = 0; p < PixelsPerByte; p++) + { + if(CurrPixel < PassWidth[a]) + { + Mask = (1 << IHDR->BitDepth) - 1; + Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth; + + SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift); + + OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((CurrPixel * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL); + + if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + CurrPixel++; + } + } + + } + else + { + OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((w * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL); + + if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + } + + DecompPtr += BytesPerPixel; + } + } + } + + return(qtrue); +} + +/* + * The PNG loader + */ + +void R_LoadPNG(const char *name, byte **pic, int *width, int *height) +{ + struct BufferedFile *ThePNG; + byte *OutBuffer; + uint8_t *Signature; + struct PNG_ChunkHeader *CH; + uint32_t ChunkHeaderLength; + uint32_t ChunkHeaderType; + struct PNG_Chunk_IHDR *IHDR; + uint32_t IHDR_Width; + uint32_t IHDR_Height; + PNG_ChunkCRC *CRC; + uint8_t *InPal; + uint8_t *DecompressedData; + uint32_t DecompressedDataLength; + uint32_t i; + + /* + * palette with 256 RGBA entries + */ + + uint8_t OutPal[1024]; + + /* + * transparent colour from the tRNS chunk + */ + + qboolean HasTransparentColour = qfalse; + uint8_t TransparentColour[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + /* + * input verification + */ + + if(!(name && pic)) + { + return; + } + + /* + * Zero out return values. + */ + + *pic = NULL; + + if(width) + { + *width = 0; + } + + if(height) + { + *height = 0; + } + + /* + * Read the file. + */ + + ThePNG = ReadBufferedFile(name); + if(!ThePNG) + { + return; + } + + /* + * Read the siganture of the file. + */ + + Signature = BufferedFileRead(ThePNG, PNG_Signature_Size); + if(!Signature) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Is it a PNG? + */ + + if(memcmp(Signature, PNG_Signature, PNG_Signature_Size)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the first chunk-header. + */ + + CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the first chunk is an IHDR. + */ + + if(!((ChunkHeaderType == PNG_ChunkType_IHDR) && (ChunkHeaderLength == PNG_Chunk_IHDR_Size))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the IHDR. + */ + + IHDR = BufferedFileRead(ThePNG, PNG_Chunk_IHDR_Size); + if(!IHDR) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC for IHDR + */ + + CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Here we could check the CRC if we wanted to. + */ + + /* + * multi-byte type swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * Check if Width and Height are valid. + */ + + if(!((IHDR_Width > 0) && (IHDR_Height > 0)) + || IHDR_Width > INT_MAX / Q3IMAGE_BYTESPERPIXEL / IHDR_Height) + { + CloseBufferedFile(ThePNG); + + Com_Printf(S_COLOR_YELLOW "%s: invalid image size\n", name); + + return; + } + + /* + * Do we need to check if the dimensions of the image are valid for Quake3? + */ + + /* + * Check if CompressionMethod and FilterMethod are valid. + */ + + if(!((IHDR->CompressionMethod == PNG_CompressionMethod_0) && (IHDR->FilterMethod == PNG_FilterMethod_0))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Check if InterlaceMethod is valid. + */ + + if(!((IHDR->InterlaceMethod == PNG_InterlaceMethod_NonInterlaced) || (IHDR->InterlaceMethod == PNG_InterlaceMethod_Interlaced))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read palette for an indexed image. + */ + + if(IHDR->ColourType == PNG_ColourType_Indexed) + { + /* + * We need the palette first. + */ + + if(!FindChunk(ThePNG, PNG_ChunkType_PLTE)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the chunk-header. + */ + + CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the chunk is an PLTE. + */ + + if(!(ChunkHeaderType == PNG_ChunkType_PLTE)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Check if Length is divisible by 3 + */ + + if(ChunkHeaderLength % 3) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the raw palette data + */ + + InPal = BufferedFileRead(ThePNG, ChunkHeaderLength); + if(!InPal) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC for the palette + */ + + CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Set some default values. + */ + + for(i = 0; i < 256; i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF; + } + + /* + * Convert to the Quake3 RGBA-format. + */ + + for(i = 0; i < (ChunkHeaderLength / 3); i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = InPal[i*3+0]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = InPal[i*3+1]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = InPal[i*3+2]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF; + } + } + + /* + * transparency information is sometimes stored in an tRNS chunk + */ + + /* + * Let's see if there is a tRNS chunk + */ + + if(FindChunk(ThePNG, PNG_ChunkType_tRNS)) + { + uint8_t *Trans; + + /* + * Read the chunk-header. + */ + + CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the chunk is an tRNS. + */ + + if(!(ChunkHeaderType == PNG_ChunkType_tRNS)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the transparency information. + */ + + Trans = BufferedFileRead(ThePNG, ChunkHeaderLength); + if(!Trans) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC. + */ + + CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Only for Grey, True and Indexed ColourType should tRNS exist. + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + if(!ChunkHeaderLength == 2) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * Grey can have one colour which is completely transparent. + * This colour is always stored in 16 bits. + */ + + TransparentColour[0] = Trans[0]; + TransparentColour[1] = Trans[1]; + + break; + } + + case PNG_ColourType_True : + { + if(!ChunkHeaderLength == 6) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * True can have one colour which is completely transparent. + * This colour is always stored in 16 bits. + */ + + TransparentColour[0] = Trans[0]; + TransparentColour[1] = Trans[1]; + TransparentColour[2] = Trans[2]; + TransparentColour[3] = Trans[3]; + TransparentColour[4] = Trans[4]; + TransparentColour[5] = Trans[5]; + + break; + } + + case PNG_ColourType_Indexed : + { + /* + * Maximum of 256 one byte transparency entries. + */ + + if(ChunkHeaderLength > 256) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * alpha values for palette entries + */ + + for(i = 0; i < ChunkHeaderLength; i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = Trans[i]; + } + + break; + } + + /* + * All other ColourTypes should not have tRNS chunks + */ + + default : + { + CloseBufferedFile(ThePNG); + + return; + } + } + } + + /* + * Rewind to the start of the file. + */ + + if(!BufferedFileRewind(ThePNG, -1)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Skip the signature + */ + + if(!BufferedFileSkip(ThePNG, PNG_Signature_Size)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Decompress all IDAT chunks + */ + + DecompressedDataLength = DecompressIDATs(ThePNG, &DecompressedData); + if(!(DecompressedDataLength && DecompressedData)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Allocate output buffer. + */ + + OutBuffer = ri.Malloc(IHDR_Width * IHDR_Height * Q3IMAGE_BYTESPERPIXEL); + if(!OutBuffer) + { + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Interlaced and Non-interlaced images need to be handled differently. + */ + + switch(IHDR->InterlaceMethod) + { + case PNG_InterlaceMethod_NonInterlaced : + { + if(!DecodeImageNonInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal)) + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + break; + } + + case PNG_InterlaceMethod_Interlaced : + { + if(!DecodeImageInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal)) + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + break; + } + + default : + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + } + + /* + * update the pointer to the image data + */ + + *pic = OutBuffer; + + /* + * Fill width and height. + */ + + if(width) + { + *width = IHDR_Width; + } + + if(height) + { + *height = IHDR_Height; + } + + /* + * DecompressedData is not needed anymore. + */ + + ri.Free(DecompressedData); + + /* + * We have all data, so close the file. + */ + + CloseBufferedFile(ThePNG); +} diff --git a/src/renderer/tr_image_tga.c b/src/renderer/tr_image_tga.c new file mode 100644 index 0000000..b5375f8 --- /dev/null +++ b/src/renderer/tr_image_tga.c @@ -0,0 +1,321 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + +void R_LoadTGA ( const char *name, byte **pic, int *width, int *height) +{ + unsigned columns, rows, numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *end; + union { + byte *b; + void *v; + } buffer; + TargaHeader targa_header; + byte *targa_rgba; + int length; + + *pic = NULL; + + if(width) + *width = 0; + if(height) + *height = 0; + + // + // load the file + // + length = ri.FS_ReadFile ( ( char * ) name, &buffer.v); + if (!buffer.b || length < 0) { + return; + } + + if(length < 18) + { + ri.Error( ERR_DROP, "LoadTGA: header too short (%s)\n", name ); + } + + buf_p = buffer.b; + end = buffer.b + length; + + targa_header.id_length = buf_p[0]; + targa_header.colormap_type = buf_p[1]; + targa_header.image_type = buf_p[2]; + + memcpy(&targa_header.colormap_index, &buf_p[3], 2); + memcpy(&targa_header.colormap_length, &buf_p[5], 2); + targa_header.colormap_size = buf_p[7]; + memcpy(&targa_header.x_origin, &buf_p[8], 2); + memcpy(&targa_header.y_origin, &buf_p[10], 2); + memcpy(&targa_header.width, &buf_p[12], 2); + memcpy(&targa_header.height, &buf_p[14], 2); + targa_header.pixel_size = buf_p[16]; + targa_header.attributes = buf_p[17]; + + targa_header.colormap_index = LittleShort(targa_header.colormap_index); + targa_header.colormap_length = LittleShort(targa_header.colormap_length); + targa_header.x_origin = LittleShort(targa_header.x_origin); + targa_header.y_origin = LittleShort(targa_header.y_origin); + targa_header.width = LittleShort(targa_header.width); + targa_header.height = LittleShort(targa_header.height); + + buf_p += 18; + + if (targa_header.image_type!=2 + && targa_header.image_type!=10 + && targa_header.image_type != 3 ) + { + ri.Error (ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n"); + } + + if ( targa_header.colormap_type != 0 ) + { + ri.Error( ERR_DROP, "LoadTGA: colormaps not supported\n" ); + } + + if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) + { + ri.Error (ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n"); + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows * 4; + + if(!columns || !rows || numPixels > 0x7FFFFFFF || numPixels / columns / 4 != rows) + { + ri.Error (ERR_DROP, "LoadTGA: %s has an invalid image size\n", name); + } + + + targa_rgba = ri.Malloc (numPixels); + + if (targa_header.id_length != 0) + { + if (buf_p + targa_header.id_length > end) + ri.Error( ERR_DROP, "LoadTGA: header too short (%s)\n", name ); + + buf_p += targa_header.id_length; // skip TARGA image comment + } + + if ( targa_header.image_type==2 || targa_header.image_type == 3 ) + { + if(buf_p + columns*rows*targa_header.pixel_size/8 > end) + { + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); + } + + // Uncompressed RGB or gray scale image + for(row=rows-1; row>=0; row--) + { + pixbuf = targa_rgba + row*columns*4; + for(column=0; column=0; row--) { + pixbuf = targa_rgba + row*columns*4; + for(column=0; column end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); + packetHeader= *buf_p++; + packetSize = 1 + (packetHeader & 0x7f); + if (packetHeader & 0x80) { // run-length packet + if(buf_p + targa_header.pixel_size/8 > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); + switch (targa_header.pixel_size) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + break; + default: + ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name ); + break; + } + + for(j=0;j0) + row--; + else + goto breakOut; + pixbuf = targa_rgba + row*columns*4; + } + } + } + else { // non run-length packet + + if(buf_p + targa_header.pixel_size/8*packetSize > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); + for(j=0;j0) + row--; + else + goto breakOut; + pixbuf = targa_rgba + row*columns*4; + } + } + } + } + breakOut:; + } + } + +#if 0 + // TTimo: this is the chunk of code to ensure a behavior that meets TGA specs + // bit 5 set => top-down + if (targa_header.attributes & 0x20) { + unsigned char *flip = (unsigned char*)malloc (columns*4); + unsigned char *src, *dst; + + for (row = 0; row < rows/2; row++) { + src = targa_rgba + row * 4 * columns; + dst = targa_rgba + (rows - row - 1) * 4 * columns; + + memcpy (flip, src, columns*4); + memcpy (src, dst, columns*4); + memcpy (dst, flip, columns*4); + } + free (flip); + } +#endif + // instead we just print a warning + if (targa_header.attributes & 0x20) { + ri.Printf( PRINT_WARNING, "WARNING: '%s' TGA file header declares top-down image, ignoring\n", name); + } + + if (width) + *width = columns; + if (height) + *height = rows; + + *pic = targa_rgba; + + ri.FS_FreeFile (buffer.v); +} diff --git a/src/renderer/tr_init.c b/src/renderer/tr_init.c new file mode 100644 index 0000000..10ea222 --- /dev/null +++ b/src/renderer/tr_init.c @@ -0,0 +1,1358 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_init.c -- functions that are not called every frame + +#include "tr_local.h" + +glconfig_t glConfig; +glstate_t glState; + +static void GfxInfo_f( void ); + +cvar_t *r_flareSize; +cvar_t *r_flareFade; +cvar_t *r_flareCoeff; + +cvar_t *r_railWidth; +cvar_t *r_railCoreWidth; +cvar_t *r_railSegmentLength; + +cvar_t *r_ignoreFastPath; + +cvar_t *r_verbose; +cvar_t *r_ignore; + +cvar_t *r_detailTextures; + +cvar_t *r_znear; +cvar_t *r_zproj; +cvar_t *r_stereoSeparation; + +cvar_t *r_smp; +cvar_t *r_showSmp; +cvar_t *r_skipBackEnd; + +cvar_t *r_stereoEnabled; +cvar_t *r_anaglyphMode; + +cvar_t *r_greyscale; + +cvar_t *r_ignorehwgamma; +cvar_t *r_measureOverdraw; + +cvar_t *r_inGameVideo; +cvar_t *r_fastsky; +cvar_t *r_drawSun; +cvar_t *r_dynamiclight; +cvar_t *r_dlightBacks; + +cvar_t *r_lodbias; +cvar_t *r_lodscale; + +cvar_t *r_norefresh; +cvar_t *r_drawentities; +cvar_t *r_drawworld; +cvar_t *r_speeds; +cvar_t *r_fullbright; +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_facePlaneCull; +cvar_t *r_showcluster; +cvar_t *r_nocurves; + +cvar_t *r_allowExtensions; + +cvar_t *r_ext_compressed_textures; +cvar_t *r_ext_multitexture; +cvar_t *r_ext_compiled_vertex_array; +cvar_t *r_ext_texture_env_add; +cvar_t *r_ext_texture_filter_anisotropic; +cvar_t *r_ext_max_anisotropy; + +cvar_t *r_ignoreGLErrors; +cvar_t *r_logFile; + +cvar_t *r_stencilbits; +cvar_t *r_depthbits; +cvar_t *r_colorbits; +cvar_t *r_primitives; +cvar_t *r_texturebits; +cvar_t *r_ext_multisample; + +cvar_t *r_drawBuffer; +cvar_t *r_lightmap; +cvar_t *r_vertexLight; +cvar_t *r_uiFullScreen; +cvar_t *r_shadows; +cvar_t *r_flares; +cvar_t *r_nobind; +cvar_t *r_singleShader; +cvar_t *r_roundImagesDown; +cvar_t *r_colorMipLevels; +cvar_t *r_picmip; +cvar_t *r_showtris; +cvar_t *r_showsky; +cvar_t *r_shownormals; +cvar_t *r_finish; +cvar_t *r_clear; +cvar_t *r_swapInterval; +cvar_t *r_textureMode; +cvar_t *r_offsetFactor; +cvar_t *r_offsetUnits; +cvar_t *r_gamma; +cvar_t *r_intensity; +cvar_t *r_lockpvs; +cvar_t *r_noportals; +cvar_t *r_portalOnly; + +cvar_t *r_subdivisions; +cvar_t *r_lodCurveError; + +cvar_t *r_fullscreen; +cvar_t *r_noborder; + +cvar_t *r_width; +cvar_t *r_height; +cvar_t *r_pixelAspect; + +cvar_t *r_overBrightBits; +cvar_t *r_mapOverBrightBits; + +cvar_t *r_debugSurface; +cvar_t *r_simpleMipMaps; + +cvar_t *r_showImages; + +cvar_t *r_ambientScale; +cvar_t *r_directedScale; +cvar_t *r_debugLight; +cvar_t *r_debugSort; +cvar_t *r_printShaders; +cvar_t *r_saveFontData; + +cvar_t *r_marksOnTriangleMeshes; + +cvar_t *r_aviMotionJpegQuality; +cvar_t *r_screenshotJpegQuality; + +cvar_t *r_maxpolys; +int max_polys; +cvar_t *r_maxpolyverts; +int max_polyverts; + +#define GENERIC_HW_R_PICMIP_DEFAULT "0" +#define GENERIC_HW_R_TEXTUREMODE_DEFAULT "GL_LINEAR_MIPMAP_LINEAR" + +/* +================== +GL_ResolveHardwareType + +Chipset specific configuration +================== +*/ +void GL_ResolveHardwareType( void ) +{ + char buf[ 1024 ]; + cvar_t *lastValidRenderer = ri.Cvar_Get( + "r_lastValidRenderer", "(uninitialized)", CVAR_ARCHIVE ); + + Q_strncpyz( buf, glConfig.renderer_string, sizeof( buf ) ); + Q_strlwr( buf ); + + // NOTE: if changing cvars, do it within this block. This allows them + // to be overridden when testing driver fixes, etc. but only sets + // them to their default state when the hardware is first installed/run. + if( Q_stricmp( lastValidRenderer->string, glConfig.renderer_string ) ) + { + glConfig.hardwareType = GLHW_GENERIC; + + ri.Cvar_Set( "r_textureMode", GENERIC_HW_R_TEXTUREMODE_DEFAULT ); + + // VOODOO GRAPHICS w/ 2MB + if ( strstr( buf, "voodoo graphics/1 tmu/2 mb" ) ) + { + ri.Cvar_Set( "r_picmip", "2" ); + ri.Cvar_Get( "r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH ); + } + else + { + ri.Cvar_Set( "r_picmip", GENERIC_HW_R_PICMIP_DEFAULT ); + + if ( strstr( buf, "rage 128" ) || strstr( buf, "rage128" ) ) + { + ri.Cvar_Set( "r_finish", "0" ); + } + // Savage3D and Savage4 should always have trilinear enabled + else if ( strstr( buf, "savage3d" ) || strstr( buf, "s3 savage4" ) ) + { + ri.Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + } + } + } + + // + // this is where hardware specific workarounds that should be + // detected/initialized every startup should go. + // + if ( strstr( buf, "banshee" ) || strstr( buf, "voodoo3" ) ) + { + glConfig.hardwareType = GLHW_3DFX_2D3D; + } + // VOODOO GRAPHICS w/ 2MB + else if ( strstr( buf, "voodoo graphics/1 tmu/2 mb" ) ) + { + } + else if ( strstr( buf, "glzicd" ) ) + { + } + else if ( strstr( buf, "rage pro" ) || + strstr( buf, "Rage Pro" ) || + strstr( buf, "ragepro" ) ) + { + glConfig.hardwareType = GLHW_RAGEPRO; + } + else if ( strstr( buf, "rage 128" ) ) + { + } + else if ( strstr( buf, "permedia2" ) ) + { + glConfig.hardwareType = GLHW_PERMEDIA2; + } + else if ( strstr( buf, "riva 128" ) ) + { + glConfig.hardwareType = GLHW_RIVA128; + } + else if ( strstr( buf, "riva tnt " ) ) + { + } +} + +/* +** InitOpenGL +** +** This function is responsible for initializing a valid OpenGL subsystem. This +** is done by calling GLimp_Init (which gives us a working OGL subsystem) then +** setting variables, checking GL constants, and reporting the gfx system config +** to the user. +*/ +static void InitOpenGL( void ) +{ + char renderer_buffer[1024]; + + // + // initialize OS specific portions of the renderer + // + // GLimp_Init directly or indirectly references the following cvars: + // - r_fullscreen + // - r_(width|height|pixelAspect) + // - r_(color|depth|stencil)bits + // - r_ignorehwgamma + // - r_gamma + // + + if ( glConfig.vidWidth == 0 ) + { + GLint temp; + + GLimp_Init(); + + strcpy( renderer_buffer, glConfig.renderer_string ); + Q_strlwr( renderer_buffer ); + + // OpenGL driver constants + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp ); + glConfig.maxTextureSize = temp; + + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) + { + glConfig.maxTextureSize = 0; + } + } + + // init command buffers and SMP + R_InitCommandBuffers(); + + // print info + GfxInfo_f(); + + // set default state + GL_SetDefaultState(); +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrors( void ) { + int err; + char s[64]; + + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + if ( r_ignoreGLErrors->integer ) { + return; + } + switch( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + Com_sprintf( s, sizeof(s), "%i", err); + break; + } + + ri.Error( ERR_FATAL, "GL_CheckErrors: %s", s ); +} + + +/* +============================================================================== + + SCREEN SHOTS + +NOTE TTimo +some thoughts about the screenshots system: +screenshots get written in fs_homepath + fs_gamedir +vanilla q3 .. baseq3/screenshots/ *.tga +team arena .. missionpack/screenshots/ *.tga + +two commands: "screenshot" and "screenshotJPEG" +we use statics to store a count and start writing the first screenshot/screenshot????.tga (.jpg) available +(with FS_FileExists / FS_FOpenFileWrite calls) +FIXME: the statics don't get a reinit between fs_game changes + +============================================================================== +*/ + +/* +================== +RB_ReadPixels + +Reads an image but takes care of alignment issues for reading RGB images. + +Reads a minimum offset for where the RGB data starts in the image from +integer stored at pointer offset. When the function has returned the actual +offset was written back to address offset. This address will always have an +alignment of packAlign to ensure efficient copying. + +Stores the length of padding after a line of pixels to address padlen + +Return value must be freed with ri.Hunk_FreeTempMemory() +================== +*/ + +byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen) +{ + byte *buffer, *bufstart; + int padwidth, linelen; + GLint packAlign; + + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); + + linelen = width * 3; + padwidth = PAD(linelen, packAlign); + + // Allocate a few more bytes so that we can choose an alignment we like + buffer = ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1); + + bufstart = (byte *) PAD((intptr_t) buffer + *offset, packAlign); + qglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, bufstart); + + *offset = bufstart - buffer; + *padlen = padwidth - linelen; + + return buffer; +} + +/* +================== +RB_TakeScreenshot +================== +*/ +void RB_TakeScreenshot(int x, int y, int width, int height, char *fileName) +{ + byte *allbuf, *buffer; + byte *srcptr, *destptr; + byte *endline, *endmem; + byte temp; + + int linelen, padlen; + size_t offset = 18, memcount; + + allbuf = RB_ReadPixels(x, y, width, height, &offset, &padlen); + buffer = allbuf + offset - 18; + + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + // swap rgb to bgr and remove padding from line endings + linelen = width * 3; + + srcptr = destptr = allbuf + offset; + endmem = srcptr + (linelen + padlen) * height; + + while(srcptr < endmem) + { + endline = srcptr + linelen; + + while(srcptr < endline) + { + temp = srcptr[0]; + *destptr++ = srcptr[2]; + *destptr++ = srcptr[1]; + *destptr++ = temp; + + srcptr += 3; + } + + // Skip the pad + srcptr += padlen; + } + + memcount = linelen * height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(allbuf + offset, memcount); + + ri.FS_WriteFile(fileName, buffer, memcount + 18); + + ri.Hunk_FreeTempMemory(allbuf); +} + +/* +================== +RB_TakeScreenshotJPEG +================== +*/ + +void RB_TakeScreenshotJPEG(int x, int y, int width, int height, char *fileName) +{ + byte *buffer; + size_t offset = 0, memcount; + int padlen; + + buffer = RB_ReadPixels(x, y, width, height, &offset, &padlen); + memcount = (width * 3 + padlen) * height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(buffer + offset, memcount); + + RE_SaveJPG(fileName, r_screenshotJpegQuality->integer, width, height, buffer + offset, padlen); + ri.Hunk_FreeTempMemory(buffer); +} + +/* +================== +RB_TakeScreenshotCmd +================== +*/ +const void *RB_TakeScreenshotCmd( const void *data ) { + const screenshotCommand_t *cmd; + + cmd = (const screenshotCommand_t *)data; + + if (cmd->jpeg) + RB_TakeScreenshotJPEG( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName); + else + RB_TakeScreenshot( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName); + + return (const void *)(cmd + 1); +} + +/* +================== +R_TakeScreenshot +================== +*/ +void R_TakeScreenshot( int x, int y, int width, int height, char *name, qboolean jpeg ) { + static char fileName[MAX_OSPATH]; // bad things if two screenshots per frame? + screenshotCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SCREENSHOT; + + cmd->x = x; + cmd->y = y; + cmd->width = width; + cmd->height = height; + Q_strncpyz( fileName, name, sizeof(fileName) ); + cmd->fileName = fileName; + cmd->jpeg = jpeg; +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilename( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.tga" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.tga" + , a, b, c, d ); +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilenameJPEG( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.jpg" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.jpg" + , a, b, c, d ); +} + +/* +==================== +R_LevelShot + +levelshots are specialized 128*128 thumbnails for +the menu system, sampled down from full screen distorted images +==================== +*/ +void R_LevelShot( void ) { + char checkname[MAX_OSPATH]; + byte *buffer; + byte *source, *allsource; + byte *src, *dst; + size_t offset = 0; + int padlen; + int x, y; + int r, g, b; + float xScale, yScale; + int xx, yy; + + Com_sprintf(checkname, sizeof(checkname), "levelshots/%s.tga", tr.world->baseName); + + allsource = RB_ReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, &offset, &padlen); + source = allsource + offset; + + buffer = ri.Hunk_AllocateTempMemory(128 * 128*3 + 18); + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = 128; + buffer[14] = 128; + buffer[16] = 24; // pixel size + + // resample from source + xScale = glConfig.vidWidth / 512.0f; + yScale = glConfig.vidHeight / 384.0f; + for ( y = 0 ; y < 128 ; y++ ) { + for ( x = 0 ; x < 128 ; x++ ) { + r = g = b = 0; + for ( yy = 0 ; yy < 3 ; yy++ ) { + for ( xx = 0 ; xx < 4 ; xx++ ) { + src = source + (3 * glConfig.vidWidth + padlen) * (int)((y*3 + yy) * yScale) + + 3 * (int) ((x*4 + xx) * xScale); + r += src[0]; + g += src[1]; + b += src[2]; + } + } + dst = buffer + 18 + 3 * ( y * 128 + x ); + dst[0] = b / 12; + dst[1] = g / 12; + dst[2] = r / 12; + } + } + + // gamma correct + if ( glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, 128 * 128 * 3 ); + } + + ri.FS_WriteFile( checkname, buffer, 128 * 128*3 + 18 ); + + ri.Hunk_FreeTempMemory(buffer); + ri.Hunk_FreeTempMemory(allsource); + + ri.Printf( PRINT_ALL, "Wrote %s\n", checkname ); +} + +/* +================== +R_ScreenShot_f + +screenshot +screenshot [silent] +screenshot [levelshot] +screenshot [filename] + +Doesn't print the pacifier message if there is a second arg +================== +*/ +void R_ScreenShot_f (void) { + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname ); + + if (!ri.FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber >= 9999 ) { + ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, qfalse ); + + if ( !silent ) { + ri.Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +} + +void R_ScreenShotJPEG_f (void) { + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilenameJPEG( lastNumber, checkname ); + + if (!ri.FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber == 10000 ) { + ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, qtrue ); + + if ( !silent ) { + ri.Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +} + +//============================================================================ + +/* +================== +RB_TakeVideoFrameCmd +================== +*/ +const void *RB_TakeVideoFrameCmd( const void *data ) +{ + const videoFrameCommand_t *cmd; + byte *cBuf; + size_t memcount, linelen; + int padwidth, avipadwidth, padlen, avipadlen; + GLint packAlign; + + cmd = (const videoFrameCommand_t *)data; + + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); + + linelen = cmd->width * 3; + + // Alignment stuff for glReadPixels + padwidth = PAD(linelen, packAlign); + padlen = padwidth - linelen; + // AVI line padding + avipadwidth = PAD(linelen, AVI_LINE_PADDING); + avipadlen = avipadwidth - linelen; + + cBuf = (byte *) PAD((intptr_t) cmd->captureBuffer, packAlign); + + qglReadPixels(0, 0, cmd->width, cmd->height, GL_RGB, + GL_UNSIGNED_BYTE, cBuf); + + memcount = padwidth * cmd->height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(cBuf, memcount); + + if(cmd->motionJpeg) + { + memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height, + r_aviMotionJpegQuality->integer, + cmd->width, cmd->height, cBuf, padlen); + ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount); + } + else + { + byte *lineend, *memend; + byte *srcptr, *destptr; + + srcptr = cBuf; + destptr = cmd->encodeBuffer; + memend = srcptr + memcount; + + // swap R and B and remove line paddings + while(srcptr < memend) + { + lineend = srcptr + linelen; + while(srcptr < lineend) + { + *destptr++ = srcptr[2]; + *destptr++ = srcptr[1]; + *destptr++ = srcptr[0]; + srcptr += 3; + } + + Com_Memset(destptr, '\0', avipadlen); + destptr += avipadlen; + + srcptr += padlen; + } + + ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, avipadwidth * cmd->height); + } + + return (const void *)(cmd + 1); +} + +//============================================================================ + +/* +** GL_SetDefaultState +*/ +void GL_SetDefaultState( void ) +{ + qglClearDepth( 1.0f ); + + qglCullFace(GL_FRONT); + + qglColor4f (1,1,1,1); + + // initialize downstream texture unit if we're running + // in a multitexture environment + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture( 0 ); + } + + qglEnable(GL_TEXTURE_2D); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + + qglShadeModel( GL_SMOOTH ); + qglDepthFunc( GL_LEQUAL ); + + // the vertex array is always enabled, but the color and texture + // arrays are enabled and disabled around the compiled vertex array call + qglEnableClientState (GL_VERTEX_ARRAY); + + // + // make sure our GL state vector is set correctly + // + glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE; + + qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + qglDepthMask( GL_TRUE ); + qglDisable( GL_DEPTH_TEST ); + qglEnable( GL_SCISSOR_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_BLEND ); +} + + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( void ) +{ + const char *enablestrings[] = + { + "disabled", + "enabled" + }; + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + ri.Printf( PRINT_ALL, "\nGL_VENDOR: %s\n", glConfig.vendor_string ); + ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string ); + ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string ); + ri.Printf( PRINT_ALL, "GL_EXTENSIONS: %s\n", glConfig.extensions_string ); + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.numTextureUnits ); + ri.Printf( PRINT_ALL, "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + ri.Printf( PRINT_ALL, "MODE: %d x %d %s hz:", glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] ); + if ( glConfig.displayFrequency ) + { + ri.Printf( PRINT_ALL, "%d\n", glConfig.displayFrequency ); + } + else + { + ri.Printf( PRINT_ALL, "N/A\n" ); + } + if ( glConfig.deviceSupportsGamma ) + { + ri.Printf( PRINT_ALL, "GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits ); + } + else + { + ri.Printf( PRINT_ALL, "GAMMA: software w/ %d overbright bits\n", tr.overbrightBits ); + } + + // rendering primitives + { + int primitives; + + // default is to use triangles if compiled vertex arrays are present + ri.Printf( PRINT_ALL, "rendering primitives: " ); + primitives = r_primitives->integer; + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + if ( primitives == -1 ) { + ri.Printf( PRINT_ALL, "none\n" ); + } else if ( primitives == 2 ) { + ri.Printf( PRINT_ALL, "single glDrawElements\n" ); + } else if ( primitives == 1 ) { + ri.Printf( PRINT_ALL, "multiple glArrayElement\n" ); + } else if ( primitives == 3 ) { + ri.Printf( PRINT_ALL, "multiple glColor4ubv + glTexCoord2fv + glVertex3fv\n" ); + } + } + + ri.Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string ); + ri.Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer ); + ri.Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer ); + ri.Printf( PRINT_ALL, "multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] ); + ri.Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] ); + ri.Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] ); + ri.Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression!=TC_NONE] ); + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) + { + ri.Printf( PRINT_ALL, "HACK: using vertex lightmap approximation\n" ); + } + if ( glConfig.hardwareType == GLHW_RAGEPRO ) + { + ri.Printf( PRINT_ALL, "HACK: ragePro approximations\n" ); + } + if ( glConfig.hardwareType == GLHW_RIVA128 ) + { + ri.Printf( PRINT_ALL, "HACK: riva128 approximations\n" ); + } + if ( glConfig.smpActive ) { + ri.Printf( PRINT_ALL, "Using dual processor acceleration\n" ); + } + if ( r_finish->integer ) { + ri.Printf( PRINT_ALL, "Forcing glFinish\n" ); + } +} + +/* +=============== +R_Register +=============== +*/ +void R_Register( void ) +{ + // + // latched and archived variables + // + r_allowExtensions = ri.Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_textures = ri.Cvar_Get( "r_ext_compressed_textures", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multitexture = ri.Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compiled_vertex_array = ri.Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH); + + r_picmip = ri.Cvar_Get ("r_picmip", GENERIC_HW_R_PICMIP_DEFAULT, + CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic", + "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_max_anisotropy = ri.Cvar_Get( "r_ext_max_anisotropy", "2", CVAR_ARCHIVE | CVAR_LATCH ); + + r_roundImagesDown = ri.Cvar_Get ("r_roundImagesDown", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorMipLevels = ri.Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH ); + ri.Cvar_CheckRange( r_picmip, 0, 16, qtrue ); + r_detailTextures = ri.Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebits = ri.Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorbits = ri.Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_stencilbits = ri.Cvar_Get( "r_stencilbits", "8", CVAR_ARCHIVE | CVAR_LATCH ); + r_depthbits = ri.Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multisample = ri.Cvar_Get( "r_ext_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH ); + ri.Cvar_CheckRange( r_ext_multisample, 0, 4, qtrue ); + r_overBrightBits = ri.Cvar_Get ("r_overBrightBits", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ignorehwgamma = ri.Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE ); + r_noborder = Cvar_Get("r_noborder", "0", CVAR_ARCHIVE); + r_width = ri.Cvar_Get( "r_width", "640", CVAR_ARCHIVE | CVAR_LATCH ); + r_height = ri.Cvar_Get( "r_height", "480", CVAR_ARCHIVE | CVAR_LATCH ); + r_pixelAspect = ri.Cvar_Get( "r_pixelAspect", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_simpleMipMaps = ri.Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_vertexLight = ri.Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_uiFullScreen = ri.Cvar_Get( "r_uifullscreen", "0", 0); + r_subdivisions = ri.Cvar_Get ("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH); + r_smp = ri.Cvar_Get( "r_smp", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_stereoEnabled = ri.Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_ignoreFastPath = ri.Cvar_Get( "r_ignoreFastPath", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_greyscale = ri.Cvar_Get("r_greyscale", "0", CVAR_ARCHIVE | CVAR_LATCH); + ri.Cvar_CheckRange(r_greyscale, 0, 1, qfalse); + + // + // temporary latched variables that can only change over a restart + // + r_fullbright = ri.Cvar_Get ("r_fullbright", "0", CVAR_LATCH|CVAR_CHEAT ); + r_mapOverBrightBits = ri.Cvar_Get ("r_mapOverBrightBits", "2", CVAR_LATCH ); + r_intensity = ri.Cvar_Get ("r_intensity", "1", CVAR_LATCH ); + r_singleShader = ri.Cvar_Get ("r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH ); + + // + // archived variables that can change at any time + // + r_lodCurveError = ri.Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE|CVAR_CHEAT ); + r_lodbias = ri.Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE ); + r_flares = ri.Cvar_Get ("r_flares", "0", CVAR_ARCHIVE ); + r_znear = ri.Cvar_Get( "r_znear", "1", CVAR_CHEAT ); + ri.Cvar_CheckRange( r_znear, 0.001f, 200, qfalse ); + r_zproj = ri.Cvar_Get( "r_zproj", "64", CVAR_ARCHIVE ); + r_stereoSeparation = ri.Cvar_Get( "r_stereoSeparation", "64", CVAR_ARCHIVE ); + r_ignoreGLErrors = ri.Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE ); + r_fastsky = ri.Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE ); + r_inGameVideo = ri.Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE ); + r_drawSun = ri.Cvar_Get( "r_drawSun", "0", CVAR_ARCHIVE ); + r_dynamiclight = ri.Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE ); + r_dlightBacks = ri.Cvar_Get( "r_dlightBacks", "1", CVAR_ARCHIVE ); + r_finish = ri.Cvar_Get ("r_finish", "0", CVAR_ARCHIVE); + r_textureMode = ri.Cvar_Get( "r_textureMode", + GENERIC_HW_R_TEXTUREMODE_DEFAULT, CVAR_ARCHIVE ); + r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0", + CVAR_ARCHIVE | CVAR_LATCH ); + r_gamma = ri.Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE ); + r_facePlaneCull = ri.Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE ); + + r_railWidth = ri.Cvar_Get( "r_railWidth", "16", CVAR_ARCHIVE ); + r_railCoreWidth = ri.Cvar_Get( "r_railCoreWidth", "6", CVAR_ARCHIVE ); + r_railSegmentLength = ri.Cvar_Get( "r_railSegmentLength", "32", CVAR_ARCHIVE ); + + r_primitives = ri.Cvar_Get( "r_primitives", "0", CVAR_ARCHIVE ); + + r_ambientScale = ri.Cvar_Get( "r_ambientScale", "0.6", CVAR_CHEAT ); + r_directedScale = ri.Cvar_Get( "r_directedScale", "1", CVAR_CHEAT ); + + r_anaglyphMode = ri.Cvar_Get("r_anaglyphMode", "0", CVAR_ARCHIVE); + + // + // temporary variables that can change at any time + // + r_showImages = ri.Cvar_Get( "r_showImages", "0", CVAR_TEMP ); + + r_debugLight = ri.Cvar_Get( "r_debuglight", "0", CVAR_TEMP ); + r_debugSort = ri.Cvar_Get( "r_debugSort", "0", CVAR_CHEAT ); + r_printShaders = ri.Cvar_Get( "r_printShaders", "0", 0 ); + r_saveFontData = ri.Cvar_Get( "r_saveFontData", "0", 0 ); + + r_nocurves = ri.Cvar_Get ("r_nocurves", "0", CVAR_CHEAT ); + r_drawworld = ri.Cvar_Get ("r_drawworld", "1", CVAR_CHEAT ); + r_lightmap = ri.Cvar_Get ("r_lightmap", "0", CVAR_CHEAT ); + r_portalOnly = ri.Cvar_Get ("r_portalOnly", "0", CVAR_CHEAT ); + + r_flareSize = ri.Cvar_Get ("r_flareSize", "40", CVAR_CHEAT); + r_flareFade = ri.Cvar_Get ("r_flareFade", "7", CVAR_CHEAT); + r_flareCoeff = ri.Cvar_Get ("r_flareCoeff", FLARE_STDCOEFF, CVAR_CHEAT); + + r_showSmp = ri.Cvar_Get ("r_showSmp", "0", CVAR_CHEAT); + r_skipBackEnd = ri.Cvar_Get ("r_skipBackEnd", "0", CVAR_CHEAT); + + r_measureOverdraw = ri.Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT ); + r_lodscale = ri.Cvar_Get( "r_lodscale", "5", CVAR_CHEAT ); + r_norefresh = ri.Cvar_Get ("r_norefresh", "0", CVAR_CHEAT); + r_drawentities = ri.Cvar_Get ("r_drawentities", "1", CVAR_CHEAT ); + r_ignore = ri.Cvar_Get( "r_ignore", "1", CVAR_CHEAT ); + r_nocull = ri.Cvar_Get ("r_nocull", "0", CVAR_CHEAT); + r_novis = ri.Cvar_Get ("r_novis", "0", CVAR_CHEAT); + r_showcluster = ri.Cvar_Get ("r_showcluster", "0", CVAR_CHEAT); + r_speeds = ri.Cvar_Get ("r_speeds", "0", CVAR_CHEAT); + r_verbose = ri.Cvar_Get( "r_verbose", "0", CVAR_CHEAT ); + r_logFile = ri.Cvar_Get( "r_logFile", "0", CVAR_CHEAT ); + r_debugSurface = ri.Cvar_Get ("r_debugSurface", "0", CVAR_CHEAT); + r_nobind = ri.Cvar_Get ("r_nobind", "0", CVAR_CHEAT); + r_showtris = ri.Cvar_Get ("r_showtris", "0", CVAR_CHEAT); + r_showsky = ri.Cvar_Get ("r_showsky", "0", CVAR_CHEAT); + r_shownormals = ri.Cvar_Get ("r_shownormals", "0", CVAR_CHEAT); + r_clear = ri.Cvar_Get ("r_clear", "0", CVAR_CHEAT); + r_offsetFactor = ri.Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT ); + r_offsetUnits = ri.Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT ); + r_drawBuffer = ri.Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT ); + r_lockpvs = ri.Cvar_Get ("r_lockpvs", "0", CVAR_CHEAT); + r_noportals = ri.Cvar_Get ("r_noportals", "0", CVAR_CHEAT); + r_shadows = ri.Cvar_Get( "cg_shadows", "1", 0 ); + + r_marksOnTriangleMeshes = ri.Cvar_Get("r_marksOnTriangleMeshes", "0", CVAR_ARCHIVE); + + r_aviMotionJpegQuality = ri.Cvar_Get("r_aviMotionJpegQuality", "90", CVAR_ARCHIVE); + r_screenshotJpegQuality = ri.Cvar_Get("r_screenshotJpegQuality", "90", CVAR_ARCHIVE); + + r_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0); + r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0); + + // make sure all the commands added here are also + // removed in R_Shutdown + ri.Cmd_AddCommand( "imagelist", R_ImageList_f ); + ri.Cmd_AddCommand( "shaderlist", R_ShaderList_f ); + ri.Cmd_AddCommand( "skinlist", R_SkinList_f ); + ri.Cmd_AddCommand( "modellist", R_Modellist_f ); + ri.Cmd_AddCommand( "screenshot", R_ScreenShot_f ); + ri.Cmd_AddCommand( "screenshotJPEG", R_ScreenShotJPEG_f ); + ri.Cmd_AddCommand( "gfxinfo", GfxInfo_f ); +} + +/* +=============== +R_Init +=============== +*/ +void R_Init( void ) { + int err; + int i; + byte *ptr; + + ri.Printf( PRINT_ALL, "----- R_Init -----\n" ); + + // clear all our internal state + Com_Memset( &tr, 0, sizeof( tr ) ); + Com_Memset( &backEnd, 0, sizeof( backEnd ) ); + Com_Memset( &tess, 0, sizeof( tess ) ); + +// Swap_Init(); + + if ( (intptr_t)tess.xyz & 15 ) { + Com_Printf( "WARNING: tess.xyz not 16 byte aligned\n" ); + } + Com_Memset( tess.constantColor255, 255, sizeof( tess.constantColor255 ) ); + + // + // init function tables + // + for ( i = 0; i < FUNCTABLE_SIZE; i++ ) + { + tr.sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) ); + tr.squareTable[i] = ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f; + tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE; + tr.inverseSawToothTable[i] = 1.0f - tr.sawToothTable[i]; + + if ( i < FUNCTABLE_SIZE / 2 ) + { + if ( i < FUNCTABLE_SIZE / 4 ) + { + tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 ); + } + else + { + tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4]; + } + } + else + { + tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2]; + } + } + + R_InitFogTable(); + + R_NoiseInit(); + + R_Register(); + + max_polys = r_maxpolys->integer; + if (max_polys < MAX_POLYS) + max_polys = MAX_POLYS; + + max_polyverts = r_maxpolyverts->integer; + if (max_polyverts < MAX_POLYVERTS) + max_polyverts = MAX_POLYVERTS; + + ptr = ri.Hunk_Alloc( sizeof( *backEndData[0] ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low); + backEndData[0] = (backEndData_t *) ptr; + backEndData[0]->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData[0] )); + backEndData[0]->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData[0] ) + sizeof(srfPoly_t) * max_polys); + if ( r_smp->integer ) { + ptr = ri.Hunk_Alloc( sizeof( *backEndData[1] ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low); + backEndData[1] = (backEndData_t *) ptr; + backEndData[1]->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData[1] )); + backEndData[1]->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData[1] ) + sizeof(srfPoly_t) * max_polys); + } else { + backEndData[1] = NULL; + } + R_ToggleSmpFrame(); + + InitOpenGL(); + + R_InitImages(); + + R_InitShaders(); + + R_InitSkins(); + + R_ModelInit(); + + R_InitFreeType(); + + + err = qglGetError(); + if ( err != GL_NO_ERROR ) + ri.Printf (PRINT_ALL, "glGetError() = 0x%x\n", err); + + ri.Printf( PRINT_ALL, "----- finished R_Init -----\n" ); +} + +/* +=============== +RE_Shutdown +=============== +*/ +void RE_Shutdown( qboolean destroyWindow ) { + + ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow ); + + ri.Cmd_RemoveCommand ("modellist"); + ri.Cmd_RemoveCommand ("screenshotJPEG"); + ri.Cmd_RemoveCommand ("screenshot"); + ri.Cmd_RemoveCommand ("imagelist"); + ri.Cmd_RemoveCommand ("shaderlist"); + ri.Cmd_RemoveCommand ("skinlist"); + ri.Cmd_RemoveCommand ("gfxinfo"); + ri.Cmd_RemoveCommand( "modelist" ); + ri.Cmd_RemoveCommand( "shaderstate" ); + + + if ( tr.registered ) { + R_SyncRenderThread(); + R_ShutdownCommandBuffers(); + R_DeleteTextures(); + } + + R_DoneFreeType(); + + // shut down platform specific OpenGL stuff + if ( destroyWindow ) { + GLimp_Shutdown(); + } + + tr.registered = qfalse; +} + + +/* +============= +RE_EndRegistration + +Touch all images to make sure they are resident +============= +*/ +void RE_EndRegistration( void ) { + R_SyncRenderThread(); + if (!Sys_LowPhysicalMemory()) { + RB_ShowImages(); + } +} + + +/* +@@@@@@@@@@@@@@@@@@@@@ +GetRefAPI + +@@@@@@@@@@@@@@@@@@@@@ +*/ +refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) { + static refexport_t re; + + ri = *rimp; + + Com_Memset( &re, 0, sizeof( re ) ); + + if ( apiVersion != REF_API_VERSION ) { + ri.Printf(PRINT_ALL, "Mismatched REF_API_VERSION: expected %i, got %i\n", + REF_API_VERSION, apiVersion ); + return NULL; + } + + // the RE_ functions are Renderer Entry points + + re.Shutdown = RE_Shutdown; + + re.BeginRegistration = RE_BeginRegistration; + re.RegisterModel = RE_RegisterModel; + re.RegisterSkin = RE_RegisterSkin; + re.RegisterShader = RE_RegisterShader; + re.RegisterShaderNoMip = RE_RegisterShaderNoMip; + re.LoadWorld = RE_LoadWorldMap; + re.SetWorldVisData = RE_SetWorldVisData; + re.EndRegistration = RE_EndRegistration; + + re.BeginFrame = RE_BeginFrame; + re.EndFrame = RE_EndFrame; + + re.MarkFragments = R_MarkFragments; + re.LerpTag = R_LerpTag; + re.ModelBounds = R_ModelBounds; + + re.ClearScene = RE_ClearScene; + re.AddRefEntityToScene = RE_AddRefEntityToScene; + re.AddPolyToScene = RE_AddPolyToScene; + re.LightForPoint = R_LightForPoint; + re.AddLightToScene = RE_AddLightToScene; + re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene; + re.RenderScene = RE_RenderScene; + + re.SetColor = RE_SetColor; + re.SetClipRegion = RE_SetClipRegion; + re.DrawStretchPic = RE_StretchPic; + re.DrawStretchRaw = RE_StretchRaw; + re.UploadCinematic = RE_UploadCinematic; + + re.RegisterFont = RE_RegisterFont; + re.RemapShader = R_RemapShader; + re.GetEntityToken = R_GetEntityToken; + re.inPVS = R_inPVS; + + re.TakeVideoFrame = RE_TakeVideoFrame; + + return &re; +} diff --git a/src/renderer/tr_light.c b/src/renderer/tr_light.c new file mode 100644 index 0000000..18dea31 --- /dev/null +++ b/src/renderer/tr_light.c @@ -0,0 +1,395 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_light.c + +#include "tr_local.h" + +#define DLIGHT_AT_RADIUS 16 +// at the edge of a dlight's influence, this amount of light will be added + +#define DLIGHT_MINIMUM_RADIUS 16 +// never calculate a range less than this to prevent huge light numbers + + +/* +=============== +R_TransformDlights + +Transforms the origins of an array of dlights. +Used by both the front end (for DlightBmodel) and +the back end (before doing the lighting calculation) +=============== +*/ +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or) { + int i; + vec3_t temp; + + for ( i = 0 ; i < count ; i++, dl++ ) { + VectorSubtract( dl->origin, or->origin, temp ); + dl->transformed[0] = DotProduct( temp, or->axis[0] ); + dl->transformed[1] = DotProduct( temp, or->axis[1] ); + dl->transformed[2] = DotProduct( temp, or->axis[2] ); + } +} + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +void R_DlightBmodel( bmodel_t *bmodel ) { + int i, j; + dlight_t *dl; + int mask; + msurface_t *surf; + + // transform all the lights + R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.or ); + + mask = 0; + for ( i=0 ; itransformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + if ( j < 3 ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + + tr.currentEntity->needDlights = (mask != 0); + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = bmodel->firstSurface + i; + + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask; + } + } +} + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +extern cvar_t *r_ambientScale; +extern cvar_t *r_directedScale; +extern cvar_t *r_debugLight; + +/* +================= +R_SetupEntityLightingGrid + +================= +*/ +static void R_SetupEntityLightingGrid( trRefEntity_t *ent ) { + vec3_t lightOrigin; + int pos[3]; + int i, j; + byte *gridData; + float frac[3]; + int gridStep[3]; + vec3_t direction; + float totalFactor; + + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + VectorSubtract( lightOrigin, tr.world->lightGridOrigin, lightOrigin ); + for ( i = 0 ; i < 3 ; i++ ) { + float v; + + v = lightOrigin[i]*tr.world->lightGridInverseSize[i]; + pos[i] = floor( v ); + frac[i] = v - pos[i]; + if ( pos[i] < 0 ) { + pos[i] = 0; + } else if ( pos[i] >= tr.world->lightGridBounds[i] - 1 ) { + pos[i] = tr.world->lightGridBounds[i] - 1; + } + } + + VectorClear( ent->ambientLight ); + VectorClear( ent->directedLight ); + VectorClear( direction ); + + assert( tr.world->lightGridData ); // NULL with -nolight maps + + // trilerp the light value + gridStep[0] = 8; + gridStep[1] = 8 * tr.world->lightGridBounds[0]; + gridStep[2] = 8 * tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1]; + gridData = tr.world->lightGridData + pos[0] * gridStep[0] + + pos[1] * gridStep[1] + pos[2] * gridStep[2]; + + totalFactor = 0; + for ( i = 0 ; i < 8 ; i++ ) { + float factor; + byte *data; + int lat, lng; + vec3_t normal; + #if idppc + float d0, d1, d2, d3, d4, d5; + #endif + factor = 1.0; + data = gridData; + for ( j = 0 ; j < 3 ; j++ ) { + if ( i & (1<ambientLight[0] += factor * d0; + ent->ambientLight[1] += factor * d1; + ent->ambientLight[2] += factor * d2; + + ent->directedLight[0] += factor * d3; + ent->directedLight[1] += factor * d4; + ent->directedLight[2] += factor * d5; + #else + ent->ambientLight[0] += factor * data[0]; + ent->ambientLight[1] += factor * data[1]; + ent->ambientLight[2] += factor * data[2]; + + ent->directedLight[0] += factor * data[3]; + ent->directedLight[1] += factor * data[4]; + ent->directedLight[2] += factor * data[5]; + #endif + lat = data[7]; + lng = data[6]; + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + normal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + VectorMA( direction, factor, normal, direction ); + } + + if ( totalFactor > 0 && totalFactor < 0.99 ) { + totalFactor = 1.0f / totalFactor; + VectorScale( ent->ambientLight, totalFactor, ent->ambientLight ); + VectorScale( ent->directedLight, totalFactor, ent->directedLight ); + } + + VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight ); + VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight ); + + VectorNormalize2( direction, ent->lightDir ); +} + + +/* +=============== +LogLight +=============== +*/ +static void LogLight( trRefEntity_t *ent ) { + int max1, max2; + + if ( !(ent->e.renderfx & RF_FIRST_PERSON ) ) { + return; + } + + max1 = ent->ambientLight[0]; + if ( ent->ambientLight[1] > max1 ) { + max1 = ent->ambientLight[1]; + } else if ( ent->ambientLight[2] > max1 ) { + max1 = ent->ambientLight[2]; + } + + max2 = ent->directedLight[0]; + if ( ent->directedLight[1] > max2 ) { + max2 = ent->directedLight[1]; + } else if ( ent->directedLight[2] > max2 ) { + max2 = ent->directedLight[2]; + } + + ri.Printf( PRINT_ALL, "amb:%i dir:%i\n", max1, max2 ); +} + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { + int i; + dlight_t *dl; + float power; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t lightOrigin; + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = qtrue; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + VectorCopy( tr.sunDirection, ent->lightDir ); + } + + // bonus items and view weapons have a fixed minimum add + if ( 1 /* ent->e.renderfx & RF_MINLIGHT */ ) { + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + } + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + + for ( i = 0 ; i < refdef->num_dlights ; i++ ) { + dl = &refdef->dlights[i]; + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + d = power / ( d * d ); + + VectorMA( ent->directedLight, d, dl->color, ent->directedLight ); + VectorMA( lightDir, d, dir, lightDir ); + } + + // clamp ambient + for ( i = 0 ; i < 3 ; i++ ) { + if ( ent->ambientLight[i] > tr.identityLightByte ) { + ent->ambientLight[i] = tr.identityLightByte; + } + } + + if ( r_debugLight->integer ) { + LogLight( ent ); + } + + // save out the byte packet version + ((byte *)&ent->ambientLightInt)[0] = myftol( ent->ambientLight[0] ); + ((byte *)&ent->ambientLightInt)[1] = myftol( ent->ambientLight[1] ); + ((byte *)&ent->ambientLightInt)[2] = myftol( ent->ambientLight[2] ); + ((byte *)&ent->ambientLightInt)[3] = 0xff; + + // transform the direction to local space + VectorNormalize( lightDir ); + ent->lightDir[0] = DotProduct( lightDir, ent->e.axis[0] ); + ent->lightDir[1] = DotProduct( lightDir, ent->e.axis[1] ); + ent->lightDir[2] = DotProduct( lightDir, ent->e.axis[2] ); +} + +/* +================= +R_LightForPoint +================= +*/ +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) +{ + trRefEntity_t ent; + + if ( tr.world->lightGridData == NULL ) + return qfalse; + + Com_Memset(&ent, 0, sizeof(ent)); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent ); + VectorCopy(ent.ambientLight, ambientLight); + VectorCopy(ent.directedLight, directedLight); + VectorCopy(ent.lightDir, lightDir); + + return qtrue; +} diff --git a/src/renderer/tr_local.h b/src/renderer/tr_local.h new file mode 100644 index 0000000..a1978c3 --- /dev/null +++ b/src/renderer/tr_local.h @@ -0,0 +1,1711 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#ifndef TR_LOCAL_H +#define TR_LOCAL_H + +#include "../qcommon/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" +#include "tr_public.h" +#include "qgl.h" + +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef unsigned int glIndex_t; + +// fast float to int conversion +#if id386 && !defined(__GNUC__) +long myftol( float f ); +#else +#define myftol(x) ((int)(x)) +#endif + + +// everything that is needed by the backend needs +// to be double buffered to allow it to run in +// parallel on a dual cpu machine +#define SMP_FRAMES 2 + +// 12 bits +// see QSORT_SHADERNUM_SHIFT +#define MAX_SHADERS 16384 + +//#define MAX_SHADER_STATES 2048 +#define MAX_STATES_PER_SHADER 32 +#define MAX_STATE_NAME 32 + +// can't be increased without changing bit packing for drawsurfs + + +typedef struct dlight_s { + vec3_t origin; + vec3_t color; // range from 0.0 to 1.0, should be color normalized + float radius; + + vec3_t transformed; // origin in local coordinate system + int additive; // texture detail is lost tho when the lightmap is dark +} dlight_t; + + +// a trRefEntity_t has all the information passed in by +// the client game, as well as some locally derived info +typedef struct { + refEntity_t e; + + float axisLength; // compensate for non-normalized axis + + qboolean needDlights; // true for bmodels that touch a dlight + qboolean lightingCalculated; + vec3_t lightDir; // normalized direction towards light + vec3_t ambientLight; // color normalized to 0-255 + int ambientLightInt; // 32 bit rgba packed + vec3_t directedLight; +} trRefEntity_t; + + +typedef struct { + vec3_t origin; // in world coordinates + vec3_t axis[3]; // orientation in world + vec3_t viewOrigin; // viewParms->or.origin in local coordinates + float modelMatrix[16]; +} orientationr_t; + +typedef struct image_s { + char imgName[MAX_QPATH]; // game path, including extension + int width, height; // source image + int uploadWidth, uploadHeight; // after power of two and picmip but not including clamp to MAX_TEXTURE_SIZE + GLuint texnum; // gl texture binding + + int frameUsed; // for texture usage in frame statistics + + int internalFormat; + int TMU; // only needed for voodoo2 + + qboolean mipmap; + qboolean allowPicmip; + int wrapClampMode; // GL_CLAMP_TO_EDGE or GL_REPEAT + + struct image_s* next; +} image_t; + +//=============================================================================== + +typedef enum { + SS_BAD, + SS_PORTAL, // mirrors, portals, viewscreens + SS_ENVIRONMENT, // sky box + SS_OPAQUE, // opaque + + SS_DECAL, // scorch marks, etc. + SS_SEE_THROUGH, // ladders, grates, grills that may have small blended edges + // in addition to alpha test + SS_BANNER, + + SS_FOG, + + SS_UNDERWATER, // for items that should be drawn in front of the water plane + + SS_BLEND0, // regular transparency and filters + SS_BLEND1, // generally only used for additive type effects + SS_BLEND2, + SS_BLEND3, + + SS_BLEND6, + SS_STENCIL_SHADOW, + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST // blood blobs +} shaderSort_t; + + +#define MAX_SHADER_STAGES 8 + +typedef enum { + GF_NONE, + + GF_SIN, + GF_SQUARE, + GF_TRIANGLE, + GF_SAWTOOTH, + GF_INVERSE_SAWTOOTH, + + GF_NOISE + +} genFunc_t; + + +typedef enum { + DEFORM_NONE, + DEFORM_WAVE, + DEFORM_NORMALS, + DEFORM_BULGE, + DEFORM_MOVE, + DEFORM_PROJECTION_SHADOW, + DEFORM_AUTOSPRITE, + DEFORM_AUTOSPRITE2, + DEFORM_TEXT0, + DEFORM_TEXT1, + DEFORM_TEXT2, + DEFORM_TEXT3, + DEFORM_TEXT4, + DEFORM_TEXT5, + DEFORM_TEXT6, + DEFORM_TEXT7 +} deform_t; + +typedef enum { + AGEN_IDENTITY, + AGEN_SKIP, + AGEN_ENTITY, + AGEN_ONE_MINUS_ENTITY, + AGEN_VERTEX, + AGEN_ONE_MINUS_VERTEX, + AGEN_LIGHTING_SPECULAR, + AGEN_WAVEFORM, + AGEN_PORTAL, + AGEN_CONST +} alphaGen_t; + +typedef enum { + CGEN_BAD, + CGEN_IDENTITY_LIGHTING, // tr.identityLight + CGEN_IDENTITY, // always (1,1,1,1) + CGEN_ENTITY, // grabbed from entity's modulate field + CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate + CGEN_EXACT_VERTEX, // tess.vertexColors + CGEN_VERTEX, // tess.vertexColors * tr.identityLight + CGEN_ONE_MINUS_VERTEX, + CGEN_WAVEFORM, // programmatically generated + CGEN_LIGHTING_DIFFUSE, + CGEN_FOG, // standard fog + CGEN_CONST // fixed color +} colorGen_t; + +typedef enum { + TCGEN_BAD, + TCGEN_IDENTITY, // clear to 0,0 + TCGEN_LIGHTMAP, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR // S and T from world coordinates +} texCoordGen_t; + +typedef enum { + ACFF_NONE, + ACFF_MODULATE_RGB, + ACFF_MODULATE_RGBA, + ACFF_MODULATE_ALPHA +} acff_t; + +typedef struct { + genFunc_t func; + + float base; + float amplitude; + float phase; + float frequency; +} waveForm_t; + +#define TR_MAX_TEXMODS 4 + +typedef enum { + TMOD_NONE, + TMOD_TRANSFORM, + TMOD_TURBULENT, + TMOD_SCROLL, + TMOD_SCALE, + TMOD_STRETCH, + TMOD_ROTATE, + TMOD_ENTITY_TRANSLATE +} texMod_t; + +#define MAX_SHADER_DEFORMS 3 +typedef struct { + deform_t deformation; // vertex coordinate modification type + + vec3_t moveVector; + waveForm_t deformationWave; + float deformationSpread; + + float bulgeWidth; + float bulgeHeight; + float bulgeSpeed; +} deformStage_t; + + +typedef struct { + texMod_t type; + + // used for TMOD_TURBULENT and TMOD_STRETCH + waveForm_t wave; + + // used for TMOD_TRANSFORM + float matrix[2][2]; // s' = s * m[0][0] + t * m[1][0] + trans[0] + float translate[2]; // t' = s * m[0][1] + t * m[0][1] + trans[1] + + // used for TMOD_SCALE + float scale[2]; // s *= scale[0] + // t *= scale[1] + + // used for TMOD_SCROLL + float scroll[2]; // s' = s + scroll[0] * time + // t' = t + scroll[1] * time + + // + = clockwise + // - = counterclockwise + float rotateSpeed; + +} texModInfo_t; + + +#define MAX_IMAGE_ANIMATIONS 8 + +typedef struct { + image_t *image[MAX_IMAGE_ANIMATIONS]; + int numImageAnimations; + float imageAnimationSpeed; + + texCoordGen_t tcGen; + vec3_t tcGenVectors[2]; + + int numTexMods; + texModInfo_t *texMods; + + int videoMapHandle; + qboolean isLightmap; + qboolean vertexLightmap; + qboolean isVideoMap; +} textureBundle_t; + +#define NUM_TEXTURE_BUNDLES 2 + +typedef struct { + qboolean active; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + colorGen_t rgbGen; + + waveForm_t alphaWave; + alphaGen_t alphaGen; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned stateBits; // GLS_xxxx mask + + acff_t adjustColorsForFog; + + qboolean isDetail; +} shaderStage_t; + +struct shaderCommands_s; + +// any change in the LIGHTMAP_* defines here MUST be reflected in +// R_FindShader() in tr_bsp.c +#define LIGHTMAP_2D -4 // shader is for 2D rendering +#define LIGHTMAP_BY_VERTEX -3 // pre-lit triangle models +#define LIGHTMAP_WHITEIMAGE -2 +#define LIGHTMAP_NONE -1 + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +typedef enum { + FP_NONE, // surface is translucent and will just be adjusted properly + FP_EQUAL, // surface is opaque but possibly alpha tested + FP_LE // surface is trnaslucent, but still needs a fog pass (fog surface) +} fogPass_t; + +typedef struct { + float cloudHeight; + image_t *outerbox[6], *innerbox[6]; +} skyParms_t; + +typedef struct { + vec3_t color; + float depthForOpaque; +} fogParms_t; + + +typedef struct shader_s { + char name[MAX_QPATH]; // game path, including extension + int lightmapIndex; // for a shader to match, both name and lightmapIndex must match + + int index; // this shader == tr.shaders[index] + int sortedIndex; // this shader == tr.sortedShaders[sortedIndex] + + float sort; // lower numbered shaders draw before higher numbered + + qboolean defaultShader; // we want to return index 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + + qboolean explicitlyDefined; // found in a .shader file + + int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags + int contentFlags; + + qboolean entityMergable; // merge across entites optimizable (smoke, blood) + + qboolean isSky; + skyParms_t sky; + fogParms_t fogParms; + + float portalRange; // distance to fog out at + + int multitextureEnv; // 0, GL_MODULATE, GL_ADD (FIXME: put in stage) + + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + qboolean polygonOffset; // set for decals and other items that must be offset + qboolean noMipMaps; // for console fonts, 2D elements, etc. + qboolean noPicMip; // for images that must always be full resolution + + fogPass_t fogPass; // draw a blended pass, possibly with depth test equals + + qboolean needsNormal; // not all shaders will need all data to be gathered + qboolean needsST1; + qboolean needsST2; + qboolean needsColor; + + int numDeforms; + deformStage_t deforms[MAX_SHADER_DEFORMS]; + + int numUnfoggedPasses; + shaderStage_t *stages[MAX_SHADER_STAGES]; + + void (*optimalStageIteratorFunc)( void ); + + float clampTime; // time this shader is clamped to + float timeOffset; // current time offset for this shader + + int numStates; // if non-zero this is a state shader + struct shader_s *currentShader; // current state if this is a state shader + struct shader_s *parentShader; // current state if this is a state shader + int currentState; // current state index for cycle purposes + long expireTime; // time in milliseconds this expires + + struct shader_s *remappedShader; // current shader this one is remapped too + + int shaderStates[MAX_STATES_PER_SHADER]; // index to valid shader states + + struct shader_s *next; +} shader_t; + +typedef struct shaderState_s { + char shaderName[MAX_QPATH]; // name of shader this state belongs to + char name[MAX_STATE_NAME]; // name of this state + char stateShader[MAX_QPATH]; // shader this name invokes + int cycleTime; // time this cycle lasts, <= 0 is forever + shader_t *shader; +} shaderState_t; + + +// trRefdef_t holds everything that comes in refdef_t, +// as well as the locally generated scene information +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + stereoFrame_t stereoFrame; + + int time; // time in milliseconds for shader effects and other time dependent rendering issues + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + qboolean areamaskModified; // qtrue if areamask changed since last scene + + float floatTime; // tr.refdef.time / 1000.0 + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + int num_entities; + trRefEntity_t *entities; + + int num_dlights; + struct dlight_s *dlights; + + int numPolys; + struct srfPoly_s *polys; + + int numDrawSurfs; + struct drawSurf_s *drawSurfs; + + +} trRefdef_t; + + +//================================================================================= + +// skins allow models to be retextured without modifying the model file +typedef struct { + char name[MAX_QPATH]; + shader_t *shader; +} skinSurface_t; + +typedef struct skin_s { + char name[MAX_QPATH]; // game path, including extension + int numSurfaces; + skinSurface_t *surfaces[MD3_MAX_SURFACES]; +} skin_t; + + +typedef struct { + int originalBrushNumber; + vec3_t bounds[2]; + + unsigned colorInt; // in packed byte format + float tcScale; // texture coordinate vector scales + fogParms_t parms; + + // for clipping distance in fog when outside + qboolean hasSurface; + float surface[4]; +} fog_t; + +typedef struct { + orientationr_t or; + orientationr_t world; + vec3_t pvsOrigin; // may be different than or.origin for portals + qboolean isPortal; // true if this view is through a portal + qboolean isMirror; // the portal is a mirror, invert the face culling + int frameSceneNum; // copied from tr.frameSceneNum + int frameCount; // copied from tr.frameCount + cplane_t portalPlane; // clip anything behind this if mirroring + int viewportX, viewportY, viewportWidth, viewportHeight; + float fovX, fovY; + float projectionMatrix[16]; + cplane_t frustum[4]; + vec3_t visBounds[2]; + float zFar; + stereoFrame_t stereoFrame; +} viewParms_t; + + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ + +// any changes in surfaceType must be mirrored in rb_surfaceTable[] +typedef enum { + SF_BAD, + SF_SKIP, // ignore + SF_FACE, + SF_GRID, + SF_TRIANGLES, + SF_POLY, + SF_MD3, + SF_MD4, +#ifdef RAVENMD4 + SF_MDR, +#endif + SF_FLARE, + SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity + SF_DISPLAY_LIST, + + SF_NUM_SURFACE_TYPES, + SF_MAX = 0x7fffffff // ensures that sizeof( surfaceType_t ) == sizeof( int ) +} surfaceType_t; + +typedef struct drawSurf_s { + unsigned sort; // bit combination for fast compares + surfaceType_t *surface; // any of surface*_t +} drawSurf_t; + +#define MAX_FACE_POINTS 64 + +#define MAX_PATCH_SIZE 32 // max dimensions of a patch mesh in map file +#define MAX_GRID_SIZE 65 // max dimensions of a grid mesh in memory + +// when cgame directly specifies a polygon, it becomes a srfPoly_t +// as soon as it is called +typedef struct srfPoly_s { + surfaceType_t surfaceType; + qhandle_t hShader; + int fogIndex; + int numVerts; + polyVert_t *verts; +} srfPoly_t; + +typedef struct srfDisplayList_s { + surfaceType_t surfaceType; + int listNum; +} srfDisplayList_t; + + +typedef struct srfFlare_s { + surfaceType_t surfaceType; + vec3_t origin; + vec3_t normal; + vec3_t color; +} srfFlare_t; + +typedef struct srfGridMesh_s { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + + // culling information + vec3_t meshBounds[2]; + vec3_t localOrigin; + float meshRadius; + + // lod information, which may be different + // than the culling information to allow for + // groups of curves that LOD as a unit + vec3_t lodOrigin; + float lodRadius; + int lodFixed; + int lodStitched; + + // vertexes + int width, height; + float *widthLodError; + float *heightLodError; + drawVert_t verts[1]; // variable sized +} srfGridMesh_t; + + + +#define VERTEXSIZE 8 +typedef struct { + surfaceType_t surfaceType; + cplane_t plane; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + + // triangle definitions (no normals at points) + int numPoints; + int numIndices; + int ofsIndices; + float points[1][VERTEXSIZE]; // variable sized + // there is a variable length list of indices here also +} srfSurfaceFace_t; + + +// misc_models in maps are turned into direct geometry by q3map +typedef struct { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + + // culling information (FIXME: use this!) + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + + // triangle definitions + int numIndexes; + int *indexes; + + int numVerts; + drawVert_t *verts; +} srfTriangles_t; + + +extern void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])(void *); + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + +typedef struct msurface_s { + int viewCount; // if == tr.viewCount, already added + struct shader_s *shader; + int fogIndex; + + surfaceType_t *data; // any of srf*_t +} msurface_t; + + + +#define CONTENTS_NODE -1 +typedef struct mnode_s { + // common with leaf and node + int contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + vec3_t mins, maxs; // for bounding box culling + struct mnode_s *parent; + + // node specific + cplane_t *plane; + struct mnode_s *children[2]; + + // leaf specific + int cluster; + int area; + + msurface_t **firstmarksurface; + int nummarksurfaces; +} mnode_t; + +typedef struct { + vec3_t bounds[2]; // for culling + msurface_t *firstSurface; + int numSurfaces; +} bmodel_t; + +typedef struct { + char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp + char baseName[MAX_QPATH]; // ie: tim_dm2 + + int dataSize; + + int numShaders; + dshader_t *shaders; + + bmodel_t *bmodels; + + int numplanes; + cplane_t *planes; + + int numnodes; // includes leafs + int numDecisionNodes; + mnode_t *nodes; + + int numsurfaces; + msurface_t *surfaces; + + int nummarksurfaces; + msurface_t **marksurfaces; + + int numfogs; + fog_t *fogs; + + vec3_t lightGridOrigin; + vec3_t lightGridSize; + vec3_t lightGridInverseSize; + int lightGridBounds[3]; + byte *lightGridData; + + + int numClusters; + int clusterBytes; + const byte *vis; // may be passed in by CM_LoadMap to save space + + byte *novis; // clusterBytes of 0xff + + char *entityString; + char *entityParsePoint; +} world_t; + +//====================================================================== + +typedef enum { + MOD_BAD, + MOD_BRUSH, + MOD_MESH, + MOD_MD4, +#ifdef RAVENMD4 + MOD_MDR +#endif +} modtype_t; + +typedef struct model_s { + char name[MAX_QPATH]; + modtype_t type; + int index; // model = tr.models[model->index] + + int dataSize; // just for listing purposes + bmodel_t *bmodel; // only if type == MOD_BRUSH + md3Header_t *md3[MD3_MAX_LODS]; // only if type == MOD_MESH + void *md4; // only if type == (MOD_MD4 | MOD_MDR) + + int numLods; +} model_t; + + +#define MAX_MOD_KNOWN 1024 + +void R_ModelInit (void); +model_t *R_GetModelByHandle( qhandle_t hModel ); +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ); +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ); + +void R_Modellist_f (void); + +//==================================================== +extern refimport_t ri; + +#define MAX_DRAWIMAGES 2048 +#define MAX_SKINS 1024 + + +#define MAX_DRAWSURFS 0x10000 +#define DRAWSURF_MASK (MAX_DRAWSURFS-1) + +/* + +the drawsurf sort data is packed into a single 32 bit value so it can be +compared quickly during the qsorting process + +the bits are allocated as follows: + +21 - 31 : sorted shader index +11 - 20 : entity index +2 - 6 : fog index +//2 : used to be clipped flag REMOVED - 03.21.00 rad +0 - 1 : dlightmap index + + TTimo - 1.32 +17-31 : sorted shader index +7-16 : entity index +2-6 : fog index +0-1 : dlightmap index +*/ +#define QSORT_SHADERNUM_SHIFT 17 +#define QSORT_ENTITYNUM_SHIFT 7 +#define QSORT_FOGNUM_SHIFT 2 + +extern int gl_filter_min, gl_filter_max; + +/* +** performanceCounters_t +*/ +typedef struct { + int c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out; + int c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out; + int c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out; + int c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out; + + int c_leafs; + int c_dlightSurfaces; + int c_dlightSurfacesCulled; +} frontEndCounters_t; + +#define FOG_TABLE_SIZE 256 +#define FUNCTABLE_SIZE 1024 +#define FUNCTABLE_SIZE2 10 +#define FUNCTABLE_MASK (FUNCTABLE_SIZE-1) + + +// the renderer front end should never modify glstate_t +typedef struct { + int currenttextures[2]; + int currenttmu; + qboolean finishCalled; + int texEnv[2]; + int faceCulling; + unsigned long glStateBits; +} glstate_t; + + +typedef struct { + int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes; + float c_overDraw; + + int c_dlightVertexes; + int c_dlightIndexes; + + int c_flareAdds; + int c_flareTests; + int c_flareRenders; + + int msec; // total msec for backend run +} backEndCounters_t; + +// all state modified by the back end is seperated +// from the front end state +typedef struct { + int smpFrame; + trRefdef_t refdef; + viewParms_t viewParms; + orientationr_t or; + backEndCounters_t pc; + qboolean isHyperspace; + trRefEntity_t *currentEntity; + qboolean skyRenderedThisView; // flag for drawing sun + + qboolean projection2D; // if qtrue, drawstretchpic doesn't need to change modes + byte color2D[4]; + qboolean vertexes2D; // shader needs to be finished + trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering +} backEndState_t; + +/* +** trGlobals_t +** +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ +typedef struct { + qboolean registered; // cleared at shutdown, set at beginRegistration + + int visCount; // incremented every time a new vis cluster is entered + int frameCount; // incremented every frame + int sceneCount; // incremented every scene + int viewCount; // incremented every view (twice a scene if portaled) + // and every R_MarkFragments call + + int smpFrame; // toggles from 0 to 1 every endFrame + + int frameSceneNum; // zeroed at RE_BeginFrame + + qboolean worldMapLoaded; + world_t *world; + + const byte *externalVisData; // from RE_SetWorldVisData, shared with CM_Load + + image_t *defaultImage; + image_t *scratchImage[32]; + image_t *fogImage; + image_t *dlightImage; // inverse-quare highlight for projective adding + image_t *flareImage; + image_t *whiteImage; // full of 0xff + image_t *identityLightImage; // full of tr.identityLightByte + + shader_t *defaultShader; + shader_t *shadowShader; + shader_t *projectionShadowShader; + + shader_t *flareShader; + shader_t *sunShader; + + int numLightmaps; + image_t **lightmaps; + + trRefEntity_t *currentEntity; + trRefEntity_t worldEntity; // point currentEntity at this when rendering world + int currentEntityNum; + int shiftedEntityNum; // currentEntityNum << QSORT_ENTITYNUM_SHIFT + model_t *currentModel; + + viewParms_t viewParms; + + float identityLight; // 1.0 / ( 1 << overbrightBits ) + int identityLightByte; // identityLight * 255 + int overbrightBits; // r_overbrightBits->integer, but set to 0 if no hw gamma + + orientationr_t or; // for current entity + + trRefdef_t refdef; + + int viewCluster; + + vec3_t sunLight; // from the sky shader for this level + vec3_t sunDirection; + + frontEndCounters_t pc; + int frontEndMsec; // not in pc due to clearing issue + + vec4_t clipRegion; // 2D clipping region + + // + // put large tables at the end, so most elements will be + // within the +/32K indexed range on risc processors + // + model_t *models[MAX_MOD_KNOWN]; + int numModels; + + int numImages; + image_t *images[MAX_DRAWIMAGES]; + + // shader indexes from other modules will be looked up in tr.shaders[] + // shader indexes from drawsurfs will be looked up in sortedShaders[] + // lower indexed sortedShaders must be rendered first (opaque surfaces before translucent) + int numShaders; + shader_t *shaders[MAX_SHADERS]; + shader_t *sortedShaders[MAX_SHADERS]; + + int numSkins; + skin_t *skins[MAX_SKINS]; + + float sinTable[FUNCTABLE_SIZE]; + float squareTable[FUNCTABLE_SIZE]; + float triangleTable[FUNCTABLE_SIZE]; + float sawToothTable[FUNCTABLE_SIZE]; + float inverseSawToothTable[FUNCTABLE_SIZE]; + float fogTable[FOG_TABLE_SIZE]; +} trGlobals_t; + +extern backEndState_t backEnd; +extern trGlobals_t tr; +extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init +extern glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init + + +// +// cvars +// +extern cvar_t *r_flareSize; +extern cvar_t *r_flareFade; +// coefficient for the flare intensity falloff function. +#define FLARE_STDCOEFF "150" +extern cvar_t *r_flareCoeff; + +extern cvar_t *r_railWidth; +extern cvar_t *r_railCoreWidth; +extern cvar_t *r_railSegmentLength; + +extern cvar_t *r_ignore; // used for debugging anything +extern cvar_t *r_verbose; // used for verbose debug spew +extern cvar_t *r_ignoreFastPath; // allows us to ignore our Tess fast paths + +extern cvar_t *r_znear; // near Z clip plane +extern cvar_t *r_zproj; // z distance of projection plane +extern cvar_t *r_stereoSeparation; // separation of cameras for stereo rendering + +extern cvar_t *r_stencilbits; // number of desired stencil bits +extern cvar_t *r_depthbits; // number of desired depth bits +extern cvar_t *r_colorbits; // number of desired color bits, only relevant for fullscreen +extern cvar_t *r_texturebits; // number of desired texture bits +extern cvar_t *r_ext_multisample; + // 0 = use framebuffer depth + // 16 = use 16-bit textures + // 32 = use 32-bit textures + // all else = error + +extern cvar_t *r_measureOverdraw; // enables stencil buffer overdraw measurement + +extern cvar_t *r_lodbias; // push/pull LOD transitions +extern cvar_t *r_lodscale; + +extern cvar_t *r_primitives; // "0" = based on compiled vertex array existance + // "1" = glDrawElemet tristrips + // "2" = glDrawElements triangles + // "-1" = no drawing + +extern cvar_t *r_inGameVideo; // controls whether in game video should be draw +extern cvar_t *r_fastsky; // controls whether sky should be cleared or drawn +extern cvar_t *r_drawSun; // controls drawing of sun quad +extern cvar_t *r_dynamiclight; // dynamic lights enabled/disabled +extern cvar_t *r_dlightBacks; // dlight non-facing surfaces for continuity + +extern cvar_t *r_norefresh; // bypasses the ref rendering +extern cvar_t *r_drawentities; // disable/enable entity rendering +extern cvar_t *r_drawworld; // disable/enable world rendering +extern cvar_t *r_speeds; // various levels of information display +extern cvar_t *r_detailTextures; // enables/disables detail texturing stages +extern cvar_t *r_novis; // disable/enable usage of PVS +extern cvar_t *r_nocull; +extern cvar_t *r_facePlaneCull; // enables culling of planar surfaces with back side test +extern cvar_t *r_nocurves; +extern cvar_t *r_showcluster; + +extern cvar_t *r_width; +extern cvar_t *r_height; +extern cvar_t *r_pixelAspect; + +extern cvar_t *r_fullscreen; +extern cvar_t *r_noborder; +extern cvar_t *r_gamma; +extern cvar_t *r_ignorehwgamma; // overrides hardware gamma capabilities + +extern cvar_t *r_allowExtensions; // global enable/disable of OpenGL extensions +extern cvar_t *r_ext_compressed_textures; // these control use of specific extensions +extern cvar_t *r_ext_multitexture; +extern cvar_t *r_ext_compiled_vertex_array; +extern cvar_t *r_ext_texture_env_add; + +extern cvar_t *r_ext_texture_filter_anisotropic; +extern cvar_t *r_ext_max_anisotropy; + +extern cvar_t *r_nobind; // turns off binding to appropriate textures +extern cvar_t *r_singleShader; // make most world faces use default shader +extern cvar_t *r_roundImagesDown; +extern cvar_t *r_colorMipLevels; // development aid to see texture mip usage +extern cvar_t *r_picmip; // controls picmip values +extern cvar_t *r_finish; +extern cvar_t *r_drawBuffer; +extern cvar_t *r_swapInterval; +extern cvar_t *r_textureMode; +extern cvar_t *r_offsetFactor; +extern cvar_t *r_offsetUnits; + +extern cvar_t *r_fullbright; // avoid lightmap pass +extern cvar_t *r_lightmap; // render lightmaps only +extern cvar_t *r_vertexLight; // vertex lighting mode for better performance +extern cvar_t *r_uiFullScreen; // ui is running fullscreen + +extern cvar_t *r_logFile; // number of frames to emit GL logs +extern cvar_t *r_showtris; // enables wireframe rendering of the world +extern cvar_t *r_showsky; // forces sky in front of all surfaces +extern cvar_t *r_shownormals; // draws wireframe normals +extern cvar_t *r_clear; // force screen clear every frame + +extern cvar_t *r_shadows; // controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection +extern cvar_t *r_flares; // light flares + +extern cvar_t *r_intensity; + +extern cvar_t *r_lockpvs; +extern cvar_t *r_noportals; +extern cvar_t *r_portalOnly; + +extern cvar_t *r_subdivisions; +extern cvar_t *r_lodCurveError; +extern cvar_t *r_smp; +extern cvar_t *r_showSmp; +extern cvar_t *r_skipBackEnd; + +extern cvar_t *r_stereoEnabled; +extern cvar_t *r_anaglyphMode; + +extern cvar_t *r_greyscale; + +extern cvar_t *r_ignoreGLErrors; + +extern cvar_t *r_overBrightBits; +extern cvar_t *r_mapOverBrightBits; + +extern cvar_t *r_debugSurface; +extern cvar_t *r_simpleMipMaps; + +extern cvar_t *r_showImages; +extern cvar_t *r_debugSort; + +extern cvar_t *r_printShaders; +extern cvar_t *r_saveFontData; + +extern cvar_t *r_marksOnTriangleMeshes; + +//==================================================================== + +float R_NoiseGet4f( float x, float y, float z, float t ); +void R_NoiseInit( void ); + +void R_SwapBuffers( int ); + +void R_RenderView( viewParms_t *parms ); + +void R_AddMD3Surfaces( trRefEntity_t *e ); +void R_AddNullModelSurfaces( trRefEntity_t *e ); +void R_AddBeamSurfaces( trRefEntity_t *e ); +void R_AddRailSurfaces( trRefEntity_t *e, qboolean isUnderwater ); +void R_AddLightningBoltSurfaces( trRefEntity_t *e ); + +void R_AddPolygonSurfaces( void ); + +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ); + +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, int fogIndex, int dlightMap ); + + +#define CULL_IN 0 // completely unclipped +#define CULL_CLIP 1 // clipped by one or more planes +#define CULL_OUT 2 // completely outside the clipping planes +void R_LocalNormalToWorld (vec3_t local, vec3_t world); +void R_LocalPointToWorld (vec3_t local, vec3_t world); +int R_CullLocalBox (vec3_t bounds[2]); +int R_CullPointAndRadius( vec3_t origin, float radius ); +int R_CullLocalPointAndRadius( vec3_t origin, float radius ); + +void R_SetupProjection(viewParms_t *dest, float zProj, qboolean computeFrustum); +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *or ); + +/* +** GL wrapper/helper functions +*/ +void GL_Bind( image_t *image ); +void GL_SetDefaultState (void); +void GL_SelectTexture( int unit ); +void GL_TextureMode( const char *string ); +void GL_CheckErrors( void ); +void GL_State( unsigned long stateVector ); +void GL_TexEnv( int env ); +void GL_Cull( int cullType ); + +#define GLS_SRCBLEND_ZERO 0x00000001 +#define GLS_SRCBLEND_ONE 0x00000002 +#define GLS_SRCBLEND_DST_COLOR 0x00000003 +#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR 0x00000004 +#define GLS_SRCBLEND_SRC_ALPHA 0x00000005 +#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA 0x00000006 +#define GLS_SRCBLEND_DST_ALPHA 0x00000007 +#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA 0x00000008 +#define GLS_SRCBLEND_ALPHA_SATURATE 0x00000009 +#define GLS_SRCBLEND_BITS 0x0000000f + +#define GLS_DSTBLEND_ZERO 0x00000010 +#define GLS_DSTBLEND_ONE 0x00000020 +#define GLS_DSTBLEND_SRC_COLOR 0x00000030 +#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR 0x00000040 +#define GLS_DSTBLEND_SRC_ALPHA 0x00000050 +#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA 0x00000060 +#define GLS_DSTBLEND_DST_ALPHA 0x00000070 +#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA 0x00000080 +#define GLS_DSTBLEND_BITS 0x000000f0 + +#define GLS_DEPTHMASK_TRUE 0x00000100 + +#define GLS_POLYMODE_LINE 0x00001000 + +#define GLS_DEPTHTEST_DISABLE 0x00010000 +#define GLS_DEPTHFUNC_EQUAL 0x00020000 + +#define GLS_ATEST_GT_0 0x10000000 +#define GLS_ATEST_LT_80 0x20000000 +#define GLS_ATEST_GE_80 0x40000000 +#define GLS_ATEST_BITS 0x70000000 + +#define GLS_DEFAULT GLS_DEPTHMASK_TRUE + +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty); +void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty); + +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_BeginRegistration( glconfig_t *glconfig ); +void RE_LoadWorldMap( const char *mapname ); +void RE_SetWorldVisData( const byte *vis ); +qhandle_t RE_RegisterModel( const char *name ); +qhandle_t RE_RegisterSkin( const char *name ); +void RE_Shutdown( qboolean destroyWindow ); + +qboolean R_GetEntityToken( char *buffer, int size ); + +model_t *R_AllocModel( void ); + +void R_Init( void ); +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ); + +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, qboolean mipmap, + qboolean allowPicmip, int wrapClampMode ); + +void R_SetColorMappings( void ); +void R_GammaCorrect( byte *buffer, int bufSize ); + +void R_ImageList_f( void ); +void R_SkinList_f( void ); +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516 +const void *RB_TakeScreenshotCmd( const void *data ); +void R_ScreenShot_f( void ); + +void R_InitFogTable( void ); +float R_FogFactor( float s, float t ); +void R_InitImages( void ); +void R_DeleteTextures( void ); +int R_SumOfUsedImages( void ); +void R_InitSkins( void ); +skin_t *R_GetSkinByHandle( qhandle_t hSkin ); + +int R_ComputeLOD( trRefEntity_t *ent ); + +const void *RB_TakeVideoFrameCmd( const void *data ); + +// +// tr_shader.c +// +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ); +qhandle_t RE_RegisterShader( const char *name ); +qhandle_t RE_RegisterShaderNoMip( const char *name ); +qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage); + +shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ); +shader_t *R_GetShaderByHandle( qhandle_t hShader ); +shader_t *R_GetShaderByState( int index, long *cycleTime ); +shader_t *R_FindShaderByName( const char *name ); +void R_InitShaders( void ); +void R_ShaderList_f( void ); +void R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset); + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +void GLimp_Init( void ); +void GLimp_Shutdown( void ); +void GLimp_EndFrame( void ); + +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ); +void *GLimp_RendererSleep( void ); +void GLimp_FrontEndSleep( void ); +void GLimp_WakeRenderer( void *data ); + +void GLimp_LogComment( char *comment ); + +// NOTE TTimo linux works with float gamma value, not the gamma table +// the params won't be used, getting the r_gamma cvar directly +void GLimp_SetGamma( unsigned char red[256], + unsigned char green[256], + unsigned char blue[256] ); + +void GL_ResolveHardwareType( void ); + +/* +==================================================================== + +TESSELATOR/SHADER DECLARATIONS + +==================================================================== +*/ +typedef byte color4ub_t[4]; + +typedef struct stageVars +{ + color4ub_t colors[SHADER_MAX_VERTEXES]; + vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; +} stageVars_t; + + +typedef struct shaderCommands_s +{ + glIndex_t indexes[SHADER_MAX_INDEXES] QALIGN(16); + vec4_t xyz[SHADER_MAX_VERTEXES] QALIGN(16); + vec4_t normal[SHADER_MAX_VERTEXES] QALIGN(16); + vec2_t texCoords[SHADER_MAX_VERTEXES][2] QALIGN(16); + color4ub_t vertexColors[SHADER_MAX_VERTEXES] QALIGN(16); + int vertexDlightBits[SHADER_MAX_VERTEXES] QALIGN(16); + + stageVars_t svars QALIGN(16); + + color4ub_t constantColor255[SHADER_MAX_VERTEXES] QALIGN(16); + + shader_t *shader; + float shaderTime; + int fogNum; + + int dlightBits; // or together of all vertexDlightBits + + int numIndexes; + int numVertexes; + + // info extracted from current shader + int numPasses; + void (*currentStageIteratorFunc)( void ); + shaderStage_t **xstages; +} shaderCommands_t; + +extern shaderCommands_t tess; + +void RB_BeginSurface(shader_t *shader, int fogNum ); +void RB_EndSurface(void); +void RB_CheckOverflow( int verts, int indexes ); +#define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);} + +void RB_StageIteratorGeneric( void ); +void RB_StageIteratorSky( void ); +void RB_StageIteratorVertexLitTexture( void ); +void RB_StageIteratorLightmappedMultitexture( void ); + +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ); +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ); + +void RB_ShowImages( void ); + + +/* +============================================================ + +WORLD MAP + +============================================================ +*/ + +void R_AddBrushModelSurfaces( trRefEntity_t *e ); +void R_AddWorldSurfaces( void ); +qboolean R_inPVS( const vec3_t p1, const vec3_t p2 ); + + +/* +============================================================ + +FLARES + +============================================================ +*/ + +void R_ClearFlares( void ); + +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ); +void RB_AddDlightFlares( void ); +void RB_RenderFlares (void); + +/* +============================================================ + +LIGHTS + +============================================================ +*/ + +void R_DlightBmodel( bmodel_t *bmodel ); +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or ); +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + + +/* +============================================================ + +SHADOWS + +============================================================ +*/ + +void RB_ShadowTessEnd( void ); +void RB_ShadowFinish( void ); +void RB_ProjectionShadowDeform( void ); + +/* +============================================================ + +SKIES + +============================================================ +*/ + +void R_BuildCloudData( shaderCommands_t *shader ); +void R_InitSkyTexCoords( float cloudLayerHeight ); +void R_DrawSkyBox( shaderCommands_t *shader ); +void RB_DrawSun( void ); +void RB_ClipSkyPolygons( shaderCommands_t *shader ); + +/* +============================================================ + +CURVE TESSELATION + +============================================================ +*/ + +#define PATCH_STITCHING + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ); +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ); +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ); +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ); + +/* +============================================================ + +MARKERS, POLYGON PROJECTION ON WORLD POLYGONS + +============================================================ +*/ + +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + +/* +============================================================ + +SCENE GENERATION + +============================================================ +*/ + +void R_ToggleSmpFrame( void ); + +void RE_ClearScene( void ); +void RE_AddRefEntityToScene( const refEntity_t *ent ); +void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_RenderScene( const refdef_t *fd ); + +#ifdef RAVENMD4 +/* +============================================================= + +UNCOMPRESSING BONES + +============================================================= +*/ + +#define MC_BITS_X (16) +#define MC_BITS_Y (16) +#define MC_BITS_Z (16) +#define MC_BITS_VECT (16) + +#define MC_SCALE_X (1.0f/64) +#define MC_SCALE_Y (1.0f/64) +#define MC_SCALE_Z (1.0f/64) + +void MC_UnCompress(float mat[3][4],const unsigned char * comp); +#endif + +/* +============================================================= + +ANIMATED MODELS + +============================================================= +*/ + +// void R_MakeAnimModel( model_t *model ); haven't seen this one really, so not needed I guess. +void R_AddAnimSurfaces( trRefEntity_t *ent ); +void RB_SurfaceAnim( md4Surface_t *surfType ); +#ifdef RAVENMD4 +void R_MDRAddAnimSurfaces( trRefEntity_t *ent ); +void RB_MDRSurfaceAnim( md4Surface_t *surface ); +#endif + +/* +============================================================= + +IMAGE LOADERS + +============================================================= +*/ + +void R_LoadBMP( const char *name, byte **pic, int *width, int *height ); +void R_LoadJPG( const char *name, byte **pic, int *width, int *height ); +void R_LoadPCX( const char *name, byte **pic, int *width, int *height ); +void R_LoadPNG( const char *name, byte **pic, int *width, int *height ); +void R_LoadTGA( const char *name, byte **pic, int *width, int *height ); + +/* +============================================================= +============================================================= +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ); +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ); + +void RB_DeformTessGeometry( void ); + +void RB_CalcEnvironmentTexCoords( float *dstTexCoords ); +void RB_CalcFogTexCoords( float *dstTexCoords ); +void RB_CalcScrollTexCoords( const float scroll[2], float *dstTexCoords ); +void RB_CalcRotateTexCoords( float rotSpeed, float *dstTexCoords ); +void RB_CalcScaleTexCoords( const float scale[2], float *dstTexCoords ); +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *dstTexCoords ); +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *dstTexCoords ); +void RB_CalcModulateColorsByFog( unsigned char *dstColors ); +void RB_CalcModulateAlphasByFog( unsigned char *dstColors ); +void RB_CalcModulateRGBAsByFog( unsigned char *dstColors ); +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcAlphaFromEntity( unsigned char *dstColors ); +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *texCoords ); +void RB_CalcColorFromEntity( unsigned char *dstColors ); +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcSpecularAlpha( unsigned char *alphas ); +void RB_CalcDiffuseColor( unsigned char *colors ); + +/* +============================================================= + +RENDERER BACK END FUNCTIONS + +============================================================= +*/ + +void RB_RenderThread( void ); +void RB_ExecuteRenderCommands( const void *data ); + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +============================================================= +*/ + +#define MAX_RENDER_COMMANDS 0x40000 + +typedef struct { + byte cmds[MAX_RENDER_COMMANDS]; + int used; +} renderCommandList_t; + +typedef struct { + int commandId; + float color[4]; +} setColorCommand_t; + +typedef struct { + int commandId; + int buffer; +} drawBufferCommand_t; + +typedef struct { + int commandId; + image_t *image; + int width; + int height; + void *data; +} subImageCommand_t; + +typedef struct { + int commandId; +} swapBuffersCommand_t; + +typedef struct { + int commandId; + int buffer; +} endFrameCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; +} stretchPicCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; + drawSurf_t *drawSurfs; + int numDrawSurfs; +} drawSurfsCommand_t; + +typedef struct { + int commandId; + int x; + int y; + int width; + int height; + char *fileName; + qboolean jpeg; +} screenshotCommand_t; + +typedef struct { + int commandId; + int width; + int height; + byte *captureBuffer; + byte *encodeBuffer; + qboolean motionJpeg; +} videoFrameCommand_t; + +typedef struct +{ + int commandId; + + GLboolean rgba[4]; +} colorMaskCommand_t; + +typedef struct +{ + int commandId; +} clearDepthCommand_t; + +typedef enum { + RC_END_OF_LIST, + RC_SET_COLOR, + RC_STRETCH_PIC, + RC_DRAW_SURFS, + RC_DRAW_BUFFER, + RC_SWAP_BUFFERS, + RC_SCREENSHOT, + RC_VIDEOFRAME, + RC_COLORMASK, + RC_CLEARDEPTH +} renderCommand_t; + + +// these are sort of arbitrary limits. +// the limits apply to the sum of all scenes in a frame -- +// the main view, all the 3D icons, etc +#define MAX_POLYS 600 +#define MAX_POLYVERTS 3000 + +// all of the information needed by the back end must be +// contained in a backEndData_t. This entire structure is +// duplicated so the front and back end can run in parallel +// on an SMP machine +typedef struct { + drawSurf_t drawSurfs[MAX_DRAWSURFS]; + dlight_t dlights[MAX_DLIGHTS]; + trRefEntity_t entities[MAX_ENTITIES]; + srfPoly_t *polys;//[MAX_POLYS]; + polyVert_t *polyVerts;//[MAX_POLYVERTS]; + renderCommandList_t commands; +} backEndData_t; + +extern int max_polys; +extern int max_polyverts; + +extern backEndData_t *backEndData[SMP_FRAMES]; // the second one may not be allocated + +extern volatile renderCommandList_t *renderCommandList; + +extern volatile qboolean renderThreadActive; + + +void *R_GetCommandBuffer( int bytes ); +void RB_ExecuteRenderCommands( const void *data ); + +void R_InitCommandBuffers( void ); +void R_ShutdownCommandBuffers( void ); + +void R_SyncRenderThread( void ); + +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ); + +void RE_SetColor( const float *rgba ); +void RE_SetClipRegion( const float *region ); +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ); +void RE_SaveJPG(char * filename, int quality, int image_width, int image_height, + unsigned char *image_buffer, int padding); +size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality, + int image_width, int image_height, byte *image_buffer, int padding); +void RE_TakeVideoFrame( int width, int height, + byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg ); + +// font stuff +void R_InitFreeType( void ); +void R_DoneFreeType( void ); +void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); + + +#endif //TR_LOCAL_H diff --git a/src/renderer/tr_main.c b/src/renderer/tr_main.c new file mode 100644 index 0000000..298d2d9 --- /dev/null +++ b/src/renderer/tr_main.c @@ -0,0 +1,1403 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_main.c -- main control flow for each frame + +#include "tr_local.h" + +#include // memcpy + +trGlobals_t tr; + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +refimport_t ri; + +// entities that will have procedurally generated surfaces will just +// point at this for their sorting surface +surfaceType_t entitySurface = SF_ENTITY; + +/* +================= +R_CullLocalBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullLocalBox (vec3_t bounds[2]) { + int i, j; + vec3_t transformed[8]; + float dists[8]; + vec3_t v; + cplane_t *frust; + int anyBack; + int front, back; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // transform into world space + for (i = 0 ; i < 8 ; i++) { + v[0] = bounds[i&1][0]; + v[1] = bounds[(i>>1)&1][1]; + v[2] = bounds[(i>>2)&1][2]; + + VectorCopy( tr.or.origin, transformed[i] ); + VectorMA( transformed[i], v[0], tr.or.axis[0], transformed[i] ); + VectorMA( transformed[i], v[1], tr.or.axis[1], transformed[i] ); + VectorMA( transformed[i], v[2], tr.or.axis[2], transformed[i] ); + } + + // check against frustum planes + anyBack = 0; + for (i = 0 ; i < 4 ; i++) { + frust = &tr.viewParms.frustum[i]; + + front = back = 0; + for (j = 0 ; j < 8 ; j++) { + dists[j] = DotProduct(transformed[j], frust->normal); + if ( dists[j] > frust->dist ) { + front = 1; + if ( back ) { + break; // a point is in front + } + } else { + back = 1; + } + } + if ( !front ) { + // all points were behind one of the planes + return CULL_OUT; + } + anyBack |= back; + } + + if ( !anyBack ) { + return CULL_IN; // completely inside frustum + } + + return CULL_CLIP; // partially clipped +} + +/* +** R_CullLocalPointAndRadius +*/ +int R_CullLocalPointAndRadius( vec3_t pt, float radius ) +{ + vec3_t transformed; + + R_LocalPointToWorld( pt, transformed ); + + return R_CullPointAndRadius( transformed, radius ); +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadius( vec3_t pt, float radius ) +{ + int i; + float dist; + cplane_t *frust; + qboolean mightBeClipped = qfalse; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // check against frustum planes + for (i = 0 ; i < 4 ; i++) + { + frust = &tr.viewParms.frustum[i]; + + dist = DotProduct( pt, frust->normal) - frust->dist; + if ( dist < -radius ) + { + return CULL_OUT; + } + else if ( dist <= radius ) + { + mightBeClipped = qtrue; + } + } + + if ( mightBeClipped ) + { + return CULL_CLIP; + } + + return CULL_IN; // completely inside frustum +} + + +/* +================= +R_LocalNormalToWorld + +================= +*/ +void R_LocalNormalToWorld (vec3_t local, vec3_t world) { + world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0]; + world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1]; + world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2]; +} + +/* +================= +R_LocalPointToWorld + +================= +*/ +void R_LocalPointToWorld (vec3_t local, vec3_t world) { + world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0] + tr.or.origin[0]; + world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1] + tr.or.origin[1]; + world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2] + tr.or.origin[2]; +} + +/* +================= +R_WorldToLocal + +================= +*/ +void R_WorldToLocal (vec3_t world, vec3_t local) { + local[0] = DotProduct(world, tr.or.axis[0]); + local[1] = DotProduct(world, tr.or.axis[1]); + local[2] = DotProduct(world, tr.or.axis[2]); +} + +/* +========================== +R_TransformModelToClip + +========================== +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ) { + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + eye[i] = + src[0] * modelMatrix[ i + 0 * 4 ] + + src[1] * modelMatrix[ i + 1 * 4 ] + + src[2] * modelMatrix[ i + 2 * 4 ] + + 1 * modelMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i++ ) { + dst[i] = + eye[0] * projectionMatrix[ i + 0 * 4 ] + + eye[1] * projectionMatrix[ i + 1 * 4 ] + + eye[2] * projectionMatrix[ i + 2 * 4 ] + + eye[3] * projectionMatrix[ i + 3 * 4 ]; + } +} + +/* +========================== +R_TransformClipToWindow + +========================== +*/ +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) { + normalized[0] = clip[0] / clip[3]; + normalized[1] = clip[1] / clip[3]; + normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] ); + + window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth; + window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight; + window[2] = normalized[2]; + + window[0] = (int) ( window[0] + 0.5 ); + window[1] = (int) ( window[1] + 0.5 ); +} + + +/* +========================== +myGlMultMatrix + +========================== +*/ +void myGlMultMatrix( const float *a, const float *b, float *out ) { + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[ i * 4 + j ] = + a [ i * 4 + 0 ] * b [ 0 * 4 + j ] + + a [ i * 4 + 1 ] * b [ 1 * 4 + j ] + + a [ i * 4 + 2 ] * b [ 2 * 4 + j ] + + a [ i * 4 + 3 ] * b [ 3 * 4 + j ]; + } + } +} + +/* +================= +R_RotateForEntity + +Generates an orientation for an entity and viewParms +Does NOT produce any GL calls +Called by both the front end and the back end +================= +*/ +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, + orientationr_t *or ) { + float glMatrix[16]; + vec3_t delta; + float axisLength; + + if ( ent->e.reType != RT_MODEL ) { + *or = viewParms->world; + return; + } + + VectorCopy( ent->e.origin, or->origin ); + + VectorCopy( ent->e.axis[0], or->axis[0] ); + VectorCopy( ent->e.axis[1], or->axis[1] ); + VectorCopy( ent->e.axis[2], or->axis[2] ); + + glMatrix[0] = or->axis[0][0]; + glMatrix[4] = or->axis[1][0]; + glMatrix[8] = or->axis[2][0]; + glMatrix[12] = or->origin[0]; + + glMatrix[1] = or->axis[0][1]; + glMatrix[5] = or->axis[1][1]; + glMatrix[9] = or->axis[2][1]; + glMatrix[13] = or->origin[1]; + + glMatrix[2] = or->axis[0][2]; + glMatrix[6] = or->axis[1][2]; + glMatrix[10] = or->axis[2][2]; + glMatrix[14] = or->origin[2]; + + glMatrix[3] = 0; + glMatrix[7] = 0; + glMatrix[11] = 0; + glMatrix[15] = 1; + + myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, or->modelMatrix ); + + // calculate the viewer origin in the model's space + // needed for fog, specular, and environment mapping + VectorSubtract( viewParms->or.origin, or->origin, delta ); + + // compensate for scale in the axes if necessary + if ( ent->e.nonNormalizedAxes ) { + axisLength = VectorLength( ent->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + } else { + axisLength = 1.0f; + } + + or->viewOrigin[0] = DotProduct( delta, or->axis[0] ) * axisLength; + or->viewOrigin[1] = DotProduct( delta, or->axis[1] ) * axisLength; + or->viewOrigin[2] = DotProduct( delta, or->axis[2] ) * axisLength; +} + +/* +================= +R_RotateForViewer + +Sets up the modelview matrix for a given viewParm +================= +*/ +void R_RotateForViewer (void) +{ + float viewerMatrix[16]; + vec3_t origin; + + Com_Memset (&tr.or, 0, sizeof(tr.or)); + tr.or.axis[0][0] = 1; + tr.or.axis[1][1] = 1; + tr.or.axis[2][2] = 1; + VectorCopy (tr.viewParms.or.origin, tr.or.viewOrigin); + + // transform by the camera placement + VectorCopy( tr.viewParms.or.origin, origin ); + + viewerMatrix[0] = tr.viewParms.or.axis[0][0]; + viewerMatrix[4] = tr.viewParms.or.axis[0][1]; + viewerMatrix[8] = tr.viewParms.or.axis[0][2]; + viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8]; + + viewerMatrix[1] = tr.viewParms.or.axis[1][0]; + viewerMatrix[5] = tr.viewParms.or.axis[1][1]; + viewerMatrix[9] = tr.viewParms.or.axis[1][2]; + viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9]; + + viewerMatrix[2] = tr.viewParms.or.axis[2][0]; + viewerMatrix[6] = tr.viewParms.or.axis[2][1]; + viewerMatrix[10] = tr.viewParms.or.axis[2][2]; + viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10]; + + viewerMatrix[3] = 0; + viewerMatrix[7] = 0; + viewerMatrix[11] = 0; + viewerMatrix[15] = 1; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.or.modelMatrix ); + + tr.viewParms.world = tr.or; + +} + +/* +** SetFarClip +*/ +static void R_SetFarClip( void ) +{ + float farthestCornerDistance = 0; + int i; + + // if not rendering the world (icons, menus, etc) + // set a 2k far clip plane + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + tr.viewParms.zFar = 2048; + return; + } + + // + // set far clipping planes dynamically + // + farthestCornerDistance = 0; + for ( i = 0; i < 8; i++ ) + { + vec3_t v; + vec3_t vecTo; + float distance; + + if ( i & 1 ) + { + v[0] = tr.viewParms.visBounds[0][0]; + } + else + { + v[0] = tr.viewParms.visBounds[1][0]; + } + + if ( i & 2 ) + { + v[1] = tr.viewParms.visBounds[0][1]; + } + else + { + v[1] = tr.viewParms.visBounds[1][1]; + } + + if ( i & 4 ) + { + v[2] = tr.viewParms.visBounds[0][2]; + } + else + { + v[2] = tr.viewParms.visBounds[1][2]; + } + + VectorSubtract( v, tr.viewParms.or.origin, vecTo ); + + distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2]; + + if ( distance > farthestCornerDistance ) + { + farthestCornerDistance = distance; + } + } + tr.viewParms.zFar = sqrt( farthestCornerDistance ); +} + +/* +================= +R_SetupFrustum + +Set up the culling frustum planes for the current view using the results we got from computing the first two rows of +the projection matrix. +================= +*/ +void R_SetupFrustum (viewParms_t *dest, float xmin, float xmax, float ymax, float zProj, float stereoSep) +{ + vec3_t ofsorigin; + float oppleg, adjleg, length; + int i; + + if(stereoSep == 0 && xmin == -xmax) + { + // symmetric case can be simplified + VectorCopy(dest->or.origin, ofsorigin); + + length = sqrt(xmax * xmax + zProj * zProj); + oppleg = xmax / length; + adjleg = zProj / length; + + VectorScale(dest->or.axis[0], oppleg, dest->frustum[0].normal); + VectorMA(dest->frustum[0].normal, adjleg, dest->or.axis[1], dest->frustum[0].normal); + + VectorScale(dest->or.axis[0], oppleg, dest->frustum[1].normal); + VectorMA(dest->frustum[1].normal, -adjleg, dest->or.axis[1], dest->frustum[1].normal); + } + else + { + // In stereo rendering, due to the modification of the projection matrix, dest->or.origin is not the + // actual origin that we're rendering so offset the tip of the view pyramid. + VectorMA(dest->or.origin, stereoSep, dest->or.axis[1], ofsorigin); + + oppleg = xmax + stereoSep; + length = sqrt(oppleg * oppleg + zProj * zProj); + VectorScale(dest->or.axis[0], oppleg / length, dest->frustum[0].normal); + VectorMA(dest->frustum[0].normal, zProj / length, dest->or.axis[1], dest->frustum[0].normal); + + oppleg = xmin + stereoSep; + length = sqrt(oppleg * oppleg + zProj * zProj); + VectorScale(dest->or.axis[0], -oppleg / length, dest->frustum[1].normal); + VectorMA(dest->frustum[1].normal, -zProj / length, dest->or.axis[1], dest->frustum[1].normal); + } + + length = sqrt(ymax * ymax + zProj * zProj); + oppleg = ymax / length; + adjleg = zProj / length; + + VectorScale(dest->or.axis[0], oppleg, dest->frustum[2].normal); + VectorMA(dest->frustum[2].normal, adjleg, dest->or.axis[2], dest->frustum[2].normal); + + VectorScale(dest->or.axis[0], oppleg, dest->frustum[3].normal); + VectorMA(dest->frustum[3].normal, -adjleg, dest->or.axis[2], dest->frustum[3].normal); + + for (i=0 ; i<4 ; i++) { + dest->frustum[i].type = PLANE_NON_AXIAL; + dest->frustum[i].dist = DotProduct (ofsorigin, dest->frustum[i].normal); + SetPlaneSignbits( &dest->frustum[i] ); + } +} + +/* +=============== +R_SetupProjection +=============== +*/ +void R_SetupProjection(viewParms_t *dest, float zProj, qboolean computeFrustum) +{ + float xmin, xmax, ymin, ymax; + float width, height, stereoSep = r_stereoSeparation->value; + + /* + * offset the view origin of the viewer for stereo rendering + * by setting the projection matrix appropriately. + */ + + if(stereoSep != 0) + { + if(dest->stereoFrame == STEREO_LEFT) + stereoSep = zProj / stereoSep; + else if(dest->stereoFrame == STEREO_RIGHT) + stereoSep = zProj / -stereoSep; + else + stereoSep = 0; + } + + ymax = zProj * tan(dest->fovY * M_PI / 360.0f); + ymin = -ymax; + + xmax = zProj * tan(dest->fovX * M_PI / 360.0f); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + + dest->projectionMatrix[0] = 2 * zProj / width; + dest->projectionMatrix[4] = 0; + dest->projectionMatrix[8] = (xmax + xmin + 2 * stereoSep) / width; + dest->projectionMatrix[12] = 2 * zProj * stereoSep / width; + + dest->projectionMatrix[1] = 0; + dest->projectionMatrix[5] = 2 * zProj / height; + dest->projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + dest->projectionMatrix[13] = 0; + + dest->projectionMatrix[3] = 0; + dest->projectionMatrix[7] = 0; + dest->projectionMatrix[11] = -1; + dest->projectionMatrix[15] = 0; + + // Now that we have all the data for the projection matrix we can also setup the view frustum. + if(computeFrustum) + R_SetupFrustum(dest, xmin, xmax, ymax, zProj, stereoSep); +} + +/* +=============== +R_SetupProjectionZ + +Sets the z-component transformation part in the projection matrix +=============== +*/ +void R_SetupProjectionZ(viewParms_t *dest) +{ + float zNear, zFar, depth; + + zNear = r_znear->value; + zFar = dest->zFar; + depth = zFar - zNear; + + dest->projectionMatrix[2] = 0; + dest->projectionMatrix[6] = 0; + dest->projectionMatrix[10] = -( zFar + zNear ) / depth; + dest->projectionMatrix[14] = -2 * zFar * zNear / depth; +} + +/* +================= +R_MirrorPoint +================= +*/ +void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + vec3_t local; + vec3_t transformed; + float d; + + VectorSubtract( in, surface->origin, local ); + + VectorClear( transformed ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(local, surface->axis[i]); + VectorMA( transformed, d, camera->axis[i], transformed ); + } + + VectorAdd( transformed, camera->origin, out ); +} + +void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + float d; + + VectorClear( out ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(in, surface->axis[i]); + VectorMA( out, d, camera->axis[i], out ); + } +} + + +/* +============= +R_PlaneForSurface +============= +*/ +void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) { + srfTriangles_t *tri; + srfPoly_t *poly; + drawVert_t *v1, *v2, *v3; + vec4_t plane4; + + if (!surfType) { + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } + switch (*surfType) { + case SF_FACE: + *plane = ((srfSurfaceFace_t *)surfType)->plane; + return; + case SF_TRIANGLES: + tri = (srfTriangles_t *)surfType; + v1 = tri->verts + tri->indexes[0]; + v2 = tri->verts + tri->indexes[1]; + v3 = tri->verts + tri->indexes[2]; + PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + case SF_POLY: + poly = (srfPoly_t *)surfType; + PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + default: + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } +} + +/* +================= +R_GetPortalOrientation + +entityNum is the entity that the portal surface is a part of, which may +be moving and rotating. + +Returns qtrue if it should be mirrored +================= +*/ +qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum, + orientation_t *surface, orientation_t *camera, + vec3_t pvsOrigin, qboolean *mirror ) { + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + vec3_t transformed; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != ENTITYNUM_WORLD ) { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin ); + } else { + plane = originalPlane; + } + + VectorCopy( plane.normal, surface->axis[0] ); + PerpendicularVector( surface->axis[1], surface->axis[0] ); + CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] ); + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // get the pvsOrigin from the entity + VectorCopy( e->e.oldorigin, pvsOrigin ); + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) { + VectorScale( plane.normal, plane.dist, surface->origin ); + VectorCopy( surface->origin, camera->origin ); + VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] ); + VectorCopy( surface->axis[1], camera->axis[1] ); + VectorCopy( surface->axis[2], camera->axis[2] ); + + *mirror = qtrue; + return qtrue; + } + + // project the origin onto the surface plane to get + // an origin point we can rotate around + d = DotProduct( e->e.origin, plane.normal ) - plane.dist; + VectorMA( e->e.origin, -d, surface->axis[0], surface->origin ); + + // now get the camera origin and orientation + VectorCopy( e->e.oldorigin, camera->origin ); + AxisCopy( e->e.axis, camera->axis ); + VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] ); + VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] ); + + // optionally rotate + if ( e->e.oldframe ) { + // if a speed is specified + if ( e->e.frame ) { + // continuous rotate + d = (tr.refdef.time/1000.0f) * e->e.frame; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } else { + // bobbing rotate, with skinNum being the rotation offset + d = sin( tr.refdef.time * 0.003f ); + d = e->e.skinNum + d * 4; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + } + else if ( e->e.skinNum ) { + d = e->e.skinNum; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + *mirror = qfalse; + return qtrue; + } + + // if we didn't locate a portal entity, don't render anything. + // We don't want to just treat it as a mirror, because without a + // portal entity the server won't have communicated a proper entity set + // in the snapshot + + // unfortunately, with local movement prediction it is easily possible + // to see a surface before the server has communicated the matching + // portal surface entity, so we don't want to print anything here... + + //ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" ); + + return qfalse; +} + +static qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum ) +{ + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != ENTITYNUM_WORLD ) + { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin ); + } + else + { + plane = originalPlane; + } + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) + { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) + { + return qtrue; + } + + return qfalse; + } + return qfalse; +} + +/* +** SurfIsOffscreen +** +** Determines if a surface is completely offscreen. +*/ +static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) { + float shortest = 100000000; + int entityNum; + int numTriangles; + shader_t *shader; + int fogNum; + int dlighted; + vec4_t clip, eye; + int i; + unsigned int pointOr = 0; + unsigned int pointAnd = (unsigned int)~0; + + if ( glConfig.smpActive ) { // FIXME! we can't do RB_BeginSurface/RB_EndSurface stuff with smp! + return qfalse; + } + + R_RotateForViewer(); + + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + RB_BeginSurface( shader, fogNum ); + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + + assert( tess.numVertexes < 128 ); + + for ( i = 0; i < tess.numVertexes; i++ ) + { + int j; + unsigned int pointFlags = 0; + + R_TransformModelToClip( tess.xyz[i], tr.or.modelMatrix, tr.viewParms.projectionMatrix, eye, clip ); + + for ( j = 0; j < 3; j++ ) + { + if ( clip[j] >= clip[3] ) + { + pointFlags |= (1 << (j*2)); + } + else if ( clip[j] <= -clip[3] ) + { + pointFlags |= ( 1 << (j*2+1)); + } + } + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd ) + { + return qtrue; + } + + // determine if this surface is backfaced and also determine the distance + // to the nearest vertex so we can cull based on portal range. Culling + // based on vertex distance isn't 100% correct (we should be checking for + // range to the surface), but it's good enough for the types of portals + // we have in the game right now. + numTriangles = tess.numIndexes / 3; + + for ( i = 0; i < tess.numIndexes; i += 3 ) + { + vec3_t normal; + float dot; + float len; + + VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.or.origin, normal ); + + len = VectorLengthSquared( normal ); // lose the sqrt + if ( len < shortest ) + { + shortest = len; + } + + if ( ( dot = DotProduct( normal, tess.normal[tess.indexes[i]] ) ) >= 0 ) + { + numTriangles--; + } + } + if ( !numTriangles ) + { + return qtrue; + } + + // mirrors can early out at this point, since we don't do a fade over distance + // with them (although we could) + if ( IsMirror( drawSurf, entityNum ) ) + { + return qfalse; + } + + if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) ) + { + return qtrue; + } + + return qfalse; +} + +/* +======================== +R_MirrorViewBySurface + +Returns qtrue if another view has been rendered +======================== +*/ +qboolean R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) { + vec4_t clipDest[128]; + viewParms_t newParms; + viewParms_t oldParms; + orientation_t surface, camera; + + // don't recursively mirror + if (tr.viewParms.isPortal) { + ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" ); + return qfalse; + } + + if ( r_noportals->integer || (r_fastsky->integer == 1) ) { + return qfalse; + } + + // trivially reject portal/mirror + if ( SurfIsOffscreen( drawSurf, clipDest ) ) { + return qfalse; + } + + // save old viewParms so we can return to it after the mirror view + oldParms = tr.viewParms; + + newParms = tr.viewParms; + newParms.isPortal = qtrue; + if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera, + newParms.pvsOrigin, &newParms.isMirror ) ) { + return qfalse; // bad portal, no portalentity + } + + R_MirrorPoint (oldParms.or.origin, &surface, &camera, newParms.or.origin ); + + VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal ); + newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal ); + + R_MirrorVector (oldParms.or.axis[0], &surface, &camera, newParms.or.axis[0]); + R_MirrorVector (oldParms.or.axis[1], &surface, &camera, newParms.or.axis[1]); + R_MirrorVector (oldParms.or.axis[2], &surface, &camera, newParms.or.axis[2]); + + // OPTIMIZE: restrict the viewport on the mirrored view + + // render the mirror view + R_RenderView (&newParms); + + tr.viewParms = oldParms; + + return qtrue; +} + +/* +================= +R_SpriteFogNum + +See if a sprite is inside a fog volume +================= +*/ +int R_SpriteFogNum( trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) { + break; + } + if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +========================================================================================== + +DRAWSURF SORTING + +========================================================================================== +*/ + +/* +=============== +R_Radix +=============== +*/ +static ID_INLINE void R_Radix( int byte, int size, drawSurf_t *source, drawSurf_t *dest ) +{ + int count[ 256 ] = { 0 }; + int index[ 256 ]; + int i; + unsigned char *sortKey = NULL; + unsigned char *end = NULL; + + sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte; + end = sortKey + ( size * sizeof( drawSurf_t ) ); + for( ; sortKey < end; sortKey += sizeof( drawSurf_t ) ) + ++count[ *sortKey ]; + + index[ 0 ] = 0; + + for( i = 1; i < 256; ++i ) + index[ i ] = index[ i - 1 ] + count[ i - 1 ]; + + sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte; + for( i = 0; i < size; ++i, sortKey += sizeof( drawSurf_t ) ) + dest[ index[ *sortKey ]++ ] = source[ i ]; +} + +/* +=============== +R_RadixSort + +Radix sort with 4 byte size buckets +=============== +*/ +static void R_RadixSort( drawSurf_t *source, int size ) +{ + static drawSurf_t scratch[ MAX_DRAWSURFS ]; +#ifdef Q3_LITTLE_ENDIAN + R_Radix( 0, size, source, scratch ); + R_Radix( 1, size, scratch, source ); + R_Radix( 2, size, source, scratch ); + R_Radix( 3, size, scratch, source ); +#else + R_Radix( 3, size, source, scratch ); + R_Radix( 2, size, scratch, source ); + R_Radix( 1, size, source, scratch ); + R_Radix( 0, size, scratch, source ); +#endif //Q3_LITTLE_ENDIAN +} + +//========================================================================================== + +/* +================= +R_AddDrawSurf +================= +*/ +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, + int fogIndex, int dlightMap ) { + int index; + + // instead of checking for overflow, we just mask the index + // so it wraps around + index = tr.refdef.numDrawSurfs & DRAWSURF_MASK; + // the sort data is packed into a single 32 bit value so it can be + // compared quickly during the qsorting process + tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT) + | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) | (int)dlightMap; + tr.refdef.drawSurfs[index].surface = surface; + tr.refdef.numDrawSurfs++; +} + +/* +================= +R_DecomposeSort +================= +*/ +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ) { + *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31; + *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ]; + *entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & 1023; + *dlightMap = sort & 3; +} + +/* +================= +R_SortDrawSurfs +================= +*/ +void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader; + int fogNum; + int entityNum; + int dlighted; + int i; + + // it is possible for some views to not have any surfaces + if ( numDrawSurfs < 1 ) { + // we still need to add it for hyperspace cases + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // if we overflowed MAX_DRAWSURFS, the drawsurfs + // wrapped around in the buffer and we will be missing + // the first surfaces, not the last ones + if ( numDrawSurfs > MAX_DRAWSURFS ) { + numDrawSurfs = MAX_DRAWSURFS; + } + + // sort the drawsurfs by sort type, then orientation, then shader + R_RadixSort( drawSurfs, numDrawSurfs ); + + // check for any pass through drawing, which + // may cause another view to be rendered first + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted ); + + if ( shader->sort > SS_PORTAL ) { + break; + } + + // no shader should ever have this sort type + if ( shader->sort == SS_BAD ) { + ri.Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name ); + } + + // if the mirror was completely clipped away, we may need to check another surface + if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) { + // this is a debug option to see exactly what is being mirrored + if ( r_portalOnly->integer ) { + return; + } + break; // only one mirror view at a time + } + } + + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); +} + +/* +============= +R_AddEntitySurfaces +============= +*/ +void R_AddEntitySurfaces (void) { + trRefEntity_t *ent; + shader_t *shader; + + if ( !r_drawentities->integer ) { + return; + } + + for ( tr.currentEntityNum = 0; + tr.currentEntityNum < tr.refdef.num_entities; + tr.currentEntityNum++ ) { + ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum]; + + ent->needDlights = qfalse; + + // preshift the value we are going to OR into the drawsurf sort + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // + // the weapon model must be handled special -- + // we don't want the hacked weapon position showing in + // mirrors, because the true body position will already be drawn + // + if ( (ent->e.renderfx & RF_FIRST_PERSON) && tr.viewParms.isPortal) { + continue; + } + + // simple generated models, like sprites and beams, are not culled + switch ( ent->e.reType ) { + case RT_PORTALSURFACE: + break; // don't draw anything + case RT_SPRITE: + case RT_BEAM: + case RT_LIGHTNING: + case RT_RAIL_CORE: + case RT_RAIL_RINGS: + // self blood sprites, talk balloons, etc should not be drawn in the primary + // view. We can't just do this check for all entities, because md3 + // entities may still want to cast shadows from them + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + continue; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0 ); + break; + + case RT_MODEL: + // we must set up parts of tr.or for model culling + R_RotateForEntity( ent, &tr.viewParms, &tr.or ); + + tr.currentModel = R_GetModelByHandle( ent->e.hModel ); + if (!tr.currentModel) { + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 ); + } else { + switch ( tr.currentModel->type ) { + case MOD_MESH: + R_AddMD3Surfaces( ent ); + break; + case MOD_MD4: + R_AddAnimSurfaces( ent ); + break; +#ifdef RAVENMD4 + case MOD_MDR: + R_MDRAddAnimSurfaces( ent ); + break; +#endif + case MOD_BRUSH: + R_AddBrushModelSurfaces( ent ); + break; + case MOD_BAD: // null model axis + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + break; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 ); + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" ); + break; + } + } + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" ); + } + } + +} + + +/* +==================== +R_GenerateDrawSurfs +==================== +*/ +void R_GenerateDrawSurfs( void ) { + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + + // dynamically compute far clip plane distance + R_SetFarClip(); + + // we know the size of the clipping volume. Now set the rest of the projection matrix. + R_SetupProjectionZ (&tr.viewParms); + + R_AddEntitySurfaces (); +} + +/* +================ +R_DebugPolygon +================ +*/ +void R_DebugPolygon( int color, int numPoints, float *points ) { + int i; + + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // draw solid shade + + qglColor3f( color&1, (color>>1)&1, (color>>2)&1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + + // draw wireframe outline + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + qglDepthRange( 0, 0 ); + qglColor3f( 1, 1, 1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + qglDepthRange( 0, 1 ); +} + +/* +==================== +R_DebugGraphics + +Visualization aid for movement clipping debugging +==================== +*/ +void R_DebugGraphics( void ) { + if ( !r_debugSurface->integer ) { + return; + } + + // the render thread can't make callbacks to the main thread + R_SyncRenderThread(); + + GL_Bind( tr.whiteImage); + GL_Cull( CT_FRONT_SIDED ); + ri.CM_DrawDebugSurface( R_DebugPolygon ); +} + + +/* +================ +R_RenderView + +A view may be either the actual camera view, +or a mirror / remote location +================ +*/ +void R_RenderView (viewParms_t *parms) { + int firstDrawSurf; + + if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) { + return; + } + + tr.viewCount++; + + tr.viewParms = *parms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + R_SetupProjection(&tr.viewParms, r_zproj->value, qtrue); + + R_GenerateDrawSurfs(); + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + + // draw main system development information (surface outlines, etc) + R_DebugGraphics(); +} + + + diff --git a/src/renderer/tr_marks.c b/src/renderer/tr_marks.c new file mode 100644 index 0000000..b3b5333 --- /dev/null +++ b/src/renderer/tr_marks.c @@ -0,0 +1,455 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_marks.c -- polygon projection on the world polygons + +#include "tr_local.h" +//#include "assert.h" + +#define MAX_VERTS_ON_POLY 64 + +#define MARKER_OFFSET 0 // 1 + +/* +============= +R_ChopPolyBehindPlane + +Out must have space for two more vertexes than in +============= +*/ +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY], + int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY], + vec3_t normal, vec_t dist, vec_t epsilon) { + float dists[MAX_VERTS_ON_POLY+4]; + int sides[MAX_VERTS_ON_POLY+4]; + int counts[3]; + float dot; + int i, j; + float *p1, *p2, *clip; + float d; + + // don't clip if it might overflow + if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) { + *numOutPoints = 0; + return; + } + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0 ; i < numInPoints ; i++ ) { + dot = DotProduct( inPoints[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *numOutPoints = 0; + + if ( !counts[0] ) { + return; + } + if ( !counts[1] ) { + *numOutPoints = numInPoints; + Com_Memcpy( outPoints, inPoints, numInPoints * sizeof(vec3_t) ); + return; + } + + for ( i = 0 ; i < numInPoints ; i++ ) { + p1 = inPoints[i]; + clip = outPoints[ *numOutPoints ]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + clip = outPoints[ *numOutPoints ]; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = inPoints[ (i+1) % numInPoints ]; + + d = dists[i] - dists[i+1]; + if ( d == 0 ) { + dot = 0; + } else { + dot = dists[i] / d; + } + + // clip xyz + + for (j=0 ; j<3 ; j++) { + clip[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + (*numOutPoints)++; + } +} + +/* +================= +R_BoxSurfaces_r + +================= +*/ +void R_BoxSurfaces_r(mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir) { + + int s, c; + msurface_t *surf, **mark; + + // do the tail recursion in a loop + while ( node->contents == -1 ) { + s = BoxOnPlaneSide( mins, maxs, node->plane ); + if (s == 1) { + node = node->children[0]; + } else if (s == 2) { + node = node->children[1]; + } else { + R_BoxSurfaces_r(node->children[0], mins, maxs, list, listsize, listlength, dir); + node = node->children[1]; + } + } + + // add the individual surfaces + mark = node->firstmarksurface; + c = node->nummarksurfaces; + while (c--) { + // + if (*listlength >= listsize) break; + // + surf = *mark; + // check if the surface has NOIMPACT or NOMARKS set + if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) ) + || ( surf->shader->contentFlags & CONTENTS_FOG ) ) { + surf->viewCount = tr.viewCount; + } + // extra check for surfaces to avoid list overflows + else if (*(surf->data) == SF_FACE) { + // the face plane should go through the box + s = BoxOnPlaneSide( mins, maxs, &(( srfSurfaceFace_t * ) surf->data)->plane ); + if (s == 1 || s == 2) { + surf->viewCount = tr.viewCount; + } else if (DotProduct((( srfSurfaceFace_t * ) surf->data)->plane.normal, dir) > -0.5) { + // don't add faces that make sharp angles with the projection direction + surf->viewCount = tr.viewCount; + } + } + else if (*(surfaceType_t *) (surf->data) != SF_GRID && + *(surfaceType_t *) (surf->data) != SF_TRIANGLES) + surf->viewCount = tr.viewCount; + // check the viewCount because the surface may have + // already been added if it spans multiple leafs + if (surf->viewCount != tr.viewCount) { + surf->viewCount = tr.viewCount; + list[*listlength] = (surfaceType_t *) surf->data; + (*listlength)++; + } + mark++; + } +} + +/* +================= +R_AddMarkFragments + +================= +*/ +void R_AddMarkFragments(int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY], + int numPlanes, vec3_t *normals, float *dists, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer, + int *returnedPoints, int *returnedFragments, + vec3_t mins, vec3_t maxs) { + int pingPong, i; + markFragment_t *mf; + + // chop the surface by all the bounding planes of the to be projected polygon + pingPong = 0; + + for ( i = 0 ; i < numPlanes ; i++ ) { + + R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong], + &numClipPoints, clipPoints[!pingPong], + normals[i], dists[i], 0.5 ); + pingPong ^= 1; + if ( numClipPoints == 0 ) { + break; + } + } + // completely clipped away? + if ( numClipPoints == 0 ) { + return; + } + + // add this fragment to the returned list + if ( numClipPoints + (*returnedPoints) > maxPoints ) { + return; // not enough space for this polygon + } + /* + // all the clip points should be within the bounding box + for ( i = 0 ; i < numClipPoints ; i++ ) { + int j; + for ( j = 0 ; j < 3 ; j++ ) { + if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break; + if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break; + } + if (j < 3) break; + } + if (i < numClipPoints) return; + */ + + mf = fragmentBuffer + (*returnedFragments); + mf->firstPoint = (*returnedPoints); + mf->numPoints = numClipPoints; + Com_Memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) ); + + (*returnedPoints) += numClipPoints; + (*returnedFragments)++; +} + +/* +================= +R_MarkFragments + +================= +*/ +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { + int numsurfaces, numPlanes; + int i, j, k, m, n; + surfaceType_t *surfaces[64]; + vec3_t mins, maxs; + int returnedFragments; + int returnedPoints; + vec3_t normals[MAX_VERTS_ON_POLY+2]; + float dists[MAX_VERTS_ON_POLY+2]; + vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; + int numClipPoints; + float *v; + srfGridMesh_t *cv; + drawVert_t *dv; + vec3_t normal; + vec3_t projectionDir; + vec3_t v1, v2; + int *indexes; + + //increment view count for double check prevention + tr.viewCount++; + + // + VectorNormalize2( projection, projectionDir ); + // find all the brushes that are to be considered + ClearBounds( mins, maxs ); + for ( i = 0 ; i < numPoints ; i++ ) { + vec3_t temp; + + AddPointToBounds( points[i], mins, maxs ); + VectorAdd( points[i], projection, temp ); + AddPointToBounds( temp, mins, maxs ); + // make sure we get all the leafs (also the one(s) in front of the hit surface) + VectorMA( points[i], -20, projectionDir, temp ); + AddPointToBounds( temp, mins, maxs ); + } + + if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY; + // create the bounding planes for the to be projected polygon + for ( i = 0 ; i < numPoints ; i++ ) { + VectorSubtract(points[(i+1)%numPoints], points[i], v1); + VectorAdd(points[i], projection, v2); + VectorSubtract(points[i], v2, v2); + CrossProduct(v1, v2, normals[i]); + VectorNormalizeFast(normals[i]); + dists[i] = DotProduct(normals[i], points[i]); + } + // add near and far clipping planes for projection + VectorCopy(projectionDir, normals[numPoints]); + dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32; + VectorCopy(projectionDir, normals[numPoints+1]); + VectorInverse(normals[numPoints+1]); + dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20; + numPlanes = numPoints + 2; + + numsurfaces = 0; + R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir); + //assert(numsurfaces <= 64); + //assert(numsurfaces != 64); + + returnedPoints = 0; + returnedFragments = 0; + + for ( i = 0 ; i < numsurfaces ; i++ ) { + + if (*surfaces[i] == SF_GRID) { + + cv = (srfGridMesh_t *) surfaces[i]; + for ( m = 0 ; m < cv->height - 1 ; m++ ) { + for ( n = 0 ; n < cv->width - 1 ; n++ ) { + // We triangulate the grid and chop all triangles within + // the bounding planes of the to be projected polygon. + // LOD is not taken into account, not such a big deal though. + // + // It's probably much nicer to chop the grid itself and deal + // with this grid as a normal SF_GRID surface so LOD will + // be applied. However the LOD of that chopped grid must + // be synced with the LOD of the original curve. + // One way to do this; the chopped grid shares vertices with + // the original curve. When LOD is applied to the original + // curve the unused vertices are flagged. Now the chopped curve + // should skip the flagged vertices. This still leaves the + // problems with the vertices at the chopped grid edges. + // + // To avoid issues when LOD applied to "hollow curves" (like + // the ones around many jump pads) we now just add a 2 unit + // offset to the triangle vertices. + // The offset is added in the vertex normal vector direction + // so all triangles will still fit together. + // The 2 unit offset should avoid pretty much all LOD problems. + + numClipPoints = 3; + + dv = cv->verts + m * cv->width + n; + + VectorCopy(dv[0].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.1) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + VectorCopy(dv[1].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.05) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + } + } + else if (*surfaces[i] == SF_FACE) { + + srfSurfaceFace_t *surf = ( srfSurfaceFace_t * ) surfaces[i]; + + // check the normal of this face + if (DotProduct(surf->plane.normal, projectionDir) > -0.5) { + continue; + } + + indexes = (int *)( (byte *)surf + surf->ofsIndices ); + for ( k = 0 ; k < surf->numIndices ; k += 3 ) { + for ( j = 0 ; j < 3 ; j++ ) { + v = surf->points[0] + VERTEXSIZE * indexes[k+j];; + VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); + } + + // add the fragments of this face + R_AddMarkFragments( 3 , clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) { + + srfTriangles_t *surf = (srfTriangles_t *) surfaces[i]; + + for (k = 0; k < surf->numIndexes; k += 3) + { + for(j = 0; j < 3; j++) + { + v = surf->verts[surf->indexes[k + j]].xyz; + VectorMA(v, MARKER_OFFSET, surf->verts[surf->indexes[k + j]].normal, clipPoints[0][j]); + } + + // add the fragments of this face + R_AddMarkFragments(3, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs); + if(returnedFragments == maxFragments) + { + return returnedFragments; // not enough space for more fragments + } + } + } + } + return returnedFragments; +} + diff --git a/src/renderer/tr_mesh.c b/src/renderer/tr_mesh.c new file mode 100644 index 0000000..38c58ed --- /dev/null +++ b/src/renderer/tr_mesh.c @@ -0,0 +1,419 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_mesh.c: triangle model functions + +#include "tr_local.h" + +static float ProjectRadius( float r, vec3_t location ) +{ + float pr; + float dist; + float c; + vec3_t p; + float projected[4]; + + c = DotProduct( tr.viewParms.or.axis[0], tr.viewParms.or.origin ); + dist = DotProduct( tr.viewParms.or.axis[0], location ) - c; + + if ( dist <= 0 ) + return 0; + + p[0] = 0; + p[1] = fabs( r ); + p[2] = -dist; + + projected[0] = p[0] * tr.viewParms.projectionMatrix[0] + + p[1] * tr.viewParms.projectionMatrix[4] + + p[2] * tr.viewParms.projectionMatrix[8] + + tr.viewParms.projectionMatrix[12]; + + projected[1] = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + projected[2] = p[0] * tr.viewParms.projectionMatrix[2] + + p[1] * tr.viewParms.projectionMatrix[6] + + p[2] * tr.viewParms.projectionMatrix[10] + + tr.viewParms.projectionMatrix[14]; + + projected[3] = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + + pr = projected[1] / projected[3]; + + if ( pr > 1.0f ) + pr = 1.0f; + + return pr; +} + +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( md3Header_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + md3Frame_t *oldFrame, *newFrame; + int i; + + // compute frame pointers + newFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + oldFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.oldframe; + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + + +/* +================= +R_ComputeLOD + +================= +*/ +int R_ComputeLOD( trRefEntity_t *ent ) { + float radius; + float flod, lodscale; + float projectedRadius; + md3Frame_t *frame; +#ifdef RAVENMD4 + mdrHeader_t *mdr; + mdrFrame_t *mdrframe; +#endif + int lod; + + if ( tr.currentModel->numLods < 2 ) + { + // model has only 1 LOD level, skip computations and bias + lod = 0; + } + else + { + // multiple LODs exist, so compute projected bounding sphere + // and use that as a criteria for selecting LOD + +#ifdef RAVENMD4 + // This is an MDR model. + + if(tr.currentModel->md4) + { + int frameSize; + mdr = (mdrHeader_t *) tr.currentModel->md4; + frameSize = (size_t) (&((mdrFrame_t *)0)->bones[mdr->numBones]); + + mdrframe = (mdrFrame_t *) ((byte *) mdr + mdr->ofsFrames + frameSize * ent->e.frame); + + radius = RadiusFromBounds(mdrframe->bounds[0], mdrframe->bounds[1]); + } + else +#endif + { + frame = ( md3Frame_t * ) ( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames ); + + frame += ent->e.frame; + + radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] ); + } + + if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 ) + { + lodscale = r_lodscale->value; + if (lodscale > 20) lodscale = 20; + flod = 1.0f - projectedRadius * lodscale; + } + else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } + + flod *= tr.currentModel->numLods; + lod = myftol( flod ); + + if ( lod < 0 ) + { + lod = 0; + } + else if ( lod >= tr.currentModel->numLods ) + { + lod = tr.currentModel->numLods - 1; + } + } + + lod += r_lodbias->integer; + + if ( lod >= tr.currentModel->numLods ) + lod = tr.currentModel->numLods - 1; + if ( lod < 0 ) + lod = 0; + + return lod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +int R_ComputeFogNum( md3Header_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + md3Frame_t *md3Frame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + md3Frame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + VectorAdd( ent->e.origin, md3Frame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - md3Frame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + md3Frame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddMD3Surfaces + +================= +*/ +void R_AddMD3Surfaces( trRefEntity_t *ent ) { + int i; + md3Header_t *header = NULL; + md3Surface_t *surface = NULL; + md3Shader_t *md3Shader = NULL; + shader_t *shader = NULL; + int cull; + int lod; + int fogNum; + qboolean personalModel; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->md3[0]->numFrames; + ent->e.oldframe %= tr.currentModel->md3[0]->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= tr.currentModel->md3[0]->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= tr.currentModel->md3[0]->numFrames) + || (ent->e.oldframe < 0) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // compute LOD + // + lod = R_ComputeLOD( ent ); + + header = tr.currentModel->md3[lod]; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel ( header, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( header, ent ); + + // + // draw all surfaces + // + surface = (md3Surface_t *)( (byte *)header + header->ofsSurfaces ); + for ( i = 0 ; i < header->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) { + shader = R_GetShaderByHandle( ent->e.customShader ); + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + if (shader == tr.defaultShader) { + ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name); + } + else if (shader->defaultShader) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name); + } + } else if ( surface->numShaders <= 0 ) { + shader = tr.defaultShader; + } else { + md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders ); + md3Shader += ent->e.skinNum % surface->numShaders; + shader = tr.shaders[ md3Shader->shaderIndex ]; + } + + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse ); + } + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { + R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse ); + } + + surface = (md3Surface_t *)( (byte *)surface + surface->ofsEnd ); + } + +} + diff --git a/src/renderer/tr_model.c b/src/renderer/tr_model.c new file mode 100644 index 0000000..b02d52a --- /dev/null +++ b/src/renderer/tr_model.c @@ -0,0 +1,1149 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_models.c -- model loading and caching + +#include "tr_local.h" + +#define LL(x) x=LittleLong(x) + +static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *name ); +static qboolean R_LoadMD4 (model_t *mod, void *buffer, const char *name ); +#ifdef RAVENMD4 +static qboolean R_LoadMDR (model_t *mod, void *buffer, int filesize, const char *name ); +#endif + +model_t *loadmodel; + +/* +** R_GetModelByHandle +*/ +model_t *R_GetModelByHandle( qhandle_t index ) { + model_t *mod; + + // out of range gets the defualt model + if ( index < 1 || index >= tr.numModels ) { + return tr.models[0]; + } + + mod = tr.models[index]; + + return mod; +} + +//=============================================================================== + +/* +** R_AllocModel +*/ +model_t *R_AllocModel( void ) { + model_t *mod; + + if ( tr.numModels == MAX_MOD_KNOWN ) { + return NULL; + } + + mod = ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low ); + mod->index = tr.numModels; + tr.models[tr.numModels] = mod; + tr.numModels++; + + return mod; +} + +/* +==================== +RE_RegisterModel + +Loads in a model for the given name + +Zero will be returned if the model fails to load. +An entry will be retained for failed models as an +optimization to prevent disk rescanning if they are +asked for again. +==================== +*/ +qhandle_t RE_RegisterModel( const char *name ) { + model_t *mod; + union { + unsigned *u; + void *v; + } buf; + int lod; + int ident; + qboolean loaded = qfalse; + qhandle_t hModel; + int numLoaded; + char *fext, defex[] = "md3", filename[MAX_QPATH], namebuf[MAX_QPATH+20]; + + if ( !name || !name[0] ) { + ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Model name exceeds MAX_QPATH\n" ); + return 0; + } + + // + // search the currently loaded models + // + for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) { + mod = tr.models[hModel]; + if ( !strcmp( mod->name, name ) ) { + if( mod->type == MOD_BAD ) { + return 0; + } + return hModel; + } + } + + // allocate a new model_t + + if ( ( mod = R_AllocModel() ) == NULL ) { + ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); + return 0; + } + + // only set the name after the model has been successfully loaded + Q_strncpyz( mod->name, name, sizeof( mod->name ) ); + + + // make sure the render thread is stopped + R_SyncRenderThread(); + + mod->numLods = 0; + + // + // load the files + // + numLoaded = 0; + + strcpy(filename, name); + + fext = strchr(filename, '.'); + if(!fext) + fext = defex; + else + { + *fext = '\0'; + fext++; + } + +#ifdef RAVENMD4 + if(!Q_stricmp(fext, "mdr")) + { + int filesize; + + filesize = ri.FS_ReadFile(name, (void **) &buf.v); + if(!buf.u) + { + ri.Printf (PRINT_WARNING,"RE_RegisterModel: couldn't load %s\n", name); + mod->type = MOD_BAD; + return 0; + } + + ident = LittleLong(*(unsigned *)buf.u); + if(ident == MDR_IDENT) + loaded = R_LoadMDR(mod, buf.u, filesize, name); + + ri.FS_FreeFile (buf.v); + + if(!loaded) + { + ri.Printf(PRINT_WARNING,"RE_RegisterModel: couldn't load mdr file %s\n", name); + mod->type = MOD_BAD; + return 0; + } + + return mod->index; + } +#endif + + fext = defex; + + for ( lod = MD3_MAX_LODS - 1 ; lod >= 0 ; lod-- ) { + if ( lod ) + Com_sprintf(namebuf, sizeof(namebuf), "%s_%d.%s", filename, lod, fext); + else + Com_sprintf(namebuf, sizeof(namebuf), "%s.%s", filename, fext); + + ri.FS_ReadFile( namebuf, &buf.v ); + if ( !buf.u ) { + continue; + } + + loadmodel = mod; + + ident = LittleLong(*(unsigned *)buf.u); + if ( ident == MD4_IDENT ) { + loaded = R_LoadMD4( mod, buf.u, name ); + } else { + if ( ident != MD3_IDENT ) { + ri.Printf (PRINT_WARNING,"RE_RegisterModel: unknown fileid for %s\n", name); + goto fail; + } + + loaded = R_LoadMD3( mod, lod, buf.u, name ); + } + + ri.FS_FreeFile (buf.v); + + if ( !loaded ) { + if ( lod == 0 ) { + goto fail; + } else { + break; + } + } else { + mod->numLods++; + numLoaded++; + // if we have a valid model and are biased + // so that we won't see any higher detail ones, + // stop loading them +// if ( lod <= r_lodbias->integer ) { +// break; +// } + } + } + + if ( numLoaded ) { + // duplicate into higher lod spots that weren't + // loaded, in case the user changes r_lodbias on the fly + for ( lod-- ; lod >= 0 ; lod-- ) { + mod->numLods++; + mod->md3[lod] = mod->md3[lod+1]; + } + + return mod->index; + } +#ifdef _DEBUG + else { + ri.Printf (PRINT_WARNING,"RE_RegisterModel: couldn't load %s\n", name); + } +#endif + +fail: + // we still keep the model_t around, so if the model name is asked for + // again, we won't bother scanning the filesystem + mod->type = MOD_BAD; + return 0; +} + + +/* +================= +R_LoadMD3 +================= +*/ +static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_name ) { + int i, j; + md3Header_t *pinmodel; + md3Frame_t *frame; + md3Surface_t *surf; + md3Shader_t *shader; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + md3Tag_t *tag; + int version; + int size; + + pinmodel = (md3Header_t *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD3_VERSION) { + ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n", + mod_name, version, MD3_VERSION); + return qfalse; + } + + mod->type = MOD_MESH; + size = LittleLong(pinmodel->ofsEnd); + mod->dataSize += size; + mod->md3[lod] = ri.Hunk_Alloc( size, h_low ); + + Com_Memcpy (mod->md3[lod], buffer, LittleLong(pinmodel->ofsEnd) ); + + LL(mod->md3[lod]->ident); + LL(mod->md3[lod]->version); + LL(mod->md3[lod]->numFrames); + LL(mod->md3[lod]->numTags); + LL(mod->md3[lod]->numSurfaces); + LL(mod->md3[lod]->ofsFrames); + LL(mod->md3[lod]->ofsTags); + LL(mod->md3[lod]->ofsSurfaces); + LL(mod->md3[lod]->ofsEnd); + + if ( mod->md3[lod]->numFrames < 1 ) { + ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has no frames\n", mod_name ); + return qfalse; + } + + // swap all the frames + frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames ); + for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) { + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + + // swap all the tags + tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags ); + for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->origin[j] = LittleFloat( tag->origin[j] ); + tag->axis[0][j] = LittleFloat( tag->axis[0][j] ); + tag->axis[1][j] = LittleFloat( tag->axis[1][j] ); + tag->axis[2][j] = LittleFloat( tag->axis[2][j] ); + } + } + + // swap all the surfaces + surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) { + + LL(surf->ident); + LL(surf->flags); + LL(surf->numFrames); + LL(surf->numShaders); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsShaders); + LL(surf->ofsSt); + LL(surf->ofsXyzNormals); + LL(surf->ofsEnd); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MD3; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j-2] == '_' ) { + surf->name[j-2] = 0; + } + + // register the shaders + shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + } + + // swap all the triangles + tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the ST + st = (md3St_t *) ( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } + + + // find the next surface + surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + + return qtrue; +} + + +#ifdef RAVENMD4 + +/* +================= +R_LoadMDR +================= +*/ +static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name ) +{ + int i, j, k, l; + mdrHeader_t *pinmodel, *mdr; + mdrFrame_t *frame; + mdrLOD_t *lod, *curlod; + mdrSurface_t *surf, *cursurf; + mdrTriangle_t *tri, *curtri; + mdrVertex_t *v, *curv; + mdrWeight_t *weight, *curweight; + mdrTag_t *tag, *curtag; + int size; + shader_t *sh; + + pinmodel = (mdrHeader_t *)buffer; + + pinmodel->version = LittleLong(pinmodel->version); + if (pinmodel->version != MDR_VERSION) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has wrong version (%i should be %i)\n", mod_name, pinmodel->version, MDR_VERSION); + return qfalse; + } + + size = LittleLong(pinmodel->ofsEnd); + + if(size > filesize) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: Header of %s is broken. Wrong filesize declared!\n", mod_name); + return qfalse; + } + + mod->type = MOD_MDR; + + LL(pinmodel->numFrames); + LL(pinmodel->numBones); + LL(pinmodel->ofsFrames); + + // This is a model that uses some type of compressed Bones. We don't want to uncompress every bone for each rendered frame + // over and over again, we'll uncompress it in this function already, so we must adjust the size of the target md4. + if(pinmodel->ofsFrames < 0) + { + // mdrFrame_t is larger than mdrCompFrame_t: + size += pinmodel->numFrames * sizeof(frame->name); + // now add enough space for the uncompressed bones. + size += pinmodel->numFrames * pinmodel->numBones * ((sizeof(mdrBone_t) - sizeof(mdrCompBone_t))); + } + + // simple bounds check + if(pinmodel->numBones < 0 || + sizeof(*mdr) + pinmodel->numFrames * (sizeof(*frame) + (pinmodel->numBones - 1) * sizeof(*frame->bones)) > size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + mod->dataSize += size; + mod->md4 = mdr = ri.Hunk_Alloc( size, h_low ); + + // Copy all the values over from the file and fix endian issues in the process, if necessary. + + mdr->ident = LittleLong(pinmodel->ident); + mdr->version = pinmodel->version; // Don't need to swap byte order on this one, we already did above. + Q_strncpyz(mdr->name, pinmodel->name, sizeof(mdr->name)); + mdr->numFrames = pinmodel->numFrames; + mdr->numBones = pinmodel->numBones; + mdr->numLODs = LittleLong(pinmodel->numLODs); + mdr->numTags = LittleLong(pinmodel->numTags); + // We don't care about the other offset values, we'll generate them ourselves while loading. + + mod->numLods = mdr->numLODs; + + if ( mdr->numFrames < 1 ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has no frames\n", mod_name); + return qfalse; + } + + /* The first frame will be put into the first free space after the header */ + frame = (mdrFrame_t *)(mdr + 1); + mdr->ofsFrames = (int)((byte *) frame - (byte *) mdr); + + if (pinmodel->ofsFrames < 0) + { + mdrCompFrame_t *cframe; + + // compressed model... + cframe = (mdrCompFrame_t *)((byte *) pinmodel - pinmodel->ofsFrames); + + for(i = 0; i < mdr->numFrames; i++) + { + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(cframe->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(cframe->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(cframe->localOrigin[j]); + } + + frame->radius = LittleFloat(cframe->radius); + frame->name[0] = '\0'; // No name supplied in the compressed version. + + for(j = 0; j < mdr->numBones; j++) + { + for(k = 0; k < (sizeof(cframe->bones[j].Comp) / 2); k++) + { + // Do swapping for the uncompressing functions. They seem to use shorts + // values only, so I assume this will work. Never tested it on other + // platforms, though. + + ((unsigned short *)(cframe->bones[j].Comp))[k] = + LittleShort( ((unsigned short *)(cframe->bones[j].Comp))[k] ); + } + + /* Now do the actual uncompressing */ + MC_UnCompress(frame->bones[j].matrix, cframe->bones[j].Comp); + } + + // Next Frame... + cframe = (mdrCompFrame_t *) &cframe->bones[j]; + frame = (mdrFrame_t *) &frame->bones[j]; + } + } + else + { + mdrFrame_t *curframe; + + // uncompressed model... + // + + curframe = (mdrFrame_t *)((byte *) pinmodel + pinmodel->ofsFrames); + + // swap all the frames + for ( i = 0 ; i < mdr->numFrames ; i++) + { + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(curframe->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(curframe->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(curframe->localOrigin[j]); + } + + frame->radius = LittleFloat(curframe->radius); + Q_strncpyz(frame->name, curframe->name, sizeof(frame->name)); + + for (j = 0; j < (int) (mdr->numBones * sizeof(mdrBone_t) / 4); j++) + { + ((float *)frame->bones)[j] = LittleFloat( ((float *)curframe->bones)[j] ); + } + + curframe = (mdrFrame_t *) &curframe->bones[mdr->numBones]; + frame = (mdrFrame_t *) &frame->bones[mdr->numBones]; + } + } + + // frame should now point to the first free address after all frames. + lod = (mdrLOD_t *) frame; + mdr->ofsLODs = (int) ((byte *) lod - (byte *)mdr); + + curlod = (mdrLOD_t *)((byte *) pinmodel + LittleLong(pinmodel->ofsLODs)); + + // swap all the LOD's + for ( l = 0 ; l < mdr->numLODs ; l++) + { + // simple bounds check + if((byte *) (lod + 1) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + lod->numSurfaces = LittleLong(curlod->numSurfaces); + + // swap all the surfaces + surf = (mdrSurface_t *) (lod + 1); + lod->ofsSurfaces = (int)((byte *) surf - (byte *) lod); + cursurf = (mdrSurface_t *) ((byte *)curlod + LittleLong(curlod->ofsSurfaces)); + + for ( i = 0 ; i < lod->numSurfaces ; i++) + { + // simple bounds check + if((byte *) (surf + 1) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + // first do some copying stuff + + surf->ident = SF_MDR; + Q_strncpyz(surf->name, cursurf->name, sizeof(surf->name)); + Q_strncpyz(surf->shader, cursurf->shader, sizeof(surf->shader)); + + surf->ofsHeader = (byte *) mdr - (byte *) surf; + + surf->numVerts = LittleLong(cursurf->numVerts); + surf->numTriangles = LittleLong(cursurf->numTriangles); + // numBoneReferences and BoneReferences generally seem to be unused + + // now do the checks that may fail. + if ( surf->numVerts > SHADER_MAX_VERTEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + return qfalse; + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + return qfalse; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // register the shaders + sh = R_FindShader(surf->shader, LIGHTMAP_NONE, qtrue); + if ( sh->defaultShader ) { + surf->shaderIndex = 0; + } else { + surf->shaderIndex = sh->index; + } + + // now copy the vertexes. + v = (mdrVertex_t *) (surf + 1); + surf->ofsVerts = (int)((byte *) v - (byte *) surf); + curv = (mdrVertex_t *) ((byte *)cursurf + LittleLong(cursurf->ofsVerts)); + + for(j = 0; j < surf->numVerts; j++) + { + LL(curv->numWeights); + + // simple bounds check + if(curv->numWeights < 0 || (byte *) (v + 1) + (curv->numWeights - 1) * sizeof(*weight) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + v->normal[0] = LittleFloat(curv->normal[0]); + v->normal[1] = LittleFloat(curv->normal[1]); + v->normal[2] = LittleFloat(curv->normal[2]); + + v->texCoords[0] = LittleFloat(curv->texCoords[0]); + v->texCoords[1] = LittleFloat(curv->texCoords[1]); + + v->numWeights = curv->numWeights; + weight = &v->weights[0]; + curweight = &curv->weights[0]; + + // Now copy all the weights + for(k = 0; k < v->numWeights; k++) + { + weight->boneIndex = LittleLong(curweight->boneIndex); + weight->boneWeight = LittleFloat(curweight->boneWeight); + + weight->offset[0] = LittleFloat(curweight->offset[0]); + weight->offset[1] = LittleFloat(curweight->offset[1]); + weight->offset[2] = LittleFloat(curweight->offset[2]); + + weight++; + curweight++; + } + + v = (mdrVertex_t *) weight; + curv = (mdrVertex_t *) curweight; + } + + // we know the offset to the triangles now: + tri = (mdrTriangle_t *) v; + surf->ofsTriangles = (int)((byte *) tri - (byte *) surf); + curtri = (mdrTriangle_t *)((byte *) cursurf + LittleLong(cursurf->ofsTriangles)); + + // simple bounds check + if(surf->numTriangles < 0 || (byte *) (tri + surf->numTriangles) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + for(j = 0; j < surf->numTriangles; j++) + { + tri->indexes[0] = LittleLong(curtri->indexes[0]); + tri->indexes[1] = LittleLong(curtri->indexes[1]); + tri->indexes[2] = LittleLong(curtri->indexes[2]); + + tri++; + curtri++; + } + + // tri now points to the end of the surface. + surf->ofsEnd = (byte *) tri - (byte *) surf; + surf = (mdrSurface_t *) tri; + + // find the next surface. + cursurf = (mdrSurface_t *) ((byte *) cursurf + LittleLong(cursurf->ofsEnd)); + } + + // surf points to the next lod now. + lod->ofsEnd = (int)((byte *) surf - (byte *) lod); + lod = (mdrLOD_t *) surf; + + // find the next LOD. + curlod = (mdrLOD_t *)((byte *) curlod + LittleLong(curlod->ofsEnd)); + } + + // lod points to the first tag now, so update the offset too. + tag = (mdrTag_t *) lod; + mdr->ofsTags = (int)((byte *) tag - (byte *) mdr); + curtag = (mdrTag_t *) ((byte *)pinmodel + LittleLong(pinmodel->ofsTags)); + + // simple bounds check + if(mdr->numTags < 0 || (byte *) (tag + mdr->numTags) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + for (i = 0 ; i < mdr->numTags ; i++) + { + tag->boneIndex = LittleLong(curtag->boneIndex); + Q_strncpyz(tag->name, curtag->name, sizeof(tag->name)); + + tag++; + curtag++; + } + + // And finally we know the real offset to the end. + mdr->ofsEnd = (int)((byte *) tag - (byte *) mdr); + + // phew! we're done. + + return qtrue; +} +#endif + +/* +================= +R_LoadMD4 +================= +*/ + +static qboolean R_LoadMD4( model_t *mod, void *buffer, const char *mod_name ) { + int i, j, k, lodindex; + md4Header_t *pinmodel, *md4; + md4Frame_t *frame; + md4LOD_t *lod; + md4Surface_t *surf; + md4Triangle_t *tri; + md4Vertex_t *v; + int version; + int size; + shader_t *sh; + int frameSize; + + pinmodel = (md4Header_t *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD4_VERSION) { + ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has wrong version (%i should be %i)\n", + mod_name, version, MD4_VERSION); + return qfalse; + } + + mod->type = MOD_MD4; + size = LittleLong(pinmodel->ofsEnd); + mod->dataSize += size; + md4 = mod->md4 = ri.Hunk_Alloc( size, h_low ); + + Com_Memcpy(md4, buffer, size); + + LL(md4->ident); + LL(md4->version); + LL(md4->numFrames); + LL(md4->numBones); + LL(md4->numLODs); + LL(md4->ofsFrames); + LL(md4->ofsLODs); + md4->ofsEnd = size; + + if ( md4->numFrames < 1 ) { + ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has no frames\n", mod_name ); + return qfalse; + } + + // we don't need to swap tags in the renderer, they aren't used + + // swap all the frames + frameSize = (size_t)( &((md4Frame_t *)0)->bones[ md4->numBones ] ); + for ( i = 0 ; i < md4->numFrames ; i++, frame++) { + frame = (md4Frame_t *) ( (byte *)md4 + md4->ofsFrames + i * frameSize ); + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + for ( j = 0 ; j < md4->numBones * sizeof( md4Bone_t ) / 4 ; j++ ) { + ((float *)frame->bones)[j] = LittleFloat( ((float *)frame->bones)[j] ); + } + } + + // swap all the LOD's + lod = (md4LOD_t *) ( (byte *)md4 + md4->ofsLODs ); + for ( lodindex = 0 ; lodindex < md4->numLODs ; lodindex++ ) { + + // swap all the surfaces + surf = (md4Surface_t *) ( (byte *)lod + lod->ofsSurfaces ); + for ( i = 0 ; i < lod->numSurfaces ; i++) { + LL(surf->ident); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsVerts); + LL(surf->ofsEnd); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MD4; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // register the shaders + sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + surf->shaderIndex = 0; + } else { + surf->shaderIndex = sh->index; + } + + // swap all the triangles + tri = (md4Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the vertexes + // FIXME + // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left + // in for reference. + //v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts + 12); + v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts); + for ( j = 0 ; j < surf->numVerts ; j++ ) { + v->normal[0] = LittleFloat( v->normal[0] ); + v->normal[1] = LittleFloat( v->normal[1] ); + v->normal[2] = LittleFloat( v->normal[2] ); + + v->texCoords[0] = LittleFloat( v->texCoords[0] ); + v->texCoords[1] = LittleFloat( v->texCoords[1] ); + + v->numWeights = LittleLong( v->numWeights ); + + for ( k = 0 ; k < v->numWeights ; k++ ) { + v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex ); + v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight ); + v->weights[k].offset[0] = LittleFloat( v->weights[k].offset[0] ); + v->weights[k].offset[1] = LittleFloat( v->weights[k].offset[1] ); + v->weights[k].offset[2] = LittleFloat( v->weights[k].offset[2] ); + } + // FIXME + // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left + // in for reference. + //v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 ); + v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights]); + } + + // find the next surface + surf = (md4Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + + // find the next LOD + lod = (md4LOD_t *)( (byte *)lod + lod->ofsEnd ); + } + + return qtrue; +} + + + +//============================================================================= + +/* +** RE_BeginRegistration +*/ +void RE_BeginRegistration( glconfig_t *glconfigOut ) { + + R_Init(); + + *glconfigOut = glConfig; + + R_SyncRenderThread(); + + tr.viewCluster = -1; // force markleafs to regenerate + R_ClearFlares(); + RE_ClearScene(); + + tr.registered = qtrue; + + // NOTE: this sucks, for some reason the first stretch pic is never drawn + // without this we'd see a white flash on a level load because the very + // first time the level shot would not be drawn +// RE_StretchPic(0, 0, 0, 0, 0, 0, 1, 1, 0); +} + +//============================================================================= + +/* +=============== +R_ModelInit +=============== +*/ +void R_ModelInit( void ) { + model_t *mod; + + // leave a space for NULL model + tr.numModels = 0; + + mod = R_AllocModel(); + mod->type = MOD_BAD; +} + + +/* +================ +R_Modellist_f +================ +*/ +void R_Modellist_f( void ) { + int i, j; + model_t *mod; + int total; + int lods; + + total = 0; + for ( i = 1 ; i < tr.numModels; i++ ) { + mod = tr.models[i]; + lods = 1; + for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) { + if ( mod->md3[j] && mod->md3[j] != mod->md3[j-1] ) { + lods++; + } + } + ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name ); + total += mod->dataSize; + } + ri.Printf( PRINT_ALL, "%8i : Total models\n", total ); + +#if 0 // not working right with new hunk + if ( tr.world ) { + ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name ); + } +#endif +} + + +//============================================================================= + + +/* +================ +R_GetTag +================ +*/ +static md3Tag_t *R_GetTag( md3Header_t *mod, int frame, const char *tagName ) { + md3Tag_t *tag; + int i; + + if ( frame >= mod->numFrames ) { + // it is possible to have a bad frame while changing models, so don't error + frame = mod->numFrames - 1; + } + + tag = (md3Tag_t *)((byte *)mod + mod->ofsTags) + frame * mod->numTags; + for ( i = 0 ; i < mod->numTags ; i++, tag++ ) { + if ( !strcmp( tag->name, tagName ) ) { + return tag; // found it + } + } + + return NULL; +} + +#ifdef RAVENMD4 +void R_GetAnimTag( mdrHeader_t *mod, int framenum, const char *tagName, md3Tag_t * dest) +{ + int i, j, k; + int frameSize; + mdrFrame_t *frame; + mdrTag_t *tag; + + if ( framenum >= mod->numFrames ) + { + // it is possible to have a bad frame while changing models, so don't error + framenum = mod->numFrames - 1; + } + + tag = (mdrTag_t *)((byte *)mod + mod->ofsTags); + for ( i = 0 ; i < mod->numTags ; i++, tag++ ) + { + if ( !strcmp( tag->name, tagName ) ) + { + Q_strncpyz(dest->name, tag->name, sizeof(dest->name)); + + // uncompressed model... + // + frameSize = (long)( &((mdrFrame_t *)0)->bones[ mod->numBones ] ); + frame = (mdrFrame_t *)((byte *)mod + mod->ofsFrames + framenum * frameSize ); + + for (j = 0; j < 3; j++) + { + for (k = 0; k < 3; k++) + dest->axis[j][k]=frame->bones[tag->boneIndex].matrix[k][j]; + } + + dest->origin[0]=frame->bones[tag->boneIndex].matrix[0][3]; + dest->origin[1]=frame->bones[tag->boneIndex].matrix[1][3]; + dest->origin[2]=frame->bones[tag->boneIndex].matrix[2][3]; + + return; + } + } + + AxisClear( dest->axis ); + VectorClear( dest->origin ); + strcpy(dest->name,""); +} +#endif + +/* +================ +R_LerpTag +================ +*/ +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ) { + md3Tag_t *start, *end; +#ifdef RAVENMD4 + md3Tag_t start_space, end_space; +#endif + int i; + float frontLerp, backLerp; + model_t *model; + + model = R_GetModelByHandle( handle ); + if ( !model->md3[0] ) + { +#ifdef RAVENMD4 + if(model->md4) + { + start = &start_space; + end = &end_space; + R_GetAnimTag((mdrHeader_t *) model->md4, startFrame, tagName, start); + R_GetAnimTag((mdrHeader_t *) model->md4, endFrame, tagName, end); + } + else +#endif + { + + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return qfalse; + + } + } + else + { + start = R_GetTag( model->md3[0], startFrame, tagName ); + end = R_GetTag( model->md3[0], endFrame, tagName ); + if ( !start || !end ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return qfalse; + } + } + + frontLerp = frac; + backLerp = 1.0f - frac; + + for ( i = 0 ; i < 3 ; i++ ) { + tag->origin[i] = start->origin[i] * backLerp + end->origin[i] * frontLerp; + tag->axis[0][i] = start->axis[0][i] * backLerp + end->axis[0][i] * frontLerp; + tag->axis[1][i] = start->axis[1][i] * backLerp + end->axis[1][i] * frontLerp; + tag->axis[2][i] = start->axis[2][i] * backLerp + end->axis[2][i] * frontLerp; + } + VectorNormalize( tag->axis[0] ); + VectorNormalize( tag->axis[1] ); + VectorNormalize( tag->axis[2] ); + return qtrue; +} + + +/* +==================== +R_ModelBounds +==================== +*/ +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) { + model_t *model; + md3Header_t *header; + md3Frame_t *frame; + + model = R_GetModelByHandle( handle ); + + if ( model->bmodel ) { + VectorCopy( model->bmodel->bounds[0], mins ); + VectorCopy( model->bmodel->bounds[1], maxs ); + return; + } + + if ( !model->md3[0] ) { + VectorClear( mins ); + VectorClear( maxs ); + return; + } + + header = model->md3[0]; + + frame = (md3Frame_t *)( (byte *)header + header->ofsFrames ); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); +} + diff --git a/src/renderer/tr_noise.c b/src/renderer/tr_noise.c new file mode 100644 index 0000000..b4c4082 --- /dev/null +++ b/src/renderer/tr_noise.c @@ -0,0 +1,92 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_noise.c +#include "tr_local.h" + +#define NOISE_SIZE 256 +#define NOISE_MASK ( NOISE_SIZE - 1 ) + +#define VAL( a ) s_noise_perm[ ( a ) & ( NOISE_MASK )] +#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) + +static float s_noise_table[NOISE_SIZE]; +static int s_noise_perm[NOISE_SIZE]; + +static float GetNoiseValue( int x, int y, int z, int t ) +{ + int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); + + return s_noise_table[index]; +} + +void R_NoiseInit( void ) +{ + int i; + + for ( i = 0; i < NOISE_SIZE; i++ ) + { + s_noise_table[i] = ( float ) ( ( ( rand() / ( float ) RAND_MAX ) * 2.0 - 1.0 ) ); + s_noise_perm[i] = ( unsigned char ) ( rand() / ( float ) RAND_MAX * 255 ); + } +} + +float R_NoiseGet4f( float x, float y, float z, float t ) +{ + int i; + int ix, iy, iz, it; + float fx, fy, fz, ft; + float front[4]; + float back[4]; + float fvalue, bvalue, value[2], finalvalue; + + ix = ( int ) floor( x ); + fx = x - ix; + iy = ( int ) floor( y ); + fy = y - iy; + iz = ( int ) floor( z ); + fz = z - iz; + it = ( int ) floor( t ); + ft = t - it; + + for ( i = 0; i < 2; i++ ) + { + front[0] = GetNoiseValue( ix, iy, iz, it + i ); + front[1] = GetNoiseValue( ix+1, iy, iz, it + i ); + front[2] = GetNoiseValue( ix, iy+1, iz, it + i ); + front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i ); + + back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); + back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i ); + back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i ); + back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i ); + + fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); + bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); + + value[i] = LERP( fvalue, bvalue, fz ); + } + + finalvalue = LERP( value[0], value[1], ft ); + + return finalvalue; +} diff --git a/src/renderer/tr_public.h b/src/renderer/tr_public.h new file mode 100644 index 0000000..5126ab4 --- /dev/null +++ b/src/renderer/tr_public.h @@ -0,0 +1,173 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#ifndef __TR_PUBLIC_H +#define __TR_PUBLIC_H + +#include "tr_types.h" + +#define REF_API_VERSION 8 + +// +// these are the functions exported by the refresh module +// +typedef struct { + // called before the library is unloaded + // if the system is just reconfiguring, pass destroyWindow = qfalse, + // which will keep the screen from flashing to the desktop. + void (*Shutdown)( qboolean destroyWindow ); + + // All data that will be used in a level should be + // registered before rendering any frames to prevent disk hits, + // but they can still be registered at a later time + // if necessary. + // + // BeginRegistration makes any existing media pointers invalid + // and returns the current gl configuration, including screen width + // and height, which can be used by the client to intelligently + // size display elements + void (*BeginRegistration)( glconfig_t *config ); + qhandle_t (*RegisterModel)( const char *name ); + qhandle_t (*RegisterSkin)( const char *name ); + qhandle_t (*RegisterShader)( const char *name ); + qhandle_t (*RegisterShaderNoMip)( const char *name ); + void (*LoadWorld)( const char *name ); + + // the vis data is a large enough block of data that we go to the trouble + // of sharing it with the clipmodel subsystem + void (*SetWorldVisData)( const byte *vis ); + + // EndRegistration will draw a tiny polygon with each texture, forcing + // them to be loaded into card memory + void (*EndRegistration)( void ); + + // a scene is built up by calls to R_ClearScene and the various R_Add functions. + // Nothing is drawn until R_RenderScene is called. + void (*ClearScene)( void ); + void (*AddRefEntityToScene)( const refEntity_t *re ); + void (*AddPolyToScene)( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); + int (*LightForPoint)( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + void (*AddLightToScene)( const vec3_t org, float intensity, float r, float g, float b ); + void (*AddAdditiveLightToScene)( const vec3_t org, float intensity, float r, float g, float b ); + void (*RenderScene)( const refdef_t *fd ); + + void (*SetColor)( const float *rgba ); // NULL = 1,1,1,1 + void (*SetClipRegion)( const float *region ); + void (*DrawStretchPic) ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); // 0 = white + + // Draw images for cinematic rendering, pass as 32 bit rgba + void (*DrawStretchRaw) (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty); + void (*UploadCinematic) (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty); + + void (*BeginFrame)( stereoFrame_t stereoFrame ); + + // if the pointers are not NULL, timing info will be returned + void (*EndFrame)( int *frontEndMsec, int *backEndMsec ); + + + int (*MarkFragments)( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + int (*LerpTag)( orientation_t *tag, qhandle_t model, int startFrame, int endFrame, + float frac, const char *tagName ); + void (*ModelBounds)( qhandle_t model, vec3_t mins, vec3_t maxs ); + +#ifdef __USEA3D + void (*A3D_RenderGeometry) (void *pVoidA3D, void *pVoidGeom, void *pVoidMat, void *pVoidGeomStatus); +#endif + void (*RegisterFont)(const char *fontName, int pointSize, fontInfo_t *font); + void (*RemapShader)(const char *oldShader, const char *newShader, const char *offsetTime); + qboolean (*GetEntityToken)( char *buffer, int size ); + qboolean (*inPVS)( const vec3_t p1, const vec3_t p2 ); + + void (*TakeVideoFrame)( int h, int w, byte* captureBuffer, byte *encodeBuffer, qboolean motionJpeg ); +} refexport_t; + +// +// these are the functions imported by the refresh module +// +typedef struct { + // print message on the local console + void (QDECL *Printf)( int printLevel, const char *fmt, ...); + + // abort the game + void (QDECL *Error)( int errorLevel, const char *fmt, ...); + + // milliseconds should only be used for profiling, never + // for anything game related. Get time from the refdef + int (*Milliseconds)( void ); + + // stack based memory allocation for per-level things that + // won't be freed +#ifdef HUNK_DEBUG + void *(*Hunk_AllocDebug)( int size, ha_pref pref, char *label, char *file, int line ); +#else + void *(*Hunk_Alloc)( int size, ha_pref pref ); +#endif + void *(*Hunk_AllocateTempMemory)( int size ); + void (*Hunk_FreeTempMemory)( void *block ); + + // dynamic memory allocator for things that need to be freed + void *(*Malloc)( int bytes ); + void (*Free)( void *buf ); + + cvar_t *(*Cvar_Get)( const char *name, const char *value, int flags ); + void (*Cvar_Set)( const char *name, const char *value ); + void (*Cvar_CheckRange)( cvar_t *cv, float minVal, float maxVal, qboolean shouldBeIntegral ); + + void (*Cmd_AddCommand)( const char *name, void(*cmd)(void) ); + void (*Cmd_RemoveCommand)( const char *name ); + + int (*Cmd_Argc) (void); + char *(*Cmd_Argv) (int i); + + void (*Cmd_ExecuteText) (int exec_when, const char *text); + + // visualization for debugging collision detection + void (*CM_DrawDebugSurface)( void (*drawPoly)(int color, int numPoints, float *points) ); + + // a -1 return means the file does not exist + // NULL can be passed for buf to just determine existance + int (*FS_FileIsInPAK)( const char *name, int *pCheckSum ); + int (*FS_ReadFile)( const char *name, void **buf ); + void (*FS_FreeFile)( void *buf ); + char ** (*FS_ListFiles)( const char *name, const char *extension, int *numfilesfound ); + void (*FS_FreeFileList)( char **filelist ); + void (*FS_WriteFile)( const char *qpath, const void *buffer, int size ); + qboolean (*FS_FileExists)( const char *file ); + + // cinematic stuff + void (*CIN_UploadCinematic)(int handle); + int (*CIN_PlayCinematic)( const char *arg0, int xpos, int ypos, int width, int height, int bits); + e_status (*CIN_RunCinematic) (int handle); + + void (*CL_WriteAVIVideoFrame)( const byte *buffer, int size ); +} refimport_t; + + +// this is the only function actually exported at the linker level +// If the module can't init to a valid rendering state, NULL will be +// returned. +refexport_t*GetRefAPI( int apiVersion, refimport_t *rimp ); + +#endif // __TR_PUBLIC_H diff --git a/src/renderer/tr_scene.c b/src/renderer/tr_scene.c new file mode 100644 index 0000000..232cb68 --- /dev/null +++ b/src/renderer/tr_scene.c @@ -0,0 +1,419 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +int r_firstSceneDrawSurf; + +int r_numdlights; +int r_firstSceneDlight; + +int r_numentities; +int r_firstSceneEntity; + +int r_numpolys; +int r_firstScenePoly; + +int r_numpolyverts; + + +/* +==================== +R_ToggleSmpFrame + +==================== +*/ +void R_ToggleSmpFrame( void ) { + if ( r_smp->integer ) { + // use the other buffers next frame, because another CPU + // may still be rendering into the current ones + tr.smpFrame ^= 1; + } else { + tr.smpFrame = 0; + } + + backEndData[tr.smpFrame]->commands.used = 0; + + r_firstSceneDrawSurf = 0; + + r_numdlights = 0; + r_firstSceneDlight = 0; + + r_numentities = 0; + r_firstSceneEntity = 0; + + r_numpolys = 0; + r_firstScenePoly = 0; + + r_numpolyverts = 0; +} + + +/* +==================== +RE_ClearScene + +==================== +*/ +void RE_ClearScene( void ) { + r_firstSceneDlight = r_numdlights; + r_firstSceneEntity = r_numentities; + r_firstScenePoly = r_numpolys; +} + +/* +=========================================================================== + +DISCRETE POLYS + +=========================================================================== +*/ + +/* +===================== +R_AddPolygonSurfaces + +Adds all the scene's polys into this view's drawsurf list +===================== +*/ +void R_AddPolygonSurfaces( void ) { + int i; + shader_t *sh; + srfPoly_t *poly; + + tr.currentEntityNum = ENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) { + sh = R_GetShaderByHandle( poly->hShader ); + R_AddDrawSurf( ( void * )poly, sh, poly->fogIndex, qfalse ); + } +} + +/* +===================== +RE_AddPolyToScene + +===================== +*/ +void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) { + srfPoly_t *poly; + int i, j; + int fogIndex; + fog_t *fog; + vec3_t bounds[2]; + + if ( !tr.registered ) { + return; + } + + if ( !hShader ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n"); + return; + } + + for ( j = 0; j < numPolys; j++ ) { + if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) { + /* + NOTE TTimo this was initially a PRINT_WARNING + but it happens a lot with high fighting scenes and particles + since we don't plan on changing the const and making for room for those effects + simply cut this message to developer only + */ + ri.Printf( PRINT_DEVELOPER, "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n"); + return; + } + + poly = &backEndData[tr.smpFrame]->polys[r_numpolys]; + poly->surfaceType = SF_POLY; + poly->hShader = hShader; + poly->numVerts = numVerts; + poly->verts = &backEndData[tr.smpFrame]->polyVerts[r_numpolyverts]; + + Com_Memcpy( poly->verts, &verts[numVerts*j], numVerts * sizeof( *verts ) ); + + if ( glConfig.hardwareType == GLHW_RAGEPRO ) { + poly->verts->modulate[0] = 255; + poly->verts->modulate[1] = 255; + poly->verts->modulate[2] = 255; + poly->verts->modulate[3] = 255; + } + // done. + r_numpolys++; + r_numpolyverts += numVerts; + + // if no world is loaded + if ( tr.world == NULL ) { + fogIndex = 0; + } + // see if it is in a fog volume + else if ( tr.world->numfogs == 1 ) { + fogIndex = 0; + } else { + // find which fog volume the poly is in + VectorCopy( poly->verts[0].xyz, bounds[0] ); + VectorCopy( poly->verts[0].xyz, bounds[1] ); + for ( i = 1 ; i < poly->numVerts ; i++ ) { + AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] ); + } + for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) { + fog = &tr.world->fogs[fogIndex]; + if ( bounds[1][0] >= fog->bounds[0][0] + && bounds[1][1] >= fog->bounds[0][1] + && bounds[1][2] >= fog->bounds[0][2] + && bounds[0][0] <= fog->bounds[1][0] + && bounds[0][1] <= fog->bounds[1][1] + && bounds[0][2] <= fog->bounds[1][2] ) { + break; + } + } + if ( fogIndex == tr.world->numfogs ) { + fogIndex = 0; + } + } + poly->fogIndex = fogIndex; + } +} + + +//================================================================================= + + +/* +===================== +RE_AddRefEntityToScene + +===================== +*/ +void RE_AddRefEntityToScene( const refEntity_t *ent ) { + if ( !tr.registered ) { + return; + } + if ( r_numentities >= MAX_ENTITIES ) { + return; + } + if ( Q_isnan(ent->origin[0]) || Q_isnan(ent->origin[1]) || Q_isnan(ent->origin[2]) ) { + static qboolean firstTime = qtrue; + if (firstTime) { + firstTime = qfalse; + Com_DPrintf(S_COLOR_YELLOW "WARNING: RE_AddRefEntityToScene passed a refEntity which has an origin with a NaN component\n"); + } + return; + } + if ( ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) { + ri.Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType ); + } + + backEndData[tr.smpFrame]->entities[r_numentities].e = *ent; + backEndData[tr.smpFrame]->entities[r_numentities].lightingCalculated = qfalse; + + r_numentities++; +} + + +/* +===================== +RE_AddDynamicLightToScene + +===================== +*/ +void RE_AddDynamicLightToScene( const vec3_t org, float intensity, float r, float g, float b, int additive ) { + dlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( r_numdlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + // these cards don't have the correct blend mode + if ( glConfig.hardwareType == GLHW_RIVA128 || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + return; + } + dl = &backEndData[tr.smpFrame]->dlights[r_numdlights++]; + VectorCopy (org, dl->origin); + dl->radius = intensity; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; + dl->additive = additive; +} + +/* +===================== +RE_AddLightToScene + +===================== +*/ +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, qfalse ); +} + +/* +===================== +RE_AddAdditiveLightToScene + +===================== +*/ +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, qtrue ); +} + +/* +@@@@@@@@@@@@@@@@@@@@@ +RE_RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +@@@@@@@@@@@@@@@@@@@@@ +*/ +void RE_RenderScene( const refdef_t *fd ) { + viewParms_t parms; + int startTime; + + if ( !tr.registered ) { + return; + } + GLimp_LogComment( "====== RE_RenderScene =====\n" ); + + if ( r_norefresh->integer ) { + return; + } + + startTime = ri.Milliseconds(); + + if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) { + ri.Error (ERR_DROP, "R_RenderScene: NULL worldmodel"); + } + + Com_Memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) ); + + tr.refdef.x = fd->x; + tr.refdef.y = fd->y; + tr.refdef.width = fd->width; + tr.refdef.height = fd->height; + tr.refdef.fov_x = fd->fov_x; + tr.refdef.fov_y = fd->fov_y; + + VectorCopy( fd->vieworg, tr.refdef.vieworg ); + VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] ); + VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] ); + VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] ); + + tr.refdef.time = fd->time; + tr.refdef.rdflags = fd->rdflags; + + // copy the areamask data over and note if it has changed, which + // will force a reset of the visible leafs even if the view hasn't moved + tr.refdef.areamaskModified = qfalse; + if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) { + int areaDiff; + int i; + + // compare the area bits + areaDiff = 0; + for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) { + areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i]; + ((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i]; + } + + if ( areaDiff ) { + // a door just opened or something + tr.refdef.areamaskModified = qtrue; + } + } + + + // derived info + + tr.refdef.floatTime = tr.refdef.time * 0.001f; + + tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; + tr.refdef.drawSurfs = backEndData[tr.smpFrame]->drawSurfs; + + tr.refdef.num_entities = r_numentities - r_firstSceneEntity; + tr.refdef.entities = &backEndData[tr.smpFrame]->entities[r_firstSceneEntity]; + + tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight; + tr.refdef.dlights = &backEndData[tr.smpFrame]->dlights[r_firstSceneDlight]; + + tr.refdef.numPolys = r_numpolys - r_firstScenePoly; + tr.refdef.polys = &backEndData[tr.smpFrame]->polys[r_firstScenePoly]; + + // turn off dynamic lighting globally by clearing all the + // dlights if it needs to be disabled or if vertex lighting is enabled + if ( r_dynamiclight->integer == 0 || + r_vertexLight->integer == 1 || + glConfig.hardwareType == GLHW_PERMEDIA2 ) { + tr.refdef.num_dlights = 0; + } + + // a single frame may have multiple scenes draw inside it -- + // a 3D game view, 3D status bar renderings, 3D menus, etc. + // They need to be distinguished by the light flare code, because + // the visibility state for a given surface may be different in + // each scene / view. + tr.frameSceneNum++; + tr.sceneCount++; + + // setup view parms for the initial view + // + // set up viewport + // The refdef takes 0-at-the-top y coordinates, so + // convert to GL's 0-at-the-bottom space + // + Com_Memset( &parms, 0, sizeof( parms ) ); + parms.viewportX = tr.refdef.x; + parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height ); + parms.viewportWidth = tr.refdef.width; + parms.viewportHeight = tr.refdef.height; + parms.isPortal = qfalse; + + parms.fovX = tr.refdef.fov_x; + parms.fovY = tr.refdef.fov_y; + + parms.stereoFrame = tr.refdef.stereoFrame; + + VectorCopy( fd->vieworg, parms.or.origin ); + VectorCopy( fd->viewaxis[0], parms.or.axis[0] ); + VectorCopy( fd->viewaxis[1], parms.or.axis[1] ); + VectorCopy( fd->viewaxis[2], parms.or.axis[2] ); + + VectorCopy( fd->vieworg, parms.pvsOrigin ); + + R_RenderView( &parms ); + + // the next scene rendered in this frame will tack on after this one + r_firstSceneDrawSurf = tr.refdef.numDrawSurfs; + r_firstSceneEntity = r_numentities; + r_firstSceneDlight = r_numdlights; + r_firstScenePoly = r_numpolys; + + tr.frontEndMsec += ri.Milliseconds() - startTime; +} diff --git a/src/renderer/tr_shade.c b/src/renderer/tr_shade.c new file mode 100644 index 0000000..93ec88b --- /dev/null +++ b/src/renderer/tr_shade.c @@ -0,0 +1,1525 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_shade.c + +#include "tr_local.h" +#if idppc_altivec && !defined(MACOS_X) +#include +#endif + +/* + + THIS ENTIRE FILE IS BACK END + + This file deals with applying shaders to surface data in the tess struct. +*/ + +/* +================ +R_ArrayElementDiscrete + +This is just for OpenGL conformance testing, it should never be the fastest +================ +*/ +static void APIENTRY R_ArrayElementDiscrete( GLint index ) { + qglColor4ubv( tess.svars.colors[ index ] ); + if ( glState.currenttmu ) { + qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] ); + qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] ); + } else { + qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] ); + } + qglVertex3fv( tess.xyz[ index ] ); +} + +/* +=================== +R_DrawStripElements + +=================== +*/ +static int c_vertexes; // for seeing how long our average strips are +static int c_begins; +static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )(GLint) ) { + int i; + int last[3] = { -1, -1, -1 }; + qboolean even; + + c_begins++; + + if ( numIndexes <= 0 ) { + return; + } + + qglBegin( GL_TRIANGLE_STRIP ); + + // prime the strip + element( indexes[0] ); + element( indexes[1] ); + element( indexes[2] ); + c_vertexes += 3; + + last[0] = indexes[0]; + last[1] = indexes[1]; + last[2] = indexes[2]; + + even = qfalse; + + for ( i = 3; i < numIndexes; i += 3 ) + { + // odd numbered triangle in potential strip + if ( !even ) + { + // check previous triangle to see if we're continuing a strip + if ( ( indexes[i+0] == last[2] ) && ( indexes[i+1] == last[1] ) ) + { + element( indexes[i+2] ); + c_vertexes++; + assert( indexes[i+2] < tess.numVertexes ); + even = qtrue; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i+0] ); + element( indexes[i+1] ); + element( indexes[i+2] ); + + c_vertexes += 3; + + even = qfalse; + } + } + else + { + // check previous triangle to see if we're continuing a strip + if ( ( last[2] == indexes[i+1] ) && ( last[0] == indexes[i+0] ) ) + { + element( indexes[i+2] ); + c_vertexes++; + + even = qfalse; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i+0] ); + element( indexes[i+1] ); + element( indexes[i+2] ); + c_vertexes += 3; + + even = qfalse; + } + } + + // cache the last three vertices + last[0] = indexes[i+0]; + last[1] = indexes[i+1]; + last[2] = indexes[i+2]; + } + + qglEnd(); +} + + + +/* +================== +R_DrawElements + +Optionally performs our own glDrawElements that looks for strip conditions +instead of using the single glDrawElements call that may be inefficient +without compiled vertex arrays. +================== +*/ +static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) { + int primitives; + + primitives = r_primitives->integer; + + // default is to use triangles if compiled vertex arrays are present + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + + + if ( primitives == 2 ) { + qglDrawElements( GL_TRIANGLES, + numIndexes, + GL_INDEX_TYPE, + indexes ); + return; + } + + if ( primitives == 1 ) { + R_DrawStripElements( numIndexes, indexes, qglArrayElement ); + return; + } + + if ( primitives == 3 ) { + R_DrawStripElements( numIndexes, indexes, R_ArrayElementDiscrete ); + return; + } + + // anything else will cause no drawing +} + + +/* +============================================================= + +SURFACE SHADERS + +============================================================= +*/ + +shaderCommands_t tess; +static qboolean setArraysOnce; + +/* +================= +R_BindAnimatedImage + +================= +*/ +static void R_BindAnimatedImage( textureBundle_t *bundle ) { + int index; + + if ( bundle->isVideoMap ) { + ri.CIN_RunCinematic(bundle->videoMapHandle); + ri.CIN_UploadCinematic(bundle->videoMapHandle); + return; + } + + if ( bundle->numImageAnimations <= 1 ) { + GL_Bind( bundle->image[0] ); + return; + } + + // it is necessary to do this messy calc to make sure animations line up + // exactly with waveforms of the same frequency + index = myftol( tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE ); + index >>= FUNCTABLE_SIZE2; + + if ( index < 0 ) { + index = 0; // may happen with shader time offsets + } + index %= bundle->numImageAnimations; + + GL_Bind( bundle->image[ index ] ); +} + +/* +================ +DrawTris + +Draws triangle outlines for debugging +================ +*/ +static void DrawTris (shaderCommands_t *input) { + GL_Bind( tr.whiteImage ); + qglColor3f (1,1,1); + + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + qglDepthRange( 0, 0 ); + + qglDisableClientState (GL_COLOR_ARRAY); + qglDisableClientState (GL_TEXTURE_COORD_ARRAY); + + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD + + if (qglLockArraysEXT) { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + if (qglUnlockArraysEXT) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + qglDepthRange( 0, 1 ); +} + + +/* +================ +DrawNormals + +Draws vertex normals for debugging +================ +*/ +static void DrawNormals (shaderCommands_t *input) { + int i; + vec3_t temp; + + GL_Bind( tr.whiteImage ); + qglColor3f (1,1,1); + qglDepthRange( 0, 0 ); // never occluded + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + + qglBegin (GL_LINES); + for (i = 0 ; i < input->numVertexes ; i++) { + qglVertex3fv (input->xyz[i]); + VectorMA (input->xyz[i], 2, input->normal[i], temp); + qglVertex3fv (temp); + } + qglEnd (); + + qglDepthRange( 0, 1 ); +} + +/* +============== +RB_BeginSurface + +We must set some things up before beginning any tesselation, +because a surface may be forced to perform a RB_End due +to overflow. +============== +*/ +void RB_BeginSurface( shader_t *shader, int fogNum ) { + + shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader; + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.shader = state; + tess.fogNum = fogNum; + tess.dlightBits = 0; // will be OR'd in by surface functions + tess.xstages = state->stages; + tess.numPasses = state->numUnfoggedPasses; + tess.currentStageIteratorFunc = state->optimalStageIteratorFunc; + + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) { + tess.shaderTime = tess.shader->clampTime; + } + + +} + +/* +=================== +DrawMultitextured + +output = t0 * t1 or t0 + t1 + +t0 = most upstream according to spec +t1 = most downstream according to spec +=================== +*/ +static void DrawMultitextured( shaderCommands_t *input, int stage ) { + shaderStage_t *pStage; + + pStage = tess.xstages[stage]; + + GL_State( pStage->stateBits ); + + // this is an ugly hack to work around a GeForce driver + // bug with multitexture and clip planes + if ( backEnd.viewParms.isPortal ) { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + + // + // base + // + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + R_BindAnimatedImage( &pStage->bundle[0] ); + + // + // lightmap/secondary pass + // + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + + if ( r_lightmap->integer ) { + GL_TexEnv( GL_REPLACE ); + } else { + GL_TexEnv( tess.shader->multitextureEnv ); + } + + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] ); + + R_BindAnimatedImage( &pStage->bundle[1] ); + + R_DrawElements( input->numIndexes, input->indexes ); + + // + // disable texturing on TEXTURE1, then select TEXTURE0 + // + //qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + qglDisable( GL_TEXTURE_2D ); + + GL_SelectTexture( 0 ); +} + + + +/* +=================== +ProjectDlightTexture + +Perform dynamic lighting with another rendering pass +=================== +*/ +#if idppc_altivec +static void ProjectDlightTexture_altivec( void ) { + int i, l; + vec_t origin0, origin1, origin2; + float texCoords0, texCoords1; + vector float floatColorVec0, floatColorVec1; + vector float modulateVec, colorVec, zero; + vector short colorShort; + vector signed int colorInt; + vector unsigned char floatColorVecPerm, modulatePerm, colorChar; + vector unsigned char vSel = VECCONST_UINT8(0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff); + float *texCoords; + byte *colors; + byte clipBits[SHADER_MAX_VERTEXES]; + float texCoordsArray[SHADER_MAX_VERTEXES][2]; + byte colorArray[SHADER_MAX_VERTEXES][4]; + unsigned hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float scale; + float radius; + vec3_t floatColor; + float modulate = 0.0f; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + // There has to be a better way to do this so that floatColor + // and/or modulate are already 16-byte aligned. + floatColorVecPerm = vec_lvsl(0,(float *)floatColor); + modulatePerm = vec_lvsl(0,(float *)&modulate); + modulatePerm = (vector unsigned char)vec_splat((vector unsigned int)modulatePerm,0); + zero = (vector float)vec_splat_s8(0); + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + texCoords = texCoordsArray[0]; + colors = colorArray[0]; + + dl = &backEnd.refdef.dlights[l]; + origin0 = dl->transformed[0]; + origin1 = dl->transformed[1]; + origin2 = dl->transformed[2]; + radius = dl->radius; + scale = 1.0f / radius; + + if(r_greyscale->integer) + { + float luminance; + + luminance = LUMA(dl->color[0], dl->color[1], dl->color[2]) * 255.0f; + floatColor[0] = floatColor[1] = floatColor[2] = luminance; + } + else if(r_greyscale->value) + { + float luminance; + + luminance = LUMA(dl->color[0], dl->color[1], dl->color[2]) * 255.0f; + floatColor[0] = LERP(dl->color[0] * 255.0f, luminance, r_greyscale->value); + floatColor[1] = LERP(dl->color[1] * 255.0f, luminance, r_greyscale->value); + floatColor[2] = LERP(dl->color[2] * 255.0f, luminance, r_greyscale->value); + } + else + { + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + } + floatColorVec0 = vec_ld(0, floatColor); + floatColorVec1 = vec_ld(11, floatColor); + floatColorVec0 = vec_perm(floatColorVec0,floatColorVec0,floatColorVecPerm); + for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { + int clip = 0; + vec_t dist0, dist1, dist2; + + dist0 = origin0 - tess.xyz[i][0]; + dist1 = origin1 - tess.xyz[i][1]; + dist2 = origin2 - tess.xyz[i][2]; + + backEnd.pc.c_dlightVertexes++; + + texCoords0 = 0.5f + dist0 * scale; + texCoords1 = 0.5f + dist1 * scale; + + if( !r_dlightBacks->integer && + // dist . tess.normal[i] + ( dist0 * tess.normal[i][0] + + dist1 * tess.normal[i][1] + + dist2 * tess.normal[i][2] ) < 0.0f ) { + clip = 63; + } else { + if ( texCoords0 < 0.0f ) { + clip |= 1; + } else if ( texCoords0 > 1.0f ) { + clip |= 2; + } + if ( texCoords1 < 0.0f ) { + clip |= 4; + } else if ( texCoords1 > 1.0f ) { + clip |= 8; + } + texCoords[0] = texCoords0; + texCoords[1] = texCoords1; + + // modulate the strength based on the height and color + if ( dist2 > radius ) { + clip |= 16; + modulate = 0.0f; + } else if ( dist2 < -radius ) { + clip |= 32; + modulate = 0.0f; + } else { + dist2 = Q_fabs(dist2); + if ( dist2 < radius * 0.5f ) { + modulate = 1.0f; + } else { + modulate = 2.0f * (radius - dist2) * scale; + } + } + } + clipBits[i] = clip; + + modulateVec = vec_ld(0,(float *)&modulate); + modulateVec = vec_perm(modulateVec,modulateVec,modulatePerm); + colorVec = vec_madd(floatColorVec0,modulateVec,zero); + colorInt = vec_cts(colorVec,0); // RGBx + colorShort = vec_pack(colorInt,colorInt); // RGBxRGBx + colorChar = vec_packsu(colorShort,colorShort); // RGBxRGBxRGBxRGBx + colorChar = vec_sel(colorChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255 + vec_ste((vector unsigned int)colorChar,0,(unsigned int *)colors); // store color + } + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) { + continue; // not lighted + } + hitIndexes[numIndexes] = a; + hitIndexes[numIndexes+1] = b; + hitIndexes[numIndexes+2] = c; + numIndexes += 3; + } + + if ( !numIndexes ) { + continue; + } + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + if ( dl->additive ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + else { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +} +#endif + + +static void ProjectDlightTexture_scalar( void ) { + int i, l; + vec3_t origin; + float *texCoords; + byte *colors; + byte clipBits[SHADER_MAX_VERTEXES]; + float texCoordsArray[SHADER_MAX_VERTEXES][2]; + byte colorArray[SHADER_MAX_VERTEXES][4]; + unsigned hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float scale; + float radius; + vec3_t floatColor; + float modulate = 0.0f; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + texCoords = texCoordsArray[0]; + colors = colorArray[0]; + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + scale = 1.0f / radius; + + if(r_greyscale->integer) + { + float luminance; + + luminance = LUMA(dl->color[0], dl->color[1], dl->color[2]) * 255.0f; + floatColor[0] = floatColor[1] = floatColor[2] = luminance; + } + else if(r_greyscale->value) + { + float luminance; + + luminance = LUMA(dl->color[0], dl->color[1], dl->color[2]) * 255.0f; + floatColor[0] = LERP(dl->color[0] * 255.0f, luminance, r_greyscale->value); + floatColor[1] = LERP(dl->color[1] * 255.0f, luminance, r_greyscale->value); + floatColor[2] = LERP(dl->color[2] * 255.0f, luminance, r_greyscale->value); + } + else + { + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + } + + for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { + int clip = 0; + vec3_t dist; + + VectorSubtract( origin, tess.xyz[i], dist ); + + backEnd.pc.c_dlightVertexes++; + + texCoords[0] = 0.5f + dist[0] * scale; + texCoords[1] = 0.5f + dist[1] * scale; + + if( !r_dlightBacks->integer && + // dist . tess.normal[i] + ( dist[0] * tess.normal[i][0] + + dist[1] * tess.normal[i][1] + + dist[2] * tess.normal[i][2] ) < 0.0f ) { + clip = 63; + } else { + if ( texCoords[0] < 0.0f ) { + clip |= 1; + } else if ( texCoords[0] > 1.0f ) { + clip |= 2; + } + if ( texCoords[1] < 0.0f ) { + clip |= 4; + } else if ( texCoords[1] > 1.0f ) { + clip |= 8; + } + texCoords[0] = texCoords[0]; + texCoords[1] = texCoords[1]; + + // modulate the strength based on the height and color + if ( dist[2] > radius ) { + clip |= 16; + modulate = 0.0f; + } else if ( dist[2] < -radius ) { + clip |= 32; + modulate = 0.0f; + } else { + dist[2] = Q_fabs(dist[2]); + if ( dist[2] < radius * 0.5f ) { + modulate = 1.0f; + } else { + modulate = 2.0f * (radius - dist[2]) * scale; + } + } + } + clipBits[i] = clip; + colors[0] = myftol(floatColor[0] * modulate); + colors[1] = myftol(floatColor[1] * modulate); + colors[2] = myftol(floatColor[2] * modulate); + colors[3] = 255; + } + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) { + continue; // not lighted + } + hitIndexes[numIndexes] = a; + hitIndexes[numIndexes+1] = b; + hitIndexes[numIndexes+2] = c; + numIndexes += 3; + } + + if ( !numIndexes ) { + continue; + } + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + if ( dl->additive ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + else { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +} + +static void ProjectDlightTexture( void ) { +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + ProjectDlightTexture_altivec(); + return; + } +#endif + ProjectDlightTexture_scalar(); +} + + +/* +=================== +RB_FogPass + +Blends a fog texture on top of everything else +=================== +*/ +static void RB_FogPass( void ) { + fog_t *fog; + int i; + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[0] ); + + GL_Bind( tr.fogImage ); + + if ( tess.shader->fogPass == FP_EQUAL ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + } else { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + + R_DrawElements( tess.numIndexes, tess.indexes ); +} + +/* +=============== +ComputeColors +=============== +*/ +static void ComputeColors( shaderStage_t *pStage ) +{ + int i; + + // + // rgbGen + // + switch ( pStage->rgbGen ) + { + case CGEN_IDENTITY: + Com_Memset( tess.svars.colors, 0xff, tess.numVertexes * 4 ); + break; + default: + case CGEN_IDENTITY_LIGHTING: + Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 ); + break; + case CGEN_LIGHTING_DIFFUSE: + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_EXACT_VERTEX: + Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + break; + case CGEN_CONST: + for ( i = 0; i < tess.numVertexes; i++ ) { + *(int *)tess.svars.colors[i] = *(int *)pStage->constantColor; + } + break; + case CGEN_VERTEX: + if ( tr.identityLight == 1 ) + { + Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight; + tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight; + tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight; + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case CGEN_ONE_MINUS_VERTEX: + if ( tr.identityLight == 1 ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0]; + tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1]; + tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2]; + } + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight; + tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight; + tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight; + } + } + break; + case CGEN_FOG: + { + fog_t *fog; + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + } + break; + case CGEN_WAVEFORM: + RB_CalcWaveColor( &pStage->rgbWave, ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_ENTITY: + RB_CalcColorFromEntity( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_ONE_MINUS_ENTITY: + RB_CalcColorFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + } + + // + // alphaGen + // + switch ( pStage->alphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_IDENTITY: + if ( pStage->rgbGen != CGEN_IDENTITY ) { + if ( ( pStage->rgbGen == CGEN_VERTEX && tr.identityLight != 1 ) || + pStage->rgbGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = 0xff; + } + } + } + break; + case AGEN_CONST: + if ( pStage->rgbGen != CGEN_CONST ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = pStage->constantColor[3]; + } + } + break; + case AGEN_WAVEFORM: + RB_CalcWaveAlpha( &pStage->alphaWave, ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_LIGHTING_SPECULAR: + RB_CalcSpecularAlpha( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ENTITY: + RB_CalcAlphaFromEntity( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ONE_MINUS_ENTITY: + RB_CalcAlphaFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_VERTEX: + if ( pStage->rgbGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case AGEN_ONE_MINUS_VERTEX: + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3]; + } + break; + case AGEN_PORTAL: + { + unsigned char alpha; + + for ( i = 0; i < tess.numVertexes; i++ ) + { + float len; + vec3_t v; + + VectorSubtract( tess.xyz[i], backEnd.viewParms.or.origin, v ); + len = VectorLength( v ); + + len /= tess.shader->portalRange; + + if ( len < 0 ) + { + alpha = 0; + } + else if ( len > 1 ) + { + alpha = 0xff; + } + else + { + alpha = len * 0xff; + } + + tess.svars.colors[i][3] = alpha; + } + } + break; + } + + // + // fog adjustment for colors to fade out as fog increases + // + if ( tess.fogNum ) + { + switch ( pStage->adjustColorsForFog ) + { + case ACFF_MODULATE_RGB: + RB_CalcModulateColorsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_ALPHA: + RB_CalcModulateAlphasByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_RGBA: + RB_CalcModulateRGBAsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_NONE: + break; + } + } + + // if in greyscale rendering mode turn all color values into greyscale. + if(r_greyscale->integer) + { + int scale; + for(i = 0; i < tess.numVertexes; i++) + { + scale = LUMA(tess.svars.colors[i][0], tess.svars.colors[i][1], tess.svars.colors[i][2]); + tess.svars.colors[i][0] = tess.svars.colors[i][1] = tess.svars.colors[i][2] = scale; + } + } + else if(r_greyscale->value) + { + float scale; + + for(i = 0; i < tess.numVertexes; i++) + { + scale = LUMA(tess.svars.colors[i][0], tess.svars.colors[i][1], tess.svars.colors[i][2]); + tess.svars.colors[i][0] = LERP(tess.svars.colors[i][0], scale, r_greyscale->value); + tess.svars.colors[i][1] = LERP(tess.svars.colors[i][1], scale, r_greyscale->value); + tess.svars.colors[i][2] = LERP(tess.svars.colors[i][2], scale, r_greyscale->value); + } + } +} + +/* +=============== +ComputeTexCoords +=============== +*/ +static void ComputeTexCoords( shaderStage_t *pStage ) { + int i; + int b; + + for ( b = 0; b < NUM_TEXTURE_BUNDLES; b++ ) { + int tm; + + // + // generate the texture coordinates + // + switch ( pStage->bundle[b].tcGen ) + { + case TCGEN_IDENTITY: + Com_Memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes ); + break; + case TCGEN_TEXTURE: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1]; + } + break; + case TCGEN_LIGHTMAP: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][1][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][1][1]; + } + break; + case TCGEN_VECTOR: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[0] ); + tess.svars.texcoords[b][i][1] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[1] ); + } + break; + case TCGEN_FOG: + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_ENVIRONMENT_MAPPED: + RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_BAD: + return; + } + + // + // alter texture coordinates + // + for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) { + switch ( pStage->bundle[b].texMods[tm].type ) + { + case TMOD_NONE: + tm = TR_MAX_TEXMODS; // break out of for loop + break; + + case TMOD_TURBULENT: + RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ENTITY_TRANSLATE: + RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCROLL: + RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].scroll, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCALE: + RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].scale, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_STRETCH: + RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_TRANSFORM: + RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm], + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ROTATE: + RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].rotateSpeed, + ( float * ) tess.svars.texcoords[b] ); + break; + + default: + ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", pStage->bundle[b].texMods[tm].type, tess.shader->name ); + break; + } + } + } +} + +/* +** RB_IterateStagesGeneric +*/ +static void RB_IterateStagesGeneric( shaderCommands_t *input ) +{ + int stage; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) + { + shaderStage_t *pStage = tess.xstages[stage]; + + if ( !pStage ) + { + break; + } + + ComputeColors( pStage ); + ComputeTexCoords( pStage ); + + if ( !setArraysOnce ) + { + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors ); + } + + // + // do multitexture + // + if ( pStage->bundle[1].image[0] != 0 ) + { + DrawMultitextured( input, stage ); + } + else + { + if ( !setArraysOnce ) + { + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + } + + // + // set state + // + if ( pStage->bundle[0].vertexLightmap && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) && r_lightmap->integer ) + { + GL_Bind( tr.whiteImage ); + } + else + R_BindAnimatedImage( &pStage->bundle[0] ); + + GL_State( pStage->stateBits ); + + // + // draw + // + R_DrawElements( input->numIndexes, input->indexes ); + } + // allow skipping out to show just lightmaps during development + if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap ) ) + { + break; + } + } +} + + +/* +** RB_StageIteratorGeneric +*/ +void RB_StageIteratorGeneric( void ) +{ + shaderCommands_t *input; + + input = &tess; + + RB_DeformTessGeometry(); + + // + // log this call + // + if ( r_logFile->integer ) + { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) ); + } + + // + // set face culling appropriately + // + GL_Cull( input->shader->cullType ); + + // set polygon offset if necessary + if ( input->shader->polygonOffset ) + { + qglEnable( GL_POLYGON_OFFSET_FILL ); + qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value ); + } + + // + // if there is only a single pass then we can enable color + // and texture arrays before we compile, otherwise we need + // to avoid compiling those arrays since they will change + // during multipass rendering + // + if ( tess.numPasses > 1 || input->shader->multitextureEnv ) + { + setArraysOnce = qfalse; + qglDisableClientState (GL_COLOR_ARRAY); + qglDisableClientState (GL_TEXTURE_COORD_ARRAY); + } + else + { + setArraysOnce = qtrue; + + qglEnableClientState( GL_COLOR_ARRAY); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + } + + // + // lock XYZ + // + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD + if (qglLockArraysEXT) + { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + // + // enable color and texcoord arrays after the lock if necessary + // + if ( !setArraysOnce ) + { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnableClientState( GL_COLOR_ARRAY ); + } + + // + // call shader function + // + RB_IterateStagesGeneric( input ); + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } +} + + +/* +** RB_StageIteratorVertexLitTexture +*/ +void RB_StageIteratorVertexLitTexture( void ) +{ + shaderCommands_t *input; + shader_t *shader; + + input = &tess; + + shader = input->shader; + + // + // compute colors + // + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); + + // + // log this call + // + if ( r_logFile->integer ) + { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va("--- RB_StageIteratorVertexLitTexturedUnfogged( %s ) ---\n", tess.shader->name) ); + } + + // + // set face culling appropriately + // + GL_Cull( input->shader->cullType ); + + // + // set arrays and lock + // + qglEnableClientState( GL_COLOR_ARRAY); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] ); + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); + + if ( qglLockArraysEXT ) + { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + // + // call special shade routine + // + R_BindAnimatedImage( &tess.xstages[0]->bundle[0] ); + GL_State( tess.xstages[0]->stateBits ); + R_DrawElements( input->numIndexes, input->indexes ); + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } +} + +//define REPLACE_MODE + +void RB_StageIteratorLightmappedMultitexture( void ) { + shaderCommands_t *input; + + input = &tess; + + // + // log this call + // + if ( r_logFile->integer ) { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va("--- RB_StageIteratorLightmappedMultitexture( %s ) ---\n", tess.shader->name) ); + } + + // + // set face culling appropriately + // + GL_Cull( input->shader->cullType ); + + // + // set color, pointers, and lock + // + GL_State( GLS_DEFAULT ); + qglVertexPointer( 3, GL_FLOAT, 16, input->xyz ); + +#ifdef REPLACE_MODE + qglDisableClientState( GL_COLOR_ARRAY ); + qglColor3f( 1, 1, 1 ); + qglShadeModel( GL_FLAT ); +#else + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.constantColor255 ); +#endif + + // + // select base stage + // + GL_SelectTexture( 0 ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + R_BindAnimatedImage( &tess.xstages[0]->bundle[0] ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] ); + + // + // configure second stage + // + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + if ( r_lightmap->integer ) { + GL_TexEnv( GL_REPLACE ); + } else { + GL_TexEnv( GL_MODULATE ); + } + R_BindAnimatedImage( &tess.xstages[0]->bundle[1] ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][1] ); + + // + // lock arrays + // + if ( qglLockArraysEXT ) { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + // + // disable texturing on TEXTURE1, then select TEXTURE0 + // + qglDisable( GL_TEXTURE_2D ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + GL_SelectTexture( 0 ); +#ifdef REPLACE_MODE + GL_TexEnv( GL_MODULATE ); + qglShadeModel( GL_SMOOTH ); +#endif + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if ( qglUnlockArraysEXT ) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } +} + +/* +** RB_EndSurface +*/ +void RB_EndSurface( void ) { + shaderCommands_t *input; + + input = &tess; + + if (input->numIndexes == 0) { + return; + } + + if (input->indexes[SHADER_MAX_INDEXES-1] != 0) { + ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit"); + } + if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) { + ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit"); + } + + if ( tess.shader == tr.shadowShader ) { + RB_ShadowTessEnd(); + return; + } + + // for debugging of sort order issues, stop rendering after a given sort value + if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) { + return; + } + + // + // update performance counters + // + backEnd.pc.c_shaders++; + backEnd.pc.c_vertexes += tess.numVertexes; + backEnd.pc.c_indexes += tess.numIndexes; + backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses; + + // + // call off to shader specific tess end function + // + tess.currentStageIteratorFunc(); + + // + // draw debugging stuff + // + if ( r_showtris->integer ) { + DrawTris (input); + } + if ( r_shownormals->integer ) { + DrawNormals (input); + } + // clear shader so we can tell we don't have any unclosed surfaces + tess.numIndexes = 0; + + GLimp_LogComment( "----------\n" ); +} + diff --git a/src/renderer/tr_shade_calc.c b/src/renderer/tr_shade_calc.c new file mode 100644 index 0000000..ba61a6d --- /dev/null +++ b/src/renderer/tr_shade_calc.c @@ -0,0 +1,1232 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_shade_calc.c + +#include "tr_local.h" +#if idppc_altivec && !defined(MACOS_X) +#include +#endif + + +#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ myftol( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) + +static float *TableForFunc( genFunc_t func ) +{ + switch ( func ) + { + case GF_SIN: + return tr.sinTable; + case GF_TRIANGLE: + return tr.triangleTable; + case GF_SQUARE: + return tr.squareTable; + case GF_SAWTOOTH: + return tr.sawToothTable; + case GF_INVERSE_SAWTOOTH: + return tr.inverseSawToothTable; + case GF_NONE: + default: + break; + } + + ri.Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'\n", func, tess.shader->name ); + return NULL; +} + +/* +** EvalWaveForm +** +** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly +*/ +static float EvalWaveForm( const waveForm_t *wf ) +{ + float *table; + + table = TableForFunc( wf->func ); + + return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency ); +} + +static float EvalWaveFormClamped( const waveForm_t *wf ) +{ + float glow = EvalWaveForm( wf ); + + if ( glow < 0 ) + { + return 0; + } + + if ( glow > 1 ) + { + return 1; + } + + return glow; +} + +/* +** RB_CalcStretchTexCoords +*/ +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st ) +{ + float p; + texModInfo_t tmi; + + p = 1.0f / EvalWaveForm( wf ); + + tmi.matrix[0][0] = p; + tmi.matrix[1][0] = 0; + tmi.translate[0] = 0.5f - 0.5f * p; + + tmi.matrix[0][1] = 0; + tmi.matrix[1][1] = p; + tmi.translate[1] = 0.5f - 0.5f * p; + + RB_CalcTransformTexCoords( &tmi, st ); +} + +/* +==================================================================== + +DEFORMATIONS + +==================================================================== +*/ + +/* +======================== +RB_CalcDeformVertexes + +======================== +*/ +void RB_CalcDeformVertexes( deformStage_t *ds ) +{ + int i; + vec3_t offset; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float *table; + + if ( ds->deformationWave.frequency == 0 ) + { + scale = EvalWaveForm( &ds->deformationWave ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } + else + { + table = TableForFunc( ds->deformationWave.func ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase + off, + ds->deformationWave.frequency ); + + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } +} + +/* +========================= +RB_CalcDeformNormals + +Wiggle the normals for wavy environment mapping +========================= +*/ +void RB_CalcDeformNormals( deformStage_t *ds ) { + int i; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { + scale = 0.98f; + scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 0 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 1 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 2 ] += ds->deformationWave.amplitude * scale; + + VectorNormalizeFast( normal ); + } +} + +/* +======================== +RB_CalcBulgeVertexes + +======================== +*/ +void RB_CalcBulgeVertexes( deformStage_t *ds ) { + int i; + const float *st = ( const float * ) tess.texCoords[0]; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float now; + + now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 4, normal += 4 ) { + int off; + float scale; + + off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); + + scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; + + xyz[0] += normal[0] * scale; + xyz[1] += normal[1] * scale; + xyz[2] += normal[2] * scale; + } +} + + +/* +====================== +RB_CalcMoveVertexes + +A deformation that can move an entire surface along a wave path +====================== +*/ +void RB_CalcMoveVertexes( deformStage_t *ds ) { + int i; + float *xyz; + float *table; + float scale; + vec3_t offset; + + table = TableForFunc( ds->deformationWave.func ); + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase, + ds->deformationWave.frequency ); + + VectorScale( ds->moveVector, scale, offset ); + + xyz = ( float * ) tess.xyz; + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + VectorAdd( xyz, offset, xyz ); + } +} + + +/* +============= +DeformText + +Change a polygon into a bunch of text polygons +============= +*/ +void DeformText( const char *text ) { + int i; + vec3_t origin, width, height; + int len; + int ch; + byte color[4]; + float bottom, top; + vec3_t mid; + + height[0] = 0; + height[1] = 0; + height[2] = -1; + CrossProduct( tess.normal[0], height, width ); + + // find the midpoint of the box + VectorClear( mid ); + bottom = 999999; + top = -999999; + for ( i = 0 ; i < 4 ; i++ ) { + VectorAdd( tess.xyz[i], mid, mid ); + if ( tess.xyz[i][2] < bottom ) { + bottom = tess.xyz[i][2]; + } + if ( tess.xyz[i][2] > top ) { + top = tess.xyz[i][2]; + } + } + VectorScale( mid, 0.25f, origin ); + + // determine the individual character size + height[0] = 0; + height[1] = 0; + height[2] = ( top - bottom ) * 0.5f; + + VectorScale( width, height[2] * -0.75f, width ); + + // determine the starting position + len = strlen( text ); + VectorMA( origin, (len-1), width, origin ); + + // clear the shader indexes + tess.numIndexes = 0; + tess.numVertexes = 0; + + color[0] = color[1] = color[2] = color[3] = 255; + + // draw each character + for ( i = 0 ; i < len ; i++ ) { + ch = text[i]; + ch &= 255; + + if ( ch != ' ' ) { + int row, col; + float frow, fcol, size; + + row = ch>>4; + col = ch&15; + + frow = row*0.0625f; + fcol = col*0.0625f; + size = 0.0625f; + + RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size ); + } + VectorMA( origin, -2, width, origin ); + } +} + +/* +================== +GlobalVectorToLocal +================== +*/ +static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) { + out[0] = DotProduct( in, backEnd.or.axis[0] ); + out[1] = DotProduct( in, backEnd.or.axis[1] ); + out[2] = DotProduct( in, backEnd.or.axis[2] ); +} + +/* +===================== +AutospriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites +===================== +*/ +static void AutospriteDeform( void ) { + int i; + int oldVerts; + float *xyz; + vec3_t mid, delta; + float radius; + vec3_t left, up; + vec3_t leftDir, upDir; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count\n", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count\n", tess.shader->name ); + } + + oldVerts = tess.numVertexes; + tess.numVertexes = 0; + tess.numIndexes = 0; + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.or.axis[1], leftDir ); + GlobalVectorToLocal( backEnd.viewParms.or.axis[2], upDir ); + } else { + VectorCopy( backEnd.viewParms.or.axis[1], leftDir ); + VectorCopy( backEnd.viewParms.or.axis[2], upDir ); + } + + for ( i = 0 ; i < oldVerts ; i+=4 ) { + // find the midpoint + xyz = tess.xyz[i]; + + mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]); + mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]); + mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]); + + VectorSubtract( xyz, mid, delta ); + radius = VectorLength( delta ) * 0.707f; // / sqrt(2) + + VectorScale( leftDir, radius, left ); + VectorScale( upDir, radius, up ); + + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + // compensate for scale in the axes if necessary + if ( backEnd.currentEntity->e.nonNormalizedAxes ) { + float axisLength; + axisLength = VectorLength( backEnd.currentEntity->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + VectorScale(left, axisLength, left); + VectorScale(up, axisLength, up); + } + + RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] ); + } +} + + +/* +===================== +Autosprite2Deform + +Autosprite2 will pivot a rectangular quad along the center of its long axis +===================== +*/ +int edgeVerts[6][2] = { + { 0, 1 }, + { 0, 2 }, + { 0, 3 }, + { 1, 2 }, + { 1, 3 }, + { 2, 3 } +}; + +static void Autosprite2Deform( void ) { + int i, j, k; + int indexes; + float *xyz; + vec3_t forward; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count", tess.shader->name ); + } + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.or.axis[0], forward ); + } else { + VectorCopy( backEnd.viewParms.or.axis[0], forward ); + } + + // this is a lot of work for two triangles... + // we could precalculate a lot of it is an issue, but it would mess up + // the shader abstraction + for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) { + float lengths[2]; + int nums[2]; + vec3_t mid[2]; + vec3_t major, minor; + float *v1, *v2; + + // find the midpoint + xyz = tess.xyz[i]; + + // identify the two shortest edges + nums[0] = nums[1] = 0; + lengths[0] = lengths[1] = 999999; + + for ( j = 0 ; j < 6 ; j++ ) { + float l; + vec3_t temp; + + v1 = xyz + 4 * edgeVerts[j][0]; + v2 = xyz + 4 * edgeVerts[j][1]; + + VectorSubtract( v1, v2, temp ); + + l = DotProduct( temp, temp ); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + for ( j = 0 ; j < 2 ; j++ ) { + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + mid[j][0] = 0.5f * (v1[0] + v2[0]); + mid[j][1] = 0.5f * (v1[1] + v2[1]); + mid[j][2] = 0.5f * (v1[2] + v2[2]); + } + + // find the vector of the major axis + VectorSubtract( mid[1], mid[0], major ); + + // cross this with the view direction to get minor axis + CrossProduct( major, forward, minor ); + VectorNormalize( minor ); + + // re-project the points + for ( j = 0 ; j < 2 ; j++ ) { + float l; + + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + l = 0.5 * sqrt( lengths[j] ); + + // we need to see which direction this edge + // is used to determine direction of projection + for ( k = 0 ; k < 5 ; k++ ) { + if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] + && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) { + break; + } + } + + if ( k == 5 ) { + VectorMA( mid[j], l, minor, v1 ); + VectorMA( mid[j], -l, minor, v2 ); + } else { + VectorMA( mid[j], -l, minor, v1 ); + VectorMA( mid[j], l, minor, v2 ); + } + } + } +} + + +/* +===================== +RB_DeformTessGeometry + +===================== +*/ +void RB_DeformTessGeometry( void ) { + int i; + deformStage_t *ds; + + for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) { + ds = &tess.shader->deforms[ i ]; + + switch ( ds->deformation ) { + case DEFORM_NONE: + break; + case DEFORM_NORMALS: + RB_CalcDeformNormals( ds ); + break; + case DEFORM_WAVE: + RB_CalcDeformVertexes( ds ); + break; + case DEFORM_BULGE: + RB_CalcBulgeVertexes( ds ); + break; + case DEFORM_MOVE: + RB_CalcMoveVertexes( ds ); + break; + case DEFORM_PROJECTION_SHADOW: + RB_ProjectionShadowDeform(); + break; + case DEFORM_AUTOSPRITE: + AutospriteDeform(); + break; + case DEFORM_AUTOSPRITE2: + Autosprite2Deform(); + break; + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: + DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] ); + break; + } + } +} + +/* +==================================================================== + +COLORS + +==================================================================== +*/ + + +/* +** RB_CalcColorFromEntity +*/ +void RB_CalcColorFromEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + int c; + + if ( !backEnd.currentEntity ) + return; + + c = * ( int * ) backEnd.currentEntity->e.shaderRGBA; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = c; + } +} + +/* +** RB_CalcColorFromOneMinusEntity +*/ +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + unsigned char invModulate[4]; + int c; + + if ( !backEnd.currentEntity ) + return; + + invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; + invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; + invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; + invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it + + c = * ( int * ) invModulate; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = * ( int * ) invModulate; + } +} + +/* +** RB_CalcAlphaFromEntity +*/ +void RB_CalcAlphaFromEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +/* +** RB_CalcAlphaFromOneMinusEntity +*/ +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +/* +** RB_CalcWaveColor +*/ +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + int *colors = ( int * ) dstColors; + byte color[4]; + + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } + else if ( glow > 1 ) { + glow = 1; + } + + v = myftol( 255 * glow ); + color[0] = color[1] = color[2] = v; + color[3] = 255; + v = *(int *)color; + + for ( i = 0; i < tess.numVertexes; i++, colors++ ) { + *colors = v; + } +} + +/* +** RB_CalcWaveAlpha +*/ +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + + glow = EvalWaveFormClamped( wf ); + + v = 255 * glow; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + dstColors[3] = v; + } +} + +/* +** RB_CalcModulateColorsByFog +*/ +void RB_CalcModulateColorsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + } +} + +/* +** RB_CalcModulateAlphasByFog +*/ +void RB_CalcModulateAlphasByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[3] *= f; + } +} + +/* +** RB_CalcModulateRGBAsByFog +*/ +void RB_CalcModulateRGBAsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + colors[3] *= f; + } +} + + +/* +==================================================================== + +TEX COORDS + +==================================================================== +*/ + +/* +======================== +RB_CalcFogTexCoords + +To do the clipped fog plane really correctly, we should use +projected textures, but I don't trust the drivers and it +doesn't fit our shader data. +======================== +*/ +void RB_CalcFogTexCoords( float *st ) { + int i; + float *v; + float s, t; + float eyeT; + qboolean eyeOutside; + fog_t *fog; + vec3_t local; + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + + fog = tr.world->fogs + tess.fogNum; + + // all fogging distance is based on world Z units + VectorSubtract( backEnd.or.origin, backEnd.viewParms.or.origin, local ); + fogDistanceVector[0] = -backEnd.or.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.or.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.or.modelMatrix[10]; + fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.or.axis[0] ); + + // scale the fog vectors based on the fog's thickness + fogDistanceVector[0] *= fog->tcScale; + fogDistanceVector[1] *= fog->tcScale; + fogDistanceVector[2] *= fog->tcScale; + fogDistanceVector[3] *= fog->tcScale; + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.or.axis[0][0] + + fog->surface[1] * backEnd.or.axis[0][1] + fog->surface[2] * backEnd.or.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.or.axis[1][0] + + fog->surface[1] * backEnd.or.axis[1][1] + fog->surface[2] * backEnd.or.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.or.axis[2][0] + + fog->surface[1] * backEnd.or.axis[2][1] + fog->surface[2] * backEnd.or.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.or.origin, fog->surface ); + + eyeT = DotProduct( backEnd.or.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + eyeT = 1; // non-surface fog always has eye inside + } + + // see if the viewpoint is outside + // this is needed for clipping distance even for constant fog + + if ( eyeT < 0 ) { + eyeOutside = qtrue; + } else { + eyeOutside = qfalse; + } + + fogDistanceVector[3] += 1.0/512; + + // calculate density for each point + for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) { + // calculate the length in fog + s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3]; + t = DotProduct( v, fogDepthVector ) + fogDepthVector[3]; + + // partially clipped fogs use the T axis + if ( eyeOutside ) { + if ( t < 1.0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); // cut the distance at the fog plane + } + } else { + if ( t < 0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 31.0/32; + } + } + + st[0] = s; + st[1] = t; + st += 2; + } +} + + + +/* +** RB_CalcEnvironmentTexCoords +*/ +void RB_CalcEnvironmentTexCoords( float *st ) +{ + int i; + float *v, *normal; + vec3_t viewer, reflected; + float d; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) + { + VectorSubtract (backEnd.or.viewOrigin, v, viewer); + VectorNormalizeFast (viewer); + + d = DotProduct (normal, viewer); + + reflected[0] = normal[0]*2*d - viewer[0]; + reflected[1] = normal[1]*2*d - viewer[1]; + reflected[2] = normal[2]*2*d - viewer[2]; + + st[0] = 0.5 + reflected[1] * 0.5; + st[1] = 0.5 - reflected[2] * 0.5; + } +} + +/* +** RB_CalcTurbulentTexCoords +*/ +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st ) +{ + int i; + float now; + + now = ( wf->phase + tess.shaderTime * wf->frequency ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + } +} + +/* +** RB_CalcScaleTexCoords +*/ +void RB_CalcScaleTexCoords( const float scale[2], float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] *= scale[0]; + st[1] *= scale[1]; + } +} + +/* +** RB_CalcScrollTexCoords +*/ +void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st ) +{ + int i; + float timeScale = tess.shaderTime; + float adjustedScrollS, adjustedScrollT; + + adjustedScrollS = scrollSpeed[0] * timeScale; + adjustedScrollT = scrollSpeed[1] * timeScale; + + // clamp so coordinates don't continuously get larger, causing problems + // with hardware limits + adjustedScrollS = adjustedScrollS - floor( adjustedScrollS ); + adjustedScrollT = adjustedScrollT - floor( adjustedScrollT ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] += adjustedScrollS; + st[1] += adjustedScrollT; + } +} + +/* +** RB_CalcTransformTexCoords +*/ +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0]; + st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1]; + } +} + +/* +** RB_CalcRotateTexCoords +*/ +void RB_CalcRotateTexCoords( float degsPerSecond, float *st ) +{ + float timeScale = tess.shaderTime; + float degs; + int index; + float sinValue, cosValue; + texModInfo_t tmi; + + degs = -degsPerSecond * timeScale; + index = degs * ( FUNCTABLE_SIZE / 360.0f ); + + sinValue = tr.sinTable[ index & FUNCTABLE_MASK ]; + cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; + + tmi.matrix[0][0] = cosValue; + tmi.matrix[1][0] = -sinValue; + tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; + + tmi.matrix[0][1] = sinValue; + tmi.matrix[1][1] = cosValue; + tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; + + RB_CalcTransformTexCoords( &tmi, st ); +} + + + + + + +#if id386 && !defined(__GNUC__) + +long myftol( float f ) { + static int tmp; + __asm fld f + __asm fistp tmp + __asm mov eax, tmp +} + +#endif + +/* +** RB_CalcSpecularAlpha +** +** Calculates specular coefficient and places it in the alpha channel +*/ +vec3_t lightOrigin = { -960, 1980, 96 }; // FIXME: track dynamically + +void RB_CalcSpecularAlpha( unsigned char *alphas ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float l, d; + int b; + vec3_t lightDir; + int numVertexes; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + alphas += 3; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) { + float ilength; + + VectorSubtract( lightOrigin, v, lightDir ); +// ilength = Q_rsqrt( DotProduct( lightDir, lightDir ) ); + VectorNormalizeFast( lightDir ); + + // calculate the specular color + d = DotProduct (normal, lightDir); +// d *= ilength; + + // we don't optimize for the d < 0 case since this tends to + // cause visual artifacts such as faceted "snapping" + reflected[0] = normal[0]*2*d - lightDir[0]; + reflected[1] = normal[1]*2*d - lightDir[1]; + reflected[2] = normal[2]*2*d - lightDir[2]; + + VectorSubtract (backEnd.or.viewOrigin, v, viewer); + ilength = Q_rsqrt( DotProduct( viewer, viewer ) ); + l = DotProduct (reflected, viewer); + l *= ilength; + + if (l < 0) { + b = 0; + } else { + l = l*l; + l = l*l; + b = l * 255; + if (b > 255) { + b = 255; + } + } + + *alphas = b; + } +} + +/* +** RB_CalcDiffuseColor +** +** The basic vertex lighting calc +*/ +#if idppc_altivec +static void RB_CalcDiffuseColor_altivec( unsigned char *colors ) +{ + int i; + float *v, *normal; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t lightDir; + int numVertexes; + vector unsigned char vSel = VECCONST_UINT8(0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff); + vector float ambientLightVec; + vector float directedLightVec; + vector float lightDirVec; + vector float normalVec0, normalVec1; + vector float incomingVec0, incomingVec1, incomingVec2; + vector float zero, jVec; + vector signed int jVecInt; + vector signed short jVecShort; + vector unsigned char jVecChar, normalPerm; + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + // A lot of this could be simplified if we made sure + // entities light info was 16-byte aligned. + jVecChar = vec_lvsl(0, ent->ambientLight); + ambientLightVec = vec_ld(0, (vector float *)ent->ambientLight); + jVec = vec_ld(11, (vector float *)ent->ambientLight); + ambientLightVec = vec_perm(ambientLightVec,jVec,jVecChar); + + jVecChar = vec_lvsl(0, ent->directedLight); + directedLightVec = vec_ld(0,(vector float *)ent->directedLight); + jVec = vec_ld(11,(vector float *)ent->directedLight); + directedLightVec = vec_perm(directedLightVec,jVec,jVecChar); + + jVecChar = vec_lvsl(0, ent->lightDir); + lightDirVec = vec_ld(0,(vector float *)ent->lightDir); + jVec = vec_ld(11,(vector float *)ent->lightDir); + lightDirVec = vec_perm(lightDirVec,jVec,jVecChar); + + zero = (vector float)vec_splat_s8(0); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + normalPerm = vec_lvsl(0,normal); + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) { + normalVec0 = vec_ld(0,(vector float *)normal); + normalVec1 = vec_ld(11,(vector float *)normal); + normalVec0 = vec_perm(normalVec0,normalVec1,normalPerm); + incomingVec0 = vec_madd(normalVec0, lightDirVec, zero); + incomingVec1 = vec_sld(incomingVec0,incomingVec0,4); + incomingVec2 = vec_add(incomingVec0,incomingVec1); + incomingVec1 = vec_sld(incomingVec1,incomingVec1,4); + incomingVec2 = vec_add(incomingVec2,incomingVec1); + incomingVec0 = vec_splat(incomingVec2,0); + incomingVec0 = vec_max(incomingVec0,zero); + normalPerm = vec_lvsl(12,normal); + jVec = vec_madd(incomingVec0, directedLightVec, ambientLightVec); + jVecInt = vec_cts(jVec,0); // RGBx + jVecShort = vec_pack(jVecInt,jVecInt); // RGBxRGBx + jVecChar = vec_packsu(jVecShort,jVecShort); // RGBxRGBxRGBxRGBx + jVecChar = vec_sel(jVecChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255 + vec_ste((vector unsigned int)jVecChar,0,(unsigned int *)&colors[i*4]); // store color + } +} +#endif + +static void RB_CalcDiffuseColor_scalar( unsigned char *colors ) +{ + int i, j; + float *v, *normal; + float incoming; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + int numVertexes; + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + VectorCopy( ent->ambientLight, ambientLight ); + VectorCopy( ent->directedLight, directedLight ); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + *(int *)&colors[i*4] = ambientLightInt; + continue; + } + j = myftol( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+0] = j; + + j = myftol( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+1] = j; + + j = myftol( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+2] = j; + + colors[i*4+3] = 255; + } +} + +void RB_CalcDiffuseColor( unsigned char *colors ) +{ +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + RB_CalcDiffuseColor_altivec( colors ); + return; + } +#endif + RB_CalcDiffuseColor_scalar( colors ); +} + diff --git a/src/renderer/tr_shader.c b/src/renderer/tr_shader.c new file mode 100644 index 0000000..22e340c --- /dev/null +++ b/src/renderer/tr_shader.c @@ -0,0 +1,3078 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + +// tr_shader.c -- this file deals with the parsing and definition of shaders + +static char *s_shaderText; + +// the shader is parsed into these global variables, then copied into +// dynamically allocated memory if it is valid. +static shaderStage_t stages[MAX_SHADER_STAGES]; +static shader_t shader; +static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS]; + +#define FILE_HASH_SIZE 1024 +static shader_t* hashTable[FILE_HASH_SIZE]; + +#define MAX_SHADERTEXT_HASH 2048 +static char **shaderTextHashTable[MAX_SHADERTEXT_HASH]; + +/* +================ +return a hash value for the filename +================ +*/ +#ifdef __GNUCC__ + #warning TODO: check if long is ok here +#endif +static long generateHashValue( const char *fname, const int size ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (size-1); + return hash; +} + +void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh, *sh2; + qhandle_t h; + + sh = R_FindShaderByName( shaderName ); + if (sh == NULL || sh == tr.defaultShader) { + h = RE_RegisterShaderLightMap(shaderName, 0); + sh = R_GetShaderByHandle(h); + } + if (sh == NULL || sh == tr.defaultShader) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName ); + return; + } + + sh2 = R_FindShaderByName( newShaderName ); + if (sh2 == NULL || sh2 == tr.defaultShader) { + h = RE_RegisterShaderLightMap(newShaderName, 0); + sh2 = R_GetShaderByHandle(h); + } + + if (sh2 == NULL || sh2 == tr.defaultShader) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName ); + return; + } + + // remap all the shaders with the given name + // even tho they might have different lightmaps + COM_StripExtension(shaderName, strippedName, sizeof(strippedName)); + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + for (sh = hashTable[hash]; sh; sh = sh->next) { + if (Q_stricmp(sh->name, strippedName) == 0) { + if (sh != sh2) { + sh->remappedShader = sh2; + } else { + sh->remappedShader = NULL; + } + } + } + if (timeOffset) { + sh2->timeOffset = atof(timeOffset); + } +} + +/* +=============== +ParseVector +=============== +*/ +static qboolean ParseVector( char **text, int count, float *v ) { + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return qfalse; + } + + for ( i = 0 ; i < count ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name ); + return qfalse; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +NameToAFunc +=============== +*/ +static unsigned NameToAFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "GT0" ) ) + { + return GLS_ATEST_GT_0; + } + else if ( !Q_stricmp( funcname, "LT128" ) ) + { + return GLS_ATEST_LT_80; + } + else if ( !Q_stricmp( funcname, "GE128" ) ) + { + return GLS_ATEST_GE_80; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name ); + return 0; +} + + +/* +=============== +NameToSrcBlendMode +=============== +*/ +static int NameToSrcBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_SRCBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_SRCBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_DST_COLOR" ) ) + { + return GLS_SRCBLEND_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) ) + { + return GLS_SRCBLEND_ALPHA_SATURATE; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_SRCBLEND_ONE; +} + +/* +=============== +NameToDstBlendMode +=============== +*/ +static int NameToDstBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_DSTBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_DSTBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_SRC_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_DSTBLEND_ONE; +} + +/* +=============== +NameToGenFunc +=============== +*/ +static genFunc_t NameToGenFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "sin" ) ) + { + return GF_SIN; + } + else if ( !Q_stricmp( funcname, "square" ) ) + { + return GF_SQUARE; + } + else if ( !Q_stricmp( funcname, "triangle" ) ) + { + return GF_TRIANGLE; + } + else if ( !Q_stricmp( funcname, "sawtooth" ) ) + { + return GF_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "inversesawtooth" ) ) + { + return GF_INVERSE_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "noise" ) ) + { + return GF_NOISE; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name ); + return GF_SIN; +} + + +/* +=================== +ParseWaveForm +=================== +*/ +static void ParseWaveForm( char **text, waveForm_t *wave ) +{ + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->func = NameToGenFunc( token ); + + // BASE, AMP, PHASE, FREQ + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->frequency = atof( token ); +} + + +/* +=================== +ParseTexMod +=================== +*/ +static void ParseTexMod( char *_text, shaderStage_t *stage ) +{ + const char *token; + char **text = &_text; + texModInfo_t *tmi; + + if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) { + ri.Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name ); + return; + } + + tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; + stage->bundle[0].numTexMods++; + + token = COM_ParseExt( text, qfalse ); + + // + // turb + // + if ( !Q_stricmp( token, "turb" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_TURBULENT; + } + // + // scale + // + else if ( !Q_stricmp( token, "scale" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[1] = atof( token ); + tmi->type = TMOD_SCALE; + } + // + // scroll + // + else if ( !Q_stricmp( token, "scroll" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[1] = atof( token ); + tmi->type = TMOD_SCROLL; + } + // + // stretch + // + else if ( !Q_stricmp( token, "stretch" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.func = NameToGenFunc( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_STRETCH; + } + // + // transform + // + else if ( !Q_stricmp( token, "transform" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); + + tmi->type = TMOD_TRANSFORM; + } + // + // rotate + // + else if ( !Q_stricmp( token, "rotate" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name ); + return; + } + tmi->rotateSpeed = atof( token ); + tmi->type = TMOD_ROTATE; + } + // + // entityTranslate + // + else if ( !Q_stricmp( token, "entityTranslate" ) ) + { + tmi->type = TMOD_ENTITY_TRANSLATE; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name ); + } +} + + +/* +=================== +ParseStage +=================== +*/ +static qboolean ParseStage( shaderStage_t *stage, char **text ) +{ + char *token; + int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0; + qboolean depthMaskExplicit = qfalse; + + stage->active = qtrue; + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" ); + return qfalse; + } + + if ( token[0] == '}' ) + { + break; + } + // + // map + // + else if ( !Q_stricmp( token, "map" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "$whiteimage" ) ) + { + stage->bundle[0].image[0] = tr.whiteImage; + continue; + } + else if ( !Q_stricmp( token, "$lightmap" ) ) + { + stage->bundle[0].isLightmap = qtrue; + if ( shader.lightmapIndex < 0 ) { + stage->bundle[0].image[0] = tr.whiteImage; + } else { + stage->bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + } + continue; + } + else + { + stage->bundle[0].image[0] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT ); + if ( !stage->bundle[0].image[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + } + // + // clampmap + // + else if ( !Q_stricmp( token, "clampmap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + stage->bundle[0].image[0] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_CLAMP_TO_EDGE ); + if ( !stage->bundle[0].image[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + // + // animMap .... + // + else if ( !Q_stricmp( token, "animMap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + stage->bundle[0].imageAnimationSpeed = atof( token ); + + // parse up to MAX_IMAGE_ANIMATIONS animations + while ( 1 ) { + int num; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + break; + } + num = stage->bundle[0].numImageAnimations; + if ( num < MAX_IMAGE_ANIMATIONS ) { + stage->bundle[0].image[num] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT ); + if ( !stage->bundle[0].image[num] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + stage->bundle[0].numImageAnimations++; + } + } + } + else if ( !Q_stricmp( token, "videoMap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic( token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader)); + if (stage->bundle[0].videoMapHandle != -1) { + stage->bundle[0].isVideoMap = qtrue; + stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } + } + // + // alphafunc + // + else if ( !Q_stricmp( token, "alphaFunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + atestBits = NameToAFunc( token ); + } + // + // depthFunc + // + else if ( !Q_stricmp( token, "depthfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "lequal" ) ) + { + depthFuncBits = 0; + } + else if ( !Q_stricmp( token, "equal" ) ) + { + depthFuncBits = GLS_DEPTHFUNC_EQUAL; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // detail + // + else if ( !Q_stricmp( token, "detail" ) ) + { + stage->isDetail = qtrue; + } + // + // blendfunc + // or blendfunc + // + else if ( !Q_stricmp( token, "blendfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + // check for "simple" blends first + if ( !Q_stricmp( token, "add" ) ) { + blendSrcBits = GLS_SRCBLEND_ONE; + blendDstBits = GLS_DSTBLEND_ONE; + } else if ( !Q_stricmp( token, "filter" ) ) { + blendSrcBits = GLS_SRCBLEND_DST_COLOR; + blendDstBits = GLS_DSTBLEND_ZERO; + } else if ( !Q_stricmp( token, "blend" ) ) { + blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; + blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else { + // complex double blends + blendSrcBits = NameToSrcBlendMode( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + blendDstBits = NameToDstBlendMode( token ); + } + + // clear depth mask for blended surfaces + if ( !depthMaskExplicit ) + { + depthMaskBits = 0; + } + } + // + // rgbGen + // + else if ( !Q_stricmp( token, "rgbGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->rgbWave ); + stage->rgbGen = CGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + vec3_t color; + + ParseVector( text, 3, color ); + stage->constantColor[0] = 255 * color[0]; + stage->constantColor[1] = 255 * color[1]; + stage->constantColor[2] = 255 * color[2]; + + stage->rgbGen = CGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->rgbGen = CGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "identityLighting" ) ) + { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->rgbGen = CGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->rgbGen = CGEN_VERTEX; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } + else if ( !Q_stricmp( token, "exactVertex" ) ) + { + stage->rgbGen = CGEN_EXACT_VERTEX; + } + else if ( !Q_stricmp( token, "lightingDiffuse" ) ) + { + stage->rgbGen = CGEN_LIGHTING_DIFFUSE; + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_VERTEX; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // alphaGen + // + else if ( !Q_stricmp( token, "alphaGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->alphaWave ); + stage->alphaGen = AGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + token = COM_ParseExt( text, qfalse ); + stage->constantColor[3] = 255 * atof( token ); + stage->alphaGen = AGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->alphaGen = AGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->alphaGen = AGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->alphaGen = AGEN_VERTEX; + } + else if ( !Q_stricmp( token, "lightingSpecular" ) ) + { + stage->alphaGen = AGEN_LIGHTING_SPECULAR; + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_VERTEX; + } + else if ( !Q_stricmp( token, "portal" ) ) + { + stage->alphaGen = AGEN_PORTAL; + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + shader.portalRange = 256; + ri.Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name ); + } + else + { + shader.portalRange = atof( token ); + } + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // tcGen + // + else if ( !Q_stricmp(token, "texgen") || !Q_stricmp( token, "tcGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "environment" ) ) + { + stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED; + } + else if ( !Q_stricmp( token, "lightmap" ) ) + { + stage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) ) + { + stage->bundle[0].tcGen = TCGEN_TEXTURE; + } + else if ( !Q_stricmp( token, "vector" ) ) + { + ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] ); + ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] ); + + stage->bundle[0].tcGen = TCGEN_VECTOR; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name ); + } + } + // + // tcMod <...> + // + else if ( !Q_stricmp( token, "tcMod" ) ) + { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseTexMod( buffer, stage ); + + continue; + } + // + // depthmask + // + else if ( !Q_stricmp( token, "depthwrite" ) ) + { + depthMaskBits = GLS_DEPTHMASK_TRUE; + depthMaskExplicit = qtrue; + + continue; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // if cgen isn't explicitly specified, use either identity or identitylighting + // + if ( stage->rgbGen == CGEN_BAD ) { + if ( blendSrcBits == 0 || + blendSrcBits == GLS_SRCBLEND_ONE || + blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } else { + stage->rgbGen = CGEN_IDENTITY; + } + } + + + // + // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending + // + if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && + ( blendDstBits == GLS_DSTBLEND_ZERO ) ) + { + blendDstBits = blendSrcBits = 0; + depthMaskBits = GLS_DEPTHMASK_TRUE; + } + + // decide which agens we can skip + if ( stage->alphaGen == AGEN_IDENTITY ) { + if ( stage->rgbGen == CGEN_IDENTITY + || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) { + stage->alphaGen = AGEN_SKIP; + } + } + + // + // compute state bits + // + stage->stateBits = depthMaskBits | + blendSrcBits | blendDstBits | + atestBits | + depthFuncBits; + + return qtrue; +} + +/* +=============== +ParseDeform + +deformVertexes wave +deformVertexes normal +deformVertexes move +deformVertexes bulge +deformVertexes projectionShadow +deformVertexes autoSprite +deformVertexes autoSprite2 +deformVertexes text[0-7] +=============== +*/ +static void ParseDeform( char **text ) { + char *token; + deformStage_t *ds; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name ); + return; + } + + if ( shader.numDeforms == MAX_SHADER_DEFORMS ) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name ); + return; + } + + ds = &shader.deforms[ shader.numDeforms ]; + shader.numDeforms++; + + if ( !Q_stricmp( token, "projectionShadow" ) ) { + ds->deformation = DEFORM_PROJECTION_SHADOW; + return; + } + + if ( !Q_stricmp( token, "autosprite" ) ) { + ds->deformation = DEFORM_AUTOSPRITE; + return; + } + + if ( !Q_stricmp( token, "autosprite2" ) ) { + ds->deformation = DEFORM_AUTOSPRITE2; + return; + } + + if ( !Q_stricmpn( token, "text", 4 ) ) { + int n; + + n = token[4] - '0'; + if ( n < 0 || n > 7 ) { + n = 0; + } + ds->deformation = DEFORM_TEXT0 + n; + return; + } + + if ( !Q_stricmp( token, "bulge" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeWidth = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeHeight = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeSpeed = atof( token ); + + ds->deformation = DEFORM_BULGE; + return; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + + if ( atof( token ) != 0 ) + { + ds->deformationSpread = 1.0f / atof( token ); + } + else + { + ds->deformationSpread = 100.0f; + ri.Printf( PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_WAVE; + return; + } + + if ( !Q_stricmp( token, "normal" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.frequency = atof( token ); + + ds->deformation = DEFORM_NORMALS; + return; + } + + if ( !Q_stricmp( token, "move" ) ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->moveVector[i] = atof( token ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_MOVE; + return; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name ); +} + + +/* +=============== +ParseSkyParms + +skyParms +=============== +*/ +static void ParseSkyParms( char **text ) { + char *token; + static char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; + char pathname[MAX_QPATH]; + int i; + + // outerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga" + , token, suf[i] ); + shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_CLAMP_TO_EDGE ); + + if ( !shader.sky.outerbox[i] ) { + shader.sky.outerbox[i] = tr.defaultImage; + } + } + } + + // cloudheight + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + shader.sky.cloudHeight = atof( token ); + if ( !shader.sky.cloudHeight ) { + shader.sky.cloudHeight = 512; + } + R_InitSkyTexCoords( shader.sky.cloudHeight ); + + + // innerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga" + , token, suf[i] ); + shader.sky.innerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_REPEAT ); + if ( !shader.sky.innerbox[i] ) { + shader.sky.innerbox[i] = tr.defaultImage; + } + } + } + + shader.isSky = qtrue; +} + + +/* +================= +ParseSort +================= +*/ +void ParseSort( char **text ) { + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name ); + return; + } + + if ( !Q_stricmp( token, "portal" ) ) { + shader.sort = SS_PORTAL; + } else if ( !Q_stricmp( token, "sky" ) ) { + shader.sort = SS_ENVIRONMENT; + } else if ( !Q_stricmp( token, "opaque" ) ) { + shader.sort = SS_OPAQUE; + }else if ( !Q_stricmp( token, "decal" ) ) { + shader.sort = SS_DECAL; + } else if ( !Q_stricmp( token, "seeThrough" ) ) { + shader.sort = SS_SEE_THROUGH; + } else if ( !Q_stricmp( token, "banner" ) ) { + shader.sort = SS_BANNER; + } else if ( !Q_stricmp( token, "additive" ) ) { + shader.sort = SS_BLEND1; + } else if ( !Q_stricmp( token, "nearest" ) ) { + shader.sort = SS_NEAREST; + } else if ( !Q_stricmp( token, "underwater" ) ) { + shader.sort = SS_UNDERWATER; + } else { + shader.sort = atof( token ); + } +} + + + +// this table is also present in q3map + +typedef struct { + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + +infoParm_t infoParms[] = { + // server relevant contents + {"water", 1, 0, CONTENTS_WATER }, + {"slime", 1, 0, CONTENTS_SLIME }, // mildly damaging + {"lava", 1, 0, CONTENTS_LAVA }, // very damaging + {"playerclip", 1, 0, CONTENTS_PLAYERCLIP }, + {"monsterclip", 1, 0, CONTENTS_MONSTERCLIP }, + {"nodrop", 1, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"nonsolid", 1, SURF_NONSOLID, 0}, // clears the solid flag + + // utility relevant attributes + {"origin", 1, 0, CONTENTS_ORIGIN }, // center of rotating brushes + {"trans", 0, 0, CONTENTS_TRANSLUCENT }, // don't eat contained surfaces + {"detail", 0, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"structural", 0, 0, CONTENTS_STRUCTURAL }, // force into structural bsp even if trnas + {"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas + {"clusterportal", 1,0, CONTENTS_CLUSTERPORTAL }, // for bots + {"donotenter", 1, 0, CONTENTS_DONOTENTER }, // for bots + + {"fog", 1, 0, CONTENTS_FOG}, // carves surfaces entering + {"sky", 0, SURF_SKY, 0 }, // emit light from an environment map + {"lightfilter", 0, SURF_LIGHTFILTER, 0 }, // filter light going through it + {"alphashadow", 0, SURF_ALPHASHADOW, 0 }, // test light on a per-pixel basis + {"hint", 0, SURF_HINT, 0 }, // use as a primary splitter + + // server attributes + {"slick", 0, SURF_SLICK, 0 }, + {"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks + {"nomarks", 0, SURF_NOMARKS, 0 }, // don't make impact marks, but still explode + {"ladder", 0, SURF_LADDER, 0 }, + {"nodamage", 0, SURF_NODAMAGE, 0 }, + {"metalsteps", 0, SURF_METALSTEPS,0 }, + {"flesh", 0, SURF_FLESH, 0 }, + {"nosteps", 0, SURF_NOSTEPS, 0 }, + + // drawsurf attributes + {"nodraw", 0, SURF_NODRAW, 0 }, // don't generate a drawsurface (or a lightmap) + {"pointlight", 0, SURF_POINTLIGHT, 0 }, // sample lighting at vertexes + {"nolightmap", 0, SURF_NOLIGHTMAP,0 }, // don't generate a lightmap + {"nodlight", 0, SURF_NODLIGHT, 0 }, // don't ever add dynamic lights + {"dust", 0, SURF_DUST, 0} // leave a dust trail when walking on this surface +}; + + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ +static void ParseSurfaceParm( char **text ) { + char *token; + int numInfoParms = ARRAY_LEN( infoParms ); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numInfoParms ; i++ ) { + if ( !Q_stricmp( token, infoParms[i].name ) ) { + shader.surfaceFlags |= infoParms[i].surfaceFlags; + shader.contentFlags |= infoParms[i].contents; +#if 0 + if ( infoParms[i].clearSolid ) { + si->contents &= ~CONTENTS_SOLID; + } +#endif + break; + } + } +} + +/* +================= +ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. Later functions +will optimize it. +================= +*/ +static qboolean ParseShader( char **text ) +{ + char *token; + int s; + + s = 0; + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) + { + ri.Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name ); + return qfalse; + } + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name ); + return qfalse; + } + + // end of shader definition + if ( token[0] == '}' ) + { + break; + } + // stage definition + else if ( token[0] == '{' ) + { + if ( s >= MAX_SHADER_STAGES ) { + ri.Printf( PRINT_WARNING, "WARNING: too many stages in shader %s\n", shader.name ); + return qfalse; + } + + if ( !ParseStage( &stages[s], text ) ) + { + return qfalse; + } + stages[s].active = qtrue; + s++; + + continue; + } + // skip stuff that only the QuakeEdRadient needs + else if ( !Q_stricmpn( token, "qer", 3 ) ) { + SkipRestOfLine( text ); + continue; + } + // sun parms + else if ( !Q_stricmp( token, "q3map_sun" ) ) { + float a, b; + + token = COM_ParseExt( text, qfalse ); + tr.sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[2] = atof( token ); + + VectorNormalize( tr.sunLight ); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + VectorScale( tr.sunLight, a, tr.sunLight); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + a = a / 180 * M_PI; + + token = COM_ParseExt( text, qfalse ); + b = atof( token ); + b = b / 180 * M_PI; + + tr.sunDirection[0] = cos( a ) * cos( b ); + tr.sunDirection[1] = sin( a ) * cos( b ); + tr.sunDirection[2] = sin( b ); + } + else if ( !Q_stricmp( token, "deformVertexes" ) ) { + ParseDeform( text ); + continue; + } + else if ( !Q_stricmp( token, "tesssize" ) ) { + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "clampTime" ) ) { + token = COM_ParseExt( text, qfalse ); + if (token[0]) { + shader.clampTime = atof(token); + } + } + // skip stuff that only the q3map needs + else if ( !Q_stricmpn( token, "q3map", 5 ) ) { + SkipRestOfLine( text ); + continue; + } + // skip stuff that only q3map or the server needs + else if ( !Q_stricmp( token, "surfaceParm" ) ) { + ParseSurfaceParm( text ); + continue; + } + // no mip maps + else if ( !Q_stricmp( token, "nomipmaps" ) ) + { + shader.noMipMaps = qtrue; + shader.noPicMip = qtrue; + continue; + } + // no picmip adjustment + else if ( !Q_stricmp( token, "nopicmip" ) ) + { + shader.noPicMip = qtrue; + continue; + } + // polygonOffset + else if ( !Q_stricmp( token, "polygonOffset" ) ) + { + shader.polygonOffset = qtrue; + continue; + } + // entityMergable, allowing sprite surfaces from multiple entities + // to be merged into one batch. This is a savings for smoke + // puffs and blood, but can't be used for anything where the + // shader calcs (not the surface function) reference the entity color or scroll + else if ( !Q_stricmp( token, "entityMergable" ) ) + { + shader.entityMergable = qtrue; + continue; + } + // fogParms + else if ( !Q_stricmp( token, "fogParms" ) ) + { + if ( !ParseVector( text, 3, shader.fogParms.color ) ) { + return qfalse; + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name ); + continue; + } + shader.fogParms.depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( text ); + continue; + } + // portal + else if ( !Q_stricmp(token, "portal") ) + { + shader.sort = SS_PORTAL; + continue; + } + // skyparms + else if ( !Q_stricmp( token, "skyparms" ) ) + { + ParseSkyParms( text ); + continue; + } + // light determines flaring in q3map, not needed here + else if ( !Q_stricmp(token, "light") ) + { + token = COM_ParseExt( text, qfalse ); + continue; + } + // cull + else if ( !Q_stricmp( token, "cull") ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "twosided" ) || !Q_stricmp( token, "disable" ) ) + { + shader.cullType = CT_TWO_SIDED; + } + else if ( !Q_stricmp( token, "back" ) || !Q_stricmp( token, "backside" ) || !Q_stricmp( token, "backsided" ) ) + { + shader.cullType = CT_BACK_SIDED; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name ); + } + continue; + } + // sort + else if ( !Q_stricmp( token, "sort" ) ) + { + ParseSort( text ); + continue; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // ignore shaders that don't have any stages, unless it is a sky or fog + // + if ( s == 0 && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG ) ) { + return qfalse; + } + + shader.explicitlyDefined = qtrue; + + return qtrue; +} + +/* +======================================================================================== + +SHADER OPTIMIZATION AND FOGGING + +======================================================================================== +*/ + +/* +=================== +ComputeStageIteratorFunc + +See if we can use on of the simple fastpath stage functions, +otherwise set to the generic stage function +=================== +*/ +static void ComputeStageIteratorFunc( void ) +{ + shader.optimalStageIteratorFunc = RB_StageIteratorGeneric; + + // + // see if this should go into the sky path + // + if ( shader.isSky ) + { + shader.optimalStageIteratorFunc = RB_StageIteratorSky; + goto done; + } + + if ( r_ignoreFastPath->integer ) + { + return; + } + + // + // see if this can go into the vertex lit fast path + // + if ( shader.numUnfoggedPasses == 1 ) + { + if ( stages[0].rgbGen == CGEN_LIGHTING_DIFFUSE ) + { + if ( stages[0].alphaGen == AGEN_IDENTITY ) + { + if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE ) + { + if ( !shader.polygonOffset ) + { + if ( !shader.multitextureEnv ) + { + if ( !shader.numDeforms ) + { + shader.optimalStageIteratorFunc = RB_StageIteratorVertexLitTexture; + goto done; + } + } + } + } + } + } + } + + // + // see if this can go into an optimized LM, multitextured path + // + if ( shader.numUnfoggedPasses == 1 ) + { + if ( ( stages[0].rgbGen == CGEN_IDENTITY ) && ( stages[0].alphaGen == AGEN_IDENTITY ) ) + { + if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE && + stages[0].bundle[1].tcGen == TCGEN_LIGHTMAP ) + { + if ( !shader.polygonOffset ) + { + if ( !shader.numDeforms ) + { + if ( shader.multitextureEnv ) + { + shader.optimalStageIteratorFunc = RB_StageIteratorLightmappedMultitexture; + goto done; + } + } + } + } + } + } + +done: + return; +} + +typedef struct { + int blendA; + int blendB; + + int multitextureEnv; + int multitextureBlend; +} collapse_t; + +static collapse_t collapse[] = { + { 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, 0 }, + + { 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, 0 }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, 0 }, + + { GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE }, +#if 0 + { 0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA, + GL_DECAL, 0 }, +#endif + { -1 } +}; + +/* +================ +CollapseMultitexture + +Attempt to combine two stages into a single multitexture stage +FIXME: I think modulated add + modulated add collapses incorrectly +================= +*/ +static qboolean CollapseMultitexture( void ) { + int abits, bbits; + int i; + textureBundle_t tmpBundle; + + if ( !qglActiveTextureARB ) { + return qfalse; + } + + // make sure both stages are active + if ( !stages[0].active || !stages[1].active ) { + return qfalse; + } + + // on voodoo2, don't combine different tmus + if ( glConfig.driverType == GLDRV_VOODOO ) { + if ( stages[0].bundle[0].image[0]->TMU == + stages[1].bundle[0].image[0]->TMU ) { + return qfalse; + } + } + + abits = stages[0].stateBits; + bbits = stages[1].stateBits; + + // make sure that both stages have identical state other than blend modes + if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) != + ( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) { + return qfalse; + } + + abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + // search for a valid multitexture blend function + for ( i = 0; collapse[i].blendA != -1 ; i++ ) { + if ( abits == collapse[i].blendA + && bbits == collapse[i].blendB ) { + break; + } + } + + // nothing found + if ( collapse[i].blendA == -1 ) { + return qfalse; + } + + // GL_ADD is a separate extension + if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) { + return qfalse; + } + + // make sure waveforms have identical parameters + if ( ( stages[0].rgbGen != stages[1].rgbGen ) || + ( stages[0].alphaGen != stages[1].alphaGen ) ) { + return qfalse; + } + + // an add collapse can only have identity colors + if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) { + return qfalse; + } + + if ( stages[0].rgbGen == CGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].rgbWave, + &stages[1].rgbWave, + sizeof( stages[0].rgbWave ) ) ) + { + return qfalse; + } + } + if ( stages[0].alphaGen == AGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].alphaWave, + &stages[1].alphaWave, + sizeof( stages[0].alphaWave ) ) ) + { + return qfalse; + } + } + + + // make sure that lightmaps are in bundle 1 for 3dfx + if ( stages[0].bundle[0].isLightmap ) + { + tmpBundle = stages[0].bundle[0]; + stages[0].bundle[0] = stages[1].bundle[0]; + stages[0].bundle[1] = tmpBundle; + } + else + { + stages[0].bundle[1] = stages[1].bundle[0]; + } + + // set the new blend state bits + shader.multitextureEnv = collapse[i].multitextureEnv; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= collapse[i].multitextureBlend; + + // + // move down subsequent shaders + // + memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) ); + Com_Memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[0] ) ); + + return qtrue; +} + +/* +============= + +FixRenderCommandList +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 +Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated +but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces +to be rendered with bad shaders. To fix this, need to go through all render commands and fix +sortedIndex. +============== +*/ +static void FixRenderCommandList( int newShader ) { + renderCommandList_t *cmdList = &backEndData[tr.smpFrame]->commands; + + if( cmdList ) { + const void *curCmd = cmdList->cmds; + + while ( 1 ) { + switch ( *(const int *)curCmd ) { + case RC_SET_COLOR: + { + const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd; + curCmd = (const void *)(sc_cmd + 1); + break; + } + case RC_STRETCH_PIC: + { + const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd; + curCmd = (const void *)(sp_cmd + 1); + break; + } + case RC_DRAW_SURFS: + { + int i; + drawSurf_t *drawSurf; + shader_t *shader; + int fogNum; + int entityNum; + int dlightMap; + int sortedIndex; + const drawSurfsCommand_t *ds_cmd = (const drawSurfsCommand_t *)curCmd; + + for( i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++ ) { + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap ); + sortedIndex = (( drawSurf->sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1)); + if( sortedIndex >= newShader ) { + sortedIndex++; + drawSurf->sort = (sortedIndex << QSORT_SHADERNUM_SHIFT) | entityNum | ( fogNum << QSORT_FOGNUM_SHIFT ) | (int)dlightMap; + } + } + curCmd = (const void *)(ds_cmd + 1); + break; + } + case RC_DRAW_BUFFER: + { + const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd; + curCmd = (const void *)(db_cmd + 1); + break; + } + case RC_SWAP_BUFFERS: + { + const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd; + curCmd = (const void *)(sb_cmd + 1); + break; + } + case RC_END_OF_LIST: + default: + return; + } + } + } +} + +/* +============== +SortNewShader + +Positions the most recently created shader in the tr.sortedShaders[] +array so that the shader->sort key is sorted reletive to the other +shaders. + +Sets shader->sortedIndex +============== +*/ +static void SortNewShader( void ) { + int i; + float sort; + shader_t *newShader; + + newShader = tr.shaders[ tr.numShaders - 1 ]; + sort = newShader->sort; + + for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) { + if ( tr.sortedShaders[ i ]->sort <= sort ) { + break; + } + tr.sortedShaders[i+1] = tr.sortedShaders[i]; + tr.sortedShaders[i+1]->sortedIndex++; + } + + // Arnout: fix rendercommandlist + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 + FixRenderCommandList( i+1 ); + + newShader->sortedIndex = i+1; + tr.sortedShaders[i+1] = newShader; +} + + +/* +==================== +GeneratePermanentShader +==================== +*/ +static shader_t *GeneratePermanentShader( void ) { + shader_t *newShader; + int i, b; + int size, hash; + + if ( tr.numShaders == MAX_SHADERS ) { + ri.Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n"); + return tr.defaultShader; + } + + newShader = ri.Hunk_Alloc( sizeof( shader_t ), h_low ); + + *newShader = shader; + + if ( shader.sort <= SS_OPAQUE ) { + newShader->fogPass = FP_EQUAL; + } else if ( shader.contentFlags & CONTENTS_FOG ) { + newShader->fogPass = FP_LE; + } + + tr.shaders[ tr.numShaders ] = newShader; + newShader->index = tr.numShaders; + + tr.sortedShaders[ tr.numShaders ] = newShader; + newShader->sortedIndex = tr.numShaders; + + tr.numShaders++; + + for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) { + if ( !stages[i].active ) { + break; + } + newShader->stages[i] = ri.Hunk_Alloc( sizeof( stages[i] ), h_low ); + *newShader->stages[i] = stages[i]; + + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + size = newShader->stages[i]->bundle[b].numTexMods * sizeof( texModInfo_t ); + newShader->stages[i]->bundle[b].texMods = ri.Hunk_Alloc( size, h_low ); + Com_Memcpy( newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size ); + } + } + + SortNewShader(); + + hash = generateHashValue(newShader->name, FILE_HASH_SIZE); + newShader->next = hashTable[hash]; + hashTable[hash] = newShader; + + return newShader; +} + +/* +================= +VertexLightingCollapse + +If vertex lighting is enabled, only render a single +pass, trying to guess which is the correct one to best aproximate +what it is supposed to look like. +================= +*/ +static void VertexLightingCollapse( void ) { + int stage; + shaderStage_t *bestStage; + int bestImageRank; + int rank; + + // if we aren't opaque, just use the first pass + if ( shader.sort == SS_OPAQUE ) { + + // pick the best texture for the single pass + bestStage = &stages[0]; + bestImageRank = -999999; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + rank = 0; + + if ( pStage->bundle[0].isLightmap ) { + rank -= 100; + } + if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) { + rank -= 5; + } + if ( pStage->bundle[0].numTexMods ) { + rank -= 5; + } + if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) { + rank -= 3; + } + + if ( rank > bestImageRank ) { + bestImageRank = rank; + bestStage = pStage; + } + } + + stages[0].bundle[0] = bestStage->bundle[0]; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= GLS_DEPTHMASK_TRUE; + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + } else { + stages[0].rgbGen = CGEN_EXACT_VERTEX; + } + stages[0].alphaGen = AGEN_SKIP; + } else { + // don't use a lightmap (tesla coils) + if ( stages[0].bundle[0].isLightmap ) { + stages[0] = stages[1]; + } + + // if we were in a cross-fade cgen, hack it to normal + if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + } + + for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + Com_Memset( pStage, 0, sizeof( *pStage ) ); + } +} + +/* +========================= +FinishShader + +Returns a freshly allocated shader with all the needed info +from the current global working shader +========================= +*/ +static shader_t *FinishShader( void ) { + int stage; + qboolean hasLightmapStage; + qboolean vertexLightmap; + + hasLightmapStage = qfalse; + vertexLightmap = qfalse; + + // + // set sky stuff appropriate + // + if ( shader.isSky ) { + shader.sort = SS_ENVIRONMENT; + } + + // + // set polygon offset + // + if ( shader.polygonOffset && !shader.sort ) { + shader.sort = SS_DECAL; + } + + // + // set appropriate stage information + // + for ( stage = 0; stage < MAX_SHADER_STAGES; ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + // check for a missing texture + if ( !pStage->bundle[0].image[0] ) { + ri.Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name ); + pStage->active = qfalse; + stage++; + continue; + } + + // + // ditch this stage if it's detail and detail textures are disabled + // + if ( pStage->isDetail && !r_detailTextures->integer ) + { + int index; + + for(index = stage + 1; index < MAX_SHADER_STAGES; index++) + { + if(!stages[index].active) + break; + } + + if(index < MAX_SHADER_STAGES) + memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage)); + else + { + if(stage + 1 < MAX_SHADER_STAGES) + memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage - 1)); + + Com_Memset(&stages[index - 1], 0, sizeof(*stages)); + } + + continue; + } + + // + // default texture coordinate generation + // + if ( pStage->bundle[0].isLightmap ) { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + hasLightmapStage = qtrue; + } else { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_TEXTURE; + } + } + + + // not a true lightmap but we want to leave existing + // behaviour in place and not print out a warning + //if (pStage->rgbGen == CGEN_VERTEX) { + // vertexLightmap = qtrue; + //} + + + + // + // determine sort order and fog color adjustment + // + if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) && + ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) { + int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS; + int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS; + + // fog color adjustment only works for blend modes that have a contribution + // that aproaches 0 as the modulate values aproach 0 -- + // GL_ONE, GL_ONE + // GL_ZERO, GL_ONE_MINUS_SRC_COLOR + // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA + + // modulate, additive + if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) || + ( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_RGB; + } + // strict blend + else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA; + } + // premultiplied alpha + else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_RGBA; + } else { + // we can't adjust this one correctly, so it won't be exactly correct in fog + } + + // don't screw with sort order if this is a portal or environment + if ( !shader.sort ) { + // see through item, like a grill or grate + if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) { + shader.sort = SS_SEE_THROUGH; + } else { + shader.sort = SS_BLEND0; + } + } + } + + stage++; + } + + // there are times when you will need to manually apply a sort to + // opaque alpha tested shaders that have later blend passes + if ( !shader.sort ) { + shader.sort = SS_OPAQUE; + } + + // + // if we are in r_vertexLight mode, never use a lightmap texture + // + if ( stage > 1 && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) ) { + VertexLightingCollapse(); + stage = 1; + hasLightmapStage = qfalse; + } + + // + // look for multitexture potential + // + if ( stage > 1 && CollapseMultitexture() ) { + stage--; + } + + if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) { + if (vertexLightmap) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name ); + } else { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name ); + shader.lightmapIndex = LIGHTMAP_NONE; + } + } + + + // + // compute number of passes + // + shader.numUnfoggedPasses = stage; + + // fogonly shaders don't have any normal passes + if (stage == 0 && !shader.isSky) + shader.sort = SS_FOG; + + // determine which stage iterator function is appropriate + ComputeStageIteratorFunc(); + + return GeneratePermanentShader(); +} + +//======================================================================================== + +/* +==================== +FindShaderInShaderText + +Scans the combined text description of all the shader files for +the given shader name. + +return NULL if not found + +If found, it will return a valid shader +===================== +*/ +static char *FindShaderInShaderText( const char *shadername ) { + + char *token, *p; + + int i, hash; + + hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH); + + if(shaderTextHashTable[hash]) + { + for (i = 0; shaderTextHashTable[hash][i]; i++) + { + p = shaderTextHashTable[hash][i]; + token = COM_ParseExt(&p, qtrue); + + if(!Q_stricmp(token, shadername)) + return p; + } + } + + p = s_shaderText; + + if ( !p ) { + return NULL; + } + + // look for label + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + if ( !Q_stricmp( token, shadername ) ) { + return p; + } + else { + // skip the definition + SkipBracedSection( &p ); + } + } + + return NULL; +} + + +/* +================== +R_FindShaderByName + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. +================== +*/ +shader_t *R_FindShaderByName( const char *name ) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh; + + if ( (name==NULL) || (name[0] == 0) ) { + return tr.defaultShader; + } + + COM_StripExtension(name, strippedName, sizeof(strippedName)); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (Q_stricmp(sh->name, strippedName) == 0) { + // match found + return sh; + } + } + + return tr.defaultShader; +} + + +/* +=============== +R_FindShader + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. + +In the interest of not requiring an explicit shader text entry to +be defined for every single image used in the game, three default +shader behaviors can be auto-created for any image: + +If lightmapIndex == LIGHTMAP_NONE, then the image will have +dynamic diffuse lighting applied to it, as apropriate for most +entity skin surfaces. + +If lightmapIndex == LIGHTMAP_2D, then the image will be used +for 2D rendering unless an explicit shader is found + +If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use +the vertex rgba modulate values, as apropriate for misc_model +pre-lit surfaces. + +Other lightmapIndex values will have a lightmap stage created +and src*dest blending applied with the texture, as apropriate for +most world construction surfaces. + +=============== +*/ +shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ) { + char strippedName[MAX_QPATH]; + int i, hash; + char *shaderText; + image_t *image; + shader_t *sh; + + if ( name[0] == 0 ) { + return tr.defaultShader; + } + + // use (fullbright) vertex lighting if the bsp file doesn't have + // lightmaps + if ( lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_BY_VERTEX; + } else if ( lightmapIndex < LIGHTMAP_2D ) { + // negative lightmap indexes cause stray pointers (think tr.lightmaps[lightmapIndex]) + ri.Printf( PRINT_WARNING, "WARNING: shader '%s' has invalid lightmap index of %d\n", name, lightmapIndex ); + lightmapIndex = LIGHTMAP_BY_VERTEX; + } + + COM_StripExtension(name, strippedName, sizeof(strippedName)); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh = hashTable[hash]; sh; sh = sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) && + !Q_stricmp(sh->name, strippedName)) { + // match found + return sh; + } + } + + // make sure the render thread is stopped, because we are probably + // going to have to upload an image + if (r_smp->integer) { + R_SyncRenderThread(); + } + + // clear the global shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + Q_strncpyz(shader.name, strippedName, sizeof(shader.name)); + shader.lightmapIndex = lightmapIndex; + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + + // FIXME: set these "need" values apropriately + shader.needsNormal = qtrue; + shader.needsST1 = qtrue; + shader.needsST2 = qtrue; + shader.needsColor = qtrue; + + // + // attempt to define shader from an explicit parameter file + // + shaderText = FindShaderInShaderText( strippedName ); + if ( shaderText ) { + // enable this when building a pak file to get a global list + // of all explicit shaders + if ( r_printShaders->integer ) { + ri.Printf( PRINT_ALL, "*SHADER* %s\n", name ); + } + + if ( !ParseShader( &shaderText ) ) { + // had errors, so use default shader + shader.defaultShader = qtrue; + } + sh = FinishShader(); + return sh; + } + + + // + // if not defined in the in-memory shader descriptions, + // look for a single supported image file + // + image = R_FindImageFile( name, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP_TO_EDGE ); + if ( !image ) { + ri.Printf( PRINT_DEVELOPER, "Couldn't find image file for shader %s\n", name ); + shader.defaultShader = qtrue; + return FinishShader(); + } + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = qtrue; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + return FinishShader(); +} + + +qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage) { + int i, hash; + shader_t *sh; + + hash = generateHashValue(name, FILE_HASH_SIZE); + + // probably not necessary since this function + // only gets called from tr_font.c with lightmapIndex == LIGHTMAP_2D + // but better safe than sorry. + if ( lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_WHITEIMAGE; + } + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) && + // index by name + !Q_stricmp(sh->name, name)) { + // match found + return sh->index; + } + } + + // make sure the render thread is stopped, because we are probably + // going to have to upload an image + if (r_smp->integer) { + R_SyncRenderThread(); + } + + // clear the global shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + Q_strncpyz(shader.name, name, sizeof(shader.name)); + shader.lightmapIndex = lightmapIndex; + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + + // FIXME: set these "need" values apropriately + shader.needsNormal = qtrue; + shader.needsST1 = qtrue; + shader.needsST2 = qtrue; + shader.needsColor = qtrue; + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = qtrue; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + sh = FinishShader(); + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmapIndex, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShader( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShaderNoMip + +For menu graphics that should never be picmiped +==================== +*/ +qhandle_t RE_RegisterShaderNoMip( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, qfalse ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + +/* +==================== +R_GetShaderByHandle + +When a handle is passed in by another module, this range checks +it and returns a valid (possibly default) shader_t to be used internally. +==================== +*/ +shader_t *R_GetShaderByHandle( qhandle_t hShader ) { + if ( hShader < 0 ) { + ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + if ( hShader >= tr.numShaders ) { + ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + return tr.shaders[hShader]; +} + +/* +=============== +R_ShaderList_f + +Dump information on all valid shaders to the console +A second parameter will cause it to print in sorted order +=============== +*/ +void R_ShaderList_f (void) { + int i; + int count; + shader_t *shader; + + ri.Printf (PRINT_ALL, "-----------------------\n"); + + count = 0; + for ( i = 0 ; i < tr.numShaders ; i++ ) { + if ( ri.Cmd_Argc() > 1 ) { + shader = tr.sortedShaders[i]; + } else { + shader = tr.shaders[i]; + } + + ri.Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses ); + + if (shader->lightmapIndex >= 0 ) { + ri.Printf (PRINT_ALL, "L "); + } else { + ri.Printf (PRINT_ALL, " "); + } + if ( shader->multitextureEnv == GL_ADD ) { + ri.Printf( PRINT_ALL, "MT(a) " ); + } else if ( shader->multitextureEnv == GL_MODULATE ) { + ri.Printf( PRINT_ALL, "MT(m) " ); + } else if ( shader->multitextureEnv == GL_DECAL ) { + ri.Printf( PRINT_ALL, "MT(d) " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + if ( shader->explicitlyDefined ) { + ri.Printf( PRINT_ALL, "E " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->optimalStageIteratorFunc == RB_StageIteratorGeneric ) { + ri.Printf( PRINT_ALL, "gen " ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorSky ) { + ri.Printf( PRINT_ALL, "sky " ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorLightmappedMultitexture ) { + ri.Printf( PRINT_ALL, "lmmt" ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorVertexLitTexture ) { + ri.Printf( PRINT_ALL, "vlt " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->defaultShader ) { + ri.Printf (PRINT_ALL, ": %s (DEFAULTED)\n", shader->name); + } else { + ri.Printf (PRINT_ALL, ": %s\n", shader->name); + } + count++; + } + ri.Printf (PRINT_ALL, "%i total shaders\n", count); + ri.Printf (PRINT_ALL, "------------------\n"); +} + +/* +==================== +ScanAndLoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names +===================== +*/ +#define MAX_SHADER_FILES 4096 +static void ScanAndLoadShaderFiles( void ) +{ + char **shaderFiles; + char *buffers[MAX_SHADER_FILES]; + char *p; + int numShaderFiles; + int i; + char *oldp, *token, *hashMem, *textEnd; + int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; + + long sum = 0, summand; + // scan for shader files + shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaderFiles ); + + if ( !shaderFiles || !numShaderFiles ) + { + ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" ); + return; + } + + if ( numShaderFiles > MAX_SHADER_FILES ) { + numShaderFiles = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaderFiles; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); + ri.Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename ); + summand = ri.FS_ReadFile( filename, (void **)&buffers[i] ); + + if ( !buffers[i] ) + ri.Error( ERR_DROP, "Couldn't load %s", filename ); + + // Do a simple check on the shader structure in that file to make sure one bad shader file cannot fuck up all other shaders. + p = buffers[i]; + while(1) + { + token = COM_ParseExt(&p, qtrue); + + if(!*token) + break; + + oldp = p; + + token = COM_ParseExt(&p, qtrue); + if(token[0] != '{' && token[1] != '\0') + { + ri.Printf(PRINT_WARNING, "WARNING: Bad shader file %s has incorrect syntax.\n", filename); + ri.FS_FreeFile(buffers[i]); + buffers[i] = NULL; + break; + } + + SkipBracedSection(&oldp); + p = oldp; + } + + + if (buffers[i]) + sum += summand; + } + + // build single large buffer + s_shaderText = ri.Hunk_Alloc( sum + numShaderFiles*2, h_low ); + s_shaderText[ 0 ] = '\0'; + textEnd = s_shaderText; + + // free in reverse order, so the temp files are all dumped + for ( i = numShaderFiles - 1; i >= 0 ; i-- ) + { + if ( !buffers[i] ) + continue; + + strcat( textEnd, buffers[i] ); + strcat( textEnd, "\n" ); + textEnd += strlen( textEnd ); + ri.FS_FreeFile( buffers[i] ); + } + + COM_Compress( s_shaderText ); + + // free up memory + ri.FS_FreeFileList( shaderFiles ); + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + size = 0; + + p = s_shaderText; + // look for shader names + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTableSizes[hash]++; + size++; + SkipBracedSection(&p); + } + + size += MAX_SHADERTEXT_HASH; + + hashMem = ri.Hunk_Alloc( size * sizeof(char *), h_low ); + + for (i = 0; i < MAX_SHADERTEXT_HASH; i++) { + shaderTextHashTable[i] = (char **) hashMem; + hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *)); + } + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + + p = s_shaderText; + // look for shader names + while ( 1 ) { + oldp = p; + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp; + + SkipBracedSection(&p); + } + + return; + +} + + +/* +==================== +CreateInternalShaders +==================== +*/ +static void CreateInternalShaders( void ) { + tr.numShaders = 0; + + // init the default shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + + shader.lightmapIndex = LIGHTMAP_NONE; + stages[0].bundle[0].image[0] = tr.defaultImage; + stages[0].active = qtrue; + stages[0].stateBits = GLS_DEFAULT; + tr.defaultShader = FinishShader(); + + // shadow shader is just a marker + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + shader.sort = SS_STENCIL_SHADOW; + tr.shadowShader = FinishShader(); +} + +static void CreateExternalShaders( void ) { + tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, qtrue ); + tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, qtrue ); + + // Hack to make fogging work correctly on flares. Fog colors are calculated + // in tr_flare.c already. + if(!tr.flareShader->defaultShader) + { + int index; + + for(index = 0; index < tr.flareShader->numUnfoggedPasses; index++) + { + tr.flareShader->stages[index]->adjustColorsForFog = ACFF_NONE; + tr.flareShader->stages[index]->stateBits |= GLS_DEPTHTEST_DISABLE; + } + } + + tr.sunShader = R_FindShader( "sun", LIGHTMAP_NONE, qtrue ); +} + +/* +================== +R_InitShaders +================== +*/ +void R_InitShaders( void ) { + ri.Printf( PRINT_ALL, "Initializing Shaders\n" ); + + Com_Memset(hashTable, 0, sizeof(hashTable)); + + CreateInternalShaders(); + + ScanAndLoadShaderFiles(); + + CreateExternalShaders(); +} diff --git a/src/renderer/tr_shadows.c b/src/renderer/tr_shadows.c new file mode 100644 index 0000000..0477779 --- /dev/null +++ b/src/renderer/tr_shadows.c @@ -0,0 +1,344 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + + +/* + + for a projection shadow: + + point[x] += light vector * ( z - shadow plane ) + point[y] += + point[z] = shadow plane + + 1 0 light[x] / light[z] + +*/ + +typedef struct { + int i2; + int facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 32 + +static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; +static int numEdgeDefs[SHADER_MAX_VERTEXES]; +static int facing[SHADER_MAX_INDEXES/3]; + +void R_AddEdgeDef( int i1, int i2, int facing ) { + int c; + + c = numEdgeDefs[ i1 ]; + if ( c == MAX_EDGE_DEFS ) { + return; // overflow + } + edgeDefs[ i1 ][ c ].i2 = i2; + edgeDefs[ i1 ][ c ].facing = facing; + + numEdgeDefs[ i1 ]++; +} + +void R_RenderShadowEdges( void ) { + int i; + +#if 0 + int numTris; + + // dumb way -- render every triangle's edges + numTris = tess.numIndexes / 3; + + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + + if ( !facing[i] ) { + continue; + } + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i3 ] ); + qglVertex3fv( tess.xyz[ i3 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] ); + qglEnd(); + } +#else + int c, c2; + int j, k; + int i2; + int c_edges, c_rejected; + int hit[2]; + + // an edge is NOT a silhouette edge if its face doesn't face the light, + // or if it has a reverse paired edge that also faces the light. + // A well behaved polyhedron would have exactly two faces for each edge, + // but lots of models have dangling edges or overfanned edges + c_edges = 0; + c_rejected = 0; + + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + c = numEdgeDefs[ i ]; + for ( j = 0 ; j < c ; j++ ) { + if ( !edgeDefs[ i ][ j ].facing ) { + continue; + } + + hit[0] = 0; + hit[1] = 0; + + i2 = edgeDefs[ i ][ j ].i2; + c2 = numEdgeDefs[ i2 ]; + for ( k = 0 ; k < c2 ; k++ ) { + if ( edgeDefs[ i2 ][ k ].i2 == i ) { + hit[ edgeDefs[ i2 ][ k ].facing ]++; + } + } + + // if it doesn't share the edge with another front facing + // triangle, it is a sil edge + if ( hit[ 1 ] == 0 ) { + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( tess.xyz[ i + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglEnd(); + c_edges++; + } else { + c_rejected++; + } + } + } +#endif +} + +/* +================= +RB_ShadowTessEnd + +triangleFromEdge[ v1 ][ v2 ] + + + set triangle from edge( v1, v2, tri ) + if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) { + } +================= +*/ +void RB_ShadowTessEnd( void ) { + int i; + int numTris; + vec3_t lightDir; + GLboolean rgba[4]; + + // we can only do this if we have enough space in the vertex buffers + if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) { + return; + } + + if ( glConfig.stencilBits < 4 ) { + return; + } + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i+tess.numVertexes] ); + } + + // decide which triangles face the light + Com_Memset( numEdgeDefs, 0, 4 * tess.numVertexes ); + + numTris = tess.numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + vec3_t d1, d2, normal; + float *v1, *v2, *v3; + float d; + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + v1 = tess.xyz[ i1 ]; + v2 = tess.xyz[ i2 ]; + v3 = tess.xyz[ i3 ]; + + VectorSubtract( v2, v1, d1 ); + VectorSubtract( v3, v1, d2 ); + CrossProduct( d1, d2, normal ); + + d = DotProduct( normal, lightDir ); + if ( d > 0 ) { + facing[ i ] = 1; + } else { + facing[ i ] = 0; + } + + // create the edges + R_AddEdgeDef( i1, i2, facing[ i ] ); + R_AddEdgeDef( i2, i3, facing[ i ] ); + R_AddEdgeDef( i3, i1, facing[ i ] ); + } + + // draw the silhouette edges + + GL_Bind( tr.whiteImage ); + qglEnable( GL_CULL_FACE ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + qglColor3f( 0.2f, 0.2f, 0.2f ); + + // don't write to the color buffer + qglGetBooleanv(GL_COLOR_WRITEMASK, rgba); + qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + // mirrors have the culling order reversed + if ( backEnd.viewParms.isMirror ) { + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } else { + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } + + + // reenable writing to the color buffer + qglColorMask(rgba[0], rgba[1], rgba[2], rgba[3]); +} + + +/* +================= +RB_ShadowFinish + +Darken everything that is is a shadow volume. +We have to delay this until everything has been shadowed, +because otherwise shadows from different body parts would +overlap and double darken. +================= +*/ +void RB_ShadowFinish( void ) { + if ( r_shadows->integer != 2 ) { + return; + } + if ( glConfig.stencilBits < 4 ) { + return; + } + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_NOTEQUAL, 0, 255 ); + + qglDisable (GL_CLIP_PLANE0); + qglDisable (GL_CULL_FACE); + + GL_Bind( tr.whiteImage ); + + qglLoadIdentity (); + + qglColor3f( 0.6f, 0.6f, 0.6f ); + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); + +// qglColor3f( 1, 0, 0 ); +// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + + qglBegin( GL_QUADS ); + qglVertex3f( -100, 100, -10 ); + qglVertex3f( 100, 100, -10 ); + qglVertex3f( 100, -100, -10 ); + qglVertex3f( -100, -100, -10 ); + qglEnd (); + + qglColor4f(1,1,1,1); + qglDisable( GL_STENCIL_TEST ); +} + + +/* +================= +RB_ProjectionShadowDeform + +================= +*/ +void RB_ProjectionShadowDeform( void ) { + float *xyz; + int i; + float h; + vec3_t ground; + vec3_t light; + float groundDist; + float d; + vec3_t lightDir; + + xyz = ( float * ) tess.xyz; + + ground[0] = backEnd.or.axis[0][2]; + ground[1] = backEnd.or.axis[1][2]; + ground[2] = backEnd.or.axis[2][2]; + + groundDist = backEnd.or.origin[2] - backEnd.currentEntity->e.shadowPlane; + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + d = DotProduct( lightDir, ground ); + // don't let the shadows get too long or go negative + if ( d < 0.5 ) { + VectorMA( lightDir, (0.5 - d), ground, lightDir ); + d = DotProduct( lightDir, ground ); + } + d = 1.0 / d; + + light[0] = lightDir[0] * d; + light[1] = lightDir[1] * d; + light[2] = lightDir[2] * d; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + h = DotProduct( xyz, ground ) + groundDist; + + xyz[0] -= light[0] * h; + xyz[1] -= light[1] * h; + xyz[2] -= light[2] * h; + } +} diff --git a/src/renderer/tr_sky.c b/src/renderer/tr_sky.c new file mode 100644 index 0000000..fd266ee --- /dev/null +++ b/src/renderer/tr_sky.c @@ -0,0 +1,846 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_sky.c +#include "tr_local.h" + +#define SKY_SUBDIVISIONS 8 +#define HALF_SKY_SUBDIVISIONS (SKY_SUBDIVISIONS/2) + +static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; +static float s_cloudTexP[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; + +/* +=================================================================================== + +POLYGON TO BOX SIDE PROJECTION + +=================================================================================== +*/ + +static vec3_t sky_clip[6] = +{ + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +static float sky_mins[2][6], sky_maxs[2][6]; +static float sky_min, sky_max; + +/* +================ +AddSkyPolygon +================ +*/ +static void AddSkyPolygon (int nump, vec3_t vecs) +{ + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + // s = [0]/[2], t = [1]/[2] + static int vec_to_st[6][3] = + { + {-2,3,1}, + {2,3,-1}, + + {1,3,2}, + {-1,3,-2}, + + {-2,-1,3}, + {-2,1,-3} + + // {-1,2,3}, + // {1,2,-3} + }; + + // decide which face it maps to + VectorCopy (vec3_origin, v); + for (i=0, vp=vecs ; i av[1] && av[0] > av[2]) + { + if (v[0] < 0) + axis = 1; + else + axis = 0; + } + else if (av[1] > av[2] && av[1] > av[0]) + { + if (v[1] < 0) + axis = 3; + else + axis = 2; + } + else + { + if (v[2] < 0) + axis = 5; + else + axis = 4; + } + + // project new texture coords + for (i=0 ; i 0) + dv = vecs[j - 1]; + else + dv = -vecs[-j - 1]; + if (dv < 0.001) + continue; // don't divide by zero + j = vec_to_st[axis][0]; + if (j < 0) + s = -vecs[-j -1] / dv; + else + s = vecs[j-1] / dv; + j = vec_to_st[axis][1]; + if (j < 0) + t = -vecs[-j -1] / dv; + else + t = vecs[j-1] / dv; + + if (s < sky_mins[0][axis]) + sky_mins[0][axis] = s; + if (t < sky_mins[1][axis]) + sky_mins[1][axis] = t; + if (s > sky_maxs[0][axis]) + sky_maxs[0][axis] = s; + if (t > sky_maxs[1][axis]) + sky_maxs[1][axis] = t; + } +} + +#define ON_EPSILON 0.1f // point on plane side epsilon +#define MAX_CLIP_VERTS 64 +/* +================ +ClipSkyPolygon +================ +*/ +static void ClipSkyPolygon (int nump, vec3_t vecs, int stage) +{ + float *norm; + float *v; + qboolean front, back; + float d, e; + float dists[MAX_CLIP_VERTS]; + int sides[MAX_CLIP_VERTS]; + vec3_t newv[2][MAX_CLIP_VERTS]; + int newc[2]; + int i, j; + + if (nump > MAX_CLIP_VERTS-2) + ri.Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS"); + if (stage == 6) + { // fully clipped, so draw it + AddSkyPolygon (nump, vecs); + return; + } + + front = back = qfalse; + norm = sky_clip[stage]; + for (i=0, v = vecs ; i ON_EPSILON) + { + front = qtrue; + sides[i] = SIDE_FRONT; + } + else if (d < -ON_EPSILON) + { + back = qtrue; + sides[i] = SIDE_BACK; + } + else + sides[i] = SIDE_ON; + dists[i] = d; + } + + if (!front || !back) + { // not clipped + ClipSkyPolygon (nump, vecs, stage+1); + return; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy (vecs, (vecs+(i*3)) ); + newc[0] = newc[1] = 0; + + for (i=0, v = vecs ; inumIndexes; i += 3 ) + { + for (j = 0 ; j < 3 ; j++) + { + VectorSubtract( input->xyz[input->indexes[i+j]], + backEnd.viewParms.or.origin, + p[j] ); + } + ClipSkyPolygon( 3, p[0], 0 ); + } +} + +/* +=================================================================================== + +CLOUD VERTEX GENERATION + +=================================================================================== +*/ + +/* +** MakeSkyVec +** +** Parms: s, t range from -1 to 1 +*/ +static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ ) +{ + // 1 = s, 2 = t, 3 = 2048 + static int st_to_vec[6][3] = + { + {3,-1,2}, + {-3,1,2}, + + {1,3,2}, + {-1,-3,2}, + + {-2,-1,3}, // 0 degrees yaw, look straight up + {2,-1,-3} // look straight down + }; + + vec3_t b; + int j, k; + float boxSize; + + boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + b[0] = s*boxSize; + b[1] = t*boxSize; + b[2] = boxSize; + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + { + outXYZ[j] = -b[-k - 1]; + } + else + { + outXYZ[j] = b[k - 1]; + } + } + + // avoid bilerp seam + s = (s+1)*0.5; + t = (t+1)*0.5; + if (s < sky_min) + { + s = sky_min; + } + else if (s > sky_max) + { + s = sky_max; + } + + if (t < sky_min) + { + t = sky_min; + } + else if (t > sky_max) + { + t = sky_max; + } + + t = 1.0 - t; + + + if ( outSt ) + { + outSt[0] = s; + outSt[1] = t; + } +} + +static int sky_texorder[6] = {0,2,1,3,4,5}; +static vec3_t s_skyPoints[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; +static float s_skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; + +static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] ) +{ + int s, t; + + GL_Bind( image ); + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t < maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + qglBegin( GL_TRIANGLE_STRIP ); + + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + qglTexCoord2fv( s_skyTexCoords[t][s] ); + qglVertex3fv( s_skyPoints[t][s] ); + + qglTexCoord2fv( s_skyTexCoords[t+1][s] ); + qglVertex3fv( s_skyPoints[t+1][s] ); + } + + qglEnd(); + } +} + +static void DrawSkyBox( shader_t *shader ) +{ + int i; + + sky_min = 0; + sky_max = 1; + + Com_Memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) ); + + for (i=0 ; i<6 ; i++) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; + sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + s_skyTexCoords[t][s], + s_skyPoints[t][s] ); + } + } + + DrawSkySide( shader->sky.outerbox[sky_texorder[i]], + sky_mins_subd, + sky_maxs_subd ); + } + +} + +static void FillCloudySkySide( const int mins[2], const int maxs[2], qboolean addIndexes ) +{ + int s, t; + int vertexStart = tess.numVertexes; + int tHeight, sWidth; + + tHeight = maxs[1] - mins[1] + 1; + sWidth = maxs[0] - mins[0] + 1; + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0]; + tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1]; + + tess.numVertexes++; + + if ( tess.numVertexes >= SHADER_MAX_VERTEXES ) + { + ri.Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()\n" ); + } + } + } + + // only add indexes for one pass, otherwise it would draw multiple times for each pass + if ( addIndexes ) { + for ( t = 0; t < tHeight-1; t++ ) + { + for ( s = 0; s < sWidth-1; s++ ) + { + tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + } + } + } +} + +static void FillCloudBox( const shader_t *shader, int stage ) +{ + int i; + + for ( i =0; i < 6; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + float MIN_T; + + if ( 1 ) // FIXME? shader->sky.fullClouds ) + { + MIN_T = -HALF_SKY_SUBDIVISIONS; + + // still don't want to draw the bottom, even if fullClouds + if ( i == 5 ) + continue; + } + else + { + switch( i ) + { + case 0: + case 1: + case 2: + case 3: + MIN_T = -1; + break; + case 5: + // don't draw clouds beneath you + continue; + case 4: // top + default: + MIN_T = -HALF_SKY_SUBDIVISIONS; + break; + } + } + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = myftol( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ); + sky_mins_subd[1] = myftol( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ); + sky_maxs_subd[0] = myftol( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ); + sky_maxs_subd[1] = myftol( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ); + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < MIN_T ) + sky_mins_subd[1] = MIN_T; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < MIN_T ) + sky_maxs_subd[1] = MIN_T; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + s_skyPoints[t][s] ); + + s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0]; + s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1]; + } + } + + // only add indexes for first stage + FillCloudySkySide( sky_mins_subd, sky_maxs_subd, ( stage == 0 ) ); + } +} + +/* +** R_BuildCloudData +*/ +void R_BuildCloudData( shaderCommands_t *input ) +{ + int i; + shader_t *shader; + + shader = input->shader; + + assert( shader->isSky ); + + sky_min = 1.0 / 256.0f; // FIXME: not correct? + sky_max = 255.0 / 256.0f; + + // set up for drawing + tess.numIndexes = 0; + tess.numVertexes = 0; + + if ( input->shader->sky.cloudHeight ) + { + for ( i = 0; i < MAX_SHADER_STAGES; i++ ) + { + if ( !tess.xstages[i] ) { + break; + } + FillCloudBox( input->shader, i ); + } + } +} + +/* +** R_InitSkyTexCoords +** Called when a sky shader is parsed +*/ +#define SQR( a ) ((a)*(a)) +void R_InitSkyTexCoords( float heightCloud ) +{ + int i, s, t; + float radiusWorld = 4096; + float p; + float sRad, tRad; + vec3_t skyVec; + vec3_t v; + + // init zfar so MakeSkyVec works even though + // a world hasn't been bounded + backEnd.viewParms.zFar = 1024; + + for ( i = 0; i < 6; i++ ) + { + for ( t = 0; t <= SKY_SUBDIVISIONS; t++ ) + { + for ( s = 0; s <= SKY_SUBDIVISIONS; s++ ) + { + // compute vector from view origin to sky side integral point + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + skyVec ); + + // compute parametric value 'p' that intersects with cloud layer + p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) * + ( -2 * skyVec[2] * radiusWorld + + 2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) + + 2 * SQR( skyVec[0] ) * radiusWorld * heightCloud + + SQR( skyVec[0] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud + + SQR( skyVec[1] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud + + SQR( skyVec[2] ) * SQR( heightCloud ) ) ); + + s_cloudTexP[i][t][s] = p; + + // compute intersection point based on p + VectorScale( skyVec, p, v ); + v[2] += radiusWorld; + + // compute vector from world origin to intersection point 'v' + VectorNormalize( v ); + + sRad = Q_acos( v[0] ); + tRad = Q_acos( v[1] ); + + s_cloudTexCoords[i][t][s][0] = sRad; + s_cloudTexCoords[i][t][s][1] = tRad; + } + } + } +} + +//====================================================================================== + +/* +** RB_DrawSun +*/ +void RB_DrawSun( void ) { + float size; + float dist; + vec3_t origin, vec1, vec2; + vec3_t temp; + + if ( !backEnd.skyRenderedThisView ) { + return; + } + if ( !r_drawSun->integer ) { + return; + } + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]); + + dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + size = dist * 0.4; + + VectorScale( tr.sunDirection, dist, origin ); + PerpendicularVector( vec1, tr.sunDirection ); + CrossProduct( tr.sunDirection, vec1, vec2 ); + + VectorScale( vec1, size, vec1 ); + VectorScale( vec2, size, vec2 ); + + // farthest depth range + qglDepthRange( 1.0, 1.0 ); + + // FIXME: use quad stamp + RB_BeginSurface( tr.sunShader, tess.fogNum ); + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); +} + + + + +/* +================ +RB_StageIteratorSky + +All of the visible sky triangles are in tess + +Other things could be stuck in here, like birds in the sky, etc +================ +*/ +void RB_StageIteratorSky( void ) { + if ( r_fastsky->integer ) { + return; + } + + // go through all the polygons and project them onto + // the sky box to see which blocks on each side need + // to be drawn + RB_ClipSkyPolygons( &tess ); + + // r_showsky will let all the sky blocks be drawn in + // front of everything to allow developers to see how + // much sky is getting sucked in + if ( r_showsky->integer ) { + qglDepthRange( 0.0, 0.0 ); + } else { + qglDepthRange( 1.0, 1.0 ); + } + + // draw the outer skybox + if ( tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage ) { + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglPushMatrix (); + GL_State( 0 ); + qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]); + + DrawSkyBox( tess.shader ); + + qglPopMatrix(); + } + + // generate the vertexes for all the clouds, which will be drawn + // by the generic shader routine + R_BuildCloudData( &tess ); + + RB_StageIteratorGeneric(); + + // draw the inner skybox + + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); + + // note that sky was drawn so we will draw a sun later + backEnd.skyRenderedThisView = qtrue; +} + diff --git a/src/renderer/tr_surface.c b/src/renderer/tr_surface.c new file mode 100644 index 0000000..f14501f --- /dev/null +++ b/src/renderer/tr_surface.c @@ -0,0 +1,1249 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_surf.c +#include "tr_local.h" +#if idppc_altivec && !defined(MACOS_X) +#include +#endif + +/* + + THIS ENTIRE FILE IS BACK END + +backEnd.currentEntity will be valid. + +Tess_Begin has already been called for the surface's shader. + +The modelview matrix will be set. + +It is safe to actually issue drawing commands here if you don't want to +use the shader system. +*/ + + +//============================================================================ + + +/* +============== +RB_CheckOverflow +============== +*/ +void RB_CheckOverflow( int verts, int indexes ) { + if (tess.numVertexes + verts < SHADER_MAX_VERTEXES + && tess.numIndexes + indexes < SHADER_MAX_INDEXES) { + return; + } + + RB_EndSurface(); + + if ( verts >= SHADER_MAX_VERTEXES ) { + ri.Error(ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES ); + } + if ( indexes >= SHADER_MAX_INDEXES ) { + ri.Error(ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES ); + } + + RB_BeginSurface(tess.shader, tess.fogNum ); +} + + +/* +============== +RB_AddQuadStampExt +============== +*/ +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ) { + vec3_t normal; + int ndx; + + RB_CHECKOVERFLOW( 4, 6 ); + + ndx = tess.numVertexes; + + // triangle indexes for a simple quad + tess.indexes[ tess.numIndexes ] = ndx; + tess.indexes[ tess.numIndexes + 1 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 2 ] = ndx + 3; + + tess.indexes[ tess.numIndexes + 3 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 4 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 5 ] = ndx + 2; + + tess.xyz[ndx][0] = origin[0] + left[0] + up[0]; + tess.xyz[ndx][1] = origin[1] + left[1] + up[1]; + tess.xyz[ndx][2] = origin[2] + left[2] + up[2]; + + tess.xyz[ndx+1][0] = origin[0] - left[0] + up[0]; + tess.xyz[ndx+1][1] = origin[1] - left[1] + up[1]; + tess.xyz[ndx+1][2] = origin[2] - left[2] + up[2]; + + tess.xyz[ndx+2][0] = origin[0] - left[0] - up[0]; + tess.xyz[ndx+2][1] = origin[1] - left[1] - up[1]; + tess.xyz[ndx+2][2] = origin[2] - left[2] - up[2]; + + tess.xyz[ndx+3][0] = origin[0] + left[0] - up[0]; + tess.xyz[ndx+3][1] = origin[1] + left[1] - up[1]; + tess.xyz[ndx+3][2] = origin[2] + left[2] - up[2]; + + + // constant normal all the way around + VectorSubtract( vec3_origin, backEnd.viewParms.or.axis[0], normal ); + + tess.normal[ndx][0] = tess.normal[ndx+1][0] = tess.normal[ndx+2][0] = tess.normal[ndx+3][0] = normal[0]; + tess.normal[ndx][1] = tess.normal[ndx+1][1] = tess.normal[ndx+2][1] = tess.normal[ndx+3][1] = normal[1]; + tess.normal[ndx][2] = tess.normal[ndx+1][2] = tess.normal[ndx+2][2] = tess.normal[ndx+3][2] = normal[2]; + + // standard square texture coordinates + tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1; + tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1; + + tess.texCoords[ndx+1][0][0] = tess.texCoords[ndx+1][1][0] = s2; + tess.texCoords[ndx+1][0][1] = tess.texCoords[ndx+1][1][1] = t1; + + tess.texCoords[ndx+2][0][0] = tess.texCoords[ndx+2][1][0] = s2; + tess.texCoords[ndx+2][0][1] = tess.texCoords[ndx+2][1][1] = t2; + + tess.texCoords[ndx+3][0][0] = tess.texCoords[ndx+3][1][0] = s1; + tess.texCoords[ndx+3][0][1] = tess.texCoords[ndx+3][1][1] = t2; + + // constant color all the way around + // should this be identity and let the shader specify from entity? + * ( unsigned int * ) &tess.vertexColors[ndx] = + * ( unsigned int * ) &tess.vertexColors[ndx+1] = + * ( unsigned int * ) &tess.vertexColors[ndx+2] = + * ( unsigned int * ) &tess.vertexColors[ndx+3] = + * ( unsigned int * )color; + + + tess.numVertexes += 4; + tess.numIndexes += 6; +} + +/* +============== +RB_AddQuadStamp +============== +*/ +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ) { + RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 ); +} + +/* +============== +RB_SurfaceSprite +============== +*/ +static void RB_SurfaceSprite( void ) { + vec3_t left, up; + float radius; + + // calculate the xyz locations for the four corners + radius = backEnd.currentEntity->e.radius; + if ( backEnd.currentEntity->e.rotation == 0 ) { + VectorScale( backEnd.viewParms.or.axis[1], radius, left ); + VectorScale( backEnd.viewParms.or.axis[2], radius, up ); + } else { + float s, c; + float ang; + + ang = M_PI * backEnd.currentEntity->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.or.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.or.axis[2], left ); + + VectorScale( backEnd.viewParms.or.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.or.axis[1], up ); + } + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + + +/* +============= +RB_SurfacePolychain +============= +*/ +static void RB_SurfacePolychain( srfPoly_t *p ) { + int i; + int numv; + + RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) ); + + // fan triangles into the tess array + numv = tess.numVertexes; + for ( i = 0; i < p->numVerts; i++ ) { + VectorCopy( p->verts[i].xyz, tess.xyz[numv] ); + tess.texCoords[numv][0][0] = p->verts[i].st[0]; + tess.texCoords[numv][0][1] = p->verts[i].st[1]; + *(int *)&tess.vertexColors[numv] = *(int *)p->verts[ i ].modulate; + + numv++; + } + + // generate fan indexes into the tess array + for ( i = 0; i < p->numVerts-2; i++ ) { + tess.indexes[tess.numIndexes + 0] = tess.numVertexes; + tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; + tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; + tess.numIndexes += 3; + } + + tess.numVertexes = numv; +} + + +/* +============= +RB_SurfaceTriangles +============= +*/ +static void RB_SurfaceTriangles( srfTriangles_t *srf ) { + int i; + drawVert_t *dv; + float *xyz, *normal, *texCoords; + byte *color; + int dlightBits; + qboolean needsNormal; + + dlightBits = srf->dlightBits[backEnd.smpFrame]; + tess.dlightBits |= dlightBits; + + RB_CHECKOVERFLOW( srf->numVerts, srf->numIndexes ); + + for ( i = 0 ; i < srf->numIndexes ; i += 3 ) { + tess.indexes[ tess.numIndexes + i + 0 ] = tess.numVertexes + srf->indexes[ i + 0 ]; + tess.indexes[ tess.numIndexes + i + 1 ] = tess.numVertexes + srf->indexes[ i + 1 ]; + tess.indexes[ tess.numIndexes + i + 2 ] = tess.numVertexes + srf->indexes[ i + 2 ]; + } + tess.numIndexes += srf->numIndexes; + + dv = srf->verts; + xyz = tess.xyz[ tess.numVertexes ]; + normal = tess.normal[ tess.numVertexes ]; + texCoords = tess.texCoords[ tess.numVertexes ][0]; + color = tess.vertexColors[ tess.numVertexes ]; + needsNormal = tess.shader->needsNormal; + + for ( i = 0 ; i < srf->numVerts ; i++, dv++, xyz += 4, normal += 4, texCoords += 4, color += 4 ) { + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + + if ( needsNormal ) { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + + texCoords[2] = dv->lightmap[0]; + texCoords[3] = dv->lightmap[1]; + + *(int *)color = *(int *)dv->color; + } + + for ( i = 0 ; i < srf->numVerts ; i++ ) { + tess.vertexDlightBits[ tess.numVertexes + i] = dlightBits; + } + + tess.numVertexes += srf->numVerts; +} + + + +/* +============== +RB_SurfaceBeam +============== +*/ +static void RB_SurfaceBeam( void ) +{ +#define NUM_BEAM_SEGS 6 + refEntity_t *e; + int i; + vec3_t perpvec; + vec3_t direction, normalized_direction; + vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; + vec3_t oldorigin, origin; + + e = &backEnd.currentEntity->e; + + oldorigin[0] = e->oldorigin[0]; + oldorigin[1] = e->oldorigin[1]; + oldorigin[2] = e->oldorigin[2]; + + origin[0] = e->origin[0]; + origin[1] = e->origin[1]; + origin[2] = e->origin[2]; + + normalized_direction[0] = direction[0] = oldorigin[0] - origin[0]; + normalized_direction[1] = direction[1] = oldorigin[1] - origin[1]; + normalized_direction[2] = direction[2] = oldorigin[2] - origin[2]; + + if ( VectorNormalize( normalized_direction ) == 0 ) + return; + + PerpendicularVector( perpvec, normalized_direction ); + + VectorScale( perpvec, 4, perpvec ); + + for ( i = 0; i < NUM_BEAM_SEGS ; i++ ) + { + RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i ); +// VectorAdd( start_points[i], origin, start_points[i] ); + VectorAdd( start_points[i], direction, end_points[i] ); + } + + GL_Bind( tr.whiteImage ); + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + qglColor3f( 1, 0, 0 ); + + qglBegin( GL_TRIANGLE_STRIP ); + for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) { + qglVertex3fv( start_points[ i % NUM_BEAM_SEGS] ); + qglVertex3fv( end_points[ i % NUM_BEAM_SEGS] ); + } + qglEnd(); +} + +//================================================================================ + +static void DoRailCore( const vec3_t start, const vec3_t end, const vec3_t up, float len, float spanWidth ) +{ + float spanWidth2; + int vbase; + float t = len / 256.0f; + + vbase = tess.numVertexes; + + spanWidth2 = -spanWidth; + + // FIXME: use quad stamp? + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 0.25; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 0.25; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 0.25; + tess.numVertexes++; + + VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = t; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = t; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +static void DoRailDiscs( int numSegs, const vec3_t start, const vec3_t dir, const vec3_t right, const vec3_t up ) +{ + int i; + vec3_t pos[4]; + vec3_t v; + int spanWidth = r_railWidth->integer; + float c, s; + float scale; + + if ( numSegs > 1 ) + numSegs--; + if ( !numSegs ) + return; + + scale = 0.25; + + for ( i = 0; i < 4; i++ ) + { + c = cos( DEG2RAD( 45 + i * 90 ) ); + s = sin( DEG2RAD( 45 + i * 90 ) ); + v[0] = ( right[0] * c + up[0] * s ) * scale * spanWidth; + v[1] = ( right[1] * c + up[1] * s ) * scale * spanWidth; + v[2] = ( right[2] * c + up[2] * s ) * scale * spanWidth; + VectorAdd( start, v, pos[i] ); + + if ( numSegs > 1 ) + { + // offset by 1 segment if we're doing a long distance shot + VectorAdd( pos[i], dir, pos[i] ); + } + } + + for ( i = 0; i < numSegs; i++ ) + { + int j; + + RB_CHECKOVERFLOW( 4, 6 ); + + for ( j = 0; j < 4; j++ ) + { + VectorCopy( pos[j], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = ( j < 2 ); + tess.texCoords[tess.numVertexes][0][1] = ( j && j != 3 ); + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorAdd( pos[j], dir, pos[j] ); + } + + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 0; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 2; + } +} + +/* +** RB_SurfaceRailRinges +*/ +static void RB_SurfaceRailRings( void ) { + refEntity_t *e; + int numSegs; + int len; + vec3_t vec; + vec3_t right, up; + vec3_t start, end; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + MakeNormalVectors( vec, right, up ); + numSegs = ( len ) / r_railSegmentLength->value; + if ( numSegs <= 0 ) { + numSegs = 1; + } + + VectorScale( vec, r_railSegmentLength->value, vec ); + + DoRailDiscs( numSegs, start, vec, right, up ); +} + +/* +** RB_SurfaceRailCore +*/ +static void RB_SurfaceRailCore( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.or.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.or.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + DoRailCore( start, end, right, len, r_railCoreWidth->integer ); +} + +/* +** RB_SurfaceLightningBolt +*/ +static void RB_SurfaceLightningBolt( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + int i; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, end ); + VectorCopy( e->origin, start ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.or.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.or.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + for ( i = 0 ; i < 4 ; i++ ) { + vec3_t temp; + + DoRailCore( start, end, right, len, 8 ); + RotatePointAroundVector( temp, vec, right, 45 ); + VectorCopy( temp, right ); + } +} + +/* +** VectorArrayNormalize +* +* The inputs to this routing seem to always be close to length = 1.0 (about 0.6 to 2.0) +* This means that we don't have to worry about zero length or enormously long vectors. +*/ +static void VectorArrayNormalize(vec4_t *normals, unsigned int count) +{ +// assert(count); + +#if idppc + { + register float half = 0.5; + register float one = 1.0; + float *components = (float *)normals; + + // Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, + // runs *much* faster than calling sqrt(). We'll use a single Newton-Raphson + // refinement step to get a little more precision. This seems to yeild results + // that are correct to 3 decimal places and usually correct to at least 4 (sometimes 5). + // (That is, for the given input range of about 0.6 to 2.0). + do { + float x, y, z; + float B, y0, y1; + + x = components[0]; + y = components[1]; + z = components[2]; + components += 4; + B = x*x + y*y + z*z; + +#ifdef __GNUC__ + asm("frsqrte %0,%1" : "=f" (y0) : "f" (B)); +#else + y0 = __frsqrte(B); +#endif + y1 = y0 + half*y0*(one - B*y0*y0); + + x = x * y1; + y = y * y1; + components[-4] = x; + z = z * y1; + components[-3] = y; + components[-2] = z; + } while(count--); + } +#else // No assembly version for this architecture, or C_ONLY defined + // given the input, it's safe to call VectorNormalizeFast + while (count--) { + VectorNormalizeFast(normals[0]); + normals++; + } +#endif + +} + + + +/* +** LerpMeshVertexes +*/ +#if idppc_altivec +static void LerpMeshVertexes_altivec(md3Surface_t *surf, float backlerp) +{ + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale QALIGN(16); + float newXyzScale QALIGN(16); + float oldNormalScale QALIGN(16); + float newNormalScale QALIGN(16); + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.frame * surf->numVerts * 4); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + vector signed short newNormalsVec0; + vector signed short newNormalsVec1; + vector signed int newNormalsIntVec; + vector float newNormalsFloatVec; + vector float newXyzScaleVec; + vector unsigned char newNormalsLoadPermute; + vector unsigned char newNormalsStorePermute; + vector float zero; + + newNormalsStorePermute = vec_lvsl(0,(float *)&newXyzScaleVec); + newXyzScaleVec = *(vector float *)&newXyzScale; + newXyzScaleVec = vec_perm(newXyzScaleVec,newXyzScaleVec,newNormalsStorePermute); + newXyzScaleVec = vec_splat(newXyzScaleVec,0); + newNormalsLoadPermute = vec_lvsl(0,newXyz); + newNormalsStorePermute = vec_lvsr(0,outXyz); + zero = (vector float)vec_splat_s8(0); + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + newNormalsLoadPermute = vec_lvsl(0,newXyz); + newNormalsStorePermute = vec_lvsr(0,outXyz); + newNormalsVec0 = vec_ld(0,newXyz); + newNormalsVec1 = vec_ld(16,newXyz); + newNormalsVec0 = vec_perm(newNormalsVec0,newNormalsVec1,newNormalsLoadPermute); + newNormalsIntVec = vec_unpackh(newNormalsVec0); + newNormalsFloatVec = vec_ctf(newNormalsIntVec,0); + newNormalsFloatVec = vec_madd(newNormalsFloatVec,newXyzScaleVec,zero); + newNormalsFloatVec = vec_perm(newNormalsFloatVec,newNormalsFloatVec,newNormalsStorePermute); + //outXyz[0] = newXyz[0] * newXyzScale; + //outXyz[1] = newXyz[1] * newXyzScale; + //outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + vec_ste(newNormalsFloatVec,0,outXyz); + vec_ste(newNormalsFloatVec,4,outXyz); + vec_ste(newNormalsFloatVec,8,outXyz); + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + +// VectorNormalize (outNormal); + } + VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts); + } +} +#endif + +static void LerpMeshVertexes_scalar(md3Surface_t *surf, float backlerp) +{ + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale, newXyzScale; + float oldNormalScale, newNormalScale; + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.frame * surf->numVerts * 4); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + + outXyz[0] = newXyz[0] * newXyzScale; + outXyz[1] = newXyz[1] * newXyzScale; + outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + +// VectorNormalize (outNormal); + } + VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts); + } +} + +static void LerpMeshVertexes(md3Surface_t *surf, float backlerp) +{ +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + LerpMeshVertexes_altivec( surf, backlerp ); + return; + } +#endif // idppc_altivec + LerpMeshVertexes_scalar( surf, backlerp ); +} + + +/* +============= +RB_SurfaceMesh +============= +*/ +static void RB_SurfaceMesh(md3Surface_t *surface) { + int j; + float backlerp; + int *triangles; + float *texCoords; + int indexes; + int Bob, Doug; + int numVerts; + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + } + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles*3 ); + + LerpMeshVertexes (surface, backlerp); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + Bob = tess.numIndexes; + Doug = tess.numVertexes; + for (j = 0 ; j < indexes ; j++) { + tess.indexes[Bob + j] = Doug + triangles[j]; + } + tess.numIndexes += indexes; + + texCoords = (float *) ((byte *)surface + surface->ofsSt); + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tess.texCoords[Doug + j][0][0] = texCoords[j*2+0]; + tess.texCoords[Doug + j][0][1] = texCoords[j*2+1]; + // FIXME: fill in lightmapST for completeness? + } + + tess.numVertexes += surface->numVerts; + +} + + +/* +============== +RB_SurfaceFace +============== +*/ +static void RB_SurfaceFace( srfSurfaceFace_t *surf ) { + int i; + unsigned *indices, *tessIndexes; + float *v; + float *normal; + int ndx; + int Bob; + int numPoints; + int dlightBits; + + RB_CHECKOVERFLOW( surf->numPoints, surf->numIndices ); + + dlightBits = surf->dlightBits[backEnd.smpFrame]; + tess.dlightBits |= dlightBits; + + indices = ( unsigned * ) ( ( ( char * ) surf ) + surf->ofsIndices ); + + Bob = tess.numVertexes; + tessIndexes = tess.indexes + tess.numIndexes; + for ( i = surf->numIndices-1 ; i >= 0 ; i-- ) { + tessIndexes[i] = indices[i] + Bob; + } + + tess.numIndexes += surf->numIndices; + + v = surf->points[0]; + + ndx = tess.numVertexes; + + numPoints = surf->numPoints; + + if ( tess.shader->needsNormal ) { + normal = surf->plane.normal; + for ( i = 0, ndx = tess.numVertexes; i < numPoints; i++, ndx++ ) { + VectorCopy( normal, tess.normal[ndx] ); + } + } + + for ( i = 0, v = surf->points[0], ndx = tess.numVertexes; i < numPoints; i++, v += VERTEXSIZE, ndx++ ) { + VectorCopy( v, tess.xyz[ndx]); + tess.texCoords[ndx][0][0] = v[3]; + tess.texCoords[ndx][0][1] = v[4]; + tess.texCoords[ndx][1][0] = v[5]; + tess.texCoords[ndx][1][1] = v[6]; + * ( unsigned int * ) &tess.vertexColors[ndx] = * ( unsigned int * ) &v[7]; + tess.vertexDlightBits[ndx] = dlightBits; + } + + + tess.numVertexes += surf->numPoints; +} + + +static float LodErrorForVolume( vec3_t local, float radius ) { + vec3_t world; + float d; + + // never let it go negative + if ( r_lodCurveError->value < 0 ) { + return 0; + } + + world[0] = local[0] * backEnd.or.axis[0][0] + local[1] * backEnd.or.axis[1][0] + + local[2] * backEnd.or.axis[2][0] + backEnd.or.origin[0]; + world[1] = local[0] * backEnd.or.axis[0][1] + local[1] * backEnd.or.axis[1][1] + + local[2] * backEnd.or.axis[2][1] + backEnd.or.origin[1]; + world[2] = local[0] * backEnd.or.axis[0][2] + local[1] * backEnd.or.axis[1][2] + + local[2] * backEnd.or.axis[2][2] + backEnd.or.origin[2]; + + VectorSubtract( world, backEnd.viewParms.or.origin, world ); + d = DotProduct( world, backEnd.viewParms.or.axis[0] ); + + if ( d < 0 ) { + d = -d; + } + d -= radius; + if ( d < 1 ) { + d = 1; + } + + return r_lodCurveError->value / d; +} + +/* +============= +RB_SurfaceGrid + +Just copy the grid of points and triangulate +============= +*/ +static void RB_SurfaceGrid( srfGridMesh_t *cv ) { + int i, j; + float *xyz; + float *texCoords; + float *normal; + unsigned char *color; + drawVert_t *dv; + int rows, irows, vrows; + int used; + int widthTable[MAX_GRID_SIZE]; + int heightTable[MAX_GRID_SIZE]; + float lodError; + int lodWidth, lodHeight; + int numVertexes; + int dlightBits; + int *vDlightBits; + qboolean needsNormal; + + dlightBits = cv->dlightBits[backEnd.smpFrame]; + tess.dlightBits |= dlightBits; + + // determine the allowable discrepance + lodError = LodErrorForVolume( cv->lodOrigin, cv->lodRadius ); + + // determine which rows and columns of the subdivision + // we are actually going to use + widthTable[0] = 0; + lodWidth = 1; + for ( i = 1 ; i < cv->width-1 ; i++ ) { + if ( cv->widthLodError[i] <= lodError ) { + widthTable[lodWidth] = i; + lodWidth++; + } + } + widthTable[lodWidth] = cv->width-1; + lodWidth++; + + heightTable[0] = 0; + lodHeight = 1; + for ( i = 1 ; i < cv->height-1 ; i++ ) { + if ( cv->heightLodError[i] <= lodError ) { + heightTable[lodHeight] = i; + lodHeight++; + } + } + heightTable[lodHeight] = cv->height-1; + lodHeight++; + + + // very large grids may have more points or indexes than can be fit + // in the tess structure, so we may have to issue it in multiple passes + + used = 0; + rows = 0; + while ( used < lodHeight - 1 ) { + // see how many rows of both verts and indexes we can add without overflowing + do { + vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth; + irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 ); + + // if we don't have enough space for at least one strip, flush the buffer + if ( vrows < 2 || irows < 1 ) { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum ); + } else { + break; + } + } while ( 1 ); + + rows = irows; + if ( vrows < irows + 1 ) { + rows = vrows - 1; + } + if ( used + rows > lodHeight ) { + rows = lodHeight - used; + } + + numVertexes = tess.numVertexes; + + xyz = tess.xyz[numVertexes]; + normal = tess.normal[numVertexes]; + texCoords = tess.texCoords[numVertexes][0]; + color = ( unsigned char * ) &tess.vertexColors[numVertexes]; + vDlightBits = &tess.vertexDlightBits[numVertexes]; + needsNormal = tess.shader->needsNormal; + + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = cv->verts + heightTable[ used + i ] * cv->width + + widthTable[ j ]; + + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + texCoords[2] = dv->lightmap[0]; + texCoords[3] = dv->lightmap[1]; + if ( needsNormal ) { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + * ( unsigned int * ) color = * ( unsigned int * ) dv->color; + *vDlightBits++ = dlightBits; + xyz += 4; + normal += 4; + texCoords += 4; + color += 4; + } + } + + + // add the indexes + { + int numIndexes; + int w, h; + + h = rows - 1; + w = lodWidth - 1; + numIndexes = tess.numIndexes; + for (i = 0 ; i < h ; i++) { + for (j = 0 ; j < w ; j++) { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = numVertexes + i*lodWidth + j + 1; + v2 = v1 - 1; + v3 = v2 + lodWidth; + v4 = v3 + 1; + + tess.indexes[numIndexes] = v2; + tess.indexes[numIndexes+1] = v3; + tess.indexes[numIndexes+2] = v1; + + tess.indexes[numIndexes+3] = v1; + tess.indexes[numIndexes+4] = v3; + tess.indexes[numIndexes+5] = v4; + numIndexes += 6; + } + } + + tess.numIndexes = numIndexes; + } + + tess.numVertexes += rows * lodWidth; + + used += rows - 1; + } +} + + +/* +=========================================================================== + +NULL MODEL + +=========================================================================== +*/ + +/* +=================== +RB_SurfaceAxis + +Draws x/y/z lines from the origin for orientation debugging +=================== +*/ +static void RB_SurfaceAxis( void ) { + GL_Bind( tr.whiteImage ); + qglLineWidth( 3 ); + qglBegin( GL_LINES ); + qglColor3f( 1,0,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 16,0,0 ); + qglColor3f( 0,1,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,16,0 ); + qglColor3f( 0,0,1 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,0,16 ); + qglEnd(); + qglLineWidth( 1 ); +} + +//=========================================================================== + +/* +==================== +RB_SurfaceEntity + +Entities that have a single procedurally generated surface +==================== +*/ +static void RB_SurfaceEntity( surfaceType_t *surfType ) { + switch( backEnd.currentEntity->e.reType ) { + case RT_SPRITE: + RB_SurfaceSprite(); + break; + case RT_BEAM: + RB_SurfaceBeam(); + break; + case RT_RAIL_CORE: + RB_SurfaceRailCore(); + break; + case RT_RAIL_RINGS: + RB_SurfaceRailRings(); + break; + case RT_LIGHTNING: + RB_SurfaceLightningBolt(); + break; + default: + RB_SurfaceAxis(); + break; + } + return; +} + +static void RB_SurfaceBad( surfaceType_t *surfType ) { + ri.Printf( PRINT_ALL, "Bad surface tesselated.\n" ); +} + +static void RB_SurfaceFlare(srfFlare_t *surf) +{ + if (r_flares->integer) + RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, surf->normal); +} + +static void RB_SurfaceDisplayList( srfDisplayList_t *surf ) { + // all apropriate state must be set in RB_BeginSurface + // this isn't implemented yet... + qglCallList( surf->listNum ); +} + +static void RB_SurfaceSkip( void *surf ) { +} + + +void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = { + (void(*)(void*))RB_SurfaceBad, // SF_BAD, + (void(*)(void*))RB_SurfaceSkip, // SF_SKIP, + (void(*)(void*))RB_SurfaceFace, // SF_FACE, + (void(*)(void*))RB_SurfaceGrid, // SF_GRID, + (void(*)(void*))RB_SurfaceTriangles, // SF_TRIANGLES, + (void(*)(void*))RB_SurfacePolychain, // SF_POLY, + (void(*)(void*))RB_SurfaceMesh, // SF_MD3, + (void(*)(void*))RB_SurfaceAnim, // SF_MD4, +#ifdef RAVENMD4 + (void(*)(void*))RB_MDRSurfaceAnim, // SF_MDR, +#endif + (void(*)(void*))RB_SurfaceFlare, // SF_FLARE, + (void(*)(void*))RB_SurfaceEntity, // SF_ENTITY + (void(*)(void*))RB_SurfaceDisplayList // SF_DISPLAY_LIST +}; diff --git a/src/renderer/tr_types.h b/src/renderer/tr_types.h new file mode 100644 index 0000000..7932721 --- /dev/null +++ b/src/renderer/tr_types.h @@ -0,0 +1,218 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces +#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing + +// renderfx flags +#define RF_MINLIGHT 0x0001 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 0x0002 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 0x0004 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 0x0008 // for view weapon Z crunching + +#define RF_CROSSHAIR 0x0010 // This item is a cross hair and will draw over everything similar to + // DEPTHHACK in stereo rendering mode, with the difference that the + // projection matrix won't be hacked to reduce the stereo separation as + // is done for the gun. + +#define RF_NOSHADOW 0x0040 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 0x0080 // use refEntity->lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting + +#define RF_SHADOW_PLANE 0x0100 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 0x0200 // mod the model frames by the maxframes to allow continuous + +// refdef flags +#define RDF_NOWORLDMODEL 0x0001 // used for player configuration screen +#define RDF_HYPERSPACE 0x0004 // teleportation effect + +typedef struct { + vec3_t xyz; + float st[2]; + byte modulate[4]; +} polyVert_t; + +typedef struct poly_s { + qhandle_t hShader; + int numVerts; + polyVert_t *verts; +} poly_t; + +typedef enum { + RT_MODEL, + RT_POLY, + RT_SPRITE, + RT_BEAM, + RT_RAIL_CORE, + RT_RAIL_RINGS, + RT_LIGHTNING, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct { + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + float origin[3]; // also used as MODEL_BEAM's "from" + int frame; // also used as MODEL_BEAM's diameter + + // previous data for frame interpolation + float oldorigin[3]; // also used as MODEL_BEAM's "to" + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers + float shaderTime; // subtracted from refdef time to control effect start times + + // extra sprite information + float radius; + float rotation; +} refEntity_t; + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + TC_NONE, + TC_S3TC, // this is for the GL_S3_s3tc extension. + TC_S3TC_ARB // this is for the GL_EXT_texture_compression_s3tc extension. +} textureCompression_t; + +typedef enum { + GLDRV_ICD, // driver is integrated with window system + // WARNING: there are tests that check for + // > GLDRV_ICD for minidriverness, so this + // should always be the lowest value in this + // enum set + GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver + GLDRV_VOODOO // driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { + GLHW_GENERIC, // where everthing works the way it should + GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is + // the hardware type then there can NOT exist a secondary + // display adapter + GLHW_RIVA128, // where you can't interpolate alpha + GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures + GLHW_PERMEDIA2 // where you don't have src*dst +} glHardwareType_t; + +typedef struct { + char renderer_string[MAX_STRING_CHARS]; + char vendor_string[MAX_STRING_CHARS]; + char version_string[MAX_STRING_CHARS]; + char extensions_string[BIG_INFO_STRING]; + + int maxTextureSize; // queried from GL + int numTextureUnits; // multitexture ability + + int colorBits, depthBits, stencilBits; + + glDriverType_t driverType; + glHardwareType_t hardwareType; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + + int vidWidth, vidHeight; + // aspect is the screen's physical width / height, which may be different + // than scrWidth / scrHeight if the pixels are non-square + // normal screens should be 4/3, but wide aspect monitors may be 16/9 + float windowAspect; + float displayAspect; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; + qboolean smpActive; // dual processor + + qboolean textureFilterAnisotropic; + int maxAnisotropy; + +} glconfig_t; + +#endif // __TR_TYPES_H diff --git a/src/renderer/tr_world.c b/src/renderer/tr_world.c new file mode 100644 index 0000000..feebb2e --- /dev/null +++ b/src/renderer/tr_world.c @@ -0,0 +1,670 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + + + +/* +================= +R_CullTriSurf + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static qboolean R_CullTriSurf( srfTriangles_t *cv ) { + int boxCull; + + boxCull = R_CullLocalBox( cv->bounds ); + + if ( boxCull == CULL_OUT ) { + return qtrue; + } + return qfalse; +} + +/* +================= +R_CullGrid + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static qboolean R_CullGrid( srfGridMesh_t *cv ) { + int boxCull; + int sphereCull; + + if ( r_nocurves->integer ) { + return qtrue; + } + + if ( tr.currentEntityNum != ENTITYNUM_WORLD ) { + sphereCull = R_CullLocalPointAndRadius( cv->localOrigin, cv->meshRadius ); + } else { + sphereCull = R_CullPointAndRadius( cv->localOrigin, cv->meshRadius ); + } + boxCull = CULL_OUT; + + // check for trivial reject + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_patch_out++; + return qtrue; + } + // check bounding box if necessary + else if ( sphereCull == CULL_CLIP ) + { + tr.pc.c_sphere_cull_patch_clip++; + + boxCull = R_CullLocalBox( cv->meshBounds ); + + if ( boxCull == CULL_OUT ) + { + tr.pc.c_box_cull_patch_out++; + return qtrue; + } + else if ( boxCull == CULL_IN ) + { + tr.pc.c_box_cull_patch_in++; + } + else + { + tr.pc.c_box_cull_patch_clip++; + } + } + else + { + tr.pc.c_sphere_cull_patch_in++; + } + + return qfalse; +} + + +/* +================ +R_CullSurface + +Tries to back face cull surfaces before they are lighted or +added to the sorting list. + +This will also allow mirrors on both sides of a model without recursion. +================ +*/ +static qboolean R_CullSurface( surfaceType_t *surface, shader_t *shader ) { + srfSurfaceFace_t *sface; + float d; + + if ( r_nocull->integer ) { + return qfalse; + } + + if ( *surface == SF_GRID ) { + return R_CullGrid( (srfGridMesh_t *)surface ); + } + + if ( *surface == SF_TRIANGLES ) { + return R_CullTriSurf( (srfTriangles_t *)surface ); + } + + if ( *surface != SF_FACE ) { + return qfalse; + } + + if ( shader->cullType == CT_TWO_SIDED ) { + return qfalse; + } + + // face culling + if ( !r_facePlaneCull->integer ) { + return qfalse; + } + + sface = ( srfSurfaceFace_t * ) surface; + d = DotProduct (tr.or.viewOrigin, sface->plane.normal); + + // don't cull exactly on the plane, because there are levels of rounding + // through the BSP, ICD, and hardware that may cause pixel gaps if an + // epsilon isn't allowed here + if ( shader->cullType == CT_FRONT_SIDED ) { + if ( d < sface->plane.dist - 8 ) { + return qtrue; + } + } else { + if ( d > sface->plane.dist + 8 ) { + return qtrue; + } + } + + return qfalse; +} + + +static int R_DlightFace( srfSurfaceFace_t *face, int dlightBits ) { + float d; + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist; + if ( d < -dl->radius || d > dl->radius ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + face->dlightBits[ tr.smpFrame ] = dlightBits; + return dlightBits; +} + +static int R_DlightGrid( srfGridMesh_t *grid, int dlightBits ) { + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits[ tr.smpFrame ] = dlightBits; + return dlightBits; +} + + +static int R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ) { + // FIXME: more dlight culling to trisurfs... + surf->dlightBits[ tr.smpFrame ] = dlightBits; + return dlightBits; +#if 0 + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits[ tr.smpFrame ] = dlightBits; + return dlightBits; +#endif +} + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +static int R_DlightSurface( msurface_t *surf, int dlightBits ) { + if ( *surf->data == SF_FACE ) { + dlightBits = R_DlightFace( (srfSurfaceFace_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_GRID ) { + dlightBits = R_DlightGrid( (srfGridMesh_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_TRIANGLES ) { + dlightBits = R_DlightTrisurf( (srfTriangles_t *)surf->data, dlightBits ); + } else { + dlightBits = 0; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } + + return dlightBits; +} + + + +/* +====================== +R_AddWorldSurface +====================== +*/ +static void R_AddWorldSurface( msurface_t *surf, int dlightBits ) { + if ( surf->viewCount == tr.viewCount ) { + return; // already in this view + } + + surf->viewCount = tr.viewCount; + // FIXME: bmodel fog? + + // try to cull before dlighting or adding + if ( R_CullSurface( surf->data, surf->shader ) ) { + return; + } + + // check for dlighting + if ( dlightBits ) { + dlightBits = R_DlightSurface( surf, dlightBits ); + dlightBits = ( dlightBits != 0 ); + } + + R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex, dlightBits ); +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +/* +================= +R_AddBrushModelSurfaces +================= +*/ +void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) { + bmodel_t *bmodel; + int clip; + model_t *pModel; + int i; + + pModel = R_GetModelByHandle( ent->e.hModel ); + + bmodel = pModel->bmodel; + + clip = R_CullLocalBox( bmodel->bounds ); + if ( clip == CULL_OUT ) { + return; + } + + R_SetupEntityLighting( &tr.refdef, ent ); + R_DlightBmodel( bmodel ); + + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + R_AddWorldSurface( bmodel->firstSurface + i, tr.currentEntity->needDlights ); + } +} + + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ + + +/* +================ +R_RecursiveWorldNode +================ +*/ +static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ) { + + do { + int newDlights[2]; + + // if the node wasn't marked as potentially visible, exit + if (node->visframe != tr.visCount) { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + + if ( !r_nocull->integer ) { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + } + + if ( node->contents != -1 ) { + break; + } + + // node is just a decision point, so go down both sides + // since we don't care about sort orders, just go positive to negative + + // determine which dlights are needed + newDlights[0] = 0; + newDlights[1] = 0; + if ( dlightBits ) { + int i; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + dlight_t *dl; + float dist; + + if ( dlightBits & ( 1 << i ) ) { + dl = &tr.refdef.dlights[i]; + dist = DotProduct( dl->origin, node->plane->normal ) - node->plane->dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } + } + } + } + + // recurse down the children, front side first + R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + msurface_t *surf, **mark; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add the individual surfaces + mark = node->firstmarksurface; + c = node->nummarksurfaces; + while (c--) { + // the surface may have already been added if it + // spans multiple leafs + surf = *mark; + R_AddWorldSurface( surf, dlightBits ); + mark++; + } + } + +} + + +/* +=============== +R_PointInLeaf +=============== +*/ +static mnode_t *R_PointInLeaf( const vec3_t p ) { + mnode_t *node; + float d; + cplane_t *plane; + + if ( !tr.world ) { + ri.Error (ERR_DROP, "R_PointInLeaf: bad model"); + } + + node = tr.world->nodes; + while( 1 ) { + if (node->contents != -1) { + break; + } + plane = node->plane; + d = DotProduct (p,plane->normal) - plane->dist; + if (d > 0) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + + return node; +} + +/* +============== +R_ClusterPVS +============== +*/ +static const byte *R_ClusterPVS (int cluster) { + if (!tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) { + return tr.world->novis; + } + + return tr.world->vis + cluster * tr.world->clusterBytes; +} + +/* +================= +R_inPVS +================= +*/ +qboolean R_inPVS( const vec3_t p1, const vec3_t p2 ) { + mnode_t *leaf; + byte *vis; + + leaf = R_PointInLeaf( p1 ); + vis = CM_ClusterPVS( leaf->cluster ); + leaf = R_PointInLeaf( p2 ); + + if ( !(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7))) ) { + return qfalse; + } + return qtrue; +} + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current +cluster +=============== +*/ +static void R_MarkLeaves (void) { + const byte *vis; + mnode_t *leaf, *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + leaf = R_PointInLeaf( tr.viewParms.pvsOrigin ); + cluster = leaf->cluster; + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + // if r_showcluster was just turned on, remark everything + if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified + && !r_showcluster->modified ) { + return; + } + + if ( r_showcluster->modified || r_showcluster->integer ) { + r_showcluster->modified = qfalse; + if ( r_showcluster->integer ) { + ri.Printf( PRINT_ALL, "cluster:%i area:%i\n", cluster, leaf->area ); + } + } + + tr.visCount++; + tr.viewCluster = cluster; + + if ( r_novis->integer || tr.viewCluster == -1 ) { + for (i=0 ; inumnodes ; i++) { + if (tr.world->nodes[i].contents != CONTENTS_SOLID) { + tr.world->nodes[i].visframe = tr.visCount; + } + } + return; + } + + vis = R_ClusterPVS (tr.viewCluster); + + for (i=0,leaf=tr.world->nodes ; inumnodes ; i++, leaf++) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) { + continue; + } + + // check for door connection + if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { + continue; // not visible + } + + parent = leaf; + do { + if (parent->visframe == tr.visCount) + break; + parent->visframe = tr.visCount; + parent = parent->parent; + } while (parent); + } +} + + +/* +============= +R_AddWorldSurfaces +============= +*/ +void R_AddWorldSurfaces (void) { + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = ENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // determine which leaves are in the PVS / areamask + R_MarkLeaves (); + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and add all the potentially visible surfaces + if ( tr.refdef.num_dlights > 32 ) { + tr.refdef.num_dlights = 32 ; + } + R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << tr.refdef.num_dlights ) - 1 ); +} diff --git a/src/sdl/sdl_gamma.c b/src/sdl/sdl_gamma.c new file mode 100644 index 0000000..dad4bb3 --- /dev/null +++ b/src/sdl/sdl_gamma.c @@ -0,0 +1,92 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#include "../renderer/tr_local.h" +#include "../qcommon/qcommon.h" + +/* +================= +GLimp_SetGamma +================= +*/ +void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] ) +{ + Uint16 table[3][256]; + int i, j; + + if( !glConfig.deviceSupportsGamma || r_ignorehwgamma->integer ) + return; + + for (i = 0; i < 256; i++) + { + table[0][i] = ( ( ( Uint16 ) red[i] ) << 8 ) | red[i]; + table[1][i] = ( ( ( Uint16 ) green[i] ) << 8 ) | green[i]; + table[2][i] = ( ( ( Uint16 ) blue[i] ) << 8 ) | blue[i]; + } + +#ifdef _WIN32 +#include + + // Win2K and newer put this odd restriction on gamma ramps... + { + OSVERSIONINFO vinfo; + + vinfo.dwOSVersionInfoSize = sizeof( vinfo ); + GetVersionEx( &vinfo ); + if( vinfo.dwMajorVersion >= 5 && vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + Com_DPrintf( "performing gamma clamp.\n" ); + for( j = 0 ; j < 3 ; j++ ) + { + for( i = 0 ; i < 128 ; i++ ) + { + if( table[ j ] [ i] > ( ( 128 + i ) << 8 ) ) + table[ j ][ i ] = ( 128 + i ) << 8; + } + + if( table[ j ] [127 ] > 254 << 8 ) + table[ j ][ 127 ] = 254 << 8; + } + } + } +#endif + + // enforce constantly increasing + for (j = 0; j < 3; j++) + { + for (i = 1; i < 256; i++) + { + if (table[j][i] < table[j][i-1]) + table[j][i] = table[j][i-1]; + } + } + + SDL_SetGammaRamp(table[0], table[1], table[2]); +} + diff --git a/src/sdl/sdl_glimp.c b/src/sdl/sdl_glimp.c new file mode 100644 index 0000000..ad30fde --- /dev/null +++ b/src/sdl/sdl_glimp.c @@ -0,0 +1,1061 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#ifdef SMP +# ifdef USE_LOCAL_HEADERS +# include "SDL_thread.h" +# else +# include +# endif +#endif + +#include +#include +#include +#include + +#include "../renderer/tr_local.h" +#include "../client/client.h" +#include "../sys/sys_local.h" +#include "sdl_icon.h" + +/* Just hack it for now. */ +#ifdef MACOS_X +#include +typedef CGLContextObj QGLContext; +#define GLimp_GetCurrentContext() CGLGetCurrentContext() +#define GLimp_SetCurrentContext(ctx) CGLSetCurrentContext(ctx) +#else +typedef void *QGLContext; +#define GLimp_GetCurrentContext() (NULL) +#define GLimp_SetCurrentContext(ctx) +#endif + +static QGLContext opengl_context; + +typedef enum +{ + RSERR_OK, + + RSERR_INVALID_FULLSCREEN, + RSERR_INVALID_MODE, + + RSERR_UNKNOWN +} rserr_t; + +static SDL_Surface *screen = NULL; +static const SDL_VideoInfo *videoInfo = NULL; + +cvar_t *r_allowSoftwareGL; // Don't abort out if a hardware visual can't be obtained +cvar_t *r_allowResize; // make window resizable +cvar_t *r_centerWindow; +cvar_t *r_sdlDriver; + +void (APIENTRYP qglActiveTextureARB) (GLenum texture); +void (APIENTRYP qglClientActiveTextureARB) (GLenum texture); +void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t); + +void (APIENTRYP qglLockArraysEXT) (GLint first, GLsizei count); +void (APIENTRYP qglUnlockArraysEXT) (void); + +/* +=============== +GLimp_Shutdown +=============== +*/ +void GLimp_Shutdown( void ) +{ + float oldDisplayAspect = glConfig.displayAspect; + + IN_Shutdown(); + + SDL_QuitSubSystem( SDL_INIT_VIDEO ); + screen = NULL; + + Com_Memset( &glConfig, 0, sizeof( glConfig ) ); + glConfig.displayAspect = oldDisplayAspect; + Com_Memset( &glState, 0, sizeof( glState ) ); +} + +/* +=============== +GLimp_Minimize + +Minimize the game so that user is back at the desktop +=============== +*/ +void GLimp_Minimize(void) +{ + SDL_WM_IconifyWindow(); +} + + +/* +=============== +GLimp_LogComment +=============== +*/ +void GLimp_LogComment( char *comment ) +{ +} + +/* +=============== +GLimp_CompareModes +=============== +*/ +static int GLimp_CompareModes( const void *a, const void *b ) +{ + const float ASPECT_EPSILON = 0.001f; + SDL_Rect *modeA = *(SDL_Rect **)a; + SDL_Rect *modeB = *(SDL_Rect **)b; + float aspectA = (float)modeA->w / (float)modeA->h; + float aspectB = (float)modeB->w / (float)modeB->h; + int areaA = modeA->w * modeA->h; + int areaB = modeB->w * modeB->h; + float aspectDiffA = fabs( aspectA - glConfig.displayAspect ); + float aspectDiffB = fabs( aspectB - glConfig.displayAspect ); + float aspectDiffsDiff = aspectDiffA - aspectDiffB; + + if( aspectDiffsDiff > ASPECT_EPSILON ) + return 1; + else if( aspectDiffsDiff < -ASPECT_EPSILON ) + return -1; + else + return areaA - areaB; +} + + +/* +=============== +GLimp_DetectAvailableModes +=============== +*/ +static void GLimp_DetectAvailableModes(void) +{ + char buf[ MAX_STRING_CHARS ] = { 0 }; + SDL_Rect **modes; + int numModes; + int i; + + modes = SDL_ListModes( videoInfo->vfmt, SDL_OPENGL | SDL_FULLSCREEN ); + + if( !modes ) + { + ri.Printf( PRINT_WARNING, "Can't get list of available modes\n" ); + return; + } + + if( modes == (SDL_Rect **)-1 ) + { + ri.Printf( PRINT_ALL, "Display supports any resolution\n" ); + return; // can set any resolution + } + + for( numModes = 0; modes[ numModes ]; numModes++ ); + + if( numModes > 1 ) + qsort( modes, numModes, sizeof( SDL_Rect* ), GLimp_CompareModes ); + + for( i = 0; i < numModes; i++ ) + { + const char *newModeString = va( "%ux%u ", modes[ i ]->w, modes[ i ]->h ); + + if( strlen( newModeString ) < (int)sizeof( buf ) - strlen( buf ) ) + Q_strcat( buf, sizeof( buf ), newModeString ); + else + ri.Printf( PRINT_WARNING, "Skipping mode %ux%x, buffer too small\n", modes[i]->w, modes[i]->h ); + } + + if( *buf ) + { + buf[ strlen( buf ) - 1 ] = 0; + ri.Printf( PRINT_ALL, "Available modes: '%s'\n", buf ); + ri.Cvar_Set( "r_availableModes", buf ); + } +} + +#define R_FAILSAFE_WIDTH 640 +#define R_FAILSAFE_HEIGHT 480 + +/* +=============== +GLimp_SetMode +=============== +*/ +static int GLimp_SetMode( qboolean failSafe, qboolean fullscreen, qboolean noborder ) +{ + const char* glstring; + int sdlcolorbits; + int colorbits, depthbits, stencilbits; + int tcolorbits, tdepthbits, tstencilbits; + int samples; + int i = 0; + SDL_Surface *vidscreen = NULL; + Uint32 flags = SDL_OPENGL; + + ri.Printf( PRINT_ALL, "Initializing OpenGL display\n"); + + if ( r_allowResize->integer ) + flags |= SDL_RESIZABLE; + + if( videoInfo == NULL ) + { + static SDL_VideoInfo sVideoInfo; + static SDL_PixelFormat sPixelFormat; + + videoInfo = SDL_GetVideoInfo( ); + + // Take a copy of the videoInfo + Com_Memcpy( &sPixelFormat, videoInfo->vfmt, sizeof( SDL_PixelFormat ) ); + sPixelFormat.palette = NULL; // Should already be the case + Com_Memcpy( &sVideoInfo, videoInfo, sizeof( SDL_VideoInfo ) ); + sVideoInfo.vfmt = &sPixelFormat; + videoInfo = &sVideoInfo; + + if( videoInfo->current_h > 0 ) + { + // Guess the display aspect ratio through the desktop resolution + // by assuming (relatively safely) that it is set at or close to + // the display's native aspect ratio + glConfig.displayAspect = (float)videoInfo->current_w / (float)videoInfo->current_h; + + ri.Printf( PRINT_ALL, "Estimated display aspect: %.3f\n", glConfig.displayAspect ); + } + else + { + ri.Printf( PRINT_ALL, + "Cannot estimate display aspect, assuming 1.333\n" ); + } + } + + if( !failSafe ) + { + glConfig.vidWidth = r_width->integer; + glConfig.vidHeight = r_height->integer; + glConfig.windowAspect = r_width->value / + ( r_height->value * r_pixelAspect->value ); + } + else if( glConfig.vidWidth != R_FAILSAFE_WIDTH && + glConfig.vidHeight != R_FAILSAFE_HEIGHT ) + { + ri.Printf( PRINT_ALL, "Setting mode %dx%d failed, falling back on mode %dx%d\n", + glConfig.vidWidth, glConfig.vidHeight, R_FAILSAFE_WIDTH, R_FAILSAFE_HEIGHT ); + + glConfig.vidWidth = R_FAILSAFE_WIDTH; + glConfig.vidHeight = R_FAILSAFE_HEIGHT; + glConfig.windowAspect = 1.0f; + } + else + return RSERR_INVALID_MODE; + + ri.Printf (PRINT_ALL, "...setting mode %dx%d\n", glConfig.vidWidth, glConfig.vidHeight); + + if (fullscreen) + { + flags |= SDL_FULLSCREEN; + glConfig.isFullscreen = qtrue; + } + else + { + if (noborder) + flags |= SDL_NOFRAME; + + glConfig.isFullscreen = qfalse; + } + + colorbits = r_colorbits->value; + if ((!colorbits) || (colorbits >= 32)) + colorbits = 24; + + if (!r_depthbits->value) + depthbits = 24; + else + depthbits = r_depthbits->value; + stencilbits = r_stencilbits->value; + samples = r_ext_multisample->value; + + for (i = 0; i < 16; i++) + { + // 0 - default + // 1 - minus colorbits + // 2 - minus depthbits + // 3 - minus stencil + if ((i % 4) == 0 && i) + { + // one pass, reduce + switch (i / 4) + { + case 2 : + if (colorbits == 24) + colorbits = 16; + break; + case 1 : + if (depthbits == 24) + depthbits = 16; + else if (depthbits == 16) + depthbits = 8; + case 3 : + if (stencilbits == 24) + stencilbits = 16; + else if (stencilbits == 16) + stencilbits = 8; + } + } + + tcolorbits = colorbits; + tdepthbits = depthbits; + tstencilbits = stencilbits; + + if ((i % 4) == 3) + { // reduce colorbits + if (tcolorbits == 24) + tcolorbits = 16; + } + + if ((i % 4) == 2) + { // reduce depthbits + if (tdepthbits == 24) + tdepthbits = 16; + else if (tdepthbits == 16) + tdepthbits = 8; + } + + if ((i % 4) == 1) + { // reduce stencilbits + if (tstencilbits == 24) + tstencilbits = 16; + else if (tstencilbits == 16) + tstencilbits = 8; + else + tstencilbits = 0; + } + + sdlcolorbits = 4; + if (tcolorbits == 24) + sdlcolorbits = 8; + +#ifdef __sgi /* Fix for SGIs grabbing too many bits of color */ + if (sdlcolorbits == 4) + sdlcolorbits = 0; /* Use minimum size for 16-bit color */ + + /* Need alpha or else SGIs choose 36+ bit RGB mode */ + SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 1); +#endif + + SDL_GL_SetAttribute( SDL_GL_RED_SIZE, sdlcolorbits ); + SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, sdlcolorbits ); + SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, sdlcolorbits ); + SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, tdepthbits ); + SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, tstencilbits ); + + SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, samples ? 1 : 0 ); + SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, samples ); + + if(r_stereoEnabled->integer) + { + glConfig.stereoEnabled = qtrue; + SDL_GL_SetAttribute(SDL_GL_STEREO, 1); + } + else + { + glConfig.stereoEnabled = qfalse; + SDL_GL_SetAttribute(SDL_GL_STEREO, 0); + } + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + +#if 0 // See http://bugzilla.icculus.org/show_bug.cgi?id=3526 + // If not allowing software GL, demand accelerated + if( !r_allowSoftwareGL->integer ) + { + if( SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 ) < 0 ) + { + ri.Printf( PRINT_ALL, "Unable to guarantee accelerated " + "visual with libSDL < 1.2.10\n" ); + } + } +#endif + + if( SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, r_swapInterval->integer ) < 0 ) + ri.Printf( PRINT_ALL, "r_swapInterval requires libSDL >= 1.2.10\n" ); + +#ifdef USE_ICON + { + SDL_Surface *icon = SDL_CreateRGBSurfaceFrom( + (void *)CLIENT_WINDOW_ICON.pixel_data, + CLIENT_WINDOW_ICON.width, + CLIENT_WINDOW_ICON.height, + CLIENT_WINDOW_ICON.bytes_per_pixel * 8, + CLIENT_WINDOW_ICON.bytes_per_pixel * CLIENT_WINDOW_ICON.width, +#ifdef Q3_LITTLE_ENDIAN + 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 +#else + 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF +#endif + ); + + SDL_WM_SetIcon( icon, NULL ); + SDL_FreeSurface( icon ); + } +#endif + + SDL_WM_SetCaption(CLIENT_WINDOW_TITLE, CLIENT_WINDOW_MIN_TITLE); + SDL_ShowCursor(0); + + if (!(vidscreen = SDL_SetVideoMode(glConfig.vidWidth, glConfig.vidHeight, colorbits, flags))) + { + ri.Printf( PRINT_DEVELOPER, "SDL_SetVideoMode failed: %s\n", SDL_GetError( ) ); + continue; + } + + opengl_context = GLimp_GetCurrentContext(); + + ri.Printf( PRINT_ALL, "Using %d/%d/%d Color bits, %d depth, %d stencil display.\n", + sdlcolorbits, sdlcolorbits, sdlcolorbits, tdepthbits, tstencilbits); + + glConfig.colorBits = tcolorbits; + glConfig.depthBits = tdepthbits; + glConfig.stencilBits = tstencilbits; + break; + } + + GLimp_DetectAvailableModes(); + + if (!vidscreen) + { + ri.Printf( PRINT_ALL, "Couldn't get a visual\n" ); + return RSERR_INVALID_MODE; + } + + screen = vidscreen; + + glstring = (char *) qglGetString (GL_RENDERER); + ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glstring ); + + return RSERR_OK; +} + +/* +=============== +GLimp_StartDriverAndSetMode +=============== +*/ +static qboolean GLimp_StartDriverAndSetMode( qboolean failSafe, qboolean fullscreen, qboolean noborder ) +{ + rserr_t err; + + if (!SDL_WasInit(SDL_INIT_VIDEO)) + { + char driverName[ 64 ]; + + if (SDL_Init(SDL_INIT_VIDEO) == -1) + { + ri.Printf( PRINT_ALL, "SDL_Init( SDL_INIT_VIDEO ) FAILED (%s)\n", + SDL_GetError()); + return qfalse; + } + + SDL_VideoDriverName( driverName, sizeof( driverName ) - 1 ); + ri.Printf( PRINT_ALL, "SDL using driver \"%s\"\n", driverName ); + Cvar_Set( "r_sdlDriver", driverName ); + } + + if (fullscreen && Cvar_VariableIntegerValue( "in_nograb" ) ) + { + ri.Printf( PRINT_ALL, "Fullscreen not allowed with in_nograb 1\n"); + ri.Cvar_Set( "r_fullscreen", "0" ); + r_fullscreen->modified = qfalse; + fullscreen = qfalse; + } + + err = GLimp_SetMode( failSafe, fullscreen, noborder ); + + switch ( err ) + { + case RSERR_INVALID_FULLSCREEN: + ri.Printf( PRINT_ALL, "...WARNING: fullscreen unavailable in this mode\n" ); + return qfalse; + case RSERR_INVALID_MODE: + ri.Printf( PRINT_ALL, "...WARNING: could not set the given mode\n" ); + return qfalse; + default: + break; + } + + return qtrue; +} + +static qboolean GLimp_HaveExtension(const char *ext) +{ + const char *ptr = Q_stristr( glConfig.extensions_string, ext ); + if (ptr == NULL) + return qfalse; + ptr += strlen(ext); + return ((*ptr == ' ') || (*ptr == '\0')); // verify it's complete string. +} + + +/* +=============== +GLimp_InitExtensions +=============== +*/ +static void GLimp_InitExtensions( void ) +{ + if ( !r_allowExtensions->integer ) + { + ri.Printf( PRINT_ALL, "* IGNORING OPENGL EXTENSIONS *\n" ); + return; + } + + ri.Printf( PRINT_ALL, "Initializing OpenGL extensions\n" ); + + glConfig.textureCompression = TC_NONE; + + // GL_EXT_texture_compression_s3tc + if ( GLimp_HaveExtension( "GL_ARB_texture_compression" ) && + GLimp_HaveExtension( "GL_EXT_texture_compression_s3tc" ) ) + { + if ( r_ext_compressed_textures->value ) + { + glConfig.textureCompression = TC_S3TC_ARB; + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_compression_s3tc\n" ); + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_compression_s3tc\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_compression_s3tc not found\n" ); + } + + // GL_S3_s3tc ... legacy extension before GL_EXT_texture_compression_s3tc. + if (glConfig.textureCompression == TC_NONE) + { + if ( GLimp_HaveExtension( "GL_S3_s3tc" ) ) + { + if ( r_ext_compressed_textures->value ) + { + glConfig.textureCompression = TC_S3TC; + ri.Printf( PRINT_ALL, "...using GL_S3_s3tc\n" ); + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_S3_s3tc\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_S3_s3tc not found\n" ); + } + } + + + // GL_EXT_texture_env_add + glConfig.textureEnvAddAvailable = qfalse; + if ( GLimp_HaveExtension( "EXT_texture_env_add" ) ) + { + if ( r_ext_texture_env_add->integer ) + { + glConfig.textureEnvAddAvailable = qtrue; + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_env_add\n" ); + } + else + { + glConfig.textureEnvAddAvailable = qfalse; + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_env_add\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_env_add not found\n" ); + } + + // GL_ARB_multitexture + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + if ( GLimp_HaveExtension( "GL_ARB_multitexture" ) ) + { + if ( r_ext_multitexture->value ) + { + qglMultiTexCoord2fARB = SDL_GL_GetProcAddress( "glMultiTexCoord2fARB" ); + qglActiveTextureARB = SDL_GL_GetProcAddress( "glActiveTextureARB" ); + qglClientActiveTextureARB = SDL_GL_GetProcAddress( "glClientActiveTextureARB" ); + + if ( qglActiveTextureARB ) + { + GLint glint = 0; + qglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glint ); + glConfig.numTextureUnits = (int) glint; + if ( glConfig.numTextureUnits > 1 ) + { + ri.Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" ); + } + else + { + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + ri.Printf( PRINT_ALL, "...not using GL_ARB_multitexture, < 2 texture units\n" ); + } + } + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_ARB_multitexture\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_ARB_multitexture not found\n" ); + } + + // GL_EXT_compiled_vertex_array + if ( GLimp_HaveExtension( "GL_EXT_compiled_vertex_array" ) ) + { + if ( r_ext_compiled_vertex_array->value ) + { + ri.Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" ); + qglLockArraysEXT = ( void ( APIENTRY * )( GLint, GLint ) ) SDL_GL_GetProcAddress( "glLockArraysEXT" ); + qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) ) SDL_GL_GetProcAddress( "glUnlockArraysEXT" ); + if (!qglLockArraysEXT || !qglUnlockArraysEXT) + { + ri.Error (ERR_FATAL, "bad getprocaddress"); + } + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n" ); + } + + glConfig.textureFilterAnisotropic = qfalse; + if ( GLimp_HaveExtension( "GL_EXT_texture_filter_anisotropic" ) ) + { + if ( r_ext_texture_filter_anisotropic->integer ) { + qglGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint *)&glConfig.maxAnisotropy ); + if ( glConfig.maxAnisotropy <= 0 ) { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not properly supported!\n" ); + glConfig.maxAnisotropy = 0; + } + else + { + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_filter_anisotropic (max: %i)\n", glConfig.maxAnisotropy ); + glConfig.textureFilterAnisotropic = qtrue; + } + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_filter_anisotropic\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not found\n" ); + } +} + +/* +=============== +GLimp_Init + +This routine is responsible for initializing the OS specific portions +of OpenGL +=============== +*/ +void GLimp_Init( void ) +{ + r_allowSoftwareGL = ri.Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH ); + r_sdlDriver = ri.Cvar_Get( "r_sdlDriver", "", CVAR_ROM ); + r_allowResize = ri.Cvar_Get( "r_allowResize", "0", CVAR_ARCHIVE ); + r_centerWindow = ri.Cvar_Get( "r_centerWindow", "0", CVAR_ARCHIVE ); + + if( Cvar_VariableIntegerValue( "com_abnormalExit" ) ) + { + ri.Cvar_Set( "r_width", va( "%d", R_FAILSAFE_WIDTH ) ); + ri.Cvar_Set( "r_height", va( "%d", R_FAILSAFE_HEIGHT ) ); + ri.Cvar_Set( "r_fullscreen", "0" ); + ri.Cvar_Set( "r_centerWindow", "0" ); + ri.Cvar_Set( "com_abnormalExit", "0" ); + } + + Sys_SetEnv( "SDL_VIDEO_CENTERED", r_centerWindow->integer ? "1" : "" ); + + Sys_GLimpInit( ); + + // Create the window and set up the context + if( GLimp_StartDriverAndSetMode( qfalse, r_fullscreen->integer, r_noborder->integer ) ) + goto success; + + // Try again, this time in a platform specific "safe mode" + Sys_GLimpSafeInit( ); + + if( GLimp_StartDriverAndSetMode( qfalse, r_fullscreen->integer, qfalse ) ) + goto success; + + // Finally, try the default screen resolution + if( GLimp_StartDriverAndSetMode( qtrue, r_fullscreen->integer, qfalse ) ) + goto success; + + // Nothing worked, give up + ri.Error( ERR_FATAL, "GLimp_Init() - could not load OpenGL subsystem\n" ); + +success: + // This values force the UI to disable driver selection + glConfig.driverType = GLDRV_ICD; + glConfig.hardwareType = GLHW_GENERIC; + glConfig.deviceSupportsGamma = SDL_SetGamma( 1.0f, 1.0f, 1.0f ) >= 0; + + // Mysteriously, if you use an NVidia graphics card and multiple monitors, + // SDL_SetGamma will incorrectly return false... the first time; ask + // again and you get the correct answer. This is a suspected driver bug, see + // http://bugzilla.icculus.org/show_bug.cgi?id=4316 + glConfig.deviceSupportsGamma = SDL_SetGamma( 1.0f, 1.0f, 1.0f ) >= 0; + + // get our config strings + Q_strncpyz( glConfig.vendor_string, (char *) qglGetString (GL_VENDOR), sizeof( glConfig.vendor_string ) ); + Q_strncpyz( glConfig.renderer_string, (char *) qglGetString (GL_RENDERER), sizeof( glConfig.renderer_string ) ); + if (*glConfig.renderer_string && glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] == '\n') + glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] = 0; + Q_strncpyz( glConfig.version_string, (char *) qglGetString (GL_VERSION), sizeof( glConfig.version_string ) ); + Q_strncpyz( glConfig.extensions_string, (char *) qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) ); + + // initialize extensions + GLimp_InitExtensions( ); + + ri.Cvar_Get( "r_availableModes", "", CVAR_ROM ); + + // This depends on SDL_INIT_VIDEO, hence having it here + IN_Init( ); +} + + +/* +=============== +GLimp_EndFrame + +Responsible for doing a swapbuffers +=============== +*/ +void GLimp_EndFrame( void ) +{ + // don't flip if drawing to front buffer + if ( Q_stricmp( r_drawBuffer->string, "GL_FRONT" ) != 0 ) + { + SDL_GL_SwapBuffers(); + } + + if( r_fullscreen->modified ) + { + qboolean fullscreen; + qboolean needToToggle = qtrue; + qboolean sdlToggled = qfalse; + SDL_Surface *s = SDL_GetVideoSurface( ); + + if( s ) + { + // Find out the current state + fullscreen = !!( s->flags & SDL_FULLSCREEN ); + + if( r_fullscreen->integer && Cvar_VariableIntegerValue( "in_nograb" ) ) + { + ri.Printf( PRINT_ALL, "Fullscreen not allowed with in_nograb 1\n"); + ri.Cvar_Set( "r_fullscreen", "0" ); + r_fullscreen->modified = qfalse; + } + + // Is the state we want different from the current state? + needToToggle = !!r_fullscreen->integer != fullscreen; + + if( needToToggle ) + sdlToggled = SDL_WM_ToggleFullScreen( s ); + } + + if( needToToggle ) + { + // SDL_WM_ToggleFullScreen didn't work, so do it the slow way + if( !sdlToggled ) + Cbuf_AddText( "vid_restart" ); + + IN_Restart( ); + } + + r_fullscreen->modified = qfalse; + } +} + + + +#ifdef SMP +/* +=========================================================== + +SMP acceleration + +=========================================================== +*/ + +/* + * I have no idea if this will even work...most platforms don't offer + * thread-safe OpenGL libraries, and it looks like the original Linux + * code counted on each thread claiming the GL context with glXMakeCurrent(), + * which you can't currently do in SDL. We'll just have to hope for the best. + */ + +static SDL_mutex *smpMutex = NULL; +static SDL_cond *renderCommandsEvent = NULL; +static SDL_cond *renderCompletedEvent = NULL; +static void (*glimpRenderThread)( void ) = NULL; +static SDL_Thread *renderThread = NULL; + +/* +=============== +GLimp_ShutdownRenderThread +=============== +*/ +static void GLimp_ShutdownRenderThread(void) +{ + if (smpMutex != NULL) + { + SDL_DestroyMutex(smpMutex); + smpMutex = NULL; + } + + if (renderCommandsEvent != NULL) + { + SDL_DestroyCond(renderCommandsEvent); + renderCommandsEvent = NULL; + } + + if (renderCompletedEvent != NULL) + { + SDL_DestroyCond(renderCompletedEvent); + renderCompletedEvent = NULL; + } + + glimpRenderThread = NULL; +} + +/* +=============== +GLimp_RenderThreadWrapper +=============== +*/ +static int GLimp_RenderThreadWrapper( void *arg ) +{ + Com_Printf( "Render thread starting\n" ); + + glimpRenderThread(); + + GLimp_SetCurrentContext(NULL); + + Com_Printf( "Render thread terminating\n" ); + + return 0; +} + +/* +=============== +GLimp_SpawnRenderThread +=============== +*/ +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) +{ + static qboolean warned = qfalse; + if (!warned) + { + Com_Printf("WARNING: You enable r_smp at your own risk!\n"); + warned = qtrue; + } + +#ifndef MACOS_X + return qfalse; /* better safe than sorry for now. */ +#endif + + if (renderThread != NULL) /* hopefully just a zombie at this point... */ + { + Com_Printf("Already a render thread? Trying to clean it up...\n"); + SDL_WaitThread(renderThread, NULL); + renderThread = NULL; + GLimp_ShutdownRenderThread(); + } + + smpMutex = SDL_CreateMutex(); + if (smpMutex == NULL) + { + Com_Printf( "smpMutex creation failed: %s\n", SDL_GetError() ); + GLimp_ShutdownRenderThread(); + return qfalse; + } + + renderCommandsEvent = SDL_CreateCond(); + if (renderCommandsEvent == NULL) + { + Com_Printf( "renderCommandsEvent creation failed: %s\n", SDL_GetError() ); + GLimp_ShutdownRenderThread(); + return qfalse; + } + + renderCompletedEvent = SDL_CreateCond(); + if (renderCompletedEvent == NULL) + { + Com_Printf( "renderCompletedEvent creation failed: %s\n", SDL_GetError() ); + GLimp_ShutdownRenderThread(); + return qfalse; + } + + glimpRenderThread = function; + renderThread = SDL_CreateThread(GLimp_RenderThreadWrapper, NULL); + if ( renderThread == NULL ) + { + ri.Printf( PRINT_ALL, "SDL_CreateThread() returned %s", SDL_GetError() ); + GLimp_ShutdownRenderThread(); + return qfalse; + } + else + { + // tma 01/09/07: don't think this is necessary anyway? + // + // !!! FIXME: No detach API available in SDL! + //ret = pthread_detach( renderThread ); + //if ( ret ) { + //ri.Printf( PRINT_ALL, "pthread_detach returned %d: %s", ret, strerror( ret ) ); + //} + } + + return qtrue; +} + +static volatile void *smpData = NULL; +static volatile qboolean smpDataReady; + +/* +=============== +GLimp_RendererSleep +=============== +*/ +void *GLimp_RendererSleep( void ) +{ + void *data = NULL; + + GLimp_SetCurrentContext(NULL); + + SDL_LockMutex(smpMutex); + { + smpData = NULL; + smpDataReady = qfalse; + + // after this, the front end can exit GLimp_FrontEndSleep + SDL_CondSignal(renderCompletedEvent); + + while ( !smpDataReady ) + SDL_CondWait(renderCommandsEvent, smpMutex); + + data = (void *)smpData; + } + SDL_UnlockMutex(smpMutex); + + GLimp_SetCurrentContext(opengl_context); + + return data; +} + +/* +=============== +GLimp_FrontEndSleep +=============== +*/ +void GLimp_FrontEndSleep( void ) +{ + SDL_LockMutex(smpMutex); + { + while ( smpData ) + SDL_CondWait(renderCompletedEvent, smpMutex); + } + SDL_UnlockMutex(smpMutex); + + GLimp_SetCurrentContext(opengl_context); +} + +/* +=============== +GLimp_WakeRenderer +=============== +*/ +void GLimp_WakeRenderer( void *data ) +{ + GLimp_SetCurrentContext(NULL); + + SDL_LockMutex(smpMutex); + { + assert( smpData == NULL ); + smpData = data; + smpDataReady = qtrue; + + // after this, the renderer can continue through GLimp_RendererSleep + SDL_CondSignal(renderCommandsEvent); + } + SDL_UnlockMutex(smpMutex); +} + +#else + +// No SMP - stubs +void GLimp_RenderThreadWrapper( void *arg ) +{ +} + +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) +{ + ri.Printf( PRINT_WARNING, "ERROR: SMP support was disabled at compile time\n"); + return qfalse; +} + +void *GLimp_RendererSleep( void ) +{ + return NULL; +} + +void GLimp_FrontEndSleep( void ) +{ +} + +void GLimp_WakeRenderer( void *data ) +{ +} + +#endif diff --git a/src/sdl/sdl_icon.h b/src/sdl/sdl_icon.h new file mode 100644 index 0000000..03f7420 --- /dev/null +++ b/src/sdl/sdl_icon.h @@ -0,0 +1,138 @@ +/* GIMP RGBA C-Source image dump (sdl_icon.h) */ + +static const struct { + unsigned int width; + unsigned int height; + unsigned int bytes_per_pixel; /* 3:RGB, 4:RGBA */ + unsigned char pixel_data[32 * 32 * 4 + 1]; +} CLIENT_WINDOW_ICON = { + 32, 32, 4, + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\17\17\34\17\17" + "\17U\17\17\17""9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\30\30\30q\2\2\2\306\5\5\5\377\4\4\4\343\33\33\33" + "\252\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\31\31" + "\31\216\7\7\7\377\0\0\0\377\0\0\0\377\0\0\0\377\3\3\3\377\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0%%%9\0\0" + "\0\0\0\0\0\0\10\10\10q\16\16\16q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\11\11\11\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\13\13\13q\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\14\14\14q\15\15\15\343\16\16\16" + """9\0\0\0\0\12\12\12\252\10\10\10\377\30\30\30U\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\3\3\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\252\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\12\12\12\216\3\3\3\377\2\2\2\252\0\0\0" + "\0\0\0\0\0\37\37\37""9\3\3\3\377\3\3\3\306\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\2\2\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\3\3\3\343\15\15\15""9\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\14\14\14q\3\3\3\377\2\2\2\377\5\5\5""9\0\0\0" + "\0\0\0\0\0\0\0\0\0\16\16\16\306\2\2\2\377\2\2\2\306\5\5\5\34\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\36\36\36\252\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\13\13\13\306\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\13\13\13\216\3\3\3\377\4\4\4\377\15\15\15U\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\17\17\34\14\14\14\306\1\1\1\377\2\2\2\306" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\36\36\36\252\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "U\0\0\0\0\0\0\0\0(((9\16\16\16\252\3\3\3\377\0\0\0\377\16\16\16\306\36\36" + "\36\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\306\0\0\0\377" + "\3\3\3\306\3\3\3\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\36\36\36q\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\6\6\6\343\31\31" + "\31""9\0\0\0\0\0\0\0\0\0\0\0\216\0\0\0\377\2\2\2\377\12\12\12\306\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\26\26\26U\7\7\7" + "\377\0\0\0\377\3\3\3\306\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\3\3\3\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\6\6\6\252\0" + "\0\0\0\15\15\15""9\6\6\6\252\0\0\0\377\0\0\0\377\12\12\12\306\16\16\16\34" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\6\6\6q\1\1\1\377\0\0\0\377\4\4\4\306\5\5\5\34\0\0\0\0\0\0\0\0\22\22\22\34" + "\32\32\32\306\1\1\1\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\21\21\21\216\33\33\33\306\3\3\3\377\0\0\0\377\1\1\1\377\11\11\11\306\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\3\3\3\216\1\1\1\377\1\1\1\377\4\4\4\306\0\0\0\0\0\0" + "\0\0\14\14\14U\4\4\4\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\4\4\4\306\3\3\3\377\0\0\0\377\0\0\0\377\11\11\11\306\15\15\15" + "\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\5\5\5\252\0\0\0\377\0\0\0\377\3\3\3\377" + "\5\5\5\377\3\3\3\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\1\1\1\343\12\12\12\252\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\16\16\16""9\7\7\7\377\0\0\0\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\2\2\2\377\2\2\2""9\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\2\2q\3\3\3\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\20\20\20\216\5\5\5\377\0\0" + "\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\306\0\0\0\252\0\0\0\252\0\0\0\252\0\0\0\252\0\0\0\252" + "\0\0\0\252\12\12\12\252\36\36\36""9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\15\15\15U\6\6" + "\6\306\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\1\1\1\377\0\0\0\252\0\0\0\252" + "\0\0\0\252\0\0\0\252\12\12\12\252\36\36\36""9\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\14\14\14""9\4\4\4\252" + "\4\4\4\377\1\1\1\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\2\2\2\216\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\31\31\31U\20\20\20\306\5\5\5\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\12\12\12\343\26" + "\26\26""9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\10\10\377\0\0\0\377\5\5\5\377\5\5\5\377" + "\0\0\0U\6\6\6\306\1\1\1\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\24\24\24" + """9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\"\"\"\252###\252\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\21\21\21""9\7\7\7\343\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\20\20\20\306" + "\30\30\30\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\7\7\7\252\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\3\3\3\377\22\22" + "\22\306\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\23\23\23U\6\6\6\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0" + "\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377" + "\22\22\22\306\36\36\36\34\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\14\14\14\306\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\2\2\2\377\4\4\4\216\3\3\3\216\2\2" + "\2\377\5\5\5\377\17\17\17U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\2\2\2\377\4\4\4\377\1\1\1\252\1\1\1\252\4\4\4\377\0\0\0\377\0" + "\0\0\377\0\0\0\377\0\0\0\377\2\2\2\377\11\11\11q\0\0\0\0\0\0\0\0\6\6\6q\2" + "\2\2\377\4\4\4\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\14\14" + "\14q\0\0\0\377\6\6\6\216\0\0\0\0\0\0\0\0\15\15\15U\2\2\2\306\0\0\0\377\0" + "\0\0\377\1\1\1\306\7\7\7U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\5\5\5\216\1\1\1" + "\377\3\3\3q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\3\3\252\2\2\2\377\5" + "\5\5U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\31\31\31""9\31\31\31\252\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\26\26\26""9\13\13\13\343\5\5\5\377" + "\30\30\30\216\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\2\2\252\6\6\6\377\21\21\21" + "U\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\26\26\26""9\10\10\10\377\14\14\14\377\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\32\32\32U\11\11\11\377\0\0\0\252\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\2\2""9\12\12\12\343\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\32\32\32U\11\11\11\377\33\33\33\252\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0%%%9\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\32\32\32""9\12\12\12" + "\343\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\17\17""9\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0", +}; + diff --git a/src/sdl/sdl_input.c b/src/sdl/sdl_input.c new file mode 100644 index 0000000..8de3407 --- /dev/null +++ b/src/sdl/sdl_input.c @@ -0,0 +1,1158 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#include +#include +#include + +#include "../renderer/tr_local.h" +#include "../client/client.h" +#include "../sys/sys_local.h" + +#ifdef MACOS_X +// Mouse acceleration needs to be disabled +#define MACOS_X_ACCELERATION_HACK +// Cursor needs hack to hide +#define MACOS_X_CURSOR_HACK +#endif + +#ifdef MACOS_X_ACCELERATION_HACK +#include +#include +#include +#include +#endif + +static cvar_t *in_keyboardDebug = NULL; + +static SDL_Joystick *stick = NULL; + +static qboolean mouseAvailable = qfalse; +static qboolean mouseActive = qfalse; +static qboolean keyRepeatEnabled = qfalse; + +static cvar_t *in_mouse = NULL; +#ifdef MACOS_X_ACCELERATION_HACK +static cvar_t *in_disablemacosxmouseaccel = NULL; +static double originalMouseSpeed = -1.0; +#endif +static cvar_t *in_nograb; + +static cvar_t *in_joystick = NULL; +static cvar_t *in_joystickDebug = NULL; +static cvar_t *in_joystickThreshold = NULL; +static cvar_t *in_joystickNo = NULL; +static cvar_t *in_joystickUseAnalog = NULL; + +static int vidRestartTime = 0; + +#define CTRL(a) ((a)-'a'+1) + +/* +=============== +IN_PrintKey +=============== +*/ +static void IN_PrintKey( const SDL_keysym *keysym, keyNum_t key, qboolean down ) +{ + if( down ) + Com_Printf( "+ " ); + else + Com_Printf( " " ); + + Com_Printf( "0x%02x \"%s\"", keysym->scancode, + SDL_GetKeyName( keysym->sym ) ); + + if( keysym->mod & KMOD_LSHIFT ) Com_Printf( " KMOD_LSHIFT" ); + if( keysym->mod & KMOD_RSHIFT ) Com_Printf( " KMOD_RSHIFT" ); + if( keysym->mod & KMOD_LCTRL ) Com_Printf( " KMOD_LCTRL" ); + if( keysym->mod & KMOD_RCTRL ) Com_Printf( " KMOD_RCTRL" ); + if( keysym->mod & KMOD_LALT ) Com_Printf( " KMOD_LALT" ); + if( keysym->mod & KMOD_RALT ) Com_Printf( " KMOD_RALT" ); + if( keysym->mod & KMOD_LMETA ) Com_Printf( " KMOD_LMETA" ); + if( keysym->mod & KMOD_RMETA ) Com_Printf( " KMOD_RMETA" ); + if( keysym->mod & KMOD_NUM ) Com_Printf( " KMOD_NUM" ); + if( keysym->mod & KMOD_CAPS ) Com_Printf( " KMOD_CAPS" ); + if( keysym->mod & KMOD_MODE ) Com_Printf( " KMOD_MODE" ); + if( keysym->mod & KMOD_RESERVED ) Com_Printf( " KMOD_RESERVED" ); + + Com_Printf( " Q:0x%02x(%s)", key, Key_KeynumToString( key ) ); + + if( keysym->unicode ) + { + Com_Printf( " U:0x%02x", keysym->unicode ); + + if( keysym->unicode > ' ' && keysym->unicode < '~' ) + Com_Printf( "(%c)", (char)keysym->unicode ); + } + + Com_Printf( "\n" ); +} + +#define MAX_CONSOLE_KEYS 16 + +/* +=============== +IN_IsConsoleKey +=============== +*/ +static qboolean IN_IsConsoleKey( keyNum_t key, const unsigned char character ) +{ + typedef struct consoleKey_s + { + enum + { + KEY, + CHARACTER + } type; + + union + { + keyNum_t key; + unsigned char character; + } u; + } consoleKey_t; + + static consoleKey_t consoleKeys[ MAX_CONSOLE_KEYS ]; + static int numConsoleKeys = 0; + int i; + + // Only parse the variable when it changes + if( cl_consoleKeys->modified ) + { + char *text_p, *token; + + cl_consoleKeys->modified = qfalse; + text_p = cl_consoleKeys->string; + numConsoleKeys = 0; + + while( numConsoleKeys < MAX_CONSOLE_KEYS ) + { + consoleKey_t *c = &consoleKeys[ numConsoleKeys ]; + int charCode = 0; + + token = COM_Parse( &text_p ); + if( !token[ 0 ] ) + break; + + if( strlen( token ) == 4 ) + charCode = Com_HexStrToInt( token ); + + if( charCode > 0 ) + { + c->type = CHARACTER; + c->u.character = (unsigned char)charCode; + } + else + { + c->type = KEY; + c->u.key = Key_StringToKeynum( token ); + + // 0 isn't a key + if( c->u.key <= 0 ) + continue; + } + + numConsoleKeys++; + } + } + + // If the character is the same as the key, prefer the character + if( key == character ) + key = 0; + + for( i = 0; i < numConsoleKeys; i++ ) + { + consoleKey_t *c = &consoleKeys[ i ]; + + switch( c->type ) + { + case KEY: + if( key && c->u.key == key ) + return qtrue; + break; + + case CHARACTER: + if( c->u.character == character ) + return qtrue; + break; + } + } + + return qfalse; +} + +/* +=============== +IN_TranslateSDLToQ3Key +=============== +*/ +static const char *IN_TranslateSDLToQ3Key( SDL_keysym *keysym, + keyNum_t *key, qboolean down ) +{ + static unsigned char buf[ 2 ] = { '\0', '\0' }; + + *buf = '\0'; + *key = 0; + + if( keysym->sym >= SDLK_SPACE && keysym->sym < SDLK_DELETE ) + { + // These happen to match the ASCII chars + *key = (int)keysym->sym; + } + else + { + switch( keysym->sym ) + { + case SDLK_PAGEUP: *key = K_PGUP; break; + case SDLK_KP9: *key = K_KP_PGUP; break; + case SDLK_PAGEDOWN: *key = K_PGDN; break; + case SDLK_KP3: *key = K_KP_PGDN; break; + case SDLK_KP7: *key = K_KP_HOME; break; + case SDLK_HOME: *key = K_HOME; break; + case SDLK_KP1: *key = K_KP_END; break; + case SDLK_END: *key = K_END; break; + case SDLK_KP4: *key = K_KP_LEFTARROW; break; + case SDLK_LEFT: *key = K_LEFTARROW; break; + case SDLK_KP6: *key = K_KP_RIGHTARROW; break; + case SDLK_RIGHT: *key = K_RIGHTARROW; break; + case SDLK_KP2: *key = K_KP_DOWNARROW; break; + case SDLK_DOWN: *key = K_DOWNARROW; break; + case SDLK_KP8: *key = K_KP_UPARROW; break; + case SDLK_UP: *key = K_UPARROW; break; + case SDLK_ESCAPE: *key = K_ESCAPE; break; + case SDLK_KP_ENTER: *key = K_KP_ENTER; break; + case SDLK_RETURN: *key = K_ENTER; break; + case SDLK_TAB: *key = K_TAB; break; + case SDLK_F1: *key = K_F1; break; + case SDLK_F2: *key = K_F2; break; + case SDLK_F3: *key = K_F3; break; + case SDLK_F4: *key = K_F4; break; + case SDLK_F5: *key = K_F5; break; + case SDLK_F6: *key = K_F6; break; + case SDLK_F7: *key = K_F7; break; + case SDLK_F8: *key = K_F8; break; + case SDLK_F9: *key = K_F9; break; + case SDLK_F10: *key = K_F10; break; + case SDLK_F11: *key = K_F11; break; + case SDLK_F12: *key = K_F12; break; + case SDLK_F13: *key = K_F13; break; + case SDLK_F14: *key = K_F14; break; + case SDLK_F15: *key = K_F15; break; + + case SDLK_BACKSPACE: *key = K_BACKSPACE; break; + case SDLK_KP_PERIOD: *key = K_KP_DEL; break; + case SDLK_DELETE: *key = K_DEL; break; + case SDLK_PAUSE: *key = K_PAUSE; break; + + case SDLK_LSHIFT: + case SDLK_RSHIFT: *key = K_SHIFT; break; + + case SDLK_LCTRL: + case SDLK_RCTRL: *key = K_CTRL; break; + + case SDLK_RMETA: + case SDLK_LMETA: *key = K_COMMAND; break; + + case SDLK_RALT: + case SDLK_LALT: *key = K_ALT; break; + + case SDLK_LSUPER: + case SDLK_RSUPER: *key = K_SUPER; break; + + case SDLK_KP5: *key = K_KP_5; break; + case SDLK_INSERT: *key = K_INS; break; + case SDLK_KP0: *key = K_KP_INS; break; + case SDLK_KP_MULTIPLY: *key = K_KP_STAR; break; + case SDLK_KP_PLUS: *key = K_KP_PLUS; break; + case SDLK_KP_MINUS: *key = K_KP_MINUS; break; + case SDLK_KP_DIVIDE: *key = K_KP_SLASH; break; + + case SDLK_MODE: *key = K_MODE; break; + case SDLK_COMPOSE: *key = K_COMPOSE; break; + case SDLK_HELP: *key = K_HELP; break; + case SDLK_PRINT: *key = K_PRINT; break; + case SDLK_SYSREQ: *key = K_SYSREQ; break; + case SDLK_BREAK: *key = K_BREAK; break; + case SDLK_MENU: *key = K_MENU; break; + case SDLK_POWER: *key = K_POWER; break; + case SDLK_EURO: *key = K_EURO; break; + case SDLK_UNDO: *key = K_UNDO; break; + case SDLK_SCROLLOCK: *key = K_SCROLLOCK; break; + case SDLK_NUMLOCK: *key = K_KP_NUMLOCK; break; + case SDLK_CAPSLOCK: *key = K_CAPSLOCK; break; + + default: + if( keysym->sym >= SDLK_WORLD_0 && keysym->sym <= SDLK_WORLD_95 ) + *key = ( keysym->sym - SDLK_WORLD_0 ) + K_WORLD_0; + break; + } + } + + if( down && keysym->unicode && !( keysym->unicode & 0xFF00 ) ) + { + unsigned char ch = (unsigned char)keysym->unicode & 0xFF; + + switch( ch ) + { + case 127: // ASCII delete + if( *key != K_DEL ) + { + // ctrl-h + *buf = CTRL('h'); + break; + } + // fallthrough + + default: *buf = ch; break; + } + } + + if( in_keyboardDebug->integer ) + IN_PrintKey( keysym, *key, down ); + + // Keys that have ASCII names but produce no character are probably + // dead keys -- ignore them + if( down && strlen( Key_KeynumToString( *key ) ) == 1 && + keysym->unicode == 0 ) + { + if( in_keyboardDebug->integer ) + Com_Printf( " Ignored dead key '%c'\n", *key ); + + *key = 0; + } + + if( IN_IsConsoleKey( *key, *buf ) ) + { + // Console keys can't be bound or generate characters + *key = K_CONSOLE; + *buf = '\0'; + } + + // Don't allow extended ASCII to generate characters + if( *buf & 0x80 ) + *buf = '\0'; + + return (char *)buf; +} + +#ifdef MACOS_X_ACCELERATION_HACK +/* +=============== +IN_GetIOHandle +=============== +*/ +static io_connect_t IN_GetIOHandle(void) // mac os x mouse accel hack +{ + io_connect_t iohandle = MACH_PORT_NULL; + kern_return_t status; + io_service_t iohidsystem = MACH_PORT_NULL; + mach_port_t masterport; + + 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; +} +#endif + +/* +=============== +IN_GobbleMotionEvents +=============== +*/ +static void IN_GobbleMotionEvents( void ) +{ + SDL_Event dummy[ 1 ]; + + // Gobble any mouse motion events + SDL_PumpEvents( ); + while( SDL_PeepEvents( dummy, 1, SDL_GETEVENT, + SDL_EVENTMASK( SDL_MOUSEMOTION ) ) ) { } +} + +/* +=============== +IN_GetUIMousePosition +=============== +*/ +static void IN_GetUIMousePosition( int *x, int *y ) +{ + if( uivm ) + { + int pos = VM_Call( uivm, UI_MOUSE_POSITION ); + *x = pos & 0xFFFF; + *y = ( pos >> 16 ) & 0xFFFF; + + *x = glConfig.vidWidth * *x / 640; + *y = glConfig.vidHeight * *y / 480; + } + else + { + *x = glConfig.vidWidth / 2; + *y = glConfig.vidHeight / 2; + } +} + +/* +=============== +IN_SetUIMousePosition +=============== +*/ +static void IN_SetUIMousePosition( int x, int y ) +{ + if( uivm ) + { + x = x * 640 / glConfig.vidWidth; + y = y * 480 / glConfig.vidHeight; + VM_Call( uivm, UI_SET_MOUSE_POSITION, x, y ); + } +} + +/* +=============== +IN_ActivateMouse +=============== +*/ +static void IN_ActivateMouse( void ) +{ + if (!mouseAvailable || !SDL_WasInit( SDL_INIT_VIDEO ) ) + return; + +#ifdef MACOS_X_ACCELERATION_HACK + if (!mouseActive) // mac os x mouse accel hack + { + // Save the status of mouse acceleration + originalMouseSpeed = -1.0; // in case of error + if(in_disablemacosxmouseaccel->integer) + { + io_connect_t mouseDev = IN_GetIOHandle(); + if(mouseDev != 0) + { + if(IOHIDGetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), &originalMouseSpeed) == kIOReturnSuccess) + { + Com_Printf("previous mouse acceleration: %f\n", originalMouseSpeed); + if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), -1.0) != kIOReturnSuccess) + { + Com_Printf("Could not disable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + Cvar_Set ("in_disablemacosxmouseaccel", 0); + } + } + else + { + Com_Printf("Could not disable mouse acceleration (failed at IOHIDGetAccelerationWithKey).\n"); + Cvar_Set ("in_disablemacosxmouseaccel", 0); + } + IOServiceClose(mouseDev); + } + else + { + Com_Printf("Could not disable mouse acceleration (failed at IO_GetIOHandle).\n"); + Cvar_Set ("in_disablemacosxmouseaccel", 0); + } + } + } +#endif + + if( !mouseActive ) + { + SDL_ShowCursor( 0 ); +#ifdef MACOS_X_CURSOR_HACK + // This is a bug in the current SDL/macosx...have to toggle it a few + // times to get the cursor to hide. + SDL_ShowCursor( 1 ); + SDL_ShowCursor( 0 ); +#endif + SDL_WM_GrabInput( SDL_GRAB_ON ); + + IN_GobbleMotionEvents( ); + } + + // in_nograb makes no sense in fullscreen mode + if( !r_fullscreen->integer ) + { + if( in_nograb->modified || !mouseActive ) + { + if( in_nograb->integer ) + SDL_WM_GrabInput( SDL_GRAB_OFF ); + else + SDL_WM_GrabInput( SDL_GRAB_ON ); + + in_nograb->modified = qfalse; + } + } + + mouseActive = qtrue; +} + +/* +=============== +IN_DeactivateMouse +=============== +*/ +static void IN_DeactivateMouse( void ) +{ + if( !SDL_WasInit( SDL_INIT_VIDEO ) ) + return; + + // Always show the cursor when the mouse is disabled, + // but not when fullscreen + if( !r_fullscreen->integer ) + { + if( ( Key_GetCatcher( ) == KEYCATCH_UI ) && + ( SDL_GetAppState( ) & SDL_APPMOUSEFOCUS ) ) + SDL_ShowCursor( 0 ); + else + SDL_ShowCursor( 1 ); + } + + if( !mouseAvailable ) + return; + +#ifdef MACOS_X_ACCELERATION_HACK + if (mouseActive) // mac os x mouse accel hack + { + if(originalMouseSpeed != -1.0) + { + io_connect_t mouseDev = IN_GetIOHandle(); + if(mouseDev != 0) + { + Com_Printf("restoring mouse acceleration to: %f\n", originalMouseSpeed); + if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), originalMouseSpeed) != kIOReturnSuccess) + Com_Printf("Could not re-enable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + IOServiceClose(mouseDev); + } + else + Com_Printf("Could not re-enable mouse acceleration (failed at IO_GetIOHandle).\n"); + } + } +#endif + + if( mouseActive ) + { + IN_GobbleMotionEvents( ); + + SDL_WM_GrabInput( SDL_GRAB_OFF ); + + // Don't warp the mouse unless the cursor is within the window + if( SDL_GetAppState( ) & SDL_APPMOUSEFOCUS ) + { + int x, y; + IN_GetUIMousePosition( &x, &y ); + SDL_WarpMouse( x, y ); + } + + mouseActive = qfalse; + } +} + +// We translate axes movement into keypresses +static int joy_keys[16] = { + K_LEFTARROW, K_RIGHTARROW, + K_UPARROW, K_DOWNARROW, + K_JOY16, K_JOY17, + K_JOY18, K_JOY19, + K_JOY20, K_JOY21, + K_JOY22, K_JOY23, + + K_JOY24, K_JOY25, + K_JOY26, K_JOY27 +}; + +// translate hat events into keypresses +// the 4 highest buttons are used for the first hat ... +static int hat_keys[16] = { + K_JOY29, K_JOY30, + K_JOY31, K_JOY32, + K_JOY25, K_JOY26, + K_JOY27, K_JOY28, + K_JOY21, K_JOY22, + K_JOY23, K_JOY24, + K_JOY17, K_JOY18, + K_JOY19, K_JOY20 +}; + + +struct +{ + qboolean buttons[16]; // !!! FIXME: these might be too many. + unsigned int oldaxes; + int oldaaxes[16]; + unsigned int oldhats; +} stick_state; + + +/* +=============== +IN_InitJoystick +=============== +*/ +static void IN_InitJoystick( void ) +{ + int i = 0; + int total = 0; + char buf[16384] = ""; + + if (stick != NULL) + SDL_JoystickClose(stick); + + stick = NULL; + memset(&stick_state, '\0', sizeof (stick_state)); + + if (!SDL_WasInit(SDL_INIT_JOYSTICK)) + { + Com_DPrintf("Calling SDL_Init(SDL_INIT_JOYSTICK)...\n"); + if (SDL_Init(SDL_INIT_JOYSTICK) == -1) + { + Com_DPrintf("SDL_Init(SDL_INIT_JOYSTICK) failed: %s\n", SDL_GetError()); + return; + } + Com_DPrintf("SDL_Init(SDL_INIT_JOYSTICK) passed.\n"); + } + + total = SDL_NumJoysticks(); + Com_DPrintf("%d possible joysticks\n", total); + + // Print list and build cvar to allow ui to select joystick. + for (i = 0; i < total; i++) + { + Q_strcat(buf, sizeof(buf), SDL_JoystickName(i)); + Q_strcat(buf, sizeof(buf), "\n"); + } + + Cvar_Get( "in_availableJoysticks", buf, CVAR_ROM ); + + if( !in_joystick->integer ) { + Com_DPrintf( "Joystick is not active.\n" ); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + return; + } + + in_joystickNo = Cvar_Get( "in_joystickNo", "0", CVAR_ARCHIVE ); + if( in_joystickNo->integer < 0 || in_joystickNo->integer >= total ) + Cvar_Set( "in_joystickNo", "0" ); + + in_joystickUseAnalog = Cvar_Get( "in_joystickUseAnalog", "0", CVAR_ARCHIVE ); + + stick = SDL_JoystickOpen( in_joystickNo->integer ); + + if (stick == NULL) { + Com_DPrintf( "No joystick opened.\n" ); + return; + } + + Com_DPrintf( "Joystick %d opened\n", in_joystickNo->integer ); + Com_DPrintf( "Name: %s\n", SDL_JoystickName(in_joystickNo->integer) ); + Com_DPrintf( "Axes: %d\n", SDL_JoystickNumAxes(stick) ); + Com_DPrintf( "Hats: %d\n", SDL_JoystickNumHats(stick) ); + Com_DPrintf( "Buttons: %d\n", SDL_JoystickNumButtons(stick) ); + Com_DPrintf( "Balls: %d\n", SDL_JoystickNumBalls(stick) ); + Com_DPrintf( "Use Analog: %s\n", in_joystickUseAnalog->integer ? "Yes" : "No" ); + + SDL_JoystickEventState(SDL_QUERY); +} + +/* +=============== +IN_ShutdownJoystick +=============== +*/ +static void IN_ShutdownJoystick( void ) +{ + if (stick) + { + SDL_JoystickClose(stick); + stick = NULL; + } + + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); +} + +/* +=============== +IN_JoyMove +=============== +*/ +static void IN_JoyMove( void ) +{ + qboolean joy_pressed[ARRAY_LEN(joy_keys)]; + unsigned int axes = 0; + unsigned int hats = 0; + int total = 0; + int i = 0; + + if (!stick) + return; + + SDL_JoystickUpdate(); + + memset(joy_pressed, '\0', sizeof (joy_pressed)); + + // update the ball state. + total = SDL_JoystickNumBalls(stick); + if (total > 0) + { + int balldx = 0; + int balldy = 0; + for (i = 0; i < total; i++) + { + int dx = 0; + int dy = 0; + SDL_JoystickGetBall(stick, i, &dx, &dy); + balldx += dx; + balldy += dy; + } + if (balldx || balldy) + { + // !!! FIXME: is this good for stick balls, or just mice? + // Scale like the mouse input... + if (abs(balldx) > 1) + balldx *= 2; + if (abs(balldy) > 1) + balldy *= 2; + Com_QueueEvent( 0, SE_MOUSE, balldx, balldy, 0, NULL ); + } + } + + // now query the stick buttons... + total = SDL_JoystickNumButtons(stick); + if (total > 0) + { + if (total > ARRAY_LEN(stick_state.buttons)) + total = ARRAY_LEN(stick_state.buttons); + for (i = 0; i < total; i++) + { + qboolean pressed = (SDL_JoystickGetButton(stick, i) != 0); + if (pressed != stick_state.buttons[i]) + { + Com_QueueEvent( 0, SE_KEY, K_JOY1 + i, pressed, 0, NULL ); + stick_state.buttons[i] = pressed; + } + } + } + + // look at the hats... + total = SDL_JoystickNumHats(stick); + if (total > 0) + { + if (total > 4) total = 4; + for (i = 0; i < total; i++) + { + ((Uint8 *)&hats)[i] = SDL_JoystickGetHat(stick, i); + } + } + + // update hat state + if (hats != stick_state.oldhats) + { + for( i = 0; i < 4; i++ ) { + if( ((Uint8 *)&hats)[i] != ((Uint8 *)&stick_state.oldhats)[i] ) { + // release event + switch( ((Uint8 *)&stick_state.oldhats)[i] ) { + case SDL_HAT_UP: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); + break; + case SDL_HAT_RIGHT: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); + break; + case SDL_HAT_DOWN: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); + break; + case SDL_HAT_LEFT: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); + break; + case SDL_HAT_RIGHTUP: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); + break; + case SDL_HAT_RIGHTDOWN: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); + break; + case SDL_HAT_LEFTUP: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); + break; + case SDL_HAT_LEFTDOWN: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); + break; + default: + break; + } + // press event + switch( ((Uint8 *)&hats)[i] ) { + case SDL_HAT_UP: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); + break; + case SDL_HAT_RIGHT: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); + break; + case SDL_HAT_DOWN: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); + break; + case SDL_HAT_LEFT: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); + break; + case SDL_HAT_RIGHTUP: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); + break; + case SDL_HAT_RIGHTDOWN: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); + break; + case SDL_HAT_LEFTUP: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); + break; + case SDL_HAT_LEFTDOWN: + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); + Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); + break; + default: + break; + } + } + } + } + + // save hat state + stick_state.oldhats = hats; + + // finally, look at the axes... + total = SDL_JoystickNumAxes(stick); + if (total > 0) + { + if (total > 16) total = 16; + for (i = 0; i < total; i++) + { + Sint16 axis = SDL_JoystickGetAxis(stick, i); + + if (in_joystickUseAnalog->integer) + { + float f = ( (float) abs(axis) ) / 32767.0f; + + if( f < in_joystickThreshold->value ) axis = 0; + + if ( axis != stick_state.oldaaxes[i] ) + { + Com_QueueEvent( 0, SE_JOYSTICK_AXIS, i, axis, 0, NULL ); + stick_state.oldaaxes[i] = axis; + } + } + else + { + float f = ( (float) axis ) / 32767.0f; + if( f < -in_joystickThreshold->value ) { + axes |= ( 1 << ( i * 2 ) ); + } else if( f > in_joystickThreshold->value ) { + axes |= ( 1 << ( ( i * 2 ) + 1 ) ); + } + } + } + } + + /* Time to update axes state based on old vs. new. */ + if (axes != stick_state.oldaxes) + { + for( i = 0; i < 16; i++ ) { + if( ( axes & ( 1 << i ) ) && !( stick_state.oldaxes & ( 1 << i ) ) ) { + Com_QueueEvent( 0, SE_KEY, joy_keys[i], qtrue, 0, NULL ); + } + + if( !( axes & ( 1 << i ) ) && ( stick_state.oldaxes & ( 1 << i ) ) ) { + Com_QueueEvent( 0, SE_KEY, joy_keys[i], qfalse, 0, NULL ); + } + } + } + + /* Save for future generations. */ + stick_state.oldaxes = axes; +} + +/* +=============== +IN_ProcessEvents +=============== +*/ +static void IN_ProcessEvents( void ) +{ + SDL_Event e; + const char *character = NULL; + keyNum_t key = 0; + + if( !SDL_WasInit( SDL_INIT_VIDEO ) ) + return; + + if( Key_GetCatcher( ) == 0 && keyRepeatEnabled ) + { + SDL_EnableKeyRepeat( 0, 0 ); + keyRepeatEnabled = qfalse; + } + else if( !keyRepeatEnabled ) + { + SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL ); + keyRepeatEnabled = qtrue; + } + + while( SDL_PollEvent( &e ) ) + { + switch( e.type ) + { + case SDL_KEYDOWN: + character = IN_TranslateSDLToQ3Key( &e.key.keysym, &key, qtrue ); + if( key ) + Com_QueueEvent( 0, SE_KEY, key, qtrue, 0, NULL ); + + if( character ) + Com_QueueEvent( 0, SE_CHAR, *character, 0, 0, NULL ); + break; + + case SDL_KEYUP: + IN_TranslateSDLToQ3Key( &e.key.keysym, &key, qfalse ); + + if( key ) + Com_QueueEvent( 0, SE_KEY, key, qfalse, 0, NULL ); + break; + + case SDL_MOUSEMOTION: + if( mouseActive ) + Com_QueueEvent( 0, SE_MOUSE, e.motion.xrel, e.motion.yrel, 0, NULL ); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + unsigned char b; + switch( e.button.button ) + { + case 1: b = K_MOUSE1; break; + case 2: b = K_MOUSE3; break; + case 3: b = K_MOUSE2; break; + case 4: b = K_MWHEELUP; break; + case 5: b = K_MWHEELDOWN; break; + case 6: b = K_MOUSE4; break; + case 7: b = K_MOUSE5; break; + default: b = K_AUX1 + ( e.button.button - 8 ) % 16; break; + } + Com_QueueEvent( 0, SE_KEY, b, + ( e.type == SDL_MOUSEBUTTONDOWN ? qtrue : qfalse ), 0, NULL ); + } + break; + + case SDL_QUIT: + Cbuf_ExecuteText(EXEC_NOW, "quit Closed window\n"); + break; + + case SDL_VIDEORESIZE: + { + char width[32], height[32]; + Com_sprintf( width, sizeof(width), "%d", e.resize.w ); + Com_sprintf( height, sizeof(height), "%d", e.resize.h ); + ri.Cvar_Set( "r_width", width ); + ri.Cvar_Set( "r_height", height ); + /* wait until user stops dragging for 1 second, so + we aren't constantly recreating the GL context while + he tries to drag...*/ + vidRestartTime = Sys_Milliseconds() + 1000; + } + break; + case SDL_ACTIVEEVENT: + if (e.active.state & SDL_APPINPUTFOCUS) { + Cvar_SetValue( "com_unfocused", !e.active.gain); + } + if (e.active.state & SDL_APPACTIVE) { + Cvar_SetValue( "com_minimized", !e.active.gain); + } + break; + + default: + break; + } + } +} + +/* +=============== +IN_Frame +=============== +*/ +void IN_Frame( void ) +{ + qboolean loading; + qboolean cursorShowing; + int x, y; + + IN_JoyMove( ); + IN_ProcessEvents( ); + + // If not DISCONNECTED (main menu) or ACTIVE (in game), we're loading + loading = !!( cls.state != CA_DISCONNECTED && cls.state != CA_ACTIVE ); + cursorShowing = Key_GetCatcher( ) & KEYCATCH_UI; + + if( !r_fullscreen->integer && ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) + { + // Console is down in windowed mode + IN_DeactivateMouse( ); + } + else if( !r_fullscreen->integer && loading ) + { + // Loading in windowed mode + IN_DeactivateMouse( ); + } + else if( !r_fullscreen->integer && cursorShowing ) + { + // Use WM cursor when not fullscreen + IN_DeactivateMouse( ); + } + else if( !( SDL_GetAppState() & SDL_APPINPUTFOCUS ) ) + { + // Window not got focus + IN_DeactivateMouse( ); + } + else + IN_ActivateMouse( ); + + if( !mouseActive ) + { + SDL_GetMouseState( &x, &y ); + IN_SetUIMousePosition( x, y ); + } + + // In case we had to delay actual restart of video system + if( ( vidRestartTime != 0 ) && ( vidRestartTime < Sys_Milliseconds( ) ) ) + { + vidRestartTime = 0; + Cbuf_AddText( "vid_restart" ); + } +} + +/* +=============== +IN_InitKeyLockStates +=============== +*/ +void IN_InitKeyLockStates( void ) +{ + unsigned char *keystate = SDL_GetKeyState(NULL); + + keys[K_SCROLLOCK].down = keystate[SDLK_SCROLLOCK]; + keys[K_KP_NUMLOCK].down = keystate[SDLK_NUMLOCK]; + keys[K_CAPSLOCK].down = keystate[SDLK_CAPSLOCK]; +} + +/* +=============== +IN_Init +=============== +*/ +void IN_Init( void ) +{ + int appState; + + if( !SDL_WasInit( SDL_INIT_VIDEO ) ) + { + Com_Error( ERR_FATAL, "IN_Init called before SDL_Init( SDL_INIT_VIDEO )\n" ); + return; + } + + Com_DPrintf( "\n------- Input Initialization -------\n" ); + + in_keyboardDebug = Cvar_Get( "in_keyboardDebug", "0", CVAR_ARCHIVE ); + + // mouse variables + in_mouse = Cvar_Get( "in_mouse", "1", CVAR_ARCHIVE ); + in_nograb = Cvar_Get( "in_nograb", "0", CVAR_ARCHIVE ); + + in_joystick = Cvar_Get( "in_joystick", "0", CVAR_ARCHIVE|CVAR_LATCH ); + in_joystickDebug = Cvar_Get( "in_joystickDebug", "0", CVAR_TEMP ); + in_joystickThreshold = Cvar_Get( "joy_threshold", "0.15", CVAR_ARCHIVE ); + +#ifdef MACOS_X_ACCELERATION_HACK + in_disablemacosxmouseaccel = Cvar_Get( "in_disablemacosxmouseaccel", "1", CVAR_ARCHIVE ); +#endif + + SDL_EnableUNICODE( 1 ); + SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL ); + keyRepeatEnabled = qtrue; + + if( in_mouse->value ) + { + mouseAvailable = qtrue; + IN_ActivateMouse( ); + } + else + { + IN_DeactivateMouse( ); + mouseAvailable = qfalse; + } + + appState = SDL_GetAppState( ); + Cvar_SetValue( "com_unfocused", !( appState & SDL_APPINPUTFOCUS ) ); + Cvar_SetValue( "com_minimized", !( appState & SDL_APPACTIVE ) ); + + IN_InitKeyLockStates( ); + + IN_InitJoystick( ); + Com_DPrintf( "------------------------------------\n" ); +} + +/* +=============== +IN_Shutdown +=============== +*/ +void IN_Shutdown( void ) +{ + IN_DeactivateMouse( ); + mouseAvailable = qfalse; + + IN_ShutdownJoystick( ); +} + +/* +=============== +IN_Restart +=============== +*/ +void IN_Restart( void ) +{ + IN_ShutdownJoystick( ); + IN_Init( ); +} diff --git a/src/sdl/sdl_snd.c b/src/sdl/sdl_snd.c new file mode 100644 index 0000000..6ab90ae --- /dev/null +++ b/src/sdl/sdl_snd.c @@ -0,0 +1,299 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include +#include + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#include "../qcommon/q_shared.h" +#include "../client/snd_local.h" + +qboolean snd_inited = qfalse; + +cvar_t *s_sdlBits; +cvar_t *s_sdlSpeed; +cvar_t *s_sdlChannels; +cvar_t *s_sdlDevSamps; +cvar_t *s_sdlMixSamps; + +/* The audio callback. All the magic happens here. */ +static int dmapos = 0; +static int dmasize = 0; + +/* +=============== +SNDDMA_AudioCallback +=============== +*/ +static void SNDDMA_AudioCallback(void *userdata, Uint8 *stream, int len) +{ + int pos = (dmapos * (dma.samplebits/8)); + if (pos >= dmasize) + dmapos = pos = 0; + + if (!snd_inited) /* shouldn't happen, but just in case... */ + { + memset(stream, '\0', len); + return; + } + else + { + int tobufend = dmasize - pos; /* bytes to buffer's end. */ + int len1 = len; + int len2 = 0; + + if (len1 > tobufend) + { + len1 = tobufend; + len2 = len - len1; + } + memcpy(stream, dma.buffer + pos, len1); + if (len2 <= 0) + dmapos += (len1 / (dma.samplebits/8)); + else /* wraparound? */ + { + memcpy(stream+len1, dma.buffer, len2); + dmapos = (len2 / (dma.samplebits/8)); + } + } + + if (dmapos >= dmasize) + dmapos = 0; +} + +static struct +{ + Uint16 enumFormat; + char *stringFormat; +} formatToStringTable[ ] = +{ + { AUDIO_U8, "AUDIO_U8" }, + { AUDIO_S8, "AUDIO_S8" }, + { AUDIO_U16LSB, "AUDIO_U16LSB" }, + { AUDIO_S16LSB, "AUDIO_S16LSB" }, + { AUDIO_U16MSB, "AUDIO_U16MSB" }, + { AUDIO_S16MSB, "AUDIO_S16MSB" } +}; + +static int formatToStringTableSize = ARRAY_LEN( formatToStringTable ); + +/* +=============== +SNDDMA_PrintAudiospec +=============== +*/ +static void SNDDMA_PrintAudiospec(const char *str, const SDL_AudioSpec *spec) +{ + int i; + char *fmt = NULL; + + Com_Printf("%s:\n", str); + + for( i = 0; i < formatToStringTableSize; i++ ) { + if( spec->format == formatToStringTable[ i ].enumFormat ) { + fmt = formatToStringTable[ i ].stringFormat; + } + } + + if( fmt ) { + Com_Printf( " Format: %s\n", fmt ); + } else { + Com_Printf( " Format: " S_COLOR_RED "UNKNOWN\n"); + } + + Com_Printf( " Freq: %d\n", (int) spec->freq ); + Com_Printf( " Samples: %d\n", (int) spec->samples ); + Com_Printf( " Channels: %d\n", (int) spec->channels ); +} + +/* +=============== +SNDDMA_Init +=============== +*/ +qboolean SNDDMA_Init(void) +{ + char drivername[128]; + SDL_AudioSpec desired; + SDL_AudioSpec obtained; + int tmp; + + if (snd_inited) + return qtrue; + + if (!s_sdlBits) { + s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE); + s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE); + s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE); + s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE); + s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE); + } + + Com_Printf( "SDL_Init( SDL_INIT_AUDIO )... " ); + + if (!SDL_WasInit(SDL_INIT_AUDIO)) + { + if (SDL_Init(SDL_INIT_AUDIO) == -1) + { + Com_Printf( "FAILED (%s)\n", SDL_GetError( ) ); + return qfalse; + } + } + + Com_Printf( "OK\n" ); + + if (SDL_AudioDriverName(drivername, sizeof (drivername)) == NULL) + strcpy(drivername, "(UNKNOWN)"); + Com_Printf("SDL audio driver is \"%s\".\n", drivername); + + memset(&desired, '\0', sizeof (desired)); + memset(&obtained, '\0', sizeof (obtained)); + + tmp = ((int) s_sdlBits->value); + if ((tmp != 16) && (tmp != 8)) + tmp = 16; + + desired.freq = (int) s_sdlSpeed->value; + if(!desired.freq) desired.freq = 22050; + desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8); + + // I dunno if this is the best idea, but I'll give it a try... + // should probably check a cvar for this... + if (s_sdlDevSamps->value) + desired.samples = s_sdlDevSamps->value; + else + { + // just pick a sane default. + if (desired.freq <= 11025) + desired.samples = 256; + else if (desired.freq <= 22050) + desired.samples = 512; + else if (desired.freq <= 44100) + desired.samples = 1024; + else + desired.samples = 2048; // (*shrug*) + } + + desired.channels = (int) s_sdlChannels->value; + desired.callback = SNDDMA_AudioCallback; + + if (SDL_OpenAudio(&desired, &obtained) == -1) + { + Com_Printf("SDL_OpenAudio() failed: %s\n", SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return qfalse; + } + + SNDDMA_PrintAudiospec("SDL_AudioSpec", &obtained); + + // dma.samples needs to be big, or id's mixer will just refuse to + // work at all; we need to keep it significantly bigger than the + // amount of SDL callback samples, and just copy a little each time + // the callback runs. + // 32768 is what the OSS driver filled in here on my system. I don't + // know if it's a good value overall, but at least we know it's + // reasonable...this is why I let the user override. + tmp = s_sdlMixSamps->value; + if (!tmp) + tmp = (obtained.samples * obtained.channels) * 10; + + if (tmp & (tmp - 1)) // not a power of two? Seems to confuse something. + { + int val = 1; + while (val < tmp) + val <<= 1; + + tmp = val; + } + + dmapos = 0; + dma.samplebits = obtained.format & 0xFF; // first byte of format is bits. + dma.channels = obtained.channels; + dma.samples = tmp; + dma.submission_chunk = 1; + dma.speed = obtained.freq; + dmasize = (dma.samples * (dma.samplebits/8)); + dma.buffer = calloc(1, dmasize); + + Com_Printf("Starting SDL audio callback...\n"); + SDL_PauseAudio(0); // start callback. + + Com_Printf("SDL audio initialized.\n"); + snd_inited = qtrue; + return qtrue; +} + +/* +=============== +SNDDMA_GetDMAPos +=============== +*/ +int SNDDMA_GetDMAPos(void) +{ + return dmapos; +} + +/* +=============== +SNDDMA_Shutdown +=============== +*/ +void SNDDMA_Shutdown(void) +{ + Com_Printf("Closing SDL audio device...\n"); + SDL_PauseAudio(1); + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + free(dma.buffer); + dma.buffer = NULL; + dmapos = dmasize = 0; + snd_inited = qfalse; + Com_Printf("SDL audio device shut down.\n"); +} + +/* +=============== +SNDDMA_Submit + +Send sound to device if buffer isn't really the dma buffer +=============== +*/ +void SNDDMA_Submit(void) +{ + SDL_UnlockAudio(); +} + +/* +=============== +SNDDMA_BeginPainting +=============== +*/ +void SNDDMA_BeginPainting (void) +{ + SDL_LockAudio(); +} diff --git a/src/server/server.h b/src/server/server.h new file mode 100644 index 0000000..0475f0b --- /dev/null +++ b/src/server/server.h @@ -0,0 +1,426 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// server.h + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../game/g_public.h" +#include "../game/bg_public.h" + +//============================================================================= + +#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND + // GAME BOTH REFERENCE !!! + +#define MAX_ENT_CLUSTERS 16 + +#ifdef USE_VOIP +typedef struct voipServerPacket_s +{ + int generation; + int sequence; + int frames; + int len; + int sender; + byte data[1024]; +} voipServerPacket_t; +#endif + +typedef struct svEntity_s { + struct worldSector_s *worldSector; + struct svEntity_s *nextEntityInWorldSector; + + entityState_t baseline; // for delta compression of initial sighting + int numClusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int lastCluster; // if all the clusters don't fit in clusternums + int areanum, areanum2; + int snapshotCounter; // used to prevent double adding from portal views +} svEntity_t; + +typedef enum { + SS_DEAD, // no map loaded + SS_LOADING, // spawning level entities + SS_GAME // actively running +} serverState_t; + +typedef struct configString_s { + char *s; + + qboolean restricted; // if true, don't send to clientList + clientList_t clientList; +} configString_t; + +typedef struct { + serverState_t state; + qboolean restarting; // if true, send configstring changes during SS_LOADING + int serverId; // changes each server start + int restartedServerId; // serverId before a map_restart + int checksumFeed; // the feed key that we use to compute the pure checksum strings + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // the serverId associated with the current checksumFeed (always <= serverId) + int checksumFeedServerId; + int snapshotCounter; // incremented for each snapshot built + int timeResidual; // <= 1000 / sv_frame->value + int nextFrameTime; // when time > nextFrameTime, process world + struct cmodel_s *models[MAX_MODELS]; + configString_t configstrings[MAX_CONFIGSTRINGS]; + svEntity_t svEntities[MAX_GENTITIES]; + + char *entityParsePoint; // used during game VM init + + // the game virtual machine will update these on init and changes + sharedEntity_t *gentities; + int gentitySize; + int num_entities; // current number, <= MAX_GENTITIES + + playerState_t *gameClients; + int gameClientSize; // will be > sizeof(playerState_t) due to game private data + + int restartTime; + int time; +} server_t; + + + + + +typedef struct { + int areabytes; + byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + playerState_t ps; + int num_entities; + int first_entity; // into the circular sv_packet_entities[] + // the entities MUST be in increasing state number + // order, otherwise the delta compression will fail + int messageSent; // time the message was transmitted + int messageAcked; // time the message was acked + int messageSize; // used to rate drop packets +} clientSnapshot_t; + +typedef enum { + CS_FREE, // can be reused for a new connection + CS_ZOMBIE, // client has been disconnected, but don't reuse + // connection for a couple seconds + CS_CONNECTED, // has been assigned to a client_t, but no gamestate yet + CS_PRIMED, // gamestate has been sent, but client hasn't sent a usercmd + CS_ACTIVE // client is fully in game +} clientState_t; + +typedef struct netchan_buffer_s { + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + struct netchan_buffer_s *next; +} netchan_buffer_t; + +typedef struct client_s { + clientState_t state; + char userinfo[MAX_INFO_STRING]; // name, etc + + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet + int reliableAcknowledge; // last acknowledged reliable message + int reliableSent; // last sent reliable message, not necesarily acknowledged yet + int messageAcknowledge; + + int gamestateMessageNum; // netchan->outgoingSequence of gamestate + int challenge; + + usercmd_t lastUsercmd; + int lastMessageNum; // for delta compression + int lastClientCommand; // reliable client message sequence + char lastClientCommandString[MAX_STRING_CHARS]; + sharedEntity_t *gentity; // SV_GentityNum(clientnum) + char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked + + // downloading + char downloadName[MAX_QPATH]; // if not empty string, we are downloading + fileHandle_t download; // file being downloaded + int downloadSize; // total bytes (can't use EOF because of paks) + int downloadCount; // bytes sent + int downloadClientBlock; // last block we sent to the client, awaiting ack + int downloadCurrentBlock; // current block number + int downloadXmitBlock; // last block we xmited + unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks + int downloadBlockSize[MAX_DOWNLOAD_WINDOW]; + qboolean downloadEOF; // We have sent the EOF block + int downloadSendTime; // time we last got an ack from the client + + int deltaMessage; // frame last client usercmd message + int nextReliableTime; // svs.time when another reliable command will be allowed + int lastPacketTime; // svs.time when packet was last received + int lastConnectTime; // svs.time when connection started + int nextSnapshotTime; // send another snapshot when svs.time >= nextSnapshotTime + qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec + int timeoutCount; // must timeout a few frames in a row so debugging doesn't break + clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here + int ping; + int rate; // bytes / second + int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked + int pureAuthentic; + qboolean gotCP; // TTimo - additional flag to distinguish between a bad pure checksum, and no cp command at all + netchan_t netchan; + // TTimo + // queuing outgoing fragmented messages to send them properly, without udp packet bursts + // in case large fragmented messages are stacking up + // buffer them into this queue, and hand them out to netchan as needed + netchan_buffer_t *netchan_start_queue; + netchan_buffer_t **netchan_end_queue; + +#ifdef USE_VOIP + qboolean hasVoip; + qboolean muteAllVoip; + qboolean ignoreVoipFromClient[MAX_CLIENTS]; + voipServerPacket_t voipPacket[64]; // !!! FIXME: WAY too much memory! + int queuedVoipPackets; +#endif + + int oldServerTime; + qboolean csUpdated[MAX_CONFIGSTRINGS+1]; +} client_t; + +//============================================================================= + + +// MAX_CHALLENGES is made large to prevent a denial +// of service attack that could cycle all of them +// out before legitimate users connected +#define MAX_CHALLENGES 1024 + +#define AUTHORIZE_TIMEOUT 5000 + +typedef struct { + netadr_t adr; + int challenge; + int clientChallenge; // challenge number coming from the client + int time; // time the last packet was sent to the autherize server + int pingTime; // time the challenge response was sent to client + int firstTime; // time the adr was first used, for authorize timeout checks + qboolean wasrefused; + qboolean connected; +} challenge_t; + +// this structure will be cleared only when the game dll changes +typedef struct { + qboolean initialized; // sv_init has completed + + int time; // will be strictly increasing across level changes + + int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer() + + client_t *clients; // [sv_maxclients->integer]; + int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES + int nextSnapshotEntities; // next snapshotEntities to use + entityState_t *snapshotEntities; // [numSnapshotEntities] + int nextHeartbeatTime; + challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + netadr_t redirectAddress; // for rcon return messages + + netadr_t authorizeAddress; // for rcon return messages +} serverStatic_t; + +//============================================================================= + +extern serverStatic_t svs; // persistant server info across maps +extern server_t sv; // cleared each map +extern vm_t *gvm; // game virtual machine + +extern cvar_t *sv_fps; +extern cvar_t *sv_timeout; +extern cvar_t *sv_zombietime; +extern cvar_t *sv_rconPassword; +extern cvar_t *sv_privatePassword; +extern cvar_t *sv_allowDownload; +extern cvar_t *sv_maxclients; + +extern cvar_t *sv_privateClients; +extern cvar_t *sv_hostname; +extern cvar_t *sv_master[MAX_MASTER_SERVERS]; +extern cvar_t *sv_reconnectlimit; +extern cvar_t *sv_showloss; +extern cvar_t *sv_padPackets; +extern cvar_t *sv_killserver; +extern cvar_t *sv_mapname; +extern cvar_t *sv_mapChecksum; +extern cvar_t *sv_serverid; +extern cvar_t *sv_minRate; +extern cvar_t *sv_maxRate; +extern cvar_t *sv_minPing; +extern cvar_t *sv_maxPing; +extern cvar_t *sv_pure; +extern cvar_t *sv_lanForceRate; +extern cvar_t *sv_dequeuePeriod; +extern cvar_t *sv_heartbeat; +extern cvar_t *sv_flatline; + +#ifdef USE_VOIP +extern cvar_t *sv_voip; +#endif + + +//=========================================================== + +// +// sv_main.c +// +void SV_FinalMessage (char *message); +void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...); + + +void SV_AddOperatorCommands (void); +void SV_RemoveOperatorCommands (void); + + +void SV_MasterShutdown (void); +void SV_MasterGameStat( const char *data ); + + + +// +// sv_init.c +// +void SV_SetConfigstring( int index, const char *val ); +void SV_GetConfigstring( int index, char *buffer, int bufferSize ); +void SV_SetConfigstringRestrictions(int index, const clientList_t* clientList); +void SV_UpdateConfigstrings( client_t *client ); + +void SV_SetUserinfo( int index, const char *val ); +void SV_GetUserinfo( int index, char *buffer, int bufferSize ); + +void SV_ChangeMaxClients( void ); +void SV_SpawnServer( char *server, qboolean killBots ); + + + +// +// sv_client.c +// +void SV_GetChallenge(netadr_t from); + +void SV_DirectConnect( netadr_t from ); + +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); +void SV_UserinfoChanged( client_t *cl ); + +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); +void SV_DropClient( client_t *drop, const char *reason ); + +void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ); +void SV_ClientThink (client_t *cl, usercmd_t *cmd); + +void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ); + +#ifdef USE_VOIP +void SV_WriteVoipToClient( client_t *cl, msg_t *msg ); +#endif + + +// +// sv_ccmds.c +// +void SV_Heartbeat_f( void ); + +// +// sv_snapshot.c +// +void SV_AddServerCommand( client_t *client, const char *cmd ); +void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ); +void SV_WriteFrameToClient (client_t *client, msg_t *msg); +void SV_SendMessageToClient( msg_t *msg, client_t *client ); +void SV_SendClientMessages( void ); +void SV_SendClientSnapshot( client_t *client ); + +// +// sv_game.c +// +int SV_NumForGentity( sharedEntity_t *ent ); +sharedEntity_t *SV_GentityNum( int num ); +playerState_t *SV_GameClientNum( int num ); +svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ); +sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ); +void SV_InitGameProgs ( void ); +void SV_ShutdownGameProgs ( void ); +void SV_RestartGameProgs( void ); +qboolean SV_inPVS (const vec3_t p1, const vec3_t p2); + +//============================================================ +// +// high level object sorting to reduce interaction tests +// + +void SV_ClearWorld (void); +// called after the world model has been loaded, before linking any entities + +void SV_UnlinkEntity( sharedEntity_t *ent ); +// call before removing an entity, and before trying to move one, +// so it doesn't clip against itself + +void SV_LinkEntity( sharedEntity_t *ent ); +// Needs to be called any time an entity changes origin, mins, maxs, +// or solid. Automatically unlinks if needed. +// sets ent->v.absmin and ent->v.absmax +// sets ent->leafnums[] for pvs determination even if the entity +// is not solid + + +clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ); + + +void SV_SectorList_f( void ); + + +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ); +// fills in a table of entity numbers with entities that have bounding boxes +// that intersect the given area. It is possible for a non-axial bmodel +// to be returned that doesn't actually intersect the area on an exact +// test. +// returns the number of pointers filled in +// The world entity is never returned in this list. + + +int SV_PointContents( const vec3_t p, int passEntityNum ); +// returns the CONTENTS_* value from the world and all entities at the given point. + + +void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, traceType_t type ); +// mins and maxs are relative + +// if the entire move stays in a solid volume, trace.allsolid will be set, +// trace.startsolid will be set, and trace.fraction will be 0 + +// if the starting point is in a solid, it will be allowed to move out +// to an open area + +// passEntityNum is explicitly excluded from clipping checks (normally ENTITYNUM_NONE) + + +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, traceType_t type ); +// clip to a specific entity + +// +// sv_net_chan.c +// +void SV_Netchan_Transmit( client_t *client, msg_t *msg); +void SV_Netchan_TransmitNextFragment( client_t *client ); +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ); + diff --git a/src/server/sv_ccmds.c b/src/server/sv_ccmds.c new file mode 100644 index 0000000..8e76fa1 --- /dev/null +++ b/src/server/sv_ccmds.c @@ -0,0 +1,326 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "server.h" + +/* +=============================================================================== + +OPERATOR CONSOLE ONLY COMMANDS + +These commands can only be entered from stdin or by a remote operator datagram +=============================================================================== +*/ + +/* +================== +SV_Map_f + +Restart the server on a different map +================== +*/ +static void SV_Map_f( void ) { + char *cmd; + char *map; + qboolean killBots, cheat; + char expanded[MAX_QPATH]; + char mapname[MAX_QPATH]; + int i; + + map = Cmd_Argv(1); + if ( !map ) { + return; + } + + // make sure the level exists before trying to change, so that + // a typo at the server console won't end the game + Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map); + if ( FS_ReadFile (expanded, NULL) == -1 ) { + Com_Printf ("Can't find map %s\n", expanded); + return; + } + + cmd = Cmd_Argv(0); + if ( !Q_stricmp( cmd, "devmap" ) ) { + cheat = qtrue; + killBots = qtrue; + } else { + cheat = qfalse; + killBots = qfalse; + } + + // save the map name here cause on a map restart we reload the autogen.cfg + // and thus nuke the arguments of the map command + Q_strncpyz(mapname, map, sizeof(mapname)); + + // start up the map + SV_SpawnServer( mapname, killBots ); + + // set the cheat value + // if the level was started with "map ", then + // cheats will not be allowed. If started with "devmap " + // then cheats will be allowed + if ( cheat ) { + Cvar_Set( "sv_cheats", "1" ); + } else { + Cvar_Set( "sv_cheats", "0" ); + } + + // This forces the local master server IP address cache + // to be updated on sending the next heartbeat + for( i = 0; i < MAX_MASTER_SERVERS; i++ ) + sv_master[ i ]->modified = qtrue; +} + +/* +================ +SV_MapRestart_f + +Completely restarts a level, but doesn't send a new gamestate to the clients. +This allows fair starts with variable load times. +================ +*/ +static void SV_MapRestart_f( void ) { + int i; + client_t *client; + char *denied; + int delay; + + // make sure we aren't restarting twice in the same frame + if ( com_frameTime == sv.serverId ) { + return; + } + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( sv.restartTime ) { + return; + } + + if (Cmd_Argc() > 1 ) { + delay = atoi( Cmd_Argv(1) ); + } + else { + delay = 5; + } + if( delay && !Cvar_VariableValue("g_doWarmup") ) { + sv.restartTime = sv.time + delay * 1000; + SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) ); + return; + } + + // check for changes in variables that can't just be restarted + // check for maxclients change + if ( sv_maxclients->modified ) { + char mapname[MAX_QPATH]; + + Com_Printf( "variable change -- restarting.\n" ); + // restart the map the slow way + Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) ); + + SV_SpawnServer( mapname, qfalse ); + return; + } + + // toggle the server bit so clients can detect that a + // map_restart has happened + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + // generate a new serverid + // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart + sv.serverId = com_frameTime; + Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); + + // if a map_restart occurs while a client is changing maps, we need + // to give them the correct time so that when they finish loading + // they don't violate the backwards time check in cl_cgame.c + for (i=0 ; iinteger ; i++) { + if (svs.clients[i].state == CS_PRIMED) { + svs.clients[i].oldServerTime = sv.restartTime; + } + } + + // reset all the vm data in place without changing memory allocation + // note that we do NOT set sv.state = SS_LOADING, so configstrings that + // had been changed from their default values will generate broadcast updates + sv.state = SS_LOADING; + sv.restarting = qtrue; + + SV_RestartGameProgs(); + + // run a few frames to allow everything to settle + for (i = 0; i < 3; i++) + { + VM_Call (gvm, GAME_RUN_FRAME, sv.time); + sv.time += 100; + svs.time += 100; + } + + sv.state = SS_GAME; + sv.restarting = qfalse; + + // connect and begin all the clients + for (i=0 ; iinteger ; i++) { + client = &svs.clients[i]; + + // send the new gamestate to all connected clients + if ( client->state < CS_CONNECTED) { + continue; + } + + // add the map_restart command + SV_AddServerCommand( client, "map_restart\n" ); + + // connect the client again, without the firstTime flag + denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse ) ); + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( client, denied ); + Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i ); + continue; + } + + if(client->state == CS_ACTIVE) + SV_ClientEnterWorld(client, &client->lastUsercmd); + else + { + // If we don't reset client->lastUsercmd and are restarting during map load, + // the client will hang because we'll use the last Usercmd from the previous map, + // which is wrong obviously. + SV_ClientEnterWorld(client, NULL); + } + } + + // run another frame to allow things to look at all the players + VM_Call (gvm, GAME_RUN_FRAME, sv.time); + sv.time += 100; + svs.time += 100; +} + + +/* +================== +SV_Heartbeat_f + +Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer +================== +*/ +void SV_Heartbeat_f( void ) { + svs.nextHeartbeatTime = -9999999; +} + + +/* +=========== +SV_Serverinfo_f + +Examine the serverinfo string +=========== +*/ +static void SV_Serverinfo_f( void ) { + Com_Printf ("Server info settings:\n"); + Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) ); +} + + +/* +=========== +SV_Systeminfo_f + +Examine or change the serverinfo string +=========== +*/ +static void SV_Systeminfo_f( void ) { + Com_Printf ("System info settings:\n"); + Info_Print ( Cvar_InfoString_Big( CVAR_SYSTEMINFO ) ); +} + + +/* +================= +SV_KillServer +================= +*/ +static void SV_KillServer_f( void ) { + SV_Shutdown( "killserver" ); +} + +//=========================================================== + +/* +================== +SV_CompleteMapName +================== +*/ +static void SV_CompleteMapName( char *args, int argNum ) { + if( argNum == 2 ) { + Field_CompleteFilename( "maps", "bsp", qtrue, qfalse ); + } +} + +/* +================== +SV_AddOperatorCommands +================== +*/ +void SV_AddOperatorCommands( void ) { + static qboolean initialized; + + if ( initialized ) { + return; + } + initialized = qtrue; + + Cmd_AddCommand ("heartbeat", SV_Heartbeat_f); + Cmd_AddCommand ("serverinfo", SV_Serverinfo_f); + Cmd_AddCommand ("systeminfo", SV_Systeminfo_f); + Cmd_AddCommand ("map_restart", SV_MapRestart_f); + Cmd_AddCommand ("sectorlist", SV_SectorList_f); + Cmd_AddCommand ("map", SV_Map_f); + Cmd_SetCommandCompletionFunc( "map", SV_CompleteMapName ); + Cmd_AddCommand ("devmap", SV_Map_f); + Cmd_SetCommandCompletionFunc( "devmap", SV_CompleteMapName ); + Cmd_AddCommand ("killserver", SV_KillServer_f); +} + +/* +================== +SV_RemoveOperatorCommands +================== +*/ +void SV_RemoveOperatorCommands( void ) { +#if 0 + // removing these won't let the server start again + Cmd_RemoveCommand ("heartbeat"); + Cmd_RemoveCommand ("serverinfo"); + Cmd_RemoveCommand ("systeminfo"); + Cmd_RemoveCommand ("map_restart"); + Cmd_RemoveCommand ("sectorlist"); +#endif +} + diff --git a/src/server/sv_client.c b/src/server/sv_client.c new file mode 100644 index 0000000..a79fc2b --- /dev/null +++ b/src/server/sv_client.c @@ -0,0 +1,1699 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// sv_client.c -- server code for dealing with clients + +#include "server.h" + +static void SV_CloseDownload( client_t *cl ); + +/* +================= +SV_GetChallenge + +A "getchallenge" OOB command has been received +Returns a challenge number that can be used +in a subsequent connectResponse command. +We do this to prevent denial of service attacks that +flood the server with invalid connection IPs. With a +challenge, they must give a valid IP address. + +If we are authorizing, a challenge request will cause a packet +to be sent to the authorize server. + +When an authorizeip is returned, a challenge response will be +sent to that ip. + +ioquake3: we added a possibility for clients to add a challenge +to their packets, to make it more difficult for malicious servers +to hi-jack client connections. +Also, the auth stuff is completely disabled for com_standalone games +as well as IPv6 connections, since there is no way to use the +v4-only auth server for these new types of connections. +================= +*/ +void SV_GetChallenge(netadr_t from) +{ + int i; + int oldest; + int oldestTime; + const char *clientChallenge = Cmd_Argv(1); + challenge_t *challenge; + + oldest = 0; + oldestTime = 0x7fffffff; + + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { + if (!challenge->connected && NET_CompareAdr( from, challenge->adr ) ) { + break; + } + if ( challenge->time < oldestTime ) { + oldestTime = challenge->time; + oldest = i; + } + } + + if (i == MAX_CHALLENGES) + { + // this is the first time this client has asked for a challenge + challenge = &svs.challenges[oldest]; + challenge->clientChallenge = 0; + challenge->adr = from; + challenge->firstTime = svs.time; + challenge->time = svs.time; + challenge->connected = qfalse; + } + + // always generate a new challenge number, so the client cannot circumvent sv_maxping + challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time; + challenge->wasrefused = qfalse; + + challenge->pingTime = svs.time; + NET_OutOfBandPrint( NS_SERVER, challenge->adr, "challengeResponse %i %s", challenge->challenge, clientChallenge); +} + +/* +================== +SV_DirectConnect + +A "connect" OOB command has been received +================== +*/ +void SV_DirectConnect( netadr_t from ) { + char userinfo[MAX_INFO_STRING]; + int i; + client_t *cl, *newcl; + client_t temp; + sharedEntity_t *ent; + int clientNum; + int version; + int qport; + int challenge; + char *password; + int startIndex; + intptr_t denied; + int count; + char *ip; + + Com_DPrintf ("SVC_DirectConnect ()\n"); + + Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); + + version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); + if ( version != PROTOCOL_VERSION ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i\n", PROTOCOL_VERSION ); + Com_DPrintf (" rejected connect from version %i\n", version); + return; + } + + challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); + qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); + + // quick reject + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state == CS_FREE ) { + continue; + } + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) { + if (( svs.time - cl->lastConnectTime) + < (sv_reconnectlimit->integer * 1000)) { + Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); + return; + } + break; + } + } + + // don't let "ip" overflow userinfo string + if ( NET_IsLocalAddress (from) ) + ip = "localhost"; + else + ip = (char *)NET_AdrToString( from ); + if( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { + NET_OutOfBandPrint( NS_SERVER, from, + "print\nUserinfo string length exceeded. " + "Try removing setu cvars from your config.\n" ); + return; + } + Info_SetValueForKey( userinfo, "ip", ip ); + + // see if the challenge is valid (LAN clients don't need to challenge) + if (!NET_IsLocalAddress(from)) + { + int ping; + challenge_t *challengeptr; + + for (i=0; iwasrefused) + { + // Return silently, so that error messages written by the server keep being displayed. + return; + } + + ping = svs.time - challengeptr->pingTime; + + // never reject a LAN client based on ping + if ( !Sys_IsLANAddress( from ) ) { + if ( sv_minPing->value && ping < sv_minPing->value ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" ); + Com_DPrintf ("Client %i rejected on a too low ping\n", i); + challengeptr->wasrefused = qtrue; + return; + } + if ( sv_maxPing->value && ping > sv_maxPing->value ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" ); + Com_DPrintf ("Client %i rejected on a too high ping\n", i); + challengeptr->wasrefused = qtrue; + return; + } + } + + Com_Printf("Client %i connecting with %i challenge ping\n", i, ping); + challengeptr->connected = qtrue; + } + + newcl = &temp; + Com_Memset (newcl, 0, sizeof(client_t)); + + // if there is already a slot for this ip, reuse it + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state == CS_FREE ) { + continue; + } + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) { + Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); + newcl = cl; + + // this doesn't work because it nukes the players userinfo + +// // disconnect the client from the game first so any flags the +// // player might have are dropped +// VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); + // + goto gotnewcl; + } + } + + // find a client slot + // if "sv_privateClients" is set > 0, then that number + // of client slots will be reserved for connections that + // have "password" set to the value of "sv_privatePassword" + // Info requests will report the maxclients as if the private + // slots didn't exist, to prevent people from trying to connect + // to a full server. + // This is to allow us to reserve a couple slots here on our + // servers so we can play without having to kick people. + + // check for privateClient password + password = Info_ValueForKey( userinfo, "password" ); + if ( !strcmp( password, sv_privatePassword->string ) ) { + startIndex = 0; + } else { + // skip past the reserved slots + startIndex = sv_privateClients->integer; + } + + newcl = NULL; + for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if (cl->state == CS_FREE) { + newcl = cl; + break; + } + } + + if ( !newcl ) { + if ( NET_IsLocalAddress( from ) ) { + Com_Error( ERR_FATAL, "server is full on local connect\n" ); + return; + } + else { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full\n" ); + Com_DPrintf ("Rejected a connection.\n"); + return; + } + } + + // we got a newcl, so reset the reliableSequence and reliableAcknowledge + cl->reliableAcknowledge = 0; + cl->reliableSequence = 0; + +gotnewcl: + // build a new connection + // accept the new client + // this is the only place a client_t is ever initialized + *newcl = temp; + clientNum = newcl - svs.clients; + ent = SV_GentityNum( clientNum ); + newcl->gentity = ent; + + // save the challenge + newcl->challenge = challenge; + + // save the address + Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); + // init the netchan queue + newcl->netchan_end_queue = &newcl->netchan_start_queue; + + // save the userinfo + Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); + + // get the game a chance to reject this connection or modify the userinfo + denied = VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue ); // firstTime = qtrue + if ( denied ) { + // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call + char *str = VM_ExplicitArgPtr( gvm, denied ); + + NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", str ); + Com_DPrintf ("Game rejected a connection: %s.\n", str); + return; + } + + SV_UserinfoChanged( newcl ); + + // send the connect packet to the client + NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); + + Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); + + newcl->state = CS_CONNECTED; + newcl->nextSnapshotTime = svs.time; + newcl->lastPacketTime = svs.time; + newcl->lastConnectTime = svs.time; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + newcl->gamestateMessageNum = -1; + + // if this was the first client on the server, or the last client + // the server can hold, send a heartbeat to the master. + count = 0; + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + if ( count == 1 || count == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + + +/* +===================== +SV_DropClient + +Called when the player is totally leaving the server, either willingly +or unwillingly. This is NOT called if the entire server is quiting +or crashing -- SV_FinalMessage() will handle that +===================== +*/ +void SV_DropClient( client_t *drop, const char *reason ) { + int i; + challenge_t *challenge; + + if ( drop->state == CS_ZOMBIE ) { + return; // already dropped + } + + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + + for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { + if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) { + Com_Memset(challenge, 0, sizeof(*challenge)); + break; + } + } + + // Kill any download + SV_CloseDownload( drop ); + + // tell everyone why they got dropped + SV_SendServerCommand( NULL, "print \"%s" S_COLOR_WHITE " %s\n\"", drop->name, reason ); + + if (drop->download) { + FS_FCloseFile( drop->download ); + drop->download = 0; + } + + // call the prog function for removing a client + // this will remove the body, among other things + VM_Call( gvm, GAME_CLIENT_DISCONNECT, drop - svs.clients ); + + // add the disconnect command + SV_SendServerCommand( drop, "disconnect \"%s\"", reason); + + // nuke user info + SV_SetUserinfo( drop - svs.clients, "" ); + + Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); + drop->state = CS_ZOMBIE; // become free in a few seconds + + // if this was the last client on the server, send a heartbeat + // to the master so it is known the server is empty + // send a heartbeat now so the master will get up to date info + // if there is already a slot for this ip, reuse it + for (i=0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + break; + } + } + if ( i == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + +/* +================ +SV_SendClientGameState + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each new map load. + +It will be resent if the client acknowledges a later message but has +the wrong gamestate. +================ +*/ +static void SV_SendClientGameState( client_t *client ) { + int start; + entityState_t *base, nullstate; + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); + Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); + client->state = CS_PRIMED; + client->pureAuthentic = 0; + client->gotCP = qfalse; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + client->gamestateMessageNum = client->netchan.outgoingSequence; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // send the gamestate + MSG_WriteByte( &msg, svc_gamestate ); + MSG_WriteLong( &msg, client->reliableSequence ); + + // write the configstrings + for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { + if (sv.configstrings[start].s[0]) { + MSG_WriteByte( &msg, svc_configstring ); + MSG_WriteShort( &msg, start ); + MSG_WriteBigString( &msg, sv.configstrings[start].s ); + } + } + + // write the baselines + Com_Memset( &nullstate, 0, sizeof( nullstate ) ); + for ( start = 0 ; start < MAX_GENTITIES; start++ ) { + base = &sv.svEntities[start].baseline; + if ( !base->number ) { + continue; + } + MSG_WriteByte( &msg, svc_baseline ); + MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); + } + + MSG_WriteByte( &msg, svc_EOF ); + + MSG_WriteLong( &msg, client - svs.clients); + + // write the checksum feed + MSG_WriteLong( &msg, sv.checksumFeed); + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + + +/* +================== +SV_ClientEnterWorld +================== +*/ +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { + int clientNum; + sharedEntity_t *ent; + + Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); + client->state = CS_ACTIVE; + + // resend all configstrings using the cs commands since these are + // no longer sent when the client is CS_PRIMED + SV_UpdateConfigstrings( client ); + + // set up the entity for the client + clientNum = client - svs.clients; + ent = SV_GentityNum( clientNum ); + ent->s.number = clientNum; + client->gentity = ent; + + client->deltaMessage = -1; + client->nextSnapshotTime = svs.time; // generate a snapshot immediately + + if(cmd) + memcpy(&client->lastUsercmd, cmd, sizeof(client->lastUsercmd)); + else + memset(&client->lastUsercmd, '\0', sizeof(client->lastUsercmd)); + + // call the game begin function + VM_Call( gvm, GAME_CLIENT_BEGIN, client - svs.clients ); +} + +/* +============================================================ + +CLIENT COMMAND EXECUTION + +============================================================ +*/ + +/* +================== +SV_CloseDownload + +clear/free any download vars +================== +*/ +static void SV_CloseDownload( client_t *cl ) { + int i; + + // EOF + if (cl->download) { + FS_FCloseFile( cl->download ); + } + cl->download = 0; + *cl->downloadName = 0; + + // Free the temporary buffer space + for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) { + if (cl->downloadBlocks[i]) { + Z_Free( cl->downloadBlocks[i] ); + cl->downloadBlocks[i] = NULL; + } + } + +} + +/* +================== +SV_StopDownload_f + +Abort a download if in progress +================== +*/ +static void SV_StopDownload_f( client_t *cl ) { + if (*cl->downloadName) + Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", (int) (cl - svs.clients), cl->downloadName ); + + SV_CloseDownload( cl ); +} + +/* +================== +SV_DoneDownload_f + +Downloads are finished +================== +*/ +static void SV_DoneDownload_f( client_t *cl ) { + Com_DPrintf( "clientDownload: %s Done\n", cl->name); + // resend the game state to update any clients that entered during the download + SV_SendClientGameState(cl); +} + +/* +================== +SV_NextDownload_f + +The argument will be the last acknowledged block from the client, it should be +the same as cl->downloadClientBlock +================== +*/ +static void SV_NextDownload_f( client_t *cl ) +{ + int block = atoi( Cmd_Argv(1) ); + + if (block == cl->downloadClientBlock) { + Com_DPrintf( "clientDownload: %d : client acknowledge of block %d\n", (int) (cl - svs.clients), block ); + + // Find out if we are done. A zero-length block indicates EOF + if (cl->downloadBlockSize[cl->downloadClientBlock % MAX_DOWNLOAD_WINDOW] == 0) { + Com_Printf( "clientDownload: %d : file \"%s\" completed\n", (int) (cl - svs.clients), cl->downloadName ); + SV_CloseDownload( cl ); + return; + } + + cl->downloadSendTime = svs.time; + cl->downloadClientBlock++; + return; + } + // We aren't getting an acknowledge for the correct block, drop the client + // FIXME: this is bad... the client will never parse the disconnect message + // because the cgame isn't loaded yet + SV_DropClient( cl, "broken download" ); +} + +/* +================== +SV_BeginDownload_f +================== +*/ +static void SV_BeginDownload_f( client_t *cl ) { + + // Kill any existing download + SV_CloseDownload( cl ); + + // cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open + // the file itself + Q_strncpyz( cl->downloadName, Cmd_Argv(1), sizeof(cl->downloadName) ); +} + +/* +================== +SV_WriteDownloadToClient + +Check to see if the client wants a file, open it if needed and start pumping the client +Fill up msg with data +================== +*/ +void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) +{ + int curindex; + int rate; + int blockspersnap; + int unreferenced = 1; + char errorMessage[1024]; + char pakbuf[MAX_QPATH], *pakptr; + int numRefPaks; + + if (!*cl->downloadName) + return; // Nothing being downloaded + + if(!cl->download) + { + // Chop off filename extension. + Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); + pakptr = Q_strrchr(pakbuf, '.'); + + if(pakptr) + { + *pakptr = '\0'; + + // Check for pk3 filename extension + if(!Q_stricmp(pakptr + 1, "pk3")) + { + const char *referencedPaks = FS_ReferencedPakNames(); + + // Check whether the file appears in the list of referenced + // paks to prevent downloading of arbitrary files. + Cmd_TokenizeStringIgnoreQuotes(referencedPaks); + numRefPaks = Cmd_Argc(); + + for(curindex = 0; curindex < numRefPaks; curindex++) + { + if(!FS_FilenameCompare(Cmd_Argv(curindex), pakbuf)) + { + unreferenced = 0; + break; + } + } + } + } + + cl->download = 0; + + // We open the file here + if ( !(sv_allowDownload->integer & DLF_ENABLE) || + (sv_allowDownload->integer & DLF_NO_UDP) || + unreferenced || + ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) < 0 ) { + // cannot auto-download file + if(unreferenced) + { + Com_Printf("clientDownload: %d : \"%s\" is not referenced and cannot be downloaded.\n", (int) (cl - svs.clients), cl->downloadName); + Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" is not referenced and cannot be downloaded.", cl->downloadName); + } + else if ( !(sv_allowDownload->integer & DLF_ENABLE) || + (sv_allowDownload->integer & DLF_NO_UDP) ) { + + Com_Printf("clientDownload: %d : \"%s\" download disabled", (int) (cl - svs.clients), cl->downloadName); + if (sv_pure->integer) { + Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "You will need to get this file elsewhere before you " + "can connect to this pure server.\n", cl->downloadName); + } else { + Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "The server you are connecting to is not a pure server, " + "set autodownload to No in your settings and you might be " + "able to join the game anyway.\n", cl->downloadName); + } + } else { + // NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme? + // if the pk3 is referenced, it must have been found somewhere in the filesystem + Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", (int) (cl - svs.clients), cl->downloadName); + Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName); + } + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, 0 ); // client is expecting block zero + MSG_WriteLong( msg, -1 ); // illegal file size + MSG_WriteString( msg, errorMessage ); + + *cl->downloadName = 0; + + if(cl->download) + FS_FCloseFile(cl->download); + + return; + } + + Com_Printf( "clientDownload: %d : beginning \"%s\"\n", (int) (cl - svs.clients), cl->downloadName ); + + // Init + cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; + cl->downloadCount = 0; + cl->downloadEOF = qfalse; + } + + // Perform any reads that we need to + while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && + cl->downloadSize != cl->downloadCount) { + + curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); + + if (!cl->downloadBlocks[curindex]) + cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE ); + + cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); + + if (cl->downloadBlockSize[curindex] < 0) { + // EOF right now + cl->downloadCount = cl->downloadSize; + break; + } + + cl->downloadCount += cl->downloadBlockSize[curindex]; + + // Load in next block + cl->downloadCurrentBlock++; + } + + // Check to see if we have eof condition and add the EOF block + if (cl->downloadCount == cl->downloadSize && + !cl->downloadEOF && + cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) { + + cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; + cl->downloadCurrentBlock++; + + cl->downloadEOF = qtrue; // We have added the EOF block + } + + // Loop up to window size times based on how many blocks we can fit in the + // client snapMsec and rate + + // based on the rate, how many bytes can we fit in the snapMsec time of the client + // normal rate / snapshotMsec calculation + rate = cl->rate; + if ( sv_maxRate->integer ) { + if ( sv_maxRate->integer < 1000 ) { + Cvar_Set( "sv_MaxRate", "1000" ); + } + if ( sv_maxRate->integer < rate ) { + rate = sv_maxRate->integer; + } + } + if ( sv_minRate->integer ) { + if ( sv_minRate->integer < 1000 ) + Cvar_Set( "sv_minRate", "1000" ); + if ( sv_minRate->integer > rate ) + rate = sv_minRate->integer; + } + + if (!rate) { + blockspersnap = 1; + } else { + blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / + MAX_DOWNLOAD_BLKSIZE; + } + + if (blockspersnap < 0) + blockspersnap = 1; + + while (blockspersnap--) { + + // Write out the next section of the file, if we have already reached our window, + // automatically start retransmitting + + if (cl->downloadClientBlock == cl->downloadCurrentBlock) + return; // Nothing to transmit + + if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { + // We have transmitted the complete window, should we start resending? + + //FIXME: This uses a hardcoded one second timeout for lost blocks + //the timeout should be based on client rate somehow + if (svs.time - cl->downloadSendTime > 1000) + cl->downloadXmitBlock = cl->downloadClientBlock; + else + return; + } + + // Send current block + curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); + + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, cl->downloadXmitBlock ); + + // block zero is special, contains file size + if ( cl->downloadXmitBlock == 0 ) + MSG_WriteLong( msg, cl->downloadSize ); + + MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); + + // Write the block + if ( cl->downloadBlockSize[curindex] ) { + MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] ); + } + + Com_DPrintf( "clientDownload: %d : writing block %d\n", (int) (cl - svs.clients), cl->downloadXmitBlock ); + + // Move on to the next block + // It will get sent with next snap shot. The rate will keep us in line. + cl->downloadXmitBlock++; + + cl->downloadSendTime = svs.time; + } +} + +#ifdef USE_VOIP +/* +================== +SV_WriteVoipToClient + +Check to see if there is any VoIP queued for a client, and send if there is. +================== +*/ +void SV_WriteVoipToClient( client_t *cl, msg_t *msg ) +{ + voipServerPacket_t *packet = &cl->voipPacket[0]; + int totalbytes = 0; + int i; + + if (*cl->downloadName) { + cl->queuedVoipPackets = 0; + return; // no VoIP allowed if download is going, to save bandwidth. + } + + // Write as many VoIP packets as we reasonably can... + for (i = 0; i < cl->queuedVoipPackets; i++, packet++) { + totalbytes += packet->len; + if (totalbytes > MAX_DOWNLOAD_BLKSIZE) + break; + + // You have to start with a svc_EOF, so legacy clients drop the + // rest of this packet. Otherwise, those without VoIP support will + // see the svc_voip command, then panic and disconnect. + // Generally we don't send VoIP packets to legacy clients, but this + // serves as both a safety measure and a means to keep demo files + // compatible. + MSG_WriteByte( msg, svc_EOF ); + MSG_WriteByte( msg, svc_extension ); + MSG_WriteByte( msg, svc_voip ); + MSG_WriteShort( msg, packet->sender ); + MSG_WriteByte( msg, (byte) packet->generation ); + MSG_WriteLong( msg, packet->sequence ); + MSG_WriteByte( msg, packet->frames ); + MSG_WriteShort( msg, packet->len ); + MSG_WriteData( msg, packet->data, packet->len ); + } + + // !!! FIXME: I hate this queue system. + cl->queuedVoipPackets -= i; + if (cl->queuedVoipPackets > 0) { + memmove( &cl->voipPacket[0], &cl->voipPacket[i], + sizeof (voipServerPacket_t) * i); + } +} +#endif + + +/* +================= +SV_Disconnect_f + +The client is going to disconnect, so remove the connection immediately FIXME: move to game? +================= +*/ +static void SV_Disconnect_f( client_t *cl ) { + SV_DropClient( cl, "disconnected" ); +} + +/* +================= +SV_VerifyPaks_f + +If we are pure, disconnect the client if they do no meet the following conditions: + +1. the first two checksums match our view of cgame and ui +2. there are no any additional checksums that we do not have + +This routine would be a bit simpler with a goto but i abstained + +================= +*/ +static void SV_VerifyPaks_f( client_t *cl ) { + int nChkSum1, nChkSum2, nClientPaks, nServerPaks, i, j, nCurArg; + int nClientChkSum[1024]; + int nServerChkSum[1024]; + const char *pPaks, *pArg; + qboolean bGood = qtrue; + + // if we are pure, we "expect" the client to load certain things from + // certain pk3 files, namely we want the client to have loaded the + // ui and cgame that we think should be loaded based on the pure setting + // + if ( sv_pure->integer != 0 ) { + + bGood = qtrue; + nChkSum1 = nChkSum2 = 0; + // we run the game, so determine which cgame and ui the client "should" be running + bGood = (FS_FileIsInPAK("vm/cgame.qvm", &nChkSum1) == 1); + if (bGood) + bGood = (FS_FileIsInPAK("vm/ui.qvm", &nChkSum2) == 1); + + nClientPaks = Cmd_Argc(); + + // start at arg 2 ( skip serverId cl_paks ) + nCurArg = 1; + + pArg = Cmd_Argv(nCurArg++); + if(!pArg) { + bGood = qfalse; + } + else + { + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // we may get incoming cp sequences from a previous checksumFeed, which we need to ignore + // since serverId is a frame count, it always goes up + if (atoi(pArg) < sv.checksumFeedServerId) + { + Com_DPrintf("ignoring outdated cp command from client %s\n", cl->name); + return; + } + } + + // we basically use this while loop to avoid using 'goto' :) + while (bGood) { + + // must be at least 6: "cl_paks cgame ui @ firstref ... numChecksums" + // numChecksums is encoded + if (nClientPaks < 6) { + bGood = qfalse; + break; + } + // verify first to be the cgame checksum + pArg = Cmd_Argv(nCurArg++); + if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum1 ) { + bGood = qfalse; + break; + } + // verify the second to be the ui checksum + pArg = Cmd_Argv(nCurArg++); + if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum2 ) { + bGood = qfalse; + break; + } + // should be sitting at the delimeter now + pArg = Cmd_Argv(nCurArg++); + if (*pArg != '@') { + bGood = qfalse; + break; + } + // store checksums since tokenization is not re-entrant + for (i = 0; nCurArg < nClientPaks; i++) { + nClientChkSum[i] = atoi(Cmd_Argv(nCurArg++)); + } + + // store number to compare against (minus one cause the last is the number of checksums) + nClientPaks = i - 1; + + // make sure none of the client check sums are the same + // so the client can't send 5 the same checksums + for (i = 0; i < nClientPaks; i++) { + for (j = 0; j < nClientPaks; j++) { + if (i == j) + continue; + if (nClientChkSum[i] == nClientChkSum[j]) { + bGood = qfalse; + break; + } + } + if (bGood == qfalse) + break; + } + if (bGood == qfalse) + break; + + // get the pure checksums of the pk3 files loaded by the server + pPaks = FS_LoadedPakPureChecksums(); + Cmd_TokenizeString( pPaks ); + nServerPaks = Cmd_Argc(); + if (nServerPaks > 1024) + nServerPaks = 1024; + + for (i = 0; i < nServerPaks; i++) { + nServerChkSum[i] = atoi(Cmd_Argv(i)); + } + + // check if the client has provided any pure checksums of pk3 files not loaded by the server + for (i = 0; i < nClientPaks; i++) { + for (j = 0; j < nServerPaks; j++) { + if (nClientChkSum[i] == nServerChkSum[j]) { + break; + } + } + if (j >= nServerPaks) { + bGood = qfalse; + break; + } + } + if ( bGood == qfalse ) { + break; + } + + // check if the number of checksums was correct + nChkSum1 = sv.checksumFeed; + for (i = 0; i < nClientPaks; i++) { + nChkSum1 ^= nClientChkSum[i]; + } + nChkSum1 ^= nClientPaks; + if (nChkSum1 != nClientChkSum[nClientPaks]) { + bGood = qfalse; + break; + } + + // break out + break; + } + + cl->gotCP = qtrue; + + if (bGood) { + cl->pureAuthentic = 1; + } + else { + cl->pureAuthentic = 0; + cl->nextSnapshotTime = -1; + cl->state = CS_ACTIVE; + SV_SendClientSnapshot( cl ); + SV_SendServerCommand( cl, "disconnect \"Unpure Client. " + "You may need to enable in-game downloads " + "to connect to this server (set " + "cl_allowDownload 1)\"" ); + SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" ); + } + } +} + +/* +================= +SV_ResetPureClient_f +================= +*/ +static void SV_ResetPureClient_f( client_t *cl ) { + cl->pureAuthentic = 0; + cl->gotCP = qfalse; +} + +/* +================= +SV_UserinfoChanged + +Pull specific info from a newly changed userinfo string +into a more C friendly form. +================= +*/ +void SV_UserinfoChanged( client_t *cl ) { + char *val; + char *ip; + int i; + int len; + + // name for C code + Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) ); + + // rate command + + // if the client is on the same subnet as the server and we aren't running an + // internet public server, assume they don't need a rate choke + if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 && sv_lanForceRate->integer == 1) { + cl->rate = 99999; // lans should not rate limit + } else { + val = Info_ValueForKey (cl->userinfo, "rate"); + if (strlen(val)) { + i = atoi(val); + cl->rate = i; + if (cl->rate < 1000) { + cl->rate = 1000; + } else if (cl->rate > 90000) { + cl->rate = 90000; + } + } else { + cl->rate = 3000; + } + } + val = Info_ValueForKey (cl->userinfo, "handicap"); + if (strlen(val)) { + i = atoi(val); + if (i<=0 || i>100 || strlen(val) > 4) { + Info_SetValueForKey( cl->userinfo, "handicap", "100" ); + } + } + + // snaps command + val = Info_ValueForKey (cl->userinfo, "snaps"); + if (strlen(val)) { + i = atoi(val); + if ( i < 1 ) { + i = 1; + } else if ( i > sv_fps->integer ) { + i = sv_fps->integer; + } + cl->snapshotMsec = 1000/i; + } else { + cl->snapshotMsec = 50; + } + +#ifdef USE_VOIP + // in the future, (val) will be a protocol version string, so only + // accept explicitly 1, not generally non-zero. + val = Info_ValueForKey (cl->userinfo, "cl_voip"); + cl->hasVoip = (atoi(val) == 1) ? qtrue : qfalse; +#endif + + // TTimo + // maintain the IP information + // the banning code relies on this being consistently present + if( NET_IsLocalAddress(cl->netchan.remoteAddress) ) + ip = "localhost"; + else + ip = (char*)NET_AdrToString( cl->netchan.remoteAddress ); + + val = Info_ValueForKey( cl->userinfo, "ip" ); + if( val[0] ) + len = strlen( ip ) - strlen( val ) + strlen( cl->userinfo ); + else + len = strlen( ip ) + 4 + strlen( cl->userinfo ); + + if( len >= MAX_INFO_STRING ) + SV_DropClient( cl, "userinfo string length exceeded" ); + else + Info_SetValueForKey( cl->userinfo, "ip", ip ); + +} + + +/* +================== +SV_UpdateUserinfo_f +================== +*/ +static void SV_UpdateUserinfo_f( client_t *cl ) { + Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); + + SV_UserinfoChanged( cl ); + // call prog code to allow overrides + VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); +} + + +#ifdef USE_VOIP +static +void SV_UpdateVoipIgnore(client_t *cl, const char *idstr, qboolean ignore) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if ((id >= 0) && (id < MAX_CLIENTS)) { + cl->ignoreVoipFromClient[id] = ignore; + } + } +} + +/* +================== +SV_Voip_f +================== +*/ +static void SV_Voip_f( client_t *cl ) { + const char *cmd = Cmd_Argv(1); + if (strcmp(cmd, "ignore") == 0) { + SV_UpdateVoipIgnore(cl, Cmd_Argv(2), qtrue); + } else if (strcmp(cmd, "unignore") == 0) { + SV_UpdateVoipIgnore(cl, Cmd_Argv(2), qfalse); + } else if (strcmp(cmd, "muteall") == 0) { + cl->muteAllVoip = qtrue; + } else if (strcmp(cmd, "unmuteall") == 0) { + cl->muteAllVoip = qfalse; + } +} +#endif + + +typedef struct { + char *name; + void (*func)( client_t *cl ); +} ucmd_t; + +static ucmd_t ucmds[] = { + {"userinfo", SV_UpdateUserinfo_f}, + {"disconnect", SV_Disconnect_f}, + {"cp", SV_VerifyPaks_f}, + {"vdr", SV_ResetPureClient_f}, + {"download", SV_BeginDownload_f}, + {"nextdl", SV_NextDownload_f}, + {"stopdl", SV_StopDownload_f}, + {"donedl", SV_DoneDownload_f}, + +#ifdef USE_VOIP + {"voip", SV_Voip_f}, +#endif + + {NULL, NULL} +}; + +/* +================== +SV_ExecuteClientCommand + +Also called by bot code +================== +*/ +void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) { + ucmd_t *u; + qboolean bProcessed = qfalse; + + Cmd_TokenizeString( s ); + + // see if it is a server level command + for (u=ucmds ; u->name ; u++) { + if (!strcmp (Cmd_Argv(0), u->name) ) { + u->func( cl ); + bProcessed = qtrue; + break; + } + } + + if (clientOK) { + // pass unknown strings to the game + if (!u->name && sv.state == SS_GAME && (cl->state == CS_ACTIVE || cl->state == CS_PRIMED)) { + Cmd_Args_Sanitize(); + VM_Call( gvm, GAME_CLIENT_COMMAND, cl - svs.clients ); + } + } + else if (!bProcessed) + Com_DPrintf( "client text ignored for %s: %s\n", cl->name, Cmd_Argv(0) ); +} + +/* +=============== +SV_ClientCommand +=============== +*/ +static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) { + int seq; + const char *s; + qboolean clientOk = qtrue; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed it + if ( cl->lastClientCommand >= seq ) { + return qtrue; + } + + Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s ); + + // drop the connection if we have somehow lost commands + if ( seq > cl->lastClientCommand + 1 ) { + Com_Printf( "Client %s lost %i clientCommands\n", cl->name, + seq - cl->lastClientCommand + 1 ); + SV_DropClient( cl, "Lost reliable commands" ); + return qfalse; + } + + // malicious users may try using too many string commands + // to lag other players. If we decide that we want to stall + // the command, we will stop processing the rest of the packet, + // including the usercmd. This causes flooders to lag themselves + // but not other people + // We don't do this when the client hasn't been active yet since its + // normal to spam a lot of commands when downloading +#if 0 // flood protection in game for trem + if ( !com_cl_running->integer && + cl->state >= CS_ACTIVE && + sv_floodProtect->integer && + svs.time < cl->nextReliableTime ) { + // ignore any other text messages from this client but let them keep playing + // TTimo - moved the ignored verbose to the actual processing in SV_ExecuteClientCommand, only printing if the core doesn't intercept + clientOk = qfalse; + } +#endif + + // don't allow another command for one second + cl->nextReliableTime = svs.time + 1000; + + SV_ExecuteClientCommand( cl, s, clientOk ); + + cl->lastClientCommand = seq; + Com_sprintf(cl->lastClientCommandString, sizeof(cl->lastClientCommandString), "%s", s); + + return qtrue; // continue procesing +} + + +//================================================================================== + + +/* +================== +SV_ClientThink + +Also called by bot code +================== +*/ +void SV_ClientThink (client_t *cl, usercmd_t *cmd) { + cl->lastUsercmd = *cmd; + + if ( cl->state != CS_ACTIVE ) { + return; // may have been kicked during the last usercmd + } + + VM_Call( gvm, GAME_CLIENT_THINK, cl - svs.clients ); +} + +/* +================== +SV_UserMove + +The message usually contains all the movement commands +that were in the last three packets, so that the information +in dropped packets can be recovered. + +On very fast clients, there may be multiple usercmd packed into +each of the backup packets. +================== +*/ +static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { + int i, key; + int cmdCount; + usercmd_t nullcmd; + usercmd_t cmds[MAX_PACKET_USERCMDS]; + usercmd_t *cmd, *oldcmd; + + if ( delta ) { + cl->deltaMessage = cl->messageAcknowledge; + } else { + cl->deltaMessage = -1; + } + + cmdCount = MSG_ReadByte( msg ); + + if ( cmdCount < 1 ) { + Com_Printf( "cmdCount < 1\n" ); + return; + } + + if ( cmdCount > MAX_PACKET_USERCMDS ) { + Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" ); + return; + } + + // use the checksum feed in the key + key = sv.checksumFeed; + // also use the message acknowledge + key ^= cl->messageAcknowledge; + // also use the last acknowledged server command in the key + key ^= MSG_HashKey(cl->reliableCommands[ cl->reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ], 32); + + Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + for ( i = 0 ; i < cmdCount ; i++ ) { + cmd = &cmds[i]; + MSG_ReadDeltaUsercmdKey( msg, key, oldcmd, cmd ); + oldcmd = cmd; + } + + // save time for ping calculation + cl->frames[ cl->messageAcknowledge & PACKET_MASK ].messageAcked = svs.time; + + // TTimo + // catch the no-cp-yet situation before SV_ClientEnterWorld + // if CS_ACTIVE, then it's time to trigger a new gamestate emission + // if not, then we are getting remaining parasite usermove commands, which we should ignore + if (sv_pure->integer != 0 && cl->pureAuthentic == 0 && !cl->gotCP) { + if (cl->state == CS_ACTIVE) + { + // we didn't get a cp yet, don't assume anything and just send the gamestate all over again + Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name); + SV_SendClientGameState( cl ); + } + return; + } + + // if this is the first usercmd we have received + // this gamestate, put the client into the world + if ( cl->state == CS_PRIMED ) { + SV_ClientEnterWorld( cl, &cmds[0] ); + // the moves can be processed normaly + } + + // a bad cp command was sent, drop the client + if (sv_pure->integer != 0 && cl->pureAuthentic == 0) { + SV_DropClient( cl, "Cannot validate pure client!"); + return; + } + + if ( cl->state != CS_ACTIVE ) { + cl->deltaMessage = -1; + return; + } + + // usually, the first couple commands will be duplicates + // of ones we have previously received, but the servertimes + // in the commands will cause them to be immediately discarded + for ( i = 0 ; i < cmdCount ; i++ ) { + // if this is a cmd from before a map_restart ignore it + if ( cmds[i].serverTime > cmds[cmdCount-1].serverTime ) { + continue; + } + // extremely lagged or cmd from before a map_restart + //if ( cmds[i].serverTime > svs.time + 3000 ) { + // continue; + //} + // don't execute if this is an old cmd which is already executed + // these old cmds are included when cl_packetdup > 0 + if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) { + continue; + } + SV_ClientThink (cl, &cmds[ i ]); + } +} + + +#ifdef USE_VOIP +static +qboolean SV_ShouldIgnoreVoipSender(const client_t *cl) +{ + if (!sv_voip->integer) + return qtrue; // VoIP disabled on this server. + else if (!cl->hasVoip) // client doesn't have VoIP support?! + return qtrue; + + // !!! FIXME: implement player blacklist. + + return qfalse; // don't ignore. +} + +static +void SV_UserVoip( client_t *cl, msg_t *msg ) { + const int sender = (int) (cl - svs.clients); + const int generation = MSG_ReadByte(msg); + const int sequence = MSG_ReadLong(msg); + const int frames = MSG_ReadByte(msg); + const int recip1 = MSG_ReadLong(msg); + const int recip2 = MSG_ReadLong(msg); + const int recip3 = MSG_ReadLong(msg); + const int packetsize = MSG_ReadShort(msg); + byte encoded[sizeof (cl->voipPacket[0].data)]; + client_t *client = NULL; + voipServerPacket_t *packet = NULL; + int i; + + if (generation < 0) + return; // short/invalid packet, bail. + else if (sequence < 0) + return; // short/invalid packet, bail. + else if (frames < 0) + return; // short/invalid packet, bail. + else if (recip1 < 0) + return; // short/invalid packet, bail. + else if (recip2 < 0) + return; // short/invalid packet, bail. + else if (recip3 < 0) + return; // short/invalid packet, bail. + else if (packetsize < 0) + return; // short/invalid packet, bail. + + if (packetsize > sizeof (encoded)) { // overlarge packet? + int bytesleft = packetsize; + while (bytesleft) { + int br = bytesleft; + if (br > sizeof (encoded)) + br = sizeof (encoded); + MSG_ReadData(msg, encoded, br); + bytesleft -= br; + } + return; // overlarge packet, bail. + } + + MSG_ReadData(msg, encoded, packetsize); + + if (SV_ShouldIgnoreVoipSender(cl)) + return; // Blacklisted, disabled, etc. + + // !!! FIXME: see if we read past end of msg... + + // !!! FIXME: reject if not speex narrowband codec. + // !!! FIXME: decide if this is bogus data? + + // (the three recip* values are 31 bits each (ignores sign bit so we can + // get a -1 error from MSG_ReadLong() ... ), allowing for 93 clients.) + assert( sv_maxclients->integer < 93 ); + + // decide who needs this VoIP packet sent to them... + for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { + if (client->state != CS_ACTIVE) + continue; // not in the game yet, don't send to this guy. + else if (i == sender) + continue; // don't send voice packet back to original author. + else if (!client->hasVoip) + continue; // no VoIP support, or support disabled. + else if (client->muteAllVoip) + continue; // client is ignoring everyone. + else if (client->ignoreVoipFromClient[sender]) + continue; // client is ignoring this talker. + else if (*cl->downloadName) // !!! FIXME: possible to DoS? + continue; // no VoIP allowed if downloading, to save bandwidth. + else if ( ((i >= 0) && (i < 31)) && ((recip1 & (1 << (i-0))) == 0) ) + continue; // not addressed to this player. + else if ( ((i >= 31) && (i < 62)) && ((recip2 & (1 << (i-31))) == 0) ) + continue; // not addressed to this player. + else if ( ((i >= 62) && (i < 93)) && ((recip3 & (1 << (i-62))) == 0) ) + continue; // not addressed to this player. + + // Transmit this packet to the client. + // !!! FIXME: I don't like this queueing system. + if (client->queuedVoipPackets >= ARRAY_LEN(client->voipPacket)) { + Com_Printf("Too many VoIP packets queued for client #%d\n", i); + continue; // no room for another packet right now. + } + + packet = &client->voipPacket[client->queuedVoipPackets]; + packet->sender = sender; + packet->frames = frames; + packet->len = packetsize; + packet->generation = generation; + packet->sequence = sequence; + memcpy(packet->data, encoded, packetsize); + client->queuedVoipPackets++; + } +} +#endif + + + +/* +=========================================================================== + +USER CMD EXECUTION + +=========================================================================== +*/ + +/* +=================== +SV_ExecuteClientMessage + +Parse a client packet +=================== +*/ +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { + int c; + int serverId; + + MSG_Bitstream(msg); + + serverId = MSG_ReadLong( msg ); + cl->messageAcknowledge = MSG_ReadLong( msg ); + + if (cl->messageAcknowledge < 0) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging +#ifndef NDEBUG + SV_DropClient( cl, "DEBUG: illegible client message" ); +#endif + return; + } + + cl->reliableAcknowledge = MSG_ReadLong( msg ); + + // NOTE: when the client message is fux0red the acknowledgement numbers + // can be out of range, this could cause the server to send thousands of server + // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient + if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging +#ifndef NDEBUG + SV_DropClient( cl, "DEBUG: illegible client message" ); +#endif + cl->reliableAcknowledge = cl->reliableSequence; + return; + } + // if this is a usercmd from a previous gamestate, + // ignore it or retransmit the current gamestate + // + // if the client was downloading, let it stay at whatever serverId and + // gamestate it was at. This allows it to keep downloading even when + // the gamestate changes. After the download is finished, we'll + // notice and send it a new game state + // + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 + // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" + // but we still need to read the next message to move to next download or send gamestate + // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else + if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) { + if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart + // they just haven't caught the map_restart yet + Com_DPrintf("%s : ignoring pre map_restart / outdated client message\n", cl->name); + return; + } + // if we can tell that the client has dropped the last + // gamestate we sent them, resend it + if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { + Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); + SV_SendClientGameState( cl ); + } + return; + } + + // this client has acknowledged the new gamestate so it's + // safe to start sending it the real time again + if( cl->oldServerTime && serverId == sv.serverId ){ + Com_DPrintf( "%s acknowledged gamestate\n", cl->name ); + cl->oldServerTime = 0; + } + + // read optional clientCommand strings + do { + c = MSG_ReadByte( msg ); + + // See if this is an extension command after the EOF, which means we + // got data that a legacy server should ignore. + if ((c == clc_EOF) && (MSG_LookaheadByte( msg ) == clc_extension)) { + MSG_ReadByte( msg ); // throw the clc_extension byte away. + c = MSG_ReadByte( msg ); // something legacy servers can't do! + // sometimes you get a clc_extension at end of stream...dangling + // bits in the huffman decoder giving a bogus value? + if (c == -1) { + c = clc_EOF; + } + } + + if ( c == clc_EOF ) { + break; + } + + if ( c != clc_clientCommand ) { + break; + } + if ( !SV_ClientCommand( cl, msg ) ) { + return; // we couldn't execute it because of the flood protection + } + if (cl->state == CS_ZOMBIE) { + return; // disconnect command + } + } while ( 1 ); + + // read the usercmd_t + if ( c == clc_move ) { + SV_UserMove( cl, msg, qtrue ); + } else if ( c == clc_moveNoDelta ) { + SV_UserMove( cl, msg, qfalse ); + } else if ( c == clc_voip ) { +#ifdef USE_VOIP + SV_UserVoip( cl, msg ); +#endif + } else if ( c != clc_EOF ) { + Com_Printf( "WARNING: bad command byte for client %i\n", (int) (cl - svs.clients) ); + } +// if ( msg->readcount != msg->cursize ) { +// Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); +// } +} diff --git a/src/server/sv_game.c b/src/server/sv_game.c new file mode 100644 index 0000000..e6c98f9 --- /dev/null +++ b/src/server/sv_game.c @@ -0,0 +1,601 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// sv_game.c -- interface to the game dll + +#include "server.h" + +void SV_GameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + +void SV_GamePrint( const char *string ) { + Com_Printf( "%s", string ); +} + +// these functions must be used instead of pointer arithmetic, because +// the game allocates gentities with private information after the server shared part +int SV_NumForGentity( sharedEntity_t *ent ) { + int num; + + num = ( (byte *)ent - (byte *)sv.gentities ) / sv.gentitySize; + + return num; +} + +sharedEntity_t *SV_GentityNum( int num ) { + sharedEntity_t *ent; + + ent = (sharedEntity_t *)((byte *)sv.gentities + sv.gentitySize*(num)); + + return ent; +} + +playerState_t *SV_GameClientNum( int num ) { + playerState_t *ps; + + ps = (playerState_t *)((byte *)sv.gameClients + sv.gameClientSize*(num)); + + return ps; +} + +svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ) { + if ( !gEnt || gEnt->s.number < 0 || gEnt->s.number >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + return &sv.svEntities[ gEnt->s.number ]; +} + +sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) { + int num; + + num = svEnt - sv.svEntities; + return SV_GentityNum( num ); +} + +/* +=============== +SV_GameSendServerCommand + +Sends a command string to a client +=============== +*/ +void SV_GameSendServerCommand( int clientNum, const char *text ) { + if ( clientNum == -1 ) { + SV_SendServerCommand( NULL, "%s", text ); + } else { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_SendServerCommand( svs.clients + clientNum, "%s", text ); + } +} + + +/* +=============== +SV_GameDropClient + +Disconnects the client with a message +=============== +*/ +void SV_GameDropClient( int clientNum, const char *reason ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_DropClient( svs.clients + clientNum, reason ); +} + + +/* +================= +SV_SetBrushModel + +sets mins and maxs for inline bmodels +================= +*/ +void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) { + clipHandle_t h; + vec3_t mins, maxs; + + if (!name) { + Com_Error( ERR_DROP, "SV_SetBrushModel: NULL" ); + } + + if (name[0] != '*') { + Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model", name ); + } + + + ent->s.modelindex = atoi( name + 1 ); + + h = CM_InlineModel( ent->s.modelindex ); + CM_ModelBounds( h, mins, maxs ); + VectorCopy (mins, ent->r.mins); + VectorCopy (maxs, ent->r.maxs); + ent->r.bmodel = qtrue; + + ent->r.contents = -1; // we don't know exactly what is in the brushes + + SV_LinkEntity( ent ); // FIXME: remove +} + + + +/* +================= +SV_inPVS + +Also checks portalareas so that doors block sight +================= +*/ +qboolean SV_inPVS (const vec3_t p1, const vec3_t p2) +{ + int leafnum; + int cluster; + int area1, area2; + byte *mask; + + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + area1 = CM_LeafArea (leafnum); + mask = CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + area2 = CM_LeafArea (leafnum); + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + return qfalse; + if (!CM_AreasConnected (area1, area2)) + return qfalse; // a door blocks sight + return qtrue; +} + + +/* +================= +SV_inPVSIgnorePortals + +Does NOT check portalareas +================= +*/ +qboolean SV_inPVSIgnorePortals( const vec3_t p1, const vec3_t p2) +{ + int leafnum; + int cluster; + byte *mask; + + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + mask = CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + return qfalse; + + return qtrue; +} + + +/* +======================== +SV_AdjustAreaPortalState +======================== +*/ +void SV_AdjustAreaPortalState( sharedEntity_t *ent, qboolean open ) { + svEntity_t *svEnt; + + svEnt = SV_SvEntityForGentity( ent ); + if ( svEnt->areanum2 == -1 ) { + return; + } + CM_AdjustAreaPortalState( svEnt->areanum, svEnt->areanum2, open ); +} + + +/* +================== +SV_EntityContact +================== +*/ +qboolean SV_EntityContact( vec3_t mins, vec3_t maxs, const sharedEntity_t *gEnt, traceType_t type ) { + const float *origin, *angles; + clipHandle_t ch; + trace_t trace; + + // check for exact collision + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + ch = SV_ClipHandleForEntity( gEnt ); + CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs, + ch, -1, origin, angles, type ); + + return trace.startsolid; +} + + +/* +=============== +SV_GetServerinfo + +=============== +*/ +void SV_GetServerinfo( char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetServerinfo: bufferSize == %i", bufferSize ); + } + Q_strncpyz( buffer, Cvar_InfoString( CVAR_SERVERINFO ), bufferSize ); +} + +/* +=============== +SV_LocateGameData + +=============== +*/ +void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *clients, int sizeofGameClient ) { + sv.gentities = gEnts; + sv.gentitySize = sizeofGEntity_t; + sv.num_entities = numGEntities; + + sv.gameClients = clients; + sv.gameClientSize = sizeofGameClient; +} + + +/* +=============== +SV_GetUsercmd + +=============== +*/ +void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_GetUsercmd: bad clientNum:%i", clientNum ); + } + *cmd = svs.clients[clientNum].lastUsercmd; +} + +//============================================== + +static int FloatAsInt( float f ) { + floatint_t fi; + fi.f = f; + return fi.i; +} + +/* +==================== +SV_GameSystemCalls + +The module is making a system call +==================== +*/ +intptr_t SV_GameSystemCalls( intptr_t *args ) { + switch( args[0] ) { + case G_PRINT: + Com_Printf( "%s", (const char*)VMA(1) ); + return 0; + case G_ERROR: + Com_Error( ERR_DROP, "%s", (const char*)VMA(1) ); + return 0; + case G_MILLISECONDS: + return Sys_Milliseconds(); + case G_CVAR_REGISTER: + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + return 0; + case G_CVAR_UPDATE: + Cvar_Update( VMA(1) ); + return 0; + case G_CVAR_SET: + Cvar_SetSafe( (const char *)VMA(1), (const char *)VMA(2) ); + return 0; + case G_CVAR_VARIABLE_INTEGER_VALUE: + return Cvar_VariableIntegerValue( (const char *)VMA(1) ); + case G_CVAR_VARIABLE_STRING_BUFFER: + Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); + return 0; + case G_ARGC: + return Cmd_Argc(); + case G_ARGV: + Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); + return 0; + case G_SEND_CONSOLE_COMMAND: + Cbuf_ExecuteText( args[1], VMA(2) ); + return 0; + + case G_FS_FOPEN_FILE: + return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); + case G_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + case G_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case G_FS_FCLOSE_FILE: + FS_FCloseFile( args[1] ); + return 0; + case G_FS_GETFILELIST: + return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); + case G_FS_SEEK: + return FS_Seek( args[1], args[2], args[3] ); + + case G_LOCATE_GAME_DATA: + SV_LocateGameData( VMA(1), args[2], args[3], VMA(4), args[5] ); + return 0; + case G_DROP_CLIENT: + SV_GameDropClient( args[1], VMA(2) ); + return 0; + case G_SEND_SERVER_COMMAND: + SV_GameSendServerCommand( args[1], VMA(2) ); + return 0; + case G_LINKENTITY: + SV_LinkEntity( VMA(1) ); + return 0; + case G_UNLINKENTITY: + SV_UnlinkEntity( VMA(1) ); + return 0; + case G_ENTITIES_IN_BOX: + return SV_AreaEntities( VMA(1), VMA(2), VMA(3), args[4] ); + case G_ENTITY_CONTACT: + return SV_EntityContact( VMA(1), VMA(2), VMA(3), TT_AABB ); + case G_ENTITY_CONTACTCAPSULE: + return SV_EntityContact( VMA(1), VMA(2), VMA(3), TT_CAPSULE ); + case G_TRACE: + SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], TT_AABB ); + return 0; + case G_TRACECAPSULE: + SV_Trace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], TT_CAPSULE ); + return 0; + case G_POINT_CONTENTS: + return SV_PointContents( VMA(1), args[2] ); + case G_SET_BRUSH_MODEL: + SV_SetBrushModel( VMA(1), VMA(2) ); + return 0; + case G_IN_PVS: + return SV_inPVS( VMA(1), VMA(2) ); + case G_IN_PVS_IGNORE_PORTALS: + return SV_inPVSIgnorePortals( VMA(1), VMA(2) ); + + case G_SET_CONFIGSTRING: + SV_SetConfigstring( args[1], VMA(2) ); + return 0; + case G_GET_CONFIGSTRING: + SV_GetConfigstring( args[1], VMA(2), args[3] ); + return 0; + case G_SET_CONFIGSTRING_RESTRICTIONS: + SV_SetConfigstringRestrictions( args[1], VMA(2) ); + return 0; + case G_SET_USERINFO: + SV_SetUserinfo( args[1], VMA(2) ); + return 0; + case G_GET_USERINFO: + SV_GetUserinfo( args[1], VMA(2), args[3] ); + return 0; + case G_GET_SERVERINFO: + SV_GetServerinfo( VMA(1), args[2] ); + return 0; + case G_ADJUST_AREA_PORTAL_STATE: + SV_AdjustAreaPortalState( VMA(1), args[2] ); + return 0; + case G_AREAS_CONNECTED: + return CM_AreasConnected( args[1], args[2] ); + + case G_GET_USERCMD: + SV_GetUsercmd( args[1], VMA(2) ); + return 0; + case G_GET_ENTITY_TOKEN: + { + const char *s; + + s = COM_Parse( &sv.entityParsePoint ); + Q_strncpyz( VMA(1), s, args[2] ); + if ( !sv.entityParsePoint && !s[0] ) { + return qfalse; + } else { + return qtrue; + } + } + + case G_REAL_TIME: + return Com_RealTime( VMA(1) ); + case G_SNAPVECTOR: + Sys_SnapVector( VMA(1) ); + return 0; + + case G_SEND_GAMESTAT: + SV_MasterGameStat( VMA(1) ); + return 0; + + //==================================== + + case G_PARSE_ADD_GLOBAL_DEFINE: + return Parse_AddGlobalDefine( VMA(1) ); + case G_PARSE_LOAD_SOURCE: + return Parse_LoadSourceHandle( VMA(1) ); + case G_PARSE_FREE_SOURCE: + return Parse_FreeSourceHandle( args[1] ); + case G_PARSE_READ_TOKEN: + return Parse_ReadTokenHandle( args[1], VMA(2) ); + case G_PARSE_SOURCE_FILE_AND_LINE: + return Parse_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + + case G_ADDCOMMAND: + Cmd_AddCommand( VMA(1), NULL ); + return 0; + case G_REMOVECOMMAND: + Cmd_RemoveCommand( VMA(1) ); + return 0; + + case TRAP_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + + case TRAP_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + + case TRAP_STRNCPY: + strncpy( VMA(1), VMA(2), args[3] ); + return args[1]; + + case TRAP_SIN: + return FloatAsInt( sin( VMF(1) ) ); + + case TRAP_COS: + return FloatAsInt( cos( VMF(1) ) ); + + case TRAP_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + + case TRAP_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + + case TRAP_MATRIXMULTIPLY: + MatrixMultiply( VMA(1), VMA(2), VMA(3) ); + return 0; + + case TRAP_ANGLEVECTORS: + AngleVectors( VMA(1), VMA(2), VMA(3), VMA(4) ); + return 0; + + case TRAP_PERPENDICULARVECTOR: + PerpendicularVector( VMA(1), VMA(2) ); + return 0; + + case TRAP_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + + case TRAP_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + + + default: + Com_Error( ERR_DROP, "Bad game system trap: %ld", (long int) args[0] ); + } + return -1; +} + +/* +=============== +SV_ShutdownGameProgs + +Called every time a map changes +=============== +*/ +void SV_ShutdownGameProgs( void ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_SHUTDOWN, qfalse ); + VM_Free( gvm ); + gvm = NULL; +} + +/* +================== +SV_InitGameVM + +Called for both a full init and a restart +================== +*/ +static void SV_InitGameVM( qboolean restart ) { + int i; + + // start the entity parsing at the beginning + sv.entityParsePoint = CM_EntityString(); + + // clear all gentity pointers that might still be set from + // a previous level + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=522 + // now done before GAME_INIT call + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + svs.clients[i].gentity = NULL; + } + + // use the current msec count for a random seed + // init for this gamestate + VM_Call (gvm, GAME_INIT, sv.time, Com_Milliseconds(), restart); +} + + + +/* +=================== +SV_RestartGameProgs + +Called on a map_restart, but not on a normal map change +=================== +*/ +void SV_RestartGameProgs( void ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_SHUTDOWN, qtrue ); + + // do a restart instead of a free + gvm = VM_Restart( gvm ); + if ( !gvm ) { + Com_Error( ERR_FATAL, "VM_Restart on game failed" ); + } + + SV_InitGameVM( qtrue ); +} + + +/* +=============== +SV_InitGameProgs + +Called on a normal map change, not on a map_restart +=============== +*/ +void SV_InitGameProgs( void ) { + // load the dll or bytecode + gvm = VM_Create( "game", SV_GameSystemCalls, Cvar_VariableValue( "vm_game" ) ); + if ( !gvm ) { + Com_Error( ERR_FATAL, "VM_Create on game failed" ); + } + + SV_InitGameVM( qfalse ); +} + + +/* +==================== +SV_GameCommand + +See if the current console command is claimed by the game +==================== +*/ +qboolean SV_GameCommand( void ) { + if ( sv.state != SS_GAME ) { + return qfalse; + } + + return VM_Call( gvm, GAME_CONSOLE_COMMAND ); +} + diff --git a/src/server/sv_init.c b/src/server/sv_init.c new file mode 100644 index 0000000..e84ff0f --- /dev/null +++ b/src/server/sv_init.c @@ -0,0 +1,754 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "server.h" + + +/* +=============== +SV_SendConfigstring + +Creates and sends the server command necessary to update the CS index for the +given client +=============== +*/ +static void SV_SendConfigstring(client_t *client, int index) +{ + int maxChunkSize = MAX_STRING_CHARS - 24; + int len; + + if( sv.configstrings[index].restricted && Com_ClientListContains( + &sv.configstrings[index].clientList, client - svs.clients ) ) { + // Send a blank config string for this client if it's listed + SV_SendServerCommand( client, "cs %i \"\"\n", index ); + return; + } + + len = strlen(sv.configstrings[index].s); + + if( len >= maxChunkSize ) { + int sent = 0; + int remaining = len; + char *cmd; + char buf[MAX_STRING_CHARS]; + + while (remaining > 0 ) { + if ( sent == 0 ) { + cmd = "bcs0"; + } + else if( remaining < maxChunkSize ) { + cmd = "bcs2"; + } + else { + cmd = "bcs1"; + } + Q_strncpyz( buf, &sv.configstrings[index].s[sent], + maxChunkSize ); + + SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, + index, buf ); + + sent += (maxChunkSize - 1); + remaining -= (maxChunkSize - 1); + } + } else { + // standard cs, just send it + SV_SendServerCommand( client, "cs %i \"%s\"\n", index, + sv.configstrings[index].s ); + } +} + +/* +=============== +SV_UpdateConfigstrings + +Called when a client goes from CS_PRIMED to CS_ACTIVE. Updates all +Configstring indexes that have changed while the client was in CS_PRIMED +=============== +*/ +void SV_UpdateConfigstrings(client_t *client) +{ + int index; + + for( index = 0; index <= MAX_CONFIGSTRINGS; index++ ) { + // if the CS hasn't changed since we went to CS_PRIMED, ignore + if(!client->csUpdated[index]) + continue; + + // do not always send server info to all clients + if ( index == CS_SERVERINFO && client->gentity && + (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { + continue; + } + SV_SendConfigstring(client, index); + client->csUpdated[index] = qfalse; + } +} + +/* +=============== +SV_SetConfigstring + +=============== +*/ +void SV_SetConfigstring (int index, const char *val) { + int i; + client_t *client; + + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error (ERR_DROP, "SV_SetConfigstring: bad index %i\n", index); + } + + if ( !val ) { + val = ""; + } + + // don't bother broadcasting an update if no change + if ( !strcmp( val, sv.configstrings[ index ].s ) ) { + return; + } + + // change the string in sv + Z_Free( sv.configstrings[index].s ); + sv.configstrings[index].s = CopyString( val ); + + // send it to all the clients if we aren't + // spawning a new server + if ( sv.state == SS_GAME || sv.restarting ) { + + // send the data to all relevent clients + for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { + if ( client->state < CS_ACTIVE ) { + if ( client->state == CS_PRIMED ) + client->csUpdated[ index ] = qtrue; + continue; + } + // do not always send server info to all clients + if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { + continue; + } + + SV_SendConfigstring(client, index); + } + } +} + +/* +=============== +SV_GetConfigstring + +=============== +*/ +void SV_GetConfigstring( int index, char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetConfigstring: bufferSize == %i", bufferSize ); + } + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error (ERR_DROP, "SV_GetConfigstring: bad index %i\n", index); + } + if ( !sv.configstrings[index].s ) { + buffer[0] = 0; + return; + } + + Q_strncpyz( buffer, sv.configstrings[index].s, bufferSize ); +} + +/* +=============== +SV_SetConfigstringRestrictions +=============== +*/ +void SV_SetConfigstringRestrictions (int index, const clientList_t* clientList) { + int i; + clientList_t oldClientList = sv.configstrings[index].clientList; + + sv.configstrings[index].clientList = *clientList; + sv.configstrings[index].restricted = qtrue; + + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + if ( Com_ClientListContains( &oldClientList, i ) != + Com_ClientListContains( clientList, i ) ) { + // A client has left or joined the restricted list, so update + SV_SendConfigstring(&svs.clients[i], index); + } + } + } +} + + +/* +=============== +SV_SetUserinfo + +=============== +*/ +void SV_SetUserinfo( int index, const char *val ) { + if ( index < 0 || index >= sv_maxclients->integer ) { + Com_Error (ERR_DROP, "SV_SetUserinfo: bad index %i\n", index); + } + + if ( !val ) { + val = ""; + } + + Q_strncpyz( svs.clients[index].userinfo, val, sizeof( svs.clients[ index ].userinfo ) ); + Q_strncpyz( svs.clients[index].name, Info_ValueForKey( val, "name" ), sizeof(svs.clients[index].name) ); +} + + + +/* +=============== +SV_GetUserinfo + +=============== +*/ +void SV_GetUserinfo( int index, char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize ); + } + if ( index < 0 || index >= sv_maxclients->integer ) { + Com_Error (ERR_DROP, "SV_GetUserinfo: bad index %i\n", index); + } + Q_strncpyz( buffer, svs.clients[ index ].userinfo, bufferSize ); +} + + +/* +================ +SV_CreateBaseline + +Entity baselines are used to compress non-delta messages +to the clients -- only the fields that differ from the +baseline will be transmitted +================ +*/ +static void SV_CreateBaseline( void ) { + sharedEntity_t *svent; + int entnum; + + for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) { + svent = SV_GentityNum(entnum); + if (!svent->r.linked) { + continue; + } + svent->s.number = entnum; + + // + // take current state as baseline + // + sv.svEntities[entnum].baseline = svent->s; + } +} + + +/* +=============== +SV_BoundMaxClients + +=============== +*/ +static void SV_BoundMaxClients( int minimum ) { + // get the current maxclients value + Cvar_Get( "sv_maxclients", "8", 0 ); + + sv_maxclients->modified = qfalse; + + if ( sv_maxclients->integer < minimum ) { + Cvar_Set( "sv_maxclients", va("%i", minimum) ); + } else if ( sv_maxclients->integer > MAX_CLIENTS ) { + Cvar_Set( "sv_maxclients", va("%i", MAX_CLIENTS) ); + } +} + + +/* +=============== +SV_Startup + +Called when a host starts a map when it wasn't running +one before. Successive map or map_restart commands will +NOT cause this to be called, unless the game is exited to +the menu system first. +=============== +*/ +static void SV_Startup( void ) { + if ( svs.initialized ) { + Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); + } + SV_BoundMaxClients( 1 ); + + svs.clients = Z_Malloc (sizeof(client_t) * sv_maxclients->integer ); + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } + svs.initialized = qtrue; + + // Don't respect sv_killserver unless a server is actually running + if ( sv_killserver->integer ) { + Cvar_Set( "sv_killserver", "0" ); + } + + Cvar_Set( "sv_running", "1" ); + + // Join the ipv6 multicast group now that a map is running so clients can scan for us on the local network. + NET_JoinMulticast6(); +} + + +/* +================== +SV_ChangeMaxClients +================== +*/ +void SV_ChangeMaxClients( void ) { + int oldMaxClients; + int i; + client_t *oldClients; + int count; + + // get the highest client number in use + count = 0; + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + if (i > count) + count = i; + } + } + count++; + + oldMaxClients = sv_maxclients->integer; + // never go below the highest client number in use + SV_BoundMaxClients( count ); + // if still the same + if ( sv_maxclients->integer == oldMaxClients ) { + return; + } + + oldClients = Hunk_AllocateTempMemory( count * sizeof(client_t) ); + // copy the clients to hunk memory + for ( i = 0 ; i < count ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + oldClients[i] = svs.clients[i]; + } + else { + Com_Memset(&oldClients[i], 0, sizeof(client_t)); + } + } + + // free old clients arrays + Z_Free( svs.clients ); + + // allocate new clients + svs.clients = Z_Malloc ( sv_maxclients->integer * sizeof(client_t) ); + Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) ); + + // copy the clients over + for ( i = 0 ; i < count ; i++ ) { + if ( oldClients[i].state >= CS_CONNECTED ) { + svs.clients[i] = oldClients[i]; + } + } + + // free the old clients on the hunk + Hunk_FreeTempMemory( oldClients ); + + // allocate new snapshot entities + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } +} + +/* +================ +SV_ClearServer +================ +*/ +static void SV_ClearServer(void) { + int i; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( sv.configstrings[i].s ) { + Z_Free( sv.configstrings[i].s ); + } + } + Com_Memset (&sv, 0, sizeof(sv)); +} + +/* +================ +SV_TouchCGame + + touch the cgame.vm so that a pure client can load it if it's in a seperate pk3 +================ +*/ +static void SV_TouchCGame(void) { + fileHandle_t f; + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", "cgame" ); + FS_FOpenFileRead( filename, &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } +} + +/* +================ +SV_SpawnServer + +Change the server to a new map, taking all connected +clients along with it. +This is NOT called for map_restart +================ +*/ +void SV_SpawnServer( char *server, qboolean killBots ) { + int i; + int checksum; + char systemInfo[16384]; + const char *p; + + // shut down the existing game if it is running + SV_ShutdownGameProgs(); + + Com_Printf ("------ Server Initialization ------\n"); + Com_Printf ("Server: %s\n",server); + + // if not running a dedicated server CL_MapLoading will connect the client to the server + // also print some status stuff + CL_MapLoading(); + + // make sure all the client stuff is unloaded + CL_ShutdownAll(); + + // clear the whole hunk because we're (re)loading the server + Hunk_Clear(); + +#ifndef DEDICATED + // Restart renderer + CL_StartHunkUsers( qtrue ); +#endif + + // clear collision map data + CM_ClearMap(); + + // init client structures and svs.numSnapshotEntities + if ( !Cvar_VariableValue("sv_running") ) { + SV_Startup(); + } else { + // check for maxclients change + if ( sv_maxclients->modified ) { + SV_ChangeMaxClients(); + } + } + + // clear pak references + FS_ClearPakReferences(0); + + // allocate the snapshot entities on the hunk + svs.snapshotEntities = Hunk_Alloc( sizeof(entityState_t)*svs.numSnapshotEntities, h_high ); + svs.nextSnapshotEntities = 0; + + // toggle the server bit so clients can detect that a + // server has changed + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + for (i=0 ; iinteger ; i++) { + // save when the server started for each client already connected + if (svs.clients[i].state >= CS_CONNECTED) { + svs.clients[i].oldServerTime = sv.time; + } + } + + // wipe the entire per-level structure + SV_ClearServer(); + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + sv.configstrings[i].s = CopyString(""); + sv.configstrings[i].restricted = qfalse; + Com_Memset(&sv.configstrings[i].clientList, 0, sizeof(clientList_t)); + } + + // make sure we are not paused + Cvar_Set("cl_paused", "0"); + + // get a new checksum feed and restart the file system + sv.checksumFeed = ( ((int) rand() << 16) ^ rand() ) ^ Com_Milliseconds(); + FS_Restart( sv.checksumFeed ); + + CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum ); + + // set serverinfo visible name + Cvar_Set( "mapname", server ); + + Cvar_Set( "sv_mapChecksum", va("%i",checksum) ); + + // serverid should be different each time + sv.serverId = com_frameTime; + sv.restartedServerId = sv.serverId; // I suppose the init here is just to be safe + sv.checksumFeedServerId = sv.serverId; + Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); + + // clear physics interaction links + SV_ClearWorld (); + + // media configstring setting should be done during + // the loading stage, so connected clients don't have + // to load during actual gameplay + sv.state = SS_LOADING; + + // load and spawn all other entities + SV_InitGameProgs(); + + // run a few frames to allow everything to settle + for (i = 0;i < 3; i++) + { + VM_Call (gvm, GAME_RUN_FRAME, sv.time); + sv.time += 100; + svs.time += 100; + } + + // create a baseline for more efficient communications + SV_CreateBaseline (); + + for (i=0 ; iinteger ; i++) { + // send the new gamestate to all connected clients + if (svs.clients[i].state >= CS_CONNECTED) { + char *denied; + + // connect the client again + denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse ) ); // firstTime = qfalse + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( &svs.clients[i], denied ); + } else { + // when we get the next packet from a connected client, + // the new gamestate will be sent + svs.clients[i].state = CS_CONNECTED; + } + } + } + + // run another frame to allow things to look at all the players + VM_Call (gvm, GAME_RUN_FRAME, sv.time); + sv.time += 100; + svs.time += 100; + + if ( sv_pure->integer ) { + // the server sends these to the clients so they will only + // load pk3s also loaded at the server + p = FS_LoadedPakChecksums(); + Cvar_Set( "sv_paks", p ); + if (strlen(p) == 0) { + Com_Printf( "WARNING: sv_pure set but no PK3 files loaded\n" ); + } + p = FS_LoadedPakNames(); + Cvar_Set( "sv_pakNames", p ); + + // if a dedicated pure server we need to touch the cgame because it could be in a + // seperate pk3 file and the client will need to load the latest cgame.qvm + if ( com_dedicated->integer ) { + SV_TouchCGame(); + } + } + else { + Cvar_Set( "sv_paks", "" ); + Cvar_Set( "sv_pakNames", "" ); + } + // the server sends these to the clients so they can figure + // out which pk3s should be auto-downloaded + p = FS_ReferencedPakChecksums(); + Cvar_Set( "sv_referencedPaks", p ); + p = FS_ReferencedPakNames(); + Cvar_Set( "sv_referencedPakNames", p ); + + // save systeminfo and serverinfo strings + Q_strncpyz( systemInfo, Cvar_InfoString_Big( CVAR_SYSTEMINFO ), sizeof( systemInfo ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + SV_SetConfigstring( CS_SYSTEMINFO, systemInfo ); + + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + + // any media configstring setting now should issue a warning + // and any configstring changes should be reliably transmitted + // to all clients + sv.state = SS_GAME; + + // send a heartbeat now so the master will get up to date info + SV_Heartbeat_f(); + + Hunk_SetMark(); + + Com_Printf ("-----------------------------------\n"); +} + +/* +=============== +SV_Init + +Only called at main exe startup, not for each game +=============== +*/ +void SV_Init (void) +{ + int index; + + SV_AddOperatorCommands (); + + // serverinfo vars + Cvar_Get ("timelimit", "0", CVAR_SERVERINFO); + Cvar_Get ("sv_keywords", "", CVAR_SERVERINFO); + sv_mapname = Cvar_Get ("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM); + sv_privateClients = Cvar_Get ("sv_privateClients", "0", CVAR_SERVERINFO); + sv_hostname = Cvar_Get ("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE ); + sv_maxclients = Cvar_Get ("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); + + sv_minRate = Cvar_Get ("sv_minRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_maxRate = Cvar_Get ("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_minPing = Cvar_Get ("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_maxPing = Cvar_Get ("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + + // systeminfo + Cvar_Get ("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM ); + sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); + sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO ); +#ifdef USE_VOIP + sv_voip = Cvar_Get ("sv_voip", "1", CVAR_SYSTEMINFO | CVAR_LATCH); + Cvar_CheckRange( sv_voip, 0, 1, qtrue ); +#endif + Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_referencedPakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); + + // server vars + sv_rconPassword = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + sv_privatePassword = Cvar_Get ("sv_privatePassword", "", CVAR_TEMP ); + sv_fps = Cvar_Get ("sv_fps", "20", CVAR_TEMP ); + sv_timeout = Cvar_Get ("sv_timeout", "200", CVAR_TEMP ); + sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP ); + + sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO); + Cvar_Get ("sv_dlURL", "http://downloads.tremulous.net", CVAR_SERVERINFO | CVAR_ARCHIVE); + + sv_master[0] = Cvar_Get("sv_master1", MASTER_SERVER_NAME, 0); + for(index = 1; index < MAX_MASTER_SERVERS; index++) + sv_master[index] = Cvar_Get(va("sv_master%d", index + 1), "", CVAR_ARCHIVE); + + sv_reconnectlimit = Cvar_Get ("sv_reconnectlimit", "3", 0); + sv_showloss = Cvar_Get ("sv_showloss", "0", 0); + sv_padPackets = Cvar_Get ("sv_padPackets", "0", 0); + sv_killserver = Cvar_Get ("sv_killserver", "0", 0); + sv_mapChecksum = Cvar_Get ("sv_mapChecksum", "", CVAR_ROM); + sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE ); + sv_dequeuePeriod = Cvar_Get ("sv_dequeuePeriod", "500", CVAR_ARCHIVE ); + sv_heartbeat = Cvar_Get("sv_heartbeat", HEARTBEAT_FOR_MASTER, CVAR_INIT); + sv_flatline = Cvar_Get("sv_flatline", FLATLINE_FOR_MASTER, CVAR_INIT); +} + + +/* +================== +SV_FinalMessage + +Used by SV_Shutdown to send a final message to all +connected clients before the server goes down. The messages are sent immediately, +not just stuck on the outgoing message list, because the server is going +to totally exit after returning from this function. +================== +*/ +void SV_FinalMessage( char *message ) { + int i, j; + client_t *cl; + + // send it twice, ignoring rate + for ( j = 0 ; j < 2 ; j++ ) { + for (i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { + if (cl->state >= CS_CONNECTED) { + // don't send a disconnect to a local client + if ( cl->netchan.remoteAddress.type != NA_LOOPBACK ) { + SV_SendServerCommand( cl, "print \"%s\n\"\n", message ); + SV_SendServerCommand( cl, "disconnect \"%s\"", message ); + } + // force a snapshot to be sent + cl->nextSnapshotTime = -1; + SV_SendClientSnapshot( cl ); + } + } + } +} + + +/* +================ +SV_Shutdown + +Called when each game quits, +before Sys_Quit or Sys_Error +================ +*/ +void SV_Shutdown( char *finalmsg ) { + if ( !com_sv_running || !com_sv_running->integer ) { + return; + } + + Com_Printf( "----- Server Shutdown (%s) -----\n", finalmsg ); + + NET_LeaveMulticast6(); + + if ( svs.clients && !com_errorEntered ) { + SV_FinalMessage( finalmsg ); + } + + SV_RemoveOperatorCommands(); + SV_MasterShutdown(); + SV_ShutdownGameProgs(); + + // free current level + SV_ClearServer(); + + // free server static data + if ( svs.clients ) { + Z_Free( svs.clients ); + } + Com_Memset( &svs, 0, sizeof( svs ) ); + + Cvar_Set( "sv_running", "0" ); + Cvar_Set("ui_singlePlayerActive", "0"); + + Com_Printf( "---------------------------\n" ); + + // disconnect any local clients + if( sv_killserver->integer != 2 ) + CL_Disconnect( qfalse ); +} + diff --git a/src/server/sv_main.c b/src/server/sv_main.c new file mode 100644 index 0000000..014c3a9 --- /dev/null +++ b/src/server/sv_main.c @@ -0,0 +1,1147 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "server.h" + +#ifdef USE_VOIP +cvar_t *sv_voip; +#endif + +serverStatic_t svs; // persistant server info +server_t sv; // local server +vm_t *gvm = NULL; // game virtual machine + +cvar_t *sv_fps = NULL; // time rate for running non-clients +cvar_t *sv_timeout; // seconds without any message +cvar_t *sv_zombietime; // seconds to sink messages after disconnect +cvar_t *sv_rconPassword; // password for remote server commands +cvar_t *sv_privatePassword; // password for the privateClient slots +cvar_t *sv_allowDownload; +cvar_t *sv_maxclients; + +cvar_t *sv_privateClients; // number of clients reserved for password +cvar_t *sv_hostname; +cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address +cvar_t *sv_reconnectlimit; // minimum seconds between connect messages +cvar_t *sv_showloss; // report when usercmds are lost +cvar_t *sv_padPackets; // add nop bytes to messages +cvar_t *sv_killserver; // menu system can set to 1 to shut server down +cvar_t *sv_mapname; +cvar_t *sv_mapChecksum; +cvar_t *sv_serverid; +cvar_t *sv_minRate; +cvar_t *sv_maxRate; +cvar_t *sv_minPing; +cvar_t *sv_maxPing; +cvar_t *sv_pure; +cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491) +cvar_t *sv_dequeuePeriod; +cvar_t *sv_heartbeat; // Heartbeat string that is sent to the master +cvar_t *sv_flatline; // If the master server supports it we can send a flatline + // when server is killed + +/* +============================================================================= + +EVENT MESSAGES + +============================================================================= +*/ + +/* +=============== +SV_ExpandNewlines + +Converts newlines to "\n" so a line prints nicer +=============== +*/ +static char *SV_ExpandNewlines( char *in ) { + static char string[1024]; + int l; + + l = 0; + while ( *in && l < sizeof(string) - 3 ) { + if ( *in == '\n' ) { + string[l++] = '\\'; + string[l++] = 'n'; + } else { + string[l++] = *in; + } + in++; + } + string[l] = 0; + + return string; +} + +/* +====================== +SV_ReplacePendingServerCommands + +FIXME: This is ugly +====================== +*/ +#if 0 // unused +static int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) { + int i, index, csnum1, csnum2; + + for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) { + index = i & ( MAX_RELIABLE_COMMANDS - 1 ); + // + if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) { + sscanf(cmd, "cs %i", &csnum1); + sscanf(client->reliableCommands[ index ], "cs %i", &csnum2); + if ( csnum1 == csnum2 ) { + Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) ); + return qtrue; + } + } + } + return qfalse; +} +#endif + +/* +====================== +SV_AddServerCommand + +The given command will be transmitted to the client, and is guaranteed to +not have future snapshot_t executed before it is executed +====================== +*/ +void SV_AddServerCommand( client_t *client, const char *cmd ) { + int index, i; + + // this is very ugly but it's also a waste to for instance send multiple config string updates + // for the same config string index in one snapshot +// if ( SV_ReplacePendingServerCommands( client, cmd ) ) { +// return; +// } + + // do not send commands until the gamestate has been sent + if( client->state < CS_PRIMED ) + return; + + client->reliableSequence++; + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + // we check == instead of >= so a broadcast print added by SV_DropClient() + // doesn't cause a recursive drop client + if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) { + Com_Printf( "===== pending server commands =====\n" ); + for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { + Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + Com_Printf( "cmd %5d: %s\n", i, cmd ); + SV_DropClient( client, "Server command overflow" ); + return; + } + index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) ); +} + +/* +================= +SV_SendServerCommand + +Sends a reliable command string to be interpreted by +the client game module: "cp", "print", "chat", etc +A NULL client will broadcast to all clients +================= +*/ +void QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) { + va_list argptr; + byte message[MAX_MSGLEN]; + client_t *client; + int j; + + va_start (argptr,fmt); + Q_vsnprintf ((char *)message, sizeof(message), fmt,argptr); + va_end (argptr); + + // Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt + // The actual cause of the bug is probably further downstream + // and should maybe be addressed later, but this certainly + // fixes the problem for now + if ( strlen ((char *)message) > 1022 ) { + return; + } + + if ( cl != NULL ) { + SV_AddServerCommand( cl, (char *)message ); + return; + } + + // hack to echo broadcast prints to console + if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) { + Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) ); + } + + // send the data to all relevent clients + for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) { + SV_AddServerCommand( client, (char *)message ); + } +} + + +/* +============================================================================== + +MASTER SERVER FUNCTIONS + +============================================================================== +*/ + +/* +================ +SV_MasterHeartbeat + +Send a message to the masters every few minutes to +let it know we are alive, and log information. +We will also have a heartbeat sent when a server +changes from empty to non-empty, and full to non-full, +but not on every player enter or exit. +================ +*/ +#define HEARTBEAT_MSEC 300*1000 +void SV_MasterHeartbeat(const char *message) +{ + static netadr_t adr[MAX_MASTER_SERVERS][2]; // [2] for v4 and v6 address for the same address string. + int i; + int res; + int netenabled; + + netenabled = Cvar_VariableIntegerValue("net_enabled"); + + // "dedicated 1" is for lan play, "dedicated 2" is for inet public play + if (!com_dedicated || com_dedicated->integer != 2 || !(netenabled & (NET_ENABLEV4 | NET_ENABLEV6))) + return; // only dedicated servers send heartbeats + + // if not time yet, don't send anything + if ( svs.time < svs.nextHeartbeatTime ) + return; + + svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC; + + // send to group masters + for (i = 0; i < MAX_MASTER_SERVERS; i++) + { + if(!sv_master[i]->string[0]) + continue; + + // see if we haven't already resolved the name + // resolving usually causes hitches on win95, so only + // do it when needed + if(sv_master[i]->modified || (adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD)) + { + sv_master[i]->modified = qfalse; + + if(netenabled & NET_ENABLEV4) + { + Com_Printf("Resolving %s (IPv4)\n", sv_master[i]->string); + res = NET_StringToAdr(sv_master[i]->string, &adr[i][0], NA_IP); + + if(res == 2) + { + // if no port was specified, use the default master port + adr[i][0].port = BigShort(PORT_MASTER); + } + + if(res) + Com_Printf( "%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i][0])); + else + Com_Printf( "%s has no IPv4 address.\n", sv_master[i]->string); + } + + if(netenabled & NET_ENABLEV6) + { + Com_Printf("Resolving %s (IPv6)\n", sv_master[i]->string); + res = NET_StringToAdr(sv_master[i]->string, &adr[i][1], NA_IP6); + + if(res == 2) + { + // if no port was specified, use the default master port + adr[i][1].port = BigShort(PORT_MASTER); + } + + if(res) + Com_Printf( "%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i][1])); + else + Com_Printf( "%s has no IPv6 address.\n", sv_master[i]->string); + } + + if(adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD) + { + Com_Printf("Couldn't resolve address: %s\n", sv_master[i]->string); + Cvar_Set(sv_master[i]->name, ""); + sv_master[i]->modified = qfalse; + continue; + } + } + + + Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string ); + + // this command should be changed if the server info / status format + // ever incompatably changes + + if(adr[i][0].type != NA_BAD) + NET_OutOfBandPrint( NS_SERVER, adr[i][0], "heartbeat %s\n", message); + if(adr[i][1].type != NA_BAD) + NET_OutOfBandPrint( NS_SERVER, adr[i][1], "heartbeat %s\n", message); + } +} + +/* +================= +SV_MasterShutdown + +Informs all masters that this server is going down +================= +*/ +void SV_MasterShutdown( void ) { + // send a hearbeat right now + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat(sv_flatline->string); + + // send it again to minimize chance of drops + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat(sv_flatline->string); + + // when the master tries to poll the server, it won't respond, so + // it will be removed from the list +} + +/* +================= +SV_MasterGameStat +================= +*/ +void SV_MasterGameStat( const char *data ) +{ + netadr_t adr; + + if( !com_dedicated || com_dedicated->integer != 2 ) + return; // only dedicated servers send stats + + Com_Printf( "Resolving %s\n", MASTER_SERVER_NAME ); + + switch( NET_StringToAdr( MASTER_SERVER_NAME, &adr, NA_UNSPEC ) ) + { + case 0: + Com_Printf( "Couldn't resolve master address: %s\n", MASTER_SERVER_NAME ); + return; + + case 2: + adr.port = BigShort( PORT_MASTER ); + default: + break; + } + + Com_Printf( "%s resolved to %s\n", MASTER_SERVER_NAME, + NET_AdrToStringwPort( adr ) ); + + Com_Printf( "Sending gamestat to %s\n", MASTER_SERVER_NAME ); + NET_OutOfBandPrint( NS_SERVER, adr, "gamestat %s", data ); +} + +/* +============================================================================== + +CONNECTIONLESS COMMANDS + +============================================================================== +*/ + +typedef struct leakyBucket_s leakyBucket_t; +struct leakyBucket_s { + netadrtype_t type; + + union { + byte _4[4]; + byte _6[16]; + } ipv; + + int lastTime; + signed char burst; + + long hash; + + leakyBucket_t *prev, *next; +}; + +// This is deliberately quite large to make it more of an effort to DoS +#define MAX_BUCKETS 16384 +#define MAX_HASHES 1024 + +static leakyBucket_t buckets[ MAX_BUCKETS ]; +static leakyBucket_t *bucketHashes[ MAX_HASHES ]; + +/* +================ +SVC_HashForAddress +================ +*/ +static long SVC_HashForAddress( netadr_t address ) { + byte *ip = NULL; + size_t size = 0; + int i; + long hash = 0; + + switch ( address.type ) { + case NA_IP: ip = address.ip; size = 4; break; + case NA_IP6: ip = address.ip6; size = 16; break; + default: break; + } + + for ( i = 0; i < size; i++ ) { + hash += (long)( ip[ i ] ) * ( i + 119 ); + } + + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ); + hash &= ( MAX_HASHES - 1 ); + + return hash; +} + +/* +================ +SVC_BucketForAddress + +Find or allocate a bucket for an address +================ +*/ +static leakyBucket_t *SVC_BucketForAddress( netadr_t address, int burst, int period ) { + leakyBucket_t *bucket = NULL; + int i; + long hash = SVC_HashForAddress( address ); + int now = Sys_Milliseconds(); + + for ( bucket = bucketHashes[ hash ]; bucket; bucket = bucket->next ) { + switch ( bucket->type ) { + case NA_IP: + if ( memcmp( bucket->ipv._4, address.ip, 4 ) == 0 ) { + return bucket; + } + break; + + case NA_IP6: + if ( memcmp( bucket->ipv._6, address.ip6, 16 ) == 0 ) { + return bucket; + } + break; + + default: + break; + } + } + + for ( i = 0; i < MAX_BUCKETS; i++ ) { + int interval; + + bucket = &buckets[ i ]; + interval = now - bucket->lastTime; + + // Reclaim expired buckets + if ( bucket->lastTime > 0 && ( interval > ( burst * period ) || + interval < 0 ) ) { + if ( bucket->prev != NULL ) { + bucket->prev->next = bucket->next; + } else { + bucketHashes[ bucket->hash ] = bucket->next; + } + + if ( bucket->next != NULL ) { + bucket->next->prev = bucket->prev; + } + + Com_Memset( bucket, 0, sizeof( leakyBucket_t ) ); + } + + if ( bucket->type == NA_BAD ) { + bucket->type = address.type; + switch ( address.type ) { + case NA_IP: Com_Memcpy( bucket->ipv._4, address.ip, 4 ); break; + case NA_IP6: Com_Memcpy( bucket->ipv._6, address.ip6, 16 ); break; + default: break; + } + + bucket->lastTime = now; + bucket->burst = 0; + bucket->hash = hash; + + // Add to the head of the relevant hash chain + bucket->next = bucketHashes[ hash ]; + if ( bucketHashes[ hash ] != NULL ) { + bucketHashes[ hash ]->prev = bucket; + } + + bucket->prev = NULL; + bucketHashes[ hash ] = bucket; + + return bucket; + } + } + + // Couldn't allocate a bucket for this address + return NULL; +} + +/* +================ +SVC_RateLimit +================ +*/ +static qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ) { + if ( bucket != NULL ) { + int now = Sys_Milliseconds(); + int interval = now - bucket->lastTime; + int expired = interval / period; + int expiredRemainder = interval % period; + + if ( expired > bucket->burst ) { + bucket->burst = 0; + bucket->lastTime = now; + } else { + bucket->burst -= expired; + bucket->lastTime = now - expiredRemainder; + } + + if ( bucket->burst < burst ) { + bucket->burst++; + + return qfalse; + } + } + + return qtrue; +} + +/* +================ +SVC_RateLimitAddress + +Rate limit for a particular address +================ +*/ +static qboolean SVC_RateLimitAddress( netadr_t from, int burst, int period ) { + leakyBucket_t *bucket = SVC_BucketForAddress( from, burst, period ); + + return SVC_RateLimit( bucket, burst, period ); +} + +/* +================ +SVC_Status + +Responds with all the info that qplug or qspy can see about the server +and all connected players. Used for getting detailed information after +the simple info query. +================ +*/ +static void SVC_Status( netadr_t from ) { + char player[1024]; + char status[MAX_MSGLEN]; + int i; + client_t *cl; + playerState_t *ps; + int statusLength; + int playerLength; + char infostring[MAX_INFO_STRING]; + static leakyBucket_t bucket; + + // Prevent using getstatus as an amplifier + if ( SVC_RateLimitAddress( from, 10, 1000 ) ) { + Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n", + NET_AdrToString( from ) ); + return; + } + + // Allow getstatus to be DoSed relatively easily, but prevent + // excess outbound bandwidth usage when being flooded inbound + if ( SVC_RateLimit( &bucket, 10, 100 ) ) { + Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" ); + return; + } + + strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); + + // echo back the parameter to status. so master servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); + + status[0] = 0; + statusLength = 0; + + for (i=0 ; i < sv_maxclients->integer ; i++) { + cl = &svs.clients[i]; + if ( cl->state >= CS_CONNECTED ) { + ps = SV_GameClientNum( i ); + Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", + ps->persistant[PERS_SCORE], cl->ping, cl->name); + playerLength = strlen(player); + if (statusLength + playerLength >= sizeof(status) ) { + break; // can't hold any more + } + strcpy (status + statusLength, player); + statusLength += playerLength; + } + } + + NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status ); +} + +/* +================ +SVC_Info + +Responds with a short info message that should be enough to determine +if a user is interested in a server to do a full status +================ +*/ +void SVC_Info( netadr_t from ) { + int i, count; + char *gamedir; + char infostring[MAX_INFO_STRING]; + + /* + * Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led + * to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory. + */ + + // A maximum challenge length of 128 should be more than plenty. + if(strlen(Cmd_Argv(1)) > 128) + return; + + // don't count privateclients + count = 0; + for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + + infostring[0] = 0; + + // echo back the parameter to status. so servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); + + Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) ); + Info_SetValueForKey( infostring, "hostname", sv_hostname->string ); + Info_SetValueForKey( infostring, "mapname", sv_mapname->string ); + Info_SetValueForKey( infostring, "clients", va("%i", count) ); + Info_SetValueForKey( infostring, "sv_maxclients", + va("%i", sv_maxclients->integer - sv_privateClients->integer ) ); + Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) ); + +#ifdef USE_VOIP + if (sv_voip->integer) { + Info_SetValueForKey( infostring, "voip", va("%i", sv_voip->integer ) ); + } +#endif + + if( sv_minPing->integer ) { + Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) ); + } + if( sv_maxPing->integer ) { + Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) ); + } + gamedir = Cvar_VariableString( "fs_game" ); + if( *gamedir ) { + Info_SetValueForKey( infostring, "game", gamedir ); + } + + NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring ); +} + +/* +================ +SVC_FlushRedirect + +================ +*/ +static void SV_FlushRedirect( char *outputbuf ) { + NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf ); +} + +/* +=============== +SVC_RemoteCommand + +An rcon packet arrived from the network. +Shift down the remaining args +Redirect all printfs +=============== +*/ +static void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { + qboolean valid; + char remaining[1024]; + // TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc. + // (OOB messages are the bottleneck here) +#define SV_OUTPUTBUF_LENGTH (1024 - 16) + char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; + char *cmd_aux; + + // Prevent using rcon as an amplifier and make dictionary attacks impractical + if ( SVC_RateLimitAddress( from, 10, 1000 ) ) { + Com_DPrintf( "SVC_RemoteCommand: rate limit from %s exceeded, dropping request\n", + NET_AdrToString( from ) ); + return; + } + + if ( !strlen( sv_rconPassword->string ) || + strcmp (Cmd_Argv(1), sv_rconPassword->string) ) { + static leakyBucket_t bucket; + + // Make DoS via rcon impractical + if ( SVC_RateLimit( &bucket, 10, 1000 ) ) { + Com_DPrintf( "SVC_RemoteCommand: rate limit exceeded, dropping request\n" ); + return; + } + + valid = qfalse; + Com_Printf ("Bad rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) ); + } else { + valid = qtrue; + Com_Printf ("Rcon from %s: %s\n", NET_AdrToString (from), Cmd_ArgsFrom(2) ); + } + + // start redirecting all print outputs to the packet + svs.redirectAddress = from; + Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect); + + if ( !strlen( sv_rconPassword->string ) ) { + Com_Printf ("No rconpassword set on the server.\n"); + } else if ( !valid ) { + Com_Printf ("Bad rconpassword.\n"); + } else { + remaining[0] = 0; + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + // get the command directly, "rcon " to avoid quoting issues + // extract the command by walking + // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing + cmd_aux = Cmd_Cmd(); + cmd_aux+=4; + while(cmd_aux[0]==' ') + cmd_aux++; + while(cmd_aux[0] && cmd_aux[0]!=' ') // password + cmd_aux++; + while(cmd_aux[0]==' ') + cmd_aux++; + + Q_strcat( remaining, sizeof(remaining), cmd_aux); + + Cmd_ExecuteString (remaining); + + } + + Com_EndRedirect (); +} + +/* +================= +SV_ConnectionlessPacket + +A connectionless packet has four leading 0xff +characters to distinguish it from a game channel. +Clients that are in the game can still send +connectionless packets. +================= +*/ +static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 marker + + if (!Q_strncmp("connect", (char *) &msg->data[4], 7)) { + Huff_Decompress(msg, 12); + } + + s = MSG_ReadStringLine( msg ); + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c); + + if (!Q_stricmp(c, "getstatus")) { + SVC_Status( from ); + } else if (!Q_stricmp(c, "getinfo")) { + SVC_Info( from ); + } else if (!Q_stricmp(c, "getchallenge")) { + SV_GetChallenge(from); + } else if (!Q_stricmp(c, "connect")) { + SV_DirectConnect( from ); + } else if (!Q_stricmp(c, "rcon")) { + SVC_RemoteCommand( from, msg ); + } else if (!Q_stricmp(c, "disconnect")) { + // if a client starts up a local server, we may see some spurious + // server disconnect messages when their new server sees our final + // sequenced messages to the old client + } else { + Com_DPrintf ("bad connectionless packet from %s:\n%s\n", + NET_AdrToString (from), s); + } +} + +//============================================================================ + +/* +================= +SV_PacketEvent +================= +*/ +void SV_PacketEvent( netadr_t from, msg_t *msg ) { + int i; + client_t *cl; + int qport; + + // check for connectionless packet (0xffffffff) first + if ( msg->cursize >= 4 && *(int *)msg->data == -1) { + SV_ConnectionlessPacket( from, msg ); + return; + } + + // read the qport out of the message so we can fix up + // stupid address translating routers + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // sequence number + qport = MSG_ReadShort( msg ) & 0xffff; + + // find which client the message is from + for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if (cl->state == CS_FREE) { + continue; + } + if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) { + continue; + } + // it is possible to have multiple clients from a single IP + // address, so they are differentiated by the qport variable + if (cl->netchan.qport != qport) { + continue; + } + + // the IP port can't be used to differentiate them, because + // some address translating routers periodically change UDP + // port assignments + if (cl->netchan.remoteAddress.port != from.port) { + Com_Printf( "SV_PacketEvent: fixing up a translated port\n" ); + cl->netchan.remoteAddress.port = from.port; + } + + // make sure it is a valid, in sequence packet + if (SV_Netchan_Process(cl, msg)) { + // zombie clients still need to do the Netchan_Process + // to make sure they don't need to retransmit the final + // reliable message, but they don't do any other processing + if (cl->state != CS_ZOMBIE) { + cl->lastPacketTime = svs.time; // don't timeout + SV_ExecuteClientMessage( cl, msg ); + } + } + return; + } + + // if we received a sequenced packet from an address we don't recognize, + // send an out of band disconnect packet to it + NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); +} + + +/* +=================== +SV_CalcPings + +Updates the cl->ping variables +=================== +*/ +static void SV_CalcPings( void ) { + int i, j; + client_t *cl; + int total, count; + int delta; + playerState_t *ps; + + for (i=0 ; i < sv_maxclients->integer ; i++) { + cl = &svs.clients[i]; + if ( cl->state != CS_ACTIVE ) { + cl->ping = 999; + continue; + } + if ( !cl->gentity ) { + cl->ping = 999; + continue; + } + + total = 0; + count = 0; + for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { + if ( cl->frames[j].messageAcked <= 0 ) { + continue; + } + delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; + count++; + total += delta; + } + if (!count) { + cl->ping = 999; + } else { + cl->ping = total/count; + if ( cl->ping > 999 ) { + cl->ping = 999; + } + } + + // let the game dll know about the ping + ps = SV_GameClientNum( i ); + ps->ping = cl->ping; + } +} + +/* +================== +SV_CheckTimeouts + +If a packet has not been received from a client for timeout->integer +seconds, drop the conneciton. Server time is used instead of +realtime to avoid dropping the local client while debugging. + +When a client is normally dropped, the client_t goes into a zombie state +for a few seconds to make sure any final reliable message gets resent +if necessary +================== +*/ +static void SV_CheckTimeouts( void ) { + int i; + client_t *cl; + int droppoint; + int zombiepoint; + + droppoint = svs.time - 1000 * sv_timeout->integer; + zombiepoint = svs.time - 1000 * sv_zombietime->integer; + + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + // message times may be wrong across a changelevel + if (cl->lastPacketTime > svs.time) { + cl->lastPacketTime = svs.time; + } + + if (cl->state == CS_ZOMBIE + && cl->lastPacketTime < zombiepoint) { + // using the client id cause the cl->name is empty at this point + Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i ); + cl->state = CS_FREE; // can now be reused + continue; + } + if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) { + // wait several frames so a debugger session doesn't + // cause a timeout + if ( ++cl->timeoutCount > 5 ) { + SV_DropClient (cl, "timed out"); + cl->state = CS_FREE; // don't bother with zombie state + } + } else { + cl->timeoutCount = 0; + } + } +} + + +/* +================== +SV_CheckPaused +================== +*/ +static qboolean SV_CheckPaused( void ) { + int count; + client_t *cl; + int i; + + if ( !cl_paused->integer ) { + return qfalse; + } + + // only pause if there is just a single client connected + count = 0; + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state >= CS_CONNECTED ) { + count++; + } + } + + if ( count > 1 ) { + // don't pause + if (sv_paused->integer) + Cvar_Set("sv_paused", "0"); + return qfalse; + } + + if (!sv_paused->integer) + Cvar_Set("sv_paused", "1"); + return qtrue; +} + +/* +================== +SV_FrameMsec +Return time in millseconds until processing of the next server frame. +================== +*/ +int SV_FrameMsec() +{ + if(sv_fps) + { + int frameMsec; + + frameMsec = 1000.0f / sv_fps->value; + + if(frameMsec < sv.timeResidual) + return 0; + else + return frameMsec - sv.timeResidual; + } + else + return 1; +} + +/* +================== +SV_Frame + +Player movement occurs as a result of packet events, which +happen before SV_Frame is called +================== +*/ +void SV_Frame( int msec ) { + int frameMsec; + int startTime; + + // the menu kills the server with this cvar + if ( sv_killserver->integer ) { + SV_Shutdown ("Server was killed"); + Cvar_Set( "sv_killserver", "0" ); + return; + } + + if (!com_sv_running->integer) + { + // Running as a server, but no map loaded +#ifdef DEDICATED + // Block until something interesting happens + Sys_Sleep(-1); +#endif + + return; + } + + // allow pause if only the local client is connected + if ( SV_CheckPaused() ) { + return; + } + + // if it isn't time for the next frame, do nothing + if ( sv_fps->integer < 1 ) { + Cvar_Set( "sv_fps", "10" ); + } + + frameMsec = 1000 / sv_fps->integer * com_timescale->value; + // don't let it scale below 1ms + if(frameMsec < 1) + { + Cvar_Set("timescale", va("%f", sv_fps->integer / 1000.0f)); + frameMsec = 1; + } + + sv.timeResidual += msec; + + // if time is about to hit the 32nd bit, kick all clients + // and clear sv.time, rather + // than checking for negative time wraparound everywhere. + // 2giga-milliseconds = 23 days, so it won't be too often + if ( svs.time > 0x70000000 ) { + SV_Shutdown( "Restarting server due to time wrapping" ); + Cbuf_AddText( va( "map %s\n", Cvar_VariableString( "mapname" ) ) ); + return; + } + // this can happen considerably earlier when lots of clients play and the map doesn't change + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) { + SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" ); + Cbuf_AddText( va( "map %s\n", Cvar_VariableString( "mapname" ) ) ); + return; + } + + if( sv.restartTime && sv.time >= sv.restartTime ) { + sv.restartTime = 0; + Cbuf_AddText( "map_restart 0\n" ); + return; + } + + // update infostrings if anything has been changed + if ( cvar_modifiedFlags & CVAR_SERVERINFO ) { + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + } + if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) { + SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + } + + if ( com_speeds->integer ) { + startTime = Sys_Milliseconds (); + } else { + startTime = 0; // quite a compiler warning + } + + // update ping based on the all received frames + SV_CalcPings(); + + // run the game simulation in chunks + while ( sv.timeResidual >= frameMsec ) { + sv.timeResidual -= frameMsec; + svs.time += frameMsec; + sv.time += frameMsec; + + // let everything in the world think and move + VM_Call (gvm, GAME_RUN_FRAME, sv.time); + } + + if ( com_speeds->integer ) { + time_game = Sys_Milliseconds () - startTime; + } + + // check timeouts + SV_CheckTimeouts(); + + // send messages back to the clients + SV_SendClientMessages(); + + // send a heartbeat to the master if needed + SV_MasterHeartbeat(sv_heartbeat->string); +} + +//============================================================================ + diff --git a/src/server/sv_net_chan.c b/src/server/sv_net_chan.c new file mode 100644 index 0000000..1adba16 --- /dev/null +++ b/src/server/sv_net_chan.c @@ -0,0 +1,210 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "server.h" + +/* +============== +SV_Netchan_Encode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Encode( client_t *client, msg_t *msg ) { + long i, index; + byte key, *string; + int srdc, sbit; + qboolean soob; + + if ( msg->cursize < SV_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = qfalse; + + /* reliableAcknowledge = */ MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->lastClientCommandString; + index = 0; + // xor the client challenge with the netchan sequence number + key = client->challenge ^ client->netchan.outgoingSequence; + for (i = SV_ENCODE_START; i < msg->cursize; i++) { + // modify the key with the last received and with this message acknowledged client command + if (!string[index]) + index = 0; + if (string[index] > 127) { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // encode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +============== +SV_Netchan_Decode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Decode( client_t *client, msg_t *msg ) { + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit; + qboolean soob; + byte key, *string; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = qfalse; + + serverId = MSG_ReadLong(msg); + messageAcknowledge = MSG_ReadLong(msg); + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // + key = client->challenge ^ serverId ^ messageAcknowledge; + for (i = msg->readcount + SV_DECODE_START; i < msg->cursize; i++) { + // modify the key with the last sent and acknowledged server command + if (!string[index]) + index = 0; + if (string[index] > 127) { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // decode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +================= +SV_Netchan_TransmitNextFragment +================= +*/ +void SV_Netchan_TransmitNextFragment( client_t *client ) { + Netchan_TransmitNextFragment( &client->netchan ); + if (!client->netchan.unsentFragments) + { + // make sure the netchan queue has been properly initialized (you never know) + if ((!client->netchan_end_queue) && (client->state >= CS_CONNECTED)) { + Com_Error(ERR_DROP, "netchan queue is not properly initialized in SV_Netchan_TransmitNextFragment\n"); + } + // the last fragment was transmitted, check wether we have queued messages + if (client->netchan_start_queue) { + netchan_buffer_t *netbuf; + Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n"); + netbuf = client->netchan_start_queue; + SV_Netchan_Encode( client, &netbuf->msg ); + Netchan_Transmit( &client->netchan, netbuf->msg.cursize, netbuf->msg.data ); + // pop from queue + client->netchan_start_queue = netbuf->next; + if (!client->netchan_start_queue) { + Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n"); + client->netchan_end_queue = &client->netchan_start_queue; + } + else + Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n"); + Z_Free(netbuf); + } + } +} + + +/* +=============== +SV_Netchan_Transmit +TTimo +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462 +if there are some unsent fragments (which may happen if the snapshots +and the gamestate are fragmenting, and collide on send for instance) +then buffer them and make sure they get sent in correct order +================ +*/ + +void SV_Netchan_Transmit( client_t *client, msg_t *msg) { //int length, const byte *data ) { + MSG_WriteByte( msg, svc_EOF ); + if (client->netchan.unsentFragments) { + netchan_buffer_t *netbuf; + Com_DPrintf("#462 SV_Netchan_Transmit: unsent fragments, stacked\n"); + netbuf = (netchan_buffer_t *)Z_Malloc(sizeof(netchan_buffer_t)); + // store the msg, we can't store it encoded, as the encoding depends on stuff we still have to finish sending + MSG_Copy(&netbuf->msg, netbuf->msgBuffer, sizeof( netbuf->msgBuffer ), msg); + netbuf->next = NULL; + // insert it in the queue, the message will be encoded and sent later + *client->netchan_end_queue = netbuf; + client->netchan_end_queue = &(*client->netchan_end_queue)->next; + // emit the next fragment of the current message for now + Netchan_TransmitNextFragment(&client->netchan); + } else { + SV_Netchan_Encode( client, msg ); + Netchan_Transmit( &client->netchan, msg->cursize, msg->data ); + } +} + +/* +================= +Netchan_SV_Process +================= +*/ +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) { + int ret; + ret = Netchan_Process( &client->netchan, msg ); + if (!ret) + return qfalse; + SV_Netchan_Decode( client, msg ); + return qtrue; +} + diff --git a/src/server/sv_snapshot.c b/src/server/sv_snapshot.c new file mode 100644 index 0000000..cfc5695 --- /dev/null +++ b/src/server/sv_snapshot.c @@ -0,0 +1,694 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "server.h" + + +/* +============================================================================= + +Delta encode a client frame onto the network channel + +A normal server packet will look like: + +4 sequence number (high bit set if an oversize fragment) + +1 svc_snapshot +4 last client reliable command +4 serverTime +1 lastframe for delta compression +1 snapFlags +1 areaBytes + + + + +============================================================================= +*/ + +/* +============= +SV_EmitPacketEntities + +Writes a delta update of an entityState_t list to the message. +============= +*/ +static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg ) { + entityState_t *oldent, *newent; + int oldindex, newindex; + int oldnum, newnum; + int from_num_entities; + + // generate the delta update + if ( !from ) { + from_num_entities = 0; + } else { + from_num_entities = from->num_entities; + } + + newent = NULL; + oldent = NULL; + newindex = 0; + oldindex = 0; + while ( newindex < to->num_entities || oldindex < from_num_entities ) { + if ( newindex >= to->num_entities ) { + newnum = 9999; + } else { + newent = &svs.snapshotEntities[(to->first_entity+newindex) % svs.numSnapshotEntities]; + newnum = newent->number; + } + + if ( oldindex >= from_num_entities ) { + oldnum = 9999; + } else { + oldent = &svs.snapshotEntities[(from->first_entity+oldindex) % svs.numSnapshotEntities]; + oldnum = oldent->number; + } + + if ( newnum == oldnum ) { + // delta update from old position + // because the force parm is qfalse, this will not result + // in any bytes being emited if the entity has not changed at all + MSG_WriteDeltaEntity (msg, oldent, newent, qfalse ); + oldindex++; + newindex++; + continue; + } + + if ( newnum < oldnum ) { + // this is a new entity, send it from the baseline + MSG_WriteDeltaEntity (msg, &sv.svEntities[newnum].baseline, newent, qtrue ); + newindex++; + continue; + } + + if ( newnum > oldnum ) { + // the old entity isn't present in the new message + MSG_WriteDeltaEntity (msg, oldent, NULL, qtrue ); + oldindex++; + continue; + } + } + + MSG_WriteBits( msg, (MAX_GENTITIES-1), GENTITYNUM_BITS ); // end of packetentities +} + + + +/* +================== +SV_WriteSnapshotToClient +================== +*/ +static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) { + clientSnapshot_t *frame, *oldframe; + int lastframe; + int i; + int snapFlags; + + // this is the snapshot we are creating + frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; + + // try to use a previous frame as the source for delta compressing the snapshot + if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) { + // client is asking for a retransmit + oldframe = NULL; + lastframe = 0; + } else if ( client->netchan.outgoingSequence - client->deltaMessage + >= (PACKET_BACKUP - 3) ) { + // client hasn't gotten a good message through in a long time + Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name); + oldframe = NULL; + lastframe = 0; + } else { + // we have a valid snapshot to delta from + oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ]; + lastframe = client->netchan.outgoingSequence - client->deltaMessage; + + // the snapshot's entities may still have rolled off the buffer, though + if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) { + Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name); + oldframe = NULL; + lastframe = 0; + } + } + + MSG_WriteByte (msg, svc_snapshot); + + // NOTE, MRE: now sent at the start of every message from server to client + // let the client know which reliable clientCommands we have received + //MSG_WriteLong( msg, client->lastClientCommand ); + + // send over the current server time so the client can drift + // its view of time to try to match + if( client->oldServerTime ) { + // The server has not yet got an acknowledgement of the + // new gamestate from this client, so continue to send it + // a time as if the server has not restarted. Note from + // the client's perspective this time is strictly speaking + // incorrect, but since it'll be busy loading a map at + // the time it doesn't really matter. + MSG_WriteLong (msg, sv.time + client->oldServerTime); + } else { + MSG_WriteLong (msg, sv.time); + } + + // what we are delta'ing from + MSG_WriteByte (msg, lastframe); + + snapFlags = svs.snapFlagServerBit; + if ( client->rateDelayed ) { + snapFlags |= SNAPFLAG_RATE_DELAYED; + } + if ( client->state != CS_ACTIVE ) { + snapFlags |= SNAPFLAG_NOT_ACTIVE; + } + + MSG_WriteByte (msg, snapFlags); + + // send over the areabits + MSG_WriteByte (msg, frame->areabytes); + MSG_WriteData (msg, frame->areabits, frame->areabytes); + + // delta encode the playerstate + if ( oldframe ) { + MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps ); + } else { + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps ); + } + + // delta encode the entities + SV_EmitPacketEntities (oldframe, frame, msg); + + // padding for rate debugging + if ( sv_padPackets->integer ) { + for ( i = 0 ; i < sv_padPackets->integer ; i++ ) { + MSG_WriteByte (msg, svc_nop); + } + } +} + + +/* +================== +SV_UpdateServerCommandsToClient + +(re)send all server commands the client hasn't acknowledged yet +================== +*/ +void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ) { + int i; + + // write any unacknowledged serverCommands + for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { + MSG_WriteByte( msg, svc_serverCommand ); + MSG_WriteLong( msg, i ); + MSG_WriteString( msg, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + client->reliableSent = client->reliableSequence; +} + +/* +============================================================================= + +Build a client snapshot structure + +============================================================================= +*/ + +#define MAX_SNAPSHOT_ENTITIES 1024 +typedef struct { + int numSnapshotEntities; + int snapshotEntities[MAX_SNAPSHOT_ENTITIES]; +} snapshotEntityNumbers_t; + +/* +======================= +SV_QsortEntityNumbers +======================= +*/ +static int QDECL SV_QsortEntityNumbers( const void *a, const void *b ) { + int *ea, *eb; + + ea = (int *)a; + eb = (int *)b; + + if ( *ea == *eb ) { + Com_Error( ERR_DROP, "SV_QsortEntityStates: duplicated entity" ); + } + + if ( *ea < *eb ) { + return -1; + } + + return 1; +} + + +/* +=============== +SV_AddEntToSnapshot +=============== +*/ +static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntity_t *gEnt, snapshotEntityNumbers_t *eNums ) { + // if we have already added this entity to this snapshot, don't add again + if ( svEnt->snapshotCounter == sv.snapshotCounter ) { + return; + } + svEnt->snapshotCounter = sv.snapshotCounter; + + // if we are full, silently discard entities + if ( eNums->numSnapshotEntities == MAX_SNAPSHOT_ENTITIES ) { + return; + } + + eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number; + eNums->numSnapshotEntities++; +} + +/* +=============== +SV_AddEntitiesVisibleFromPoint +=============== +*/ +static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, + snapshotEntityNumbers_t *eNums, qboolean portal ) { + int e, i; + sharedEntity_t *ent; + svEntity_t *svEnt; + int l; + int clientarea, clientcluster; + int leafnum; + byte *clientpvs; + byte *bitvector; + + // during an error shutdown message we may need to transmit + // the shutdown message after the server has shutdown, so + // specfically check for it + if ( !sv.state ) { + return; + } + + leafnum = CM_PointLeafnum (origin); + clientarea = CM_LeafArea (leafnum); + clientcluster = CM_LeafCluster (leafnum); + + // calculate the visible areas + frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea ); + + clientpvs = CM_ClusterPVS (clientcluster); + + for ( e = 0 ; e < sv.num_entities ; e++ ) { + ent = SV_GentityNum(e); + + // never send entities that aren't linked in + if ( !ent->r.linked ) { + continue; + } + + if (ent->s.number != e) { + Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n"); + ent->s.number = e; + } + + // entities can be flagged to explicitly not be sent to the client + if ( ent->r.svFlags & SVF_NOCLIENT ) { + continue; + } + + // entities can be flagged to be sent to only one client + if ( ent->r.svFlags & SVF_SINGLECLIENT ) { + if ( ent->r.singleClient != frame->ps.clientNum ) { + continue; + } + } + // entities can be flagged to be sent to everyone but one client + if ( ent->r.svFlags & SVF_NOTSINGLECLIENT ) { + if ( ent->r.singleClient == frame->ps.clientNum ) { + continue; + } + } + // entities can be flagged to be sent to a given mask of clients + if ( ent->r.svFlags & SVF_CLIENTMASK ) { + if (frame->ps.clientNum >= 32) + Com_Error( ERR_DROP, "SVF_CLIENTMASK: clientNum >= 32\n" ); + if (~ent->r.singleClient & (1 << frame->ps.clientNum)) + continue; + } + + svEnt = SV_SvEntityForGentity( ent ); + + // don't double add an entity through portals + if ( svEnt->snapshotCounter == sv.snapshotCounter ) { + continue; + } + + // broadcast entities are always sent + if ( ent->r.svFlags & SVF_BROADCAST ) { + SV_AddEntToSnapshot( svEnt, ent, eNums ); + continue; + } + + // ignore if not touching a PV leaf + // check area + if ( !CM_AreasConnected( clientarea, svEnt->areanum ) ) { + // doors can legally straddle two areas, so + // we may need to check another one + if ( !CM_AreasConnected( clientarea, svEnt->areanum2 ) ) { + continue; // blocked by a door + } + } + + bitvector = clientpvs; + + // check individual leafs + if ( !svEnt->numClusters ) { + continue; + } + l = 0; + for ( i=0 ; i < svEnt->numClusters ; i++ ) { + l = svEnt->clusternums[i]; + if ( bitvector[l >> 3] & (1 << (l&7) ) ) { + break; + } + } + + // if we haven't found it to be visible, + // check overflow clusters that coudln't be stored + if ( i == svEnt->numClusters ) { + if ( svEnt->lastCluster ) { + for ( ; l <= svEnt->lastCluster ; l++ ) { + if ( bitvector[l >> 3] & (1 << (l&7) ) ) { + break; + } + } + if ( l == svEnt->lastCluster ) { + continue; // not visible + } + } else { + continue; + } + } + + // add it + SV_AddEntToSnapshot( svEnt, ent, eNums ); + + // if its a portal entity, add everything visible from its camera position + if ( ent->r.svFlags & SVF_PORTAL ) { + if ( ent->s.generic1 ) { + vec3_t dir; + VectorSubtract(ent->s.origin, origin, dir); + if ( VectorLengthSquared(dir) > (float) ent->s.generic1 * ent->s.generic1 ) { + continue; + } + } + SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue ); + } + + } +} + +/* +============= +SV_BuildClientSnapshot + +Decides which entities are going to be visible to the client, and +copies off the playerstate and areabits. + +This properly handles multiple recursive portals, but the render +currently doesn't. + +For viewing through other player's eyes, clent can be something other than client->gentity +============= +*/ +static void SV_BuildClientSnapshot( client_t *client ) { + vec3_t org; + clientSnapshot_t *frame; + snapshotEntityNumbers_t entityNumbers; + int i; + sharedEntity_t *ent; + entityState_t *state; + svEntity_t *svEnt; + sharedEntity_t *clent; + int clientNum; + playerState_t *ps; + + // bump the counter used to prevent double adding + sv.snapshotCounter++; + + // this is the frame we are creating + frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; + + // clear everything in this snapshot + entityNumbers.numSnapshotEntities = 0; + Com_Memset( frame->areabits, 0, sizeof( frame->areabits ) ); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=62 + frame->num_entities = 0; + + clent = client->gentity; + if ( !clent || client->state == CS_ZOMBIE ) { + return; + } + + // grab the current playerState_t + ps = SV_GameClientNum( client - svs.clients ); + frame->ps = *ps; + + // never send client's own entity, because it can + // be regenerated from the playerstate + clientNum = frame->ps.clientNum; + if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + svEnt = &sv.svEntities[ clientNum ]; + + svEnt->snapshotCounter = sv.snapshotCounter; + + // find the client's viewpoint + VectorCopy( ps->origin, org ); + org[2] += ps->viewheight; + + // add all the entities directly visible to the eye, which + // may include portal entities that merge other viewpoints + SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse ); + + // if there were portals visible, there may be out of order entities + // in the list which will need to be resorted for the delta compression + // to work correctly. This also catches the error condition + // of an entity being included twice. + qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities, + sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers ); + + // now that all viewpoint's areabits have been OR'd together, invert + // all of them to make it a mask vector, which is what the renderer wants + for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) { + ((int *)frame->areabits)[i] = ((int *)frame->areabits)[i] ^ -1; + } + + // copy the entity states out + frame->num_entities = 0; + frame->first_entity = svs.nextSnapshotEntities; + for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) { + ent = SV_GentityNum(entityNumbers.snapshotEntities[i]); + state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; + *state = ent->s; + svs.nextSnapshotEntities++; + // this should never hit, map should always be restarted first in SV_Frame + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) { + Com_Error(ERR_FATAL, "svs.nextSnapshotEntities wrapped"); + } + frame->num_entities++; + } +} + + +/* +==================== +SV_RateMsec + +Return the number of msec a given size message is supposed +to take to clear, based on the current rate +==================== +*/ +#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead +static int SV_RateMsec( client_t *client, int messageSize ) { + int rate; + int rateMsec; + + // individual messages will never be larger than fragment size + if ( messageSize > 1500 ) { + messageSize = 1500; + } + rate = client->rate; + if ( sv_maxRate->integer ) { + if ( sv_maxRate->integer < 1000 ) { + Cvar_Set( "sv_MaxRate", "1000" ); + } + if ( sv_maxRate->integer < rate ) { + rate = sv_maxRate->integer; + } + } + if ( sv_minRate->integer ) { + if ( sv_minRate->integer < 1000 ) + Cvar_Set( "sv_minRate", "1000" ); + if ( sv_minRate->integer > rate ) + rate = sv_minRate->integer; + } + + rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / ((int) (rate * com_timescale->value)); + + return rateMsec; +} + +/* +======================= +SV_SendMessageToClient + +Called by SV_SendClientSnapshot and SV_SendClientGameState +======================= +*/ +void SV_SendMessageToClient( msg_t *msg, client_t *client ) { + int rateMsec; + + // record information about the message + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; + + // send the datagram + SV_Netchan_Transmit( client, msg ); //msg->cursize, msg->data ); + + // set nextSnapshotTime based on rate and requested number of updates + + // local clients get snapshots every server frame + // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=491 + // added sv_lanForceRate check + if ( client->netchan.remoteAddress.type == NA_LOOPBACK || (sv_lanForceRate->integer && Sys_IsLANAddress (client->netchan.remoteAddress)) ) { + client->nextSnapshotTime = svs.time + ((int) (1000.0 / sv_fps->integer * com_timescale->value)); + return; + } + + // normal rate / snapshotMsec calculation + rateMsec = SV_RateMsec(client, msg->cursize); + + if ( rateMsec < client->snapshotMsec * com_timescale->value) { + // never send more packets than this, no matter what the rate is at + rateMsec = client->snapshotMsec * com_timescale->value; + client->rateDelayed = qfalse; + } else { + client->rateDelayed = qtrue; + } + + client->nextSnapshotTime = svs.time + ((int) (rateMsec * com_timescale->value)); + + // don't pile up empty snapshots while connecting + if ( client->state != CS_ACTIVE ) { + // a gigantic connection message may have already put the nextSnapshotTime + // more than a second away, so don't shorten it + // do shorten if client is downloading + if (!*client->downloadName && client->nextSnapshotTime < svs.time + ((int) (1000.0 * com_timescale->value))) + client->nextSnapshotTime = svs.time + ((int) (1000 * com_timescale->value)); + } +} + + +/* +======================= +SV_SendClientSnapshot + +Also called by SV_FinalMessage + +======================= +*/ +void SV_SendClientSnapshot( client_t *client ) { + byte msg_buf[MAX_MSGLEN]; + msg_t msg; + + // build the snapshot + SV_BuildClientSnapshot( client ); + + MSG_Init (&msg, msg_buf, sizeof(msg_buf)); + msg.allowoverflow = qtrue; + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // (re)send any reliable server commands + SV_UpdateServerCommandsToClient( client, &msg ); + + // send over all the relevant entityState_t + // and the playerState_t + SV_WriteSnapshotToClient( client, &msg ); + + // Add any download data if the client is downloading + SV_WriteDownloadToClient( client, &msg ); + +#ifdef USE_VOIP + SV_WriteVoipToClient( client, &msg ); +#endif + + // check for overflow + if ( msg.overflowed ) { + Com_Printf ("WARNING: msg overflowed for %s\n", client->name); + MSG_Clear (&msg); + } + + SV_SendMessageToClient( &msg, client ); +} + + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages( void ) { + int i; + client_t *c; + + // send a message to each connected client + for (i=0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++) { + if (!c->state) { + continue; // not connected + } + + if ( svs.time < c->nextSnapshotTime ) { + continue; // not time yet + } + + // send additional message fragments if the last message + // was too large to send at once + if ( c->netchan.unsentFragments ) { + c->nextSnapshotTime = svs.time + + SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart ); + SV_Netchan_TransmitNextFragment( c ); + continue; + } + + // generate and send a new message + SV_SendClientSnapshot( c ); + } +} + diff --git a/src/server/sv_world.c b/src/server/sv_world.c new file mode 100644 index 0000000..a91f6f2 --- /dev/null +++ b/src/server/sv_world.c @@ -0,0 +1,692 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// world.c -- world query functions + +#include "server.h" + +/* +================ +SV_ClipHandleForEntity + +Returns a headnode that can be used for testing or clipping to a +given entity. If the entity is a bsp model, the headnode will +be returned, otherwise a custom box tree will be constructed. +================ +*/ +clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ) { + if ( ent->r.bmodel ) { + // explicit hulls in the BSP model + return CM_InlineModel( ent->s.modelindex ); + } + if ( ent->r.svFlags & SVF_CAPSULE ) { + // create a temp capsule from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue ); + } + + // create a temp tree from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse ); +} + + + +/* +=============================================================================== + +ENTITY CHECKING + +To avoid linearly searching through lists of entities during environment testing, +the world is carved up with an evenly spaced, axially aligned bsp tree. Entities +are kept in chains either at the final leafs, or at the first node that splits +them, which prevents having to deal with multiple fragments of a single entity. + +=============================================================================== +*/ + +typedef struct worldSector_s { + int axis; // -1 = leaf node + float dist; + struct worldSector_s *children[2]; + svEntity_t *entities; +} worldSector_t; + +#define AREA_DEPTH 4 +#define AREA_NODES 64 + +worldSector_t sv_worldSectors[AREA_NODES]; +int sv_numworldSectors; + + +/* +=============== +SV_SectorList_f +=============== +*/ +void SV_SectorList_f( void ) { + int i, c; + worldSector_t *sec; + svEntity_t *ent; + + for ( i = 0 ; i < AREA_NODES ; i++ ) { + sec = &sv_worldSectors[i]; + + c = 0; + for ( ent = sec->entities ; ent ; ent = ent->nextEntityInWorldSector ) { + c++; + } + Com_Printf( "sector %i: %i entities\n", i, c ); + } +} + +/* +=============== +SV_CreateworldSector + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +static worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) { + worldSector_t *anode; + vec3_t size; + vec3_t mins1, maxs1, mins2, maxs2; + + anode = &sv_worldSectors[sv_numworldSectors]; + sv_numworldSectors++; + + if (depth == AREA_DEPTH) { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + return anode; + } + + VectorSubtract (maxs, mins, size); + if (size[0] > size[1]) { + anode->axis = 0; + } else { + anode->axis = 1; + } + + anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]); + VectorCopy (mins, mins1); + VectorCopy (mins, mins2); + VectorCopy (maxs, maxs1); + VectorCopy (maxs, maxs2); + + maxs1[anode->axis] = mins2[anode->axis] = anode->dist; + + anode->children[0] = SV_CreateworldSector (depth+1, mins2, maxs2); + anode->children[1] = SV_CreateworldSector (depth+1, mins1, maxs1); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld( void ) { + clipHandle_t h; + vec3_t mins, maxs; + + Com_Memset( sv_worldSectors, 0, sizeof(sv_worldSectors) ); + sv_numworldSectors = 0; + + // get world map bounds + h = CM_InlineModel( 0 ); + CM_ModelBounds( h, mins, maxs ); + SV_CreateworldSector( 0, mins, maxs ); +} + + +/* +=============== +SV_UnlinkEntity + +=============== +*/ +void SV_UnlinkEntity( sharedEntity_t *gEnt ) { + svEntity_t *ent; + svEntity_t *scan; + worldSector_t *ws; + + ent = SV_SvEntityForGentity( gEnt ); + + gEnt->r.linked = qfalse; + + ws = ent->worldSector; + if ( !ws ) { + return; // not linked in anywhere + } + ent->worldSector = NULL; + + if ( ws->entities == ent ) { + ws->entities = ent->nextEntityInWorldSector; + return; + } + + for ( scan = ws->entities ; scan ; scan = scan->nextEntityInWorldSector ) { + if ( scan->nextEntityInWorldSector == ent ) { + scan->nextEntityInWorldSector = ent->nextEntityInWorldSector; + return; + } + } + + Com_Printf( "WARNING: SV_UnlinkEntity: not found in worldSector\n" ); +} + + +/* +=============== +SV_LinkEntity + +=============== +*/ +#define MAX_TOTAL_ENT_LEAFS 128 +void SV_LinkEntity( sharedEntity_t *gEnt ) { + worldSector_t *node; + int leafs[MAX_TOTAL_ENT_LEAFS]; + int cluster; + int num_leafs; + int i, j, k; + int area; + int lastLeaf; + float *origin, *angles; + svEntity_t *ent; + + ent = SV_SvEntityForGentity( gEnt ); + + if ( ent->worldSector ) { + SV_UnlinkEntity( gEnt ); // unlink from old position + } + + // encode the size into the entityState_t for client prediction + if ( gEnt->r.bmodel ) { + gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value + } else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { + // assume that x/y are equal and symetric + i = gEnt->r.maxs[0]; + if (i<1) + i = 1; + if (i>255) + i = 255; + + // z is not symetric + j = (-gEnt->r.mins[2]); + if (j<1) + j = 1; + if (j>255) + j = 255; + + // and z maxs can be negative... + k = (gEnt->r.maxs[2]+32); + if (k<1) + k = 1; + if (k>255) + k = 255; + + gEnt->s.solid = (k<<16) | (j<<8) | i; + } else { + gEnt->s.solid = 0; + } + + // get the position + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + // set the abs box + if ( gEnt->r.bmodel && (angles[0] || angles[1] || angles[2]) ) { + // expand for rotation + float max; + int i; + + max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs ); + for (i=0 ; i<3 ; i++) { + gEnt->r.absmin[i] = origin[i] - max; + gEnt->r.absmax[i] = origin[i] + max; + } + } else { + // normal + VectorAdd (origin, gEnt->r.mins, gEnt->r.absmin); + VectorAdd (origin, gEnt->r.maxs, gEnt->r.absmax); + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + gEnt->r.absmin[0] -= 1; + gEnt->r.absmin[1] -= 1; + gEnt->r.absmin[2] -= 1; + gEnt->r.absmax[0] += 1; + gEnt->r.absmax[1] += 1; + gEnt->r.absmax[2] += 1; + + // link to PVS leafs + ent->numClusters = 0; + ent->lastCluster = 0; + ent->areanum = -1; + ent->areanum2 = -1; + + //get all leafs, including solids + num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax, + leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf ); + + // if none of the leafs were inside the map, the + // entity is outside the world and can be considered unlinked + if ( !num_leafs ) { + return; + } + + // set areas, even from clusters that don't fit in the entity array + for (i=0 ; iareanum != -1 && ent->areanum != area) { + if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) { + Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n", + gEnt->s.number, + gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2]); + } + ent->areanum2 = area; + } else { + ent->areanum = area; + } + } + } + + // store as many explicit clusters as we can + ent->numClusters = 0; + for (i=0 ; i < num_leafs ; i++) { + cluster = CM_LeafCluster( leafs[i] ); + if ( cluster != -1 ) { + ent->clusternums[ent->numClusters++] = cluster; + if ( ent->numClusters == MAX_ENT_CLUSTERS ) { + break; + } + } + } + + // store off a last cluster if we need to + if ( i != num_leafs ) { + ent->lastCluster = CM_LeafCluster( lastLeaf ); + } + + gEnt->r.linkcount++; + + // find the first world sector node that the ent's box crosses + node = sv_worldSectors; + while (1) + { + if (node->axis == -1) + break; + if ( gEnt->r.absmin[node->axis] > node->dist) + node = node->children[0]; + else if ( gEnt->r.absmax[node->axis] < node->dist) + node = node->children[1]; + else + break; // crosses the node + } + + // link it in + ent->worldSector = node; + ent->nextEntityInWorldSector = node->entities; + node->entities = ent; + + gEnt->r.linked = qtrue; +} + +/* +============================================================================ + +AREA QUERY + +Fills in a list of all entities who's absmin / absmax intersects the given +bounds. This does NOT mean that they actually touch in the case of bmodels. +============================================================================ +*/ + +typedef struct { + const float *mins; + const float *maxs; + int *list; + int count, maxcount; +} areaParms_t; + + +/* +==================== +SV_AreaEntities_r + +==================== +*/ +static void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { + svEntity_t *check, *next; + sharedEntity_t *gcheck; + int count; + + count = 0; + + for ( check = node->entities ; check ; check = next ) { + next = check->nextEntityInWorldSector; + + gcheck = SV_GEntityForSvEntity( check ); + + if ( gcheck->r.absmin[0] > ap->maxs[0] + || gcheck->r.absmin[1] > ap->maxs[1] + || gcheck->r.absmin[2] > ap->maxs[2] + || gcheck->r.absmax[0] < ap->mins[0] + || gcheck->r.absmax[1] < ap->mins[1] + || gcheck->r.absmax[2] < ap->mins[2]) { + continue; + } + + if ( ap->count == ap->maxcount ) { + Com_Printf ("SV_AreaEntities: MAXCOUNT\n"); + return; + } + + ap->list[ap->count] = check - sv.svEntities; + ap->count++; + } + + if (node->axis == -1) { + return; // terminal node + } + + // recurse down both sides + if ( ap->maxs[node->axis] > node->dist ) { + SV_AreaEntities_r ( node->children[0], ap ); + } + if ( ap->mins[node->axis] < node->dist ) { + SV_AreaEntities_r ( node->children[1], ap ); + } +} + +/* +================ +SV_AreaEntities +================ +*/ +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ) { + areaParms_t ap; + + ap.mins = mins; + ap.maxs = maxs; + ap.list = entityList; + ap.count = 0; + ap.maxcount = maxcount; + + SV_AreaEntities_r( sv_worldSectors, &ap ); + + return ap.count; +} + + + +//=========================================================================== + + +typedef struct { + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + const float *mins; + const float *maxs; // size of the moving object + const float *start; + vec3_t end; + trace_t trace; + int passEntityNum; + int contentmask; + traceType_t collisionType; +} moveclip_t; + + +/* +==================== +SV_ClipToEntity + +==================== +*/ +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, traceType_t type ) { + sharedEntity_t *touch; + clipHandle_t clipHandle; + float *origin, *angles; + + touch = SV_GentityNum( entityNum ); + + Com_Memset(trace, 0, sizeof(trace_t)); + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( contentmask & touch->r.contents ) ) { + trace->fraction = 1.0; + return; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace ( trace, (float *)start, (float *)end, + (float *)mins, (float *)maxs, clipHandle, contentmask, + origin, angles, type); + + if ( trace->fraction < 1 ) { + trace->entityNum = touch->s.number; + } +} + + +/* +==================== +SV_ClipMoveToEntities + +==================== +*/ +static void SV_ClipMoveToEntities( moveclip_t *clip ) { + int i, num; + int touchlist[MAX_GENTITIES]; + sharedEntity_t *touch; + int passOwnerNum; + trace_t trace; + clipHandle_t clipHandle; + float *origin, *angles; + + num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); + + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum; + if ( passOwnerNum == ENTITYNUM_NONE ) { + passOwnerNum = -1; + } + } else { + passOwnerNum = -1; + } + + for ( i=0 ; itrace.allsolid ) { + return; + } + touch = SV_GentityNum( touchlist[i] ); + + // see if we should ignore this entity + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + if ( touchlist[i] == clip->passEntityNum ) { + continue; // don't clip against the pass entity + } + if ( touch->r.ownerNum == clip->passEntityNum ) { + continue; // don't clip against own missiles + } + if ( touch->r.ownerNum == passOwnerNum ) { + continue; // don't clip against other missiles from our owner + } + } + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( clip->contentmask & touch->r.contents ) ) { + continue; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace ( &trace, (float *)clip->start, (float *)clip->end, + (float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask, + origin, angles, clip->collisionType); + + if ( trace.allsolid ) { + clip->trace.allsolid = qtrue; + clip->trace.entityNum = touch->s.number; + } else if ( trace.startsolid ) { + clip->trace.startsolid = qtrue; + clip->trace.entityNum = touch->s.number; + } + + if ( trace.fraction < clip->trace.fraction ) { + qboolean oldStart; + + // make sure we keep a startsolid from a previous trace + oldStart = clip->trace.startsolid; + + trace.entityNum = touch->s.number; + clip->trace = trace; + clip->trace.startsolid |= oldStart; + } + } +} + + +/* +================== +SV_Trace + +Moves the given mins/maxs volume through the world from start to end. +passEntityNum and entities owned by passEntityNum are explicitly not checked. +================== +*/ +void SV_Trace( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, traceType_t type ) { + moveclip_t clip; + int i; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + Com_Memset ( &clip, 0, sizeof ( moveclip_t ) ); + + // clip to world + CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, type ); + clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( clip.trace.fraction == 0 ) { + *results = clip.trace; + return; // blocked immediately by the world + } + + clip.contentmask = contentmask; + clip.start = start; +// VectorCopy( clip.trace.endpos, clip.end ); + VectorCopy( end, clip.end ); + clip.mins = mins; + clip.maxs = maxs; + clip.passEntityNum = passEntityNum; + clip.collisionType = type; + + // create the bounding box of the entire move + // we can limit it to the part of the move not + // already clipped off by the world, which can be + // a significant savings for line of sight and shot traces + for ( i=0 ; i<3 ; i++ ) { + if ( end[i] > start[i] ) { + clip.boxmins[i] = clip.start[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.end[i] + clip.maxs[i] + 1; + } else { + clip.boxmins[i] = clip.end[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.start[i] + clip.maxs[i] + 1; + } + } + + // clip to other solid entities + SV_ClipMoveToEntities ( &clip ); + + *results = clip.trace; +} + + + +/* +============= +SV_PointContents +============= +*/ +int SV_PointContents( const vec3_t p, int passEntityNum ) { + int touch[MAX_GENTITIES]; + sharedEntity_t *hit; + int i, num; + int contents, c2; + clipHandle_t clipHandle; + float *angles; + + // get base contents from world + contents = CM_PointContents( p, 0 ); + + // or in contents from all the other entities + num = SV_AreaEntities( p, p, touch, MAX_GENTITIES ); + + for ( i=0 ; is.angles; + if ( !hit->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + c2 = CM_TransformedPointContents (p, clipHandle, hit->s.origin, hit->s.angles); + + contents |= c2; + } + + return contents; +} + + diff --git a/src/sys/con_log.c b/src/sys/con_log.c new file mode 100644 index 0000000..a54117c --- /dev/null +++ b/src/sys/con_log.c @@ -0,0 +1,130 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "sys_local.h" + +#define MAX_LOG 32768 + +static char consoleLog[ MAX_LOG ]; +static unsigned int writePos = 0; +static unsigned int readPos = 0; + +/* +================== +CON_LogSize +================== +*/ +unsigned int CON_LogSize( void ) +{ + if( readPos <= writePos ) + return writePos - readPos; + else + return writePos + MAX_LOG - readPos; +} + +/* +================== +CON_LogFree +================== +*/ +static unsigned int CON_LogFree( void ) +{ + return MAX_LOG - CON_LogSize( ) - 1; +} + +/* +================== +CON_LogWrite +================== +*/ +unsigned int CON_LogWrite( const char *in ) +{ + unsigned int length = strlen( in ); + unsigned int firstChunk; + unsigned int secondChunk; + + while( CON_LogFree( ) < length && CON_LogSize( ) > 0 ) + { + // Free enough space + while( consoleLog[ readPos ] != '\n' && CON_LogSize( ) > 1 ) + readPos = ( readPos + 1 ) % MAX_LOG; + + // Skip past the '\n' + readPos = ( readPos + 1 ) % MAX_LOG; + } + + if( CON_LogFree( ) < length ) + return 0; + + if( writePos + length > MAX_LOG ) + { + firstChunk = MAX_LOG - writePos; + secondChunk = length - firstChunk; + } + else + { + firstChunk = length; + secondChunk = 0; + } + + Com_Memcpy( consoleLog + writePos, in, firstChunk ); + Com_Memcpy( consoleLog, in + firstChunk, secondChunk ); + + writePos = ( writePos + length ) % MAX_LOG; + + return length; +} + +/* +================== +CON_LogRead +================== +*/ +unsigned int CON_LogRead( char *out, unsigned int outSize ) +{ + unsigned int firstChunk; + unsigned int secondChunk; + + if( CON_LogSize( ) < outSize ) + outSize = CON_LogSize( ); + + if( readPos + outSize > MAX_LOG ) + { + firstChunk = MAX_LOG - readPos; + secondChunk = outSize - firstChunk; + } + else + { + firstChunk = outSize; + secondChunk = 0; + } + + Com_Memcpy( out, consoleLog + readPos, firstChunk ); + Com_Memcpy( out + firstChunk, out, secondChunk ); + + readPos = ( readPos + outSize ) % MAX_LOG; + + return outSize; +} diff --git a/src/sys/con_passive.c b/src/sys/con_passive.c new file mode 100644 index 0000000..1650b10 --- /dev/null +++ b/src/sys/con_passive.c @@ -0,0 +1,69 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "sys_local.h" + +#include + +/* +================== +CON_Shutdown +================== +*/ +void CON_Shutdown( void ) +{ +} + +/* +================== +CON_Init +================== +*/ +void CON_Init( void ) +{ +} + +/* +================== +CON_Input +================== +*/ +char *CON_Input( void ) +{ + return NULL; +} + +/* +================== +CON_Print +================== +*/ +void CON_Print( const char *msg ) +{ + if( com_ansiColor && com_ansiColor->integer ) + Sys_AnsiColorPrint( msg ); + else + fputs( msg, stderr ); +} diff --git a/src/sys/con_tty.c b/src/sys/con_tty.c new file mode 100644 index 0000000..e4b2ad5 --- /dev/null +++ b/src/sys/con_tty.c @@ -0,0 +1,478 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "sys_local.h" + +#include +#include +#include +#include +#include + +/* +============================================================= +tty console routines + +NOTE: if the user is editing a line when something gets printed to the early +console then it won't look good so we provide CON_Hide and CON_Show to be +called before and after a stdout or stderr output +============================================================= +*/ + +extern qboolean stdinIsATTY; +static qboolean stdin_active; +// general flag to tell about tty console mode +static qboolean ttycon_on = qfalse; +static int ttycon_hide = 0; + +// some key codes that the terminal may be using, initialised on start up +static int TTY_erase; +static int TTY_eof; + +static struct termios TTY_tc; + +static field_t TTY_con; + +// This is somewhat of aduplicate of the graphical console history +// but it's safer more modular to have our own here +#define CON_HISTORY 32 +static field_t ttyEditLines[ CON_HISTORY ]; +static int hist_current = -1, hist_count = 0; + +/* +================== +CON_FlushIn + +Flush stdin, I suspect some terminals are sending a LOT of shit +FIXME relevant? +================== +*/ +static void CON_FlushIn( void ) +{ + char key; + while (read(STDIN_FILENO, &key, 1)!=-1); +} + +/* +================== +CON_Back + +Output a backspace + +NOTE: it seems on some terminals just sending '\b' is not enough so instead we +send "\b \b" +(FIXME there may be a way to find out if '\b' alone would work though) +================== +*/ +static void CON_Back( void ) +{ + char key; + size_t size; + + key = '\b'; + size = write(STDOUT_FILENO, &key, 1); + key = ' '; + size = write(STDOUT_FILENO, &key, 1); + key = '\b'; + size = write(STDOUT_FILENO, &key, 1); +} + +/* +================== +CON_Hide + +Clear the display of the line currently edited +bring cursor back to beginning of line +================== +*/ +static void CON_Hide( void ) +{ + if( ttycon_on ) + { + int i; + if (ttycon_hide) + { + ttycon_hide++; + return; + } + if (TTY_con.cursor>0) + { + for (i=0; i0); + ttycon_hide--; + if (ttycon_hide == 0) + { + size_t size; + size = write(STDOUT_FILENO, "]", 1); + if (TTY_con.cursor) + { + for (i=0; i= 0); + assert(hist_current >= -1); + assert(hist_current <= hist_count); + // make some room + for (i=CON_HISTORY-1; i>0; i--) + { + ttyEditLines[i] = ttyEditLines[i-1]; + } + ttyEditLines[0] = *field; + if (hist_count= 0); + assert(hist_current >= -1); + assert(hist_current <= hist_count); + hist_prev = hist_current + 1; + if (hist_prev >= hist_count) + { + return NULL; + } + hist_current++; + return &(ttyEditLines[hist_current]); +} + +/* +================== +Hist_Next +================== +*/ +field_t *Hist_Next( void ) +{ + assert(hist_count <= CON_HISTORY); + assert(hist_count >= 0); + assert(hist_current >= -1); + assert(hist_current <= hist_count); + if (hist_current >= 0) + { + hist_current--; + } + if (hist_current == -1) + { + return NULL; + } + return &(ttyEditLines[hist_current]); +} + +/* +================== +CON_SigCont +Reinitialize console input after receiving SIGCONT, as on Linux the terminal seems to lose all +set attributes if user did CTRL+Z and then does fg again. +================== +*/ + +void CON_SigCont(int signum) +{ + CON_Init(); +} + +/* +================== +CON_Init + +Initialize the console input (tty mode if possible) +================== +*/ +void CON_Init( void ) +{ + struct termios tc; + + // If the process is backgrounded (running non interactively) + // then SIGTTIN or SIGTOU is emitted, if not caught, turns into a SIGSTP + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + + // If SIGCONT is received, reinitialize console + signal(SIGCONT, CON_SigCont); + + // Make stdin reads non-blocking + fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK ); + + if (!stdinIsATTY) + { + Com_Printf("tty console mode disabled\n"); + ttycon_on = qfalse; + stdin_active = qtrue; + return; + } + + Field_Clear(&TTY_con); + tcgetattr (STDIN_FILENO, &TTY_tc); + TTY_erase = TTY_tc.c_cc[VERASE]; + TTY_eof = TTY_tc.c_cc[VEOF]; + tc = TTY_tc; + + /* + ECHO: don't echo input characters + ICANON: enable canonical mode. This enables the special + characters EOF, EOL, EOL2, ERASE, KILL, REPRINT, + STATUS, and WERASE, and buffers by lines. + ISIG: when any of the characters INTR, QUIT, SUSP, or + DSUSP are received, generate the corresponding sig­ + nal + */ + tc.c_lflag &= ~(ECHO | ICANON); + + /* + ISTRIP strip off bit 8 + INPCK enable input parity checking + */ + tc.c_iflag &= ~(ISTRIP | INPCK); + tc.c_cc[VMIN] = 1; + tc.c_cc[VTIME] = 0; + tcsetattr (STDIN_FILENO, TCSADRAIN, &tc); + ttycon_on = qtrue; +} + +/* +================== +CON_Input +================== +*/ +char *CON_Input( void ) +{ + // we use this when sending back commands + static char text[MAX_EDIT_LINE]; + int avail; + char key; + field_t *history; + size_t size; + + if(ttycon_on) + { + avail = read(STDIN_FILENO, &key, 1); + if (avail != -1) + { + // we have something + // backspace? + // NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere + if ((key == TTY_erase) || (key == 127) || (key == 8)) + { + if (TTY_con.cursor > 0) + { + TTY_con.cursor--; + TTY_con.buffer[TTY_con.cursor] = '\0'; + CON_Back(); + } + return NULL; + } + // check if this is a control char + if ((key) && (key) < ' ') + { + if (key == '\n') + { + // push it in history + Hist_Add(&TTY_con); + Q_strncpyz(text, TTY_con.buffer, sizeof(text)); + Field_Clear(&TTY_con); + key = '\n'; + size = write(1, &key, 1); + size = write( 1, "]", 1 ); + return text; + } + if (key == '\t') + { + CON_Hide(); + Field_AutoComplete( &TTY_con ); + CON_Show(); + return NULL; + } + avail = read(STDIN_FILENO, &key, 1); + if (avail != -1) + { + // VT 100 keys + if (key == '[' || key == 'O') + { + avail = read(STDIN_FILENO, &key, 1); + if (avail != -1) + { + switch (key) + { + case 'A': + history = Hist_Prev(); + if (history) + { + CON_Hide(); + TTY_con = *history; + CON_Show(); + } + CON_FlushIn(); + return NULL; + break; + case 'B': + history = Hist_Next(); + CON_Hide(); + if (history) + { + TTY_con = *history; + } else + { + Field_Clear(&TTY_con); + } + CON_Show(); + CON_FlushIn(); + return NULL; + break; + case 'C': + return NULL; + case 'D': + return NULL; + } + } + } + } + Com_DPrintf("droping ISCTL sequence: %d, TTY_erase: %d\n", key, TTY_erase); + CON_FlushIn(); + return NULL; + } + if (TTY_con.cursor >= sizeof(text) - 1) + return NULL; + // push regular character + TTY_con.buffer[TTY_con.cursor] = key; + TTY_con.cursor++; + // print the current line (this is differential) + size = write(STDOUT_FILENO, &key, 1); + } + + return NULL; + } + else if (stdin_active) + { + int len; + fd_set fdset; + struct timeval timeout; + + FD_ZERO(&fdset); + FD_SET(STDIN_FILENO, &fdset); // stdin + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if(select (STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(STDIN_FILENO, &fdset)) + return NULL; + + len = read(STDIN_FILENO, text, sizeof(text)); + if (len == 0) + { // eof! + stdin_active = qfalse; + return NULL; + } + + if (len < 1) + return NULL; + text[len-1] = 0; // rip off the /n and terminate + + return text; + } + return NULL; +} + +/* +================== +CON_Print +================== +*/ +void CON_Print( const char *msg ) +{ + CON_Hide( ); + + if( com_ansiColor && com_ansiColor->integer ) + Sys_AnsiColorPrint( msg ); + else + fputs( msg, stderr ); + + CON_Show( ); +} diff --git a/src/sys/con_win32.c b/src/sys/con_win32.c new file mode 100644 index 0000000..987c553 --- /dev/null +++ b/src/sys/con_win32.c @@ -0,0 +1,359 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "sys_local.h" +#include "windows.h" + +#define QCONSOLE_HISTORY 32 + +static WORD qconsole_attrib; + +// saved console status +static DWORD qconsole_orig_mode; +static CONSOLE_CURSOR_INFO qconsole_orig_cursorinfo; + +// cmd history +static char qconsole_history[ QCONSOLE_HISTORY ][ MAX_EDIT_LINE ]; +static int qconsole_history_pos = -1; +static int qconsole_history_oldest = 0; + +// current edit buffer +static char qconsole_line[ MAX_EDIT_LINE ]; +static int qconsole_linelen = 0; + +static HANDLE qconsole_hout; +static HANDLE qconsole_hin; + +/* +================== +CON_CtrlHandler + +The Windows Console doesn't use signals for terminating the application +with Ctrl-C, logging off, window closing, etc. Instead it uses a special +handler routine. Fortunately, the values for Ctrl signals don't seem to +overlap with true signal codes that Windows provides, so calling +Sys_SigHandler() with those numbers should be safe for generating unique +shutdown messages. +================== +*/ +static BOOL WINAPI CON_CtrlHandler( DWORD sig ) +{ + Sys_SigHandler( sig ); + return TRUE; +} + +/* +================== +CON_HistAdd +================== +*/ +static void CON_HistAdd( void ) +{ + Q_strncpyz( qconsole_history[ qconsole_history_oldest ], qconsole_line, + sizeof( qconsole_history[ qconsole_history_oldest ] ) ); + + if( qconsole_history_oldest >= QCONSOLE_HISTORY - 1 ) + qconsole_history_oldest = 0; + else + qconsole_history_oldest++; + + qconsole_history_pos = qconsole_history_oldest; +} + +/* +================== +CON_HistPrev +================== +*/ +static void CON_HistPrev( void ) +{ + int pos; + + pos = ( qconsole_history_pos < 1 ) ? + ( QCONSOLE_HISTORY - 1 ) : ( qconsole_history_pos - 1 ); + + // don' t allow looping through history + if( pos == qconsole_history_oldest ) + return; + + qconsole_history_pos = pos; + Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ], + sizeof( qconsole_line ) ); + qconsole_linelen = strlen( qconsole_line ); +} + +/* +================== +CON_HistNext +================== +*/ +static void CON_HistNext( void ) +{ + int pos; + + pos = ( qconsole_history_pos >= QCONSOLE_HISTORY - 1 ) ? + 0 : ( qconsole_history_pos + 1 ); + + // clear the edit buffer if they try to advance to a future command + if( pos == qconsole_history_oldest ) + { + qconsole_line[ 0 ] = '\0'; + qconsole_linelen = 0; + return; + } + + qconsole_history_pos = pos; + Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ], + sizeof( qconsole_line ) ); + qconsole_linelen = strlen( qconsole_line ); +} + + +/* +================== +CON_Show +================== +*/ +static void CON_Show( void ) +{ + CONSOLE_SCREEN_BUFFER_INFO binfo; + COORD writeSize = { MAX_EDIT_LINE, 1 }; + COORD writePos = { 0, 0 }; + SMALL_RECT writeArea = { 0, 0, 0, 0 }; + int i; + CHAR_INFO line[ MAX_EDIT_LINE ]; + + GetConsoleScreenBufferInfo( qconsole_hout, &binfo ); + + // if we're in the middle of printf, don't bother writing the buffer + if( binfo.dwCursorPosition.X != 0 ) + return; + + writeArea.Left = 0; + writeArea.Top = binfo.dwCursorPosition.Y; + writeArea.Bottom = binfo.dwCursorPosition.Y; + writeArea.Right = MAX_EDIT_LINE; + + // build a space-padded CHAR_INFO array + for( i = 0; i < MAX_EDIT_LINE; i++ ) + { + if( i < qconsole_linelen ) + line[ i ].Char.AsciiChar = qconsole_line[ i ]; + else + line[ i ].Char.AsciiChar = ' '; + + line[ i ].Attributes = qconsole_attrib; + } + + if( qconsole_linelen > binfo.srWindow.Right ) + { + WriteConsoleOutput( qconsole_hout, + line + (qconsole_linelen - binfo.srWindow.Right ), + writeSize, writePos, &writeArea ); + } + else + { + WriteConsoleOutput( qconsole_hout, line, writeSize, + writePos, &writeArea ); + } +} + +/* +================== +CON_Shutdown +================== +*/ +void CON_Shutdown( void ) +{ + SetConsoleMode( qconsole_hin, qconsole_orig_mode ); + SetConsoleCursorInfo( qconsole_hout, &qconsole_orig_cursorinfo ); + CloseHandle( qconsole_hout ); + CloseHandle( qconsole_hin ); +} + +/* +================== +CON_Init +================== +*/ +void CON_Init( void ) +{ + CONSOLE_CURSOR_INFO curs; + CONSOLE_SCREEN_BUFFER_INFO info; + int i; + + // handle Ctrl-C or other console termination + SetConsoleCtrlHandler( CON_CtrlHandler, TRUE ); + + qconsole_hin = GetStdHandle( STD_INPUT_HANDLE ); + if( qconsole_hin == INVALID_HANDLE_VALUE ) + return; + + qconsole_hout = GetStdHandle( STD_OUTPUT_HANDLE ); + if( qconsole_hout == INVALID_HANDLE_VALUE ) + return; + + GetConsoleMode( qconsole_hin, &qconsole_orig_mode ); + + // allow mouse wheel scrolling + SetConsoleMode( qconsole_hin, + qconsole_orig_mode & ~ENABLE_MOUSE_INPUT ); + + FlushConsoleInputBuffer( qconsole_hin ); + + GetConsoleScreenBufferInfo( qconsole_hout, &info ); + qconsole_attrib = info.wAttributes; + + SetConsoleTitle("Tremulous Dedicated Server Console"); + + // make cursor invisible + GetConsoleCursorInfo( qconsole_hout, &qconsole_orig_cursorinfo ); + curs.dwSize = 1; + curs.bVisible = FALSE; + SetConsoleCursorInfo( qconsole_hout, &curs ); + + // initialize history + for( i = 0; i < QCONSOLE_HISTORY; i++ ) + qconsole_history[ i ][ 0 ] = '\0'; +} + +/* +================== +CON_Input +================== +*/ +char *CON_Input( void ) +{ + INPUT_RECORD buff[ MAX_EDIT_LINE ]; + DWORD count = 0, events = 0; + WORD key = 0; + int i; + int newlinepos = -1; + + if( !GetNumberOfConsoleInputEvents( qconsole_hin, &events ) ) + return NULL; + + if( events < 1 ) + return NULL; + + // if we have overflowed, start dropping oldest input events + if( events >= MAX_EDIT_LINE ) + { + ReadConsoleInput( qconsole_hin, buff, 1, &events ); + return NULL; + } + + if( !ReadConsoleInput( qconsole_hin, buff, events, &count ) ) + return NULL; + + FlushConsoleInputBuffer( qconsole_hin ); + + for( i = 0; i < count; i++ ) + { + if( buff[ i ].EventType != KEY_EVENT ) + continue; + if( !buff[ i ].Event.KeyEvent.bKeyDown ) + continue; + + key = buff[ i ].Event.KeyEvent.wVirtualKeyCode; + + if( key == VK_RETURN ) + { + newlinepos = i; + break; + } + else if( key == VK_UP ) + { + CON_HistPrev(); + break; + } + else if( key == VK_DOWN ) + { + CON_HistNext(); + break; + } + else if( key == VK_TAB ) + { + field_t f; + + Q_strncpyz( f.buffer, qconsole_line, + sizeof( f.buffer ) ); + Field_AutoComplete( &f ); + Q_strncpyz( qconsole_line, f.buffer, + sizeof( qconsole_line ) ); + qconsole_linelen = strlen( qconsole_line ); + break; + } + + if( qconsole_linelen < sizeof( qconsole_line ) - 1 ) + { + char c = buff[ i ].Event.KeyEvent.uChar.AsciiChar; + + if( key == VK_BACK ) + { + int pos = ( qconsole_linelen > 0 ) ? + qconsole_linelen - 1 : 0; + + qconsole_line[ pos ] = '\0'; + qconsole_linelen = pos; + } + else if( c ) + { + qconsole_line[ qconsole_linelen++ ] = c; + qconsole_line[ qconsole_linelen ] = '\0'; + } + } + } + + CON_Show(); + + if( newlinepos < 0) + return NULL; + + if( !qconsole_linelen ) + { + Com_Printf( "\n" ); + return NULL; + } + + CON_HistAdd(); + Com_Printf( "%s\n", qconsole_line ); + + qconsole_linelen = 0; + + return qconsole_line; +} + +/* +================== +CON_Print +================== +*/ +void CON_Print( const char *msg ) +{ + fputs( msg, stderr ); + + CON_Show( ); +} diff --git a/src/sys/sys_loadlib.h b/src/sys/sys_loadlib.h new file mode 100644 index 0000000..02bb762 --- /dev/null +++ b/src/sys/sys_loadlib.h @@ -0,0 +1,50 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifdef DEDICATED +# ifdef _WIN32 +# include +# define Sys_LoadLibrary(f) (void*)LoadLibrary(f) +# define Sys_UnloadLibrary(h) FreeLibrary((HMODULE)h) +# define Sys_LoadFunction(h,fn) (void*)GetProcAddress((HMODULE)h,fn) +# define Sys_LibraryError() "unknown" +# else +# include +# define Sys_LoadLibrary(f) dlopen(f,RTLD_NOW) +# define Sys_UnloadLibrary(h) dlclose(h) +# define Sys_LoadFunction(h,fn) dlsym(h,fn) +# define Sys_LibraryError() dlerror() +# endif +#else +# ifdef USE_LOCAL_HEADERS +# include "SDL.h" +# include "SDL_loadso.h" +# else +# include +# include +# endif +# define Sys_LoadLibrary(f) SDL_LoadObject(f) +# define Sys_UnloadLibrary(h) SDL_UnloadObject(h) +# define Sys_LoadFunction(h,fn) SDL_LoadFunction(h,fn) +# define Sys_LibraryError() SDL_GetError() +#endif diff --git a/src/sys/sys_local.h b/src/sys/sys_local.h new file mode 100644 index 0000000..8f99e7c --- /dev/null +++ b/src/sys/sys_local.h @@ -0,0 +1,61 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +// Require a minimum version of SDL +#define MINSDL_MAJOR 1 +#define MINSDL_MINOR 2 +#define MINSDL_PATCH 10 + +// Input subsystem +void IN_Init( void ); +void IN_Frame( void ); +void IN_Shutdown( void ); +void IN_Restart( void ); + +// Console +void CON_Shutdown( void ); +void CON_Init( void ); +char *CON_Input( void ); +void CON_Print( const char *message ); + +unsigned int CON_LogSize( void ); +unsigned int CON_LogWrite( const char *in ); +unsigned int CON_LogRead( char *out, unsigned int outSize ); + +#ifdef MACOS_X +char *Sys_StripAppBundle( char *pwd ); +#endif + +void Sys_GLimpSafeInit( void ); +void Sys_GLimpInit( void ); +void Sys_PlatformInit( void ); +void Sys_PlatformExit( void ); +void Sys_SigHandler( int signal ); +void Sys_ErrorDialog( const char *error ); +void Sys_AnsiColorPrint( const char *msg ); + +int Sys_PID( void ); +qboolean Sys_PIDIsRunning( int pid ); diff --git a/src/sys/sys_main.c b/src/sys/sys_main.c new file mode 100644 index 0000000..46a795e --- /dev/null +++ b/src/sys/sys_main.c @@ -0,0 +1,605 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef DEDICATED +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +# include "SDL_cpuinfo.h" +#else +# include +# include +#endif +#endif + +#include "sys_local.h" +#include "sys_loadlib.h" + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +static char binaryPath[ MAX_OSPATH ] = { 0 }; +static char installPath[ MAX_OSPATH ] = { 0 }; + +/* +================= +Sys_SetBinaryPath +================= +*/ +void Sys_SetBinaryPath(const char *path) +{ + Q_strncpyz(binaryPath, path, sizeof(binaryPath)); +} + +/* +================= +Sys_BinaryPath +================= +*/ +char *Sys_BinaryPath(void) +{ + return binaryPath; +} + +/* +================= +Sys_SetDefaultInstallPath +================= +*/ +void Sys_SetDefaultInstallPath(const char *path) +{ + Q_strncpyz(installPath, path, sizeof(installPath)); +} + +/* +================= +Sys_DefaultInstallPath +================= +*/ +char *Sys_DefaultInstallPath(void) +{ + if (*installPath) + return installPath; + else + return Sys_Cwd(); +} + +/* +================= +Sys_DefaultAppPath +================= +*/ +char *Sys_DefaultAppPath(void) +{ + return Sys_BinaryPath(); +} + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( void ) +{ + IN_Restart( ); +} + +/* +================= +Sys_ConsoleInput + +Handle new console input +================= +*/ +char *Sys_ConsoleInput(void) +{ + return CON_Input( ); +} + +#ifdef DEDICATED +# define PID_FILENAME PRODUCT_NAME "_server.pid" +#else +# define PID_FILENAME PRODUCT_NAME ".pid" +#endif + +/* +================= +Sys_PIDFileName +================= +*/ +static char *Sys_PIDFileName( void ) +{ + return va( "%s/%s", Sys_TempPath( ), PID_FILENAME ); +} + +/* +================= +Sys_WritePIDFile + +Return qtrue if there is an existing stale PID file +================= +*/ +qboolean Sys_WritePIDFile( void ) +{ + char *pidFile = Sys_PIDFileName( ); + FILE *f; + qboolean stale = qfalse; + + // First, check if the pid file is already there + if( ( f = fopen( pidFile, "r" ) ) != NULL ) + { + char pidBuffer[ 64 ] = { 0 }; + int pid; + + pid = fread( pidBuffer, sizeof( char ), sizeof( pidBuffer ) - 1, f ); + fclose( f ); + + if(pid > 0) + { + pid = atoi( pidBuffer ); + if( !Sys_PIDIsRunning( pid ) ) + stale = qtrue; + } + else + stale = qtrue; + } + + if( ( f = fopen( pidFile, "w" ) ) != NULL ) + { + fprintf( f, "%d", Sys_PID( ) ); + fclose( f ); + } + else + Com_Printf( S_COLOR_YELLOW "Couldn't write %s.\n", pidFile ); + + return stale; +} + +/* +================= +Sys_Exit + +Single exit point (regular exit or in case of error) +================= +*/ +static void Sys_Exit( int exitCode ) +{ + CON_Shutdown( ); + +#ifndef DEDICATED + SDL_Quit( ); +#endif + + if( exitCode < 2 ) + { + // Normal exit + remove( Sys_PIDFileName( ) ); + } + + Sys_PlatformExit( ); + + exit( exitCode ); +} + +/* +================= +Sys_Quit +================= +*/ +void Sys_Quit( void ) +{ + Sys_Exit( 0 ); +} + +/* +================= +Sys_GetProcessorFeatures +================= +*/ +cpuFeatures_t Sys_GetProcessorFeatures( void ) +{ + cpuFeatures_t features = 0; + +#ifndef DEDICATED + if( SDL_HasRDTSC( ) ) features |= CF_RDTSC; + if( SDL_HasMMX( ) ) features |= CF_MMX; + if( SDL_HasMMXExt( ) ) features |= CF_MMX_EXT; + if( SDL_Has3DNow( ) ) features |= CF_3DNOW; + if( SDL_Has3DNowExt( ) ) features |= CF_3DNOW_EXT; + if( SDL_HasSSE( ) ) features |= CF_SSE; + if( SDL_HasSSE2( ) ) features |= CF_SSE2; + if( SDL_HasAltiVec( ) ) features |= CF_ALTIVEC; +#endif + + return features; +} + +/* +================= +Sys_Init +================= +*/ +void Sys_Init(void) +{ + Cmd_AddCommand( "in_restart", Sys_In_Restart_f ); + Cvar_Set( "arch", OS_STRING " " ARCH_STRING ); + Cvar_Set( "username", Sys_GetCurrentUser( ) ); +} + +/* +================= +Sys_AnsiColorPrint + +Transform Q3 colour codes to ANSI escape sequences +================= +*/ +void Sys_AnsiColorPrint( const char *msg ) +{ + static char buffer[ MAXPRINTMSG ]; + int length = 0; + static int q3ToAnsi[ 8 ] = + { + 7, // COLOR_BLACK + 31, // COLOR_RED + 32, // COLOR_GREEN + 33, // COLOR_YELLOW + 34, // COLOR_BLUE + 36, // COLOR_CYAN + 35, // COLOR_MAGENTA + 0 // COLOR_WHITE + }; + + while( *msg ) + { + if( Q_IsColorString( msg ) || *msg == '\n' ) + { + // First empty the buffer + if( length > 0 ) + { + buffer[ length ] = '\0'; + fputs( buffer, stderr ); + length = 0; + } + + if( *msg == '\n' ) + { + // Issue a reset and then the newline + fputs( "\033[0m\n", stderr ); + msg++; + } + else + { + // Print the color code (reset first to clear potential inverse (black)) + Com_sprintf( buffer, sizeof( buffer ), "\033[0m\033[%dm", + q3ToAnsi[ ColorIndex( *( msg + 1 ) ) ] ); + fputs( buffer, stderr ); + msg += 2; + } + } + else + { + if( length >= MAXPRINTMSG - 1 ) + break; + + buffer[ length ] = *msg; + length++; + msg++; + } + } + + // Empty anything still left in the buffer + if( length > 0 ) + { + buffer[ length ] = '\0'; + fputs( buffer, stderr ); + } +} + +/* +================= +Sys_Print +================= +*/ +void Sys_Print( const char *msg ) +{ + CON_LogWrite( msg ); + CON_Print( msg ); +} + +/* +================= +Sys_Error +================= +*/ +void Sys_Error( const char *error, ... ) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,error); + Q_vsnprintf (string, sizeof(string), error, argptr); + va_end (argptr); + + CL_Shutdown( string ); + Sys_ErrorDialog( string ); + + Sys_Exit( 3 ); +} + +/* +================= +Sys_Warn +================= +*/ +void Sys_Warn( char *warning, ... ) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,warning); + Q_vsnprintf (string, sizeof(string), warning, argptr); + va_end (argptr); + + CON_Print( va( "Warning: %s", string ) ); +} + +/* +============ +Sys_FileTime + +returns -1 if not present +============ +*/ +int Sys_FileTime( char *path ) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + +/* +================= +Sys_UnloadDll +================= +*/ +void Sys_UnloadDll( void *dllHandle ) +{ + if( !dllHandle ) + { + Com_Printf("Sys_UnloadDll(NULL)\n"); + return; + } + + Sys_UnloadLibrary(dllHandle); +} + +/* +================= +Sys_LoadDll + +Used to load a development dll instead of a virtual machine +#1 look in fs_homepath +#2 look in fs_basepath +================= +*/ +void *Sys_LoadDll( const char *name, + intptr_t (**entryPoint)(int, ...), + intptr_t (*systemcalls)(intptr_t, ...) ) +{ + void *libHandle; + void (*dllEntry)( intptr_t (*syscallptr)(intptr_t, ...) ); + char fname[MAX_OSPATH]; + char *netpath; + + assert( name ); + + Com_sprintf(fname, sizeof(fname), "%s" ARCH_STRING DLL_EXT, name); + + netpath = FS_FindDll(fname); + + if(!netpath) { + Com_Printf( "Sys_LoadDll(%s) could not find it\n", fname ); + return NULL; + } + + Com_Printf( "Loading DLL file: %s\n", netpath); + libHandle = Sys_LoadLibrary(netpath); + + if(!libHandle) { + Com_Printf( "Sys_LoadDll(%s) failed:\n\"%s\"\n", netpath, Sys_LibraryError() ); + return NULL; + } + + dllEntry = Sys_LoadFunction( libHandle, "dllEntry" ); + *entryPoint = Sys_LoadFunction( libHandle, "vmMain" ); + + if ( !*entryPoint || !dllEntry ) + { + Com_Printf ( "Sys_LoadDll(%s) failed to find vmMain function:\n\"%s\" !\n", name, Sys_LibraryError( ) ); + Sys_UnloadLibrary(libHandle); + + return NULL; + } + + Com_Printf ( "Sys_LoadDll(%s) found vmMain function at %p\n", name, *entryPoint ); + dllEntry( systemcalls ); + + return libHandle; +} + +/* +================= +Sys_ParseArgs +================= +*/ +void Sys_ParseArgs( int argc, char **argv ) +{ + if( argc == 2 ) + { + if( !strcmp( argv[1], "--version" ) || + !strcmp( argv[1], "-v" ) ) + { + const char* date = __DATE__; +#ifdef DEDICATED + fprintf( stdout, Q3_VERSION " dedicated server (%s)\n", date ); +#else + fprintf( stdout, Q3_VERSION " client (%s)\n", date ); +#endif + Sys_Exit( 0 ); + } + } +} + +#ifndef DEFAULT_BASEDIR +# ifdef MACOS_X +# define DEFAULT_BASEDIR Sys_StripAppBundle(Sys_BinaryPath()) +# else +# define DEFAULT_BASEDIR Sys_BinaryPath() +# endif +#endif + +/* +================= +Sys_SigHandler +================= +*/ +void Sys_SigHandler( int signal ) +{ + static qboolean signalcaught = qfalse; + + if( signalcaught ) + { + fprintf( stderr, "DOUBLE SIGNAL FAULT: Received signal %d, exiting...\n", + signal ); + } + else + { + signalcaught = qtrue; +#ifndef DEDICATED + CL_Shutdown( va( "Received signal %d", signal ) ); +#endif + SV_Shutdown( va( "Received signal %d", signal ) ); + } + + if( signal == SIGTERM || signal == SIGINT ) + Sys_Exit( 1 ); + else + Sys_Exit( 2 ); +} + +/* +================= +main +================= +*/ +int main( int argc, char **argv ) +{ + int i; + char commandLine[ MAX_STRING_CHARS ] = { 0 }; + +#ifndef DEDICATED + // SDL version check + + // Compile time +# if !SDL_VERSION_ATLEAST(MINSDL_MAJOR,MINSDL_MINOR,MINSDL_PATCH) +# error A more recent version of SDL is required +# endif + + // Run time + const SDL_version *ver = SDL_Linked_Version( ); + +#define MINSDL_VERSION \ + XSTRING(MINSDL_MAJOR) "." \ + XSTRING(MINSDL_MINOR) "." \ + XSTRING(MINSDL_PATCH) + + if( SDL_VERSIONNUM( ver->major, ver->minor, ver->patch ) < + SDL_VERSIONNUM( MINSDL_MAJOR, MINSDL_MINOR, MINSDL_PATCH ) ) + { + Sys_Dialog( DT_ERROR, va( "SDL version " MINSDL_VERSION " or greater is required, " + "but only version %d.%d.%d was found. You may be able to obtain a more recent copy " + "from http://www.libsdl.org/.", ver->major, ver->minor, ver->patch ), "SDL Library Too Old" ); + + Sys_Exit( 1 ); + } +#endif + + Sys_PlatformInit( ); + + // Set the initial time base + Sys_Milliseconds( ); + + Sys_ParseArgs( argc, argv ); + Sys_SetBinaryPath( Sys_Dirname( argv[ 0 ] ) ); + Sys_SetDefaultInstallPath( DEFAULT_BASEDIR ); + + // Concatenate the command line for passing to Com_Init + for( i = 1; i < argc; i++ ) + { + const qboolean containsSpaces = strchr(argv[i], ' ') != NULL; + if (containsSpaces) + Q_strcat( commandLine, sizeof( commandLine ), "\"" ); + + Q_strcat( commandLine, sizeof( commandLine ), argv[ i ] ); + + if (containsSpaces) + Q_strcat( commandLine, sizeof( commandLine ), "\"" ); + + Q_strcat( commandLine, sizeof( commandLine ), " " ); + } + + Com_Init( commandLine ); + NET_Init( ); + + CON_Init( ); + + signal( SIGILL, Sys_SigHandler ); + signal( SIGFPE, Sys_SigHandler ); + signal( SIGSEGV, Sys_SigHandler ); + signal( SIGTERM, Sys_SigHandler ); + signal( SIGINT, Sys_SigHandler ); + + while( 1 ) + { + IN_Frame( ); + Com_Frame( ); + } + + return 0; +} + diff --git a/src/sys/sys_osx.m b/src/sys/sys_osx.m new file mode 100644 index 0000000..1efd08c --- /dev/null +++ b/src/sys/sys_osx.m @@ -0,0 +1,141 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef MACOS_X +#error This file is for Mac OS X only. You probably should not compile it. +#endif + +// Please note that this file is just some Mac-specific bits. Most of the +// Mac OS X code is shared with other Unix platforms in sys_unix.c ... + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "sys_local.h" + +#import +#import + +/* +================ +Sys_TempPath +================ +*/ +const char *Sys_TempPath( void ) +{ + static UInt8 posixPath[ MAX_OSPATH ]; + FSRef ref; + if( FSFindFolder( kOnAppropriateDisk, + kTemporaryFolderType, kCreateFolder, &ref ) == noErr ) + { + if( FSRefMakePath( &ref, posixPath, + sizeof( posixPath ) - 1 ) == noErr ) + { + return (const char *)posixPath; + } + } + + return "/tmp"; +} + +/* +============== +Sys_Dialog + +Display an OS X dialog box +============== +*/ +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ) +{ + dialogResult_t result = DR_OK; + NSAlert *alert = [NSAlert new]; + + [alert setMessageText: [NSString stringWithUTF8String: title]]; + [alert setInformativeText: [NSString stringWithUTF8String: message]]; + + if( type == DT_ERROR ) + [alert setAlertStyle: NSCriticalAlertStyle]; + else + [alert setAlertStyle: NSWarningAlertStyle]; + + switch( type ) + { + default: + [alert runModal]; + result = DR_OK; + break; + + case DT_YES_NO: + [alert addButtonWithTitle: @"Yes"]; + [alert addButtonWithTitle: @"No"]; + switch( [alert runModal] ) + { + default: + case NSAlertFirstButtonReturn: result = DR_YES; break; + case NSAlertSecondButtonReturn: result = DR_NO; break; + } + break; + + case DT_OK_CANCEL: + [alert addButtonWithTitle: @"OK"]; + [alert addButtonWithTitle: @"Cancel"]; + + switch( [alert runModal] ) + { + default: + case NSAlertFirstButtonReturn: result = DR_OK; break; + case NSAlertSecondButtonReturn: result = DR_CANCEL; break; + } + break; + } + + [alert release]; + + return result; +} + +/* +================= +Sys_StripAppBundle + +Discovers if passed dir is suffixed with the directory structure of a Mac OS X +.app bundle. If it is, the .app directory structure is stripped off the end and +the result is returned. If not, dir is returned untouched. +================= +*/ +char *Sys_StripAppBundle( char *dir ) +{ + static char cwd[MAX_OSPATH]; + + Q_strncpyz(cwd, dir, sizeof(cwd)); + if(strcmp(Sys_Basename(cwd), "MacOS")) + return dir; + Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd)); + if(strcmp(Sys_Basename(cwd), "Contents")) + return dir; + Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd)); + if(!strstr(Sys_Basename(cwd), ".app")) + return dir; + Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd)); + return cwd; +} diff --git a/src/sys/sys_unix.c b/src/sys/sys_unix.c new file mode 100644 index 0000000..abbe623 --- /dev/null +++ b/src/sys/sys_unix.c @@ -0,0 +1,818 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "sys_local.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +qboolean stdinIsATTY; + +// Used to determine where to store user-specific files +static char homePath[ MAX_OSPATH ] = { 0 }; + +/* +================== +Sys_DefaultHomePath +================== +*/ +char *Sys_DefaultHomePath(void) +{ + char *p; + + if( !*homePath ) + { + if( ( p = getenv( "HOME" ) ) != NULL ) + { + Com_sprintf(homePath, sizeof(homePath), "%s%c", p, PATH_SEP); +#ifdef MACOS_X + Q_strcat( homePath, sizeof( homePath ), + "/Library/Application Support/Tremulous" ); +#else + Q_strcat( homePath, sizeof( homePath ), "/.tremulous" ); +#endif + } + } + + return homePath; +} + +#ifndef MACOS_X +/* +================ +Sys_TempPath +================ +*/ +const char *Sys_TempPath( void ) +{ + const char *TMPDIR = getenv( "TMPDIR" ); + + if( TMPDIR == NULL || TMPDIR[ 0 ] == '\0' ) + return "/tmp"; + else + return TMPDIR; +} +#endif + +/* +================ +Sys_Milliseconds +================ +*/ +/* base time in seconds, that's our origin + timeval:tv_sec is an int: + assuming this wraps every 0x7fffffff - ~68 years since the Epoch (1970) - we're safe till 2038 */ +unsigned long sys_timeBase = 0; +/* current time in ms, using sys_timeBase as origin + NOTE: sys_timeBase*1000 + curtime -> ms since the Epoch + 0x7fffffff ms - ~24 days + although timeval:tv_usec is an int, I'm not sure wether it is actually used as an unsigned int + (which would affect the wrap period) */ +int curtime; +int Sys_Milliseconds (void) +{ + struct timeval tp; + + gettimeofday(&tp, NULL); + + if (!sys_timeBase) + { + sys_timeBase = tp.tv_sec; + return tp.tv_usec/1000; + } + + curtime = (tp.tv_sec - sys_timeBase)*1000 + tp.tv_usec/1000; + + return curtime; +} + +#if !id386 +/* +================== +fastftol +================== +*/ +long fastftol( float f ) +{ + return (long)f; +} + +/* +================== +Sys_SnapVector +================== +*/ +void Sys_SnapVector( float *v ) +{ + v[0] = rint(v[0]); + v[1] = rint(v[1]); + v[2] = rint(v[2]); +} +#endif + + +/* +================== +Sys_RandomBytes +================== +*/ +qboolean Sys_RandomBytes( byte *string, int len ) +{ + FILE *fp; + + fp = fopen( "/dev/urandom", "r" ); + if( !fp ) + return qfalse; + + if( !fread( string, sizeof( byte ), len, fp ) ) + { + fclose( fp ); + return qfalse; + } + + fclose( fp ); + return qtrue; +} + +/* +================== +Sys_GetCurrentUser +================== +*/ +char *Sys_GetCurrentUser( void ) +{ + struct passwd *p; + + if ( (p = getpwuid( getuid() )) == NULL ) { + return "player"; + } + return p->pw_name; +} + +/* +================== +Sys_GetClipboardData +================== +*/ +char *Sys_GetClipboardData(void) +{ + return NULL; +} + +#define MEM_THRESHOLD 96*1024*1024 + +/* +================== +Sys_LowPhysicalMemory + +TODO +================== +*/ +qboolean Sys_LowPhysicalMemory( void ) +{ + return qfalse; +} + +/* +================== +Sys_Basename +================== +*/ +const char *Sys_Basename( char *path ) +{ + return basename( path ); +} + +/* +================== +Sys_Dirname +================== +*/ +const char *Sys_Dirname( char *path ) +{ + return dirname( path ); +} + +/* +================== +Sys_Mkdir +================== +*/ +qboolean Sys_Mkdir( const char *path ) +{ + int result = mkdir( path, 0750 ); + + if( result != 0 ) + return errno == EEXIST; + + return qtrue; +} + +/* +================== +Sys_Mkfifo +================== +*/ +FILE *Sys_Mkfifo( const char *ospath ) +{ + FILE *fifo; + int result; + int fn; + struct stat buf; + + // if file already exists AND is a pipefile, remove it + if( !stat( ospath, &buf ) && S_ISFIFO( buf.st_mode ) ) + FS_Remove( ospath ); + + result = mkfifo( ospath, 0600 ); + if( result != 0 ) + return NULL; + + fifo = fopen( ospath, "w+" ); + if( fifo ) + { + fn = fileno( fifo ); + fcntl( fn, F_SETFL, O_NONBLOCK ); + } + + return fifo; +} + +/* +================== +Sys_Cwd +================== +*/ +char *Sys_Cwd( void ) +{ + static char cwd[MAX_OSPATH]; + + char *result = getcwd( cwd, sizeof( cwd ) - 1 ); + if( result != cwd ) + return NULL; + + cwd[MAX_OSPATH-1] = 0; + + return cwd; +} + +/* +============================================================== + +DIRECTORY SCANNING + +============================================================== +*/ + +#define MAX_FOUND_FILES 0x1000 + +/* +================== +Sys_ListFilteredFiles +================== +*/ +void Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **list, int *numfiles ) +{ + char search[MAX_OSPATH], newsubdirs[MAX_OSPATH]; + char filename[MAX_OSPATH]; + DIR *fdir; + struct dirent *d; + struct stat st; + + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + return; + } + + if (strlen(subdirs)) { + Com_sprintf( search, sizeof(search), "%s/%s", basedir, subdirs ); + } + else { + Com_sprintf( search, sizeof(search), "%s", basedir ); + } + + if ((fdir = opendir(search)) == NULL) { + return; + } + + while ((d = readdir(fdir)) != NULL) { + Com_sprintf(filename, sizeof(filename), "%s/%s", search, d->d_name); + if (stat(filename, &st) == -1) + continue; + + if (st.st_mode & S_IFDIR) { + if (Q_stricmp(d->d_name, ".") && Q_stricmp(d->d_name, "..")) { + if (strlen(subdirs)) { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s/%s", subdirs, d->d_name); + } + else { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", d->d_name); + } + Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles ); + } + } + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + break; + } + Com_sprintf( filename, sizeof(filename), "%s/%s", subdirs, d->d_name ); + if (!Com_FilterPath( filter, filename, qfalse )) + continue; + list[ *numfiles ] = CopyString( filename ); + (*numfiles)++; + } + + closedir(fdir); +} + +/* +================== +Sys_ListFiles +================== +*/ +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ) +{ + struct dirent *d; + DIR *fdir; + qboolean dironly = wantsubs; + char search[MAX_OSPATH]; + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + int i; + struct stat st; + + int extLen; + + if (filter) { + + nfiles = 0; + Sys_ListFilteredFiles( directory, "", filter, list, &nfiles ); + + list[ nfiles ] = NULL; + *numfiles = nfiles; + + if (!nfiles) + return NULL; + + listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; + } + + if ( !extension) + extension = ""; + + if ( extension[0] == '/' && extension[1] == 0 ) { + extension = ""; + dironly = qtrue; + } + + extLen = strlen( extension ); + + // search + nfiles = 0; + + if ((fdir = opendir(directory)) == NULL) { + *numfiles = 0; + return NULL; + } + + while ((d = readdir(fdir)) != NULL) { + Com_sprintf(search, sizeof(search), "%s/%s", directory, d->d_name); + if (stat(search, &st) == -1) + continue; + if ((dironly && !(st.st_mode & S_IFDIR)) || + (!dironly && (st.st_mode & S_IFDIR))) + continue; + + if (*extension) { + if ( strlen( d->d_name ) < strlen( extension ) || + Q_stricmp( + d->d_name + strlen( d->d_name ) - strlen( extension ), + extension ) ) { + continue; // didn't match + } + } + + if ( nfiles == MAX_FOUND_FILES - 1 ) + break; + list[ nfiles ] = CopyString( d->d_name ); + nfiles++; + } + + list[ nfiles ] = NULL; + + closedir(fdir); + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + +/* +================== +Sys_FreeFileList +================== +*/ +void Sys_FreeFileList( char **list ) +{ + int i; + + if ( !list ) { + return; + } + + for ( i = 0 ; list[i] ; i++ ) { + Z_Free( list[i] ); + } + + Z_Free( list ); +} + +/* +================== +Sys_Sleep + +Block execution for msec or until input is recieved. +================== +*/ +void Sys_Sleep( int msec ) +{ + if( msec == 0 ) + return; + + if( stdinIsATTY ) + { + fd_set fdset; + + FD_ZERO(&fdset); + FD_SET(STDIN_FILENO, &fdset); + if( msec < 0 ) + { + select(STDIN_FILENO + 1, &fdset, NULL, NULL, NULL); + } + else + { + struct timeval timeout; + + timeout.tv_sec = msec/1000; + timeout.tv_usec = (msec%1000)*1000; + select(STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout); + } + } + else + { + // With nothing to select() on, we can't wait indefinitely + if( msec < 0 ) + msec = 10; + + usleep( msec * 1000 ); + } +} + +/* +============== +Sys_ErrorDialog + +Display an error message +============== +*/ +void Sys_ErrorDialog( const char *error ) +{ + char buffer[ 1024 ]; + unsigned int size; + int f = -1; + const char *homepath = Cvar_VariableString( "fs_homepath" ); + const char *gamedir = Cvar_VariableString( "fs_game" ); + const char *fileName = "crashlog.txt"; + char *ospath = FS_BuildOSPath( homepath, gamedir, fileName ); + + Sys_Print( va( "%s\n", error ) ); + +#ifndef DEDICATED + Sys_Dialog( DT_ERROR, va( "%s. See \"%s\" for details.", error, ospath ), "Error" ); +#endif + + // Make sure the write path for the crashlog exists... + if( FS_CreatePath( ospath ) ) { + Com_Printf( "ERROR: couldn't create path '%s' for crash log.\n", ospath ); + return; + } + + // We might be crashing because we maxed out the Quake MAX_FILE_HANDLES, + // which will come through here, so we don't want to recurse forever by + // calling FS_FOpenFileWrite()...use the Unix system APIs instead. + f = open( ospath, O_CREAT | O_TRUNC | O_WRONLY, 0640 ); + if( f == -1 ) + { + Com_Printf( "ERROR: couldn't open %s\n", fileName ); + return; + } + + // We're crashing, so we don't care much if write() or close() fails. + while( ( size = CON_LogRead( buffer, sizeof( buffer ) ) ) > 0 ) { + if( write( f, buffer, size ) != size ) { + Com_Printf( "ERROR: couldn't fully write to %s\n", fileName ); + break; + } + } + + close( f ); +} + +#ifndef MACOS_X +/* +============== +Sys_ZenityCommand +============== +*/ +static int Sys_ZenityCommand( dialogType_t type, const char *message, const char *title ) +{ + const char *options = ""; + char command[ 1024 ]; + + switch( type ) + { + default: + case DT_INFO: options = "--info"; break; + case DT_WARNING: options = "--warning"; break; + case DT_ERROR: options = "--error"; break; + case DT_YES_NO: options = "--question --ok-label=\"Yes\" --cancel-label=\"No\""; break; + case DT_OK_CANCEL: options = "--question --ok-label=\"OK\" --cancel-label=\"Cancel\""; break; + } + + Com_sprintf( command, sizeof( command ), "zenity %s --text=\"%s\" --title=\"%s\"", + options, message, title ); + + return system( command ); +} + +/* +============== +Sys_KdialogCommand +============== +*/ +static int Sys_KdialogCommand( dialogType_t type, const char *message, const char *title ) +{ + const char *options = ""; + char command[ 1024 ]; + + switch( type ) + { + default: + case DT_INFO: options = "--msgbox"; break; + case DT_WARNING: options = "--sorry"; break; + case DT_ERROR: options = "--error"; break; + case DT_YES_NO: options = "--warningyesno"; break; + case DT_OK_CANCEL: options = "--warningcontinuecancel"; break; + } + + Com_sprintf( command, sizeof( command ), "kdialog %s \"%s\" --title \"%s\"", + options, message, title ); + + return system( command ); +} + +/* +============== +Sys_XmessageCommand +============== +*/ +static int Sys_XmessageCommand( dialogType_t type, const char *message, const char *title ) +{ + const char *options = ""; + char command[ 1024 ]; + + switch( type ) + { + default: options = "-buttons OK"; break; + case DT_YES_NO: options = "-buttons Yes:0,No:1"; break; + case DT_OK_CANCEL: options = "-buttons OK:0,Cancel:1"; break; + } + + Com_sprintf( command, sizeof( command ), "xmessage -center %s \"%s\"", + options, message ); + + return system( command ); +} + +/* +============== +Sys_Dialog + +Display a *nix dialog box +============== +*/ +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ) +{ + typedef enum + { + NONE = 0, + ZENITY, + KDIALOG, + XMESSAGE, + NUM_DIALOG_PROGRAMS + } dialogCommandType_t; + typedef int (*dialogCommandBuilder_t)( dialogType_t, const char *, const char * ); + + const char *session = getenv( "DESKTOP_SESSION" ); + qboolean tried[ NUM_DIALOG_PROGRAMS ] = { qfalse }; + dialogCommandBuilder_t commands[ NUM_DIALOG_PROGRAMS ] = { NULL }; + dialogCommandType_t preferredCommandType = NONE; + + commands[ ZENITY ] = &Sys_ZenityCommand; + commands[ KDIALOG ] = &Sys_KdialogCommand; + commands[ XMESSAGE ] = &Sys_XmessageCommand; + + // This may not be the best way + if( !Q_stricmp( session, "gnome" ) ) + preferredCommandType = ZENITY; + else if( !Q_stricmp( session, "kde" ) ) + preferredCommandType = KDIALOG; + + while( 1 ) + { + int i; + int exitCode; + + for( i = NONE + 1; i < NUM_DIALOG_PROGRAMS; i++ ) + { + if( preferredCommandType != NONE && preferredCommandType != i ) + continue; + + if( !tried[ i ] ) + { + exitCode = commands[ i ]( type, message, title ); + + if( exitCode >= 0 ) + { + switch( type ) + { + case DT_YES_NO: return exitCode ? DR_NO : DR_YES; + case DT_OK_CANCEL: return exitCode ? DR_CANCEL : DR_OK; + default: return DR_OK; + } + } + + tried[ i ] = qtrue; + + // The preference failed, so start again in order + if( preferredCommandType != NONE ) + { + preferredCommandType = NONE; + break; + } + } + } + + for( i = NONE + 1; i < NUM_DIALOG_PROGRAMS; i++ ) + { + if( !tried[ i ] ) + continue; + } + + break; + } + + Com_DPrintf( S_COLOR_YELLOW "WARNING: failed to show a dialog\n" ); + return DR_OK; +} +#endif + +/* +============== +Sys_GLimpSafeInit + +Unix specific "safe" GL implementation initialisation +============== +*/ +void Sys_GLimpSafeInit( void ) +{ + // NOP +} + +/* +============== +Sys_GLimpInit + +Unix specific GL implementation initialisation +============== +*/ +void Sys_GLimpInit( void ) +{ + // NOP +} + +/* +============== +Sys_PlatformInit + +Unix specific initialisation +============== +*/ +void Sys_PlatformInit( void ) +{ + const char* term = getenv( "TERM" ); + + signal( SIGHUP, Sys_SigHandler ); + signal( SIGQUIT, Sys_SigHandler ); + signal( SIGTRAP, Sys_SigHandler ); + signal( SIGIOT, Sys_SigHandler ); + signal( SIGBUS, Sys_SigHandler ); + + stdinIsATTY = isatty( STDIN_FILENO ) && + !( term && ( !strcmp( term, "raw" ) || !strcmp( term, "dumb" ) ) ); +} + +/* +============== +Sys_PlatformExit + +Unix specific deinitialisation +============== +*/ +void Sys_PlatformExit( void ) +{ +} + +/* +============== +Sys_SetEnv + +set/unset environment variables (empty value removes it) +============== +*/ + +void Sys_SetEnv(const char *name, const char *value) +{ + if(value && *value) + setenv(name, value, 1); + else + unsetenv(name); +} + +/* +============== +Sys_PID +============== +*/ +int Sys_PID( void ) +{ + return getpid( ); +} + +/* +============== +Sys_PIDIsRunning +============== +*/ +qboolean Sys_PIDIsRunning( int pid ) +{ + return kill( pid, 0 ) == 0; +} diff --git a/src/sys/sys_win32.c b/src/sys/sys_win32.c new file mode 100644 index 0000000..8ca1bf6 --- /dev/null +++ b/src/sys/sys_win32.c @@ -0,0 +1,811 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "sys_local.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Used to determine where to store user-specific files +static char homePath[ MAX_OSPATH ] = { 0 }; + +#ifndef DEDICATED +static UINT timerResolution = 0; +#endif + +#ifdef __WIN64__ +void Sys_SnapVector( float *v ) +{ + v[0] = rint(v[0]); + v[1] = rint(v[1]); + v[2] = rint(v[2]); +} +#endif + +/* +================ +Sys_DefaultHomePath +================ +*/ +char *Sys_DefaultHomePath( void ) +{ + TCHAR szPath[MAX_PATH]; + FARPROC qSHGetFolderPath; + HMODULE shfolder = LoadLibrary("shfolder.dll"); + + if( !*homePath ) + { + if(shfolder == NULL) + { + Com_Printf("Unable to load SHFolder.dll\n"); + return NULL; + } + + qSHGetFolderPath = GetProcAddress(shfolder, "SHGetFolderPathA"); + if(qSHGetFolderPath == NULL) + { + Com_Printf("Unable to find SHGetFolderPath in SHFolder.dll\n"); + FreeLibrary(shfolder); + return NULL; + } + + if( !SUCCEEDED( qSHGetFolderPath( NULL, CSIDL_APPDATA, + NULL, 0, szPath ) ) ) + { + Com_Printf("Unable to detect CSIDL_APPDATA\n"); + FreeLibrary(shfolder); + return NULL; + } + Q_strncpyz( homePath, szPath, sizeof( homePath ) ); + Q_strcat( homePath, sizeof( homePath ), "\\Tremulous" ); + FreeLibrary(shfolder); + } + + return homePath; +} + +/* +================ +Sys_TempPath +================ +*/ +const char *Sys_TempPath( void ) +{ + static TCHAR path[ MAX_PATH ]; + DWORD length; + + length = GetTempPath( sizeof( path ), path ); + + if( length > sizeof( path ) || length == 0 ) + return Sys_DefaultHomePath( ); + else + return path; +} + +/* +================ +Sys_Milliseconds +================ +*/ +int sys_timeBase; +int Sys_Milliseconds (void) +{ + int sys_curtime; + static qboolean initialized = qfalse; + + if (!initialized) { + sys_timeBase = timeGetTime(); + initialized = qtrue; + } + sys_curtime = timeGetTime() - sys_timeBase; + + return sys_curtime; +} + +#ifndef __GNUC__ //see snapvectora.s +/* +================ +Sys_SnapVector +================ +*/ +void Sys_SnapVector( float *v ) +{ + int i; + float f; + + f = *v; + __asm fld f; + __asm fistp i; + *v = i; + v++; + f = *v; + __asm fld f; + __asm fistp i; + *v = i; + v++; + f = *v; + __asm fld f; + __asm fistp i; + *v = i; +} +#endif + +/* +================ +Sys_RandomBytes +================ +*/ +qboolean Sys_RandomBytes( byte *string, int len ) +{ + HCRYPTPROV prov; + + if( !CryptAcquireContext( &prov, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) ) { + + return qfalse; + } + + if( !CryptGenRandom( prov, len, (BYTE *)string ) ) { + CryptReleaseContext( prov, 0 ); + return qfalse; + } + CryptReleaseContext( prov, 0 ); + return qtrue; +} + +/* +================ +Sys_GetCurrentUser +================ +*/ +char *Sys_GetCurrentUser( void ) +{ + static char s_userName[1024]; + unsigned long size = sizeof( s_userName ); + + if( !GetUserName( s_userName, &size ) ) + strcpy( s_userName, "player" ); + + if( !s_userName[0] ) + { + strcpy( s_userName, "player" ); + } + + return s_userName; +} + +/* +================ +Sys_GetClipboardData +================ +*/ +char *Sys_GetClipboardData( void ) +{ + char *data = NULL; + char *cliptext; + + if ( OpenClipboard( NULL ) != 0 ) { + HANDLE hClipboardData; + + if ( ( hClipboardData = GetClipboardData( CF_TEXT ) ) != 0 ) { + if ( ( cliptext = GlobalLock( hClipboardData ) ) != 0 ) { + data = Z_Malloc( GlobalSize( hClipboardData ) + 1 ); + Q_strncpyz( data, cliptext, GlobalSize( hClipboardData ) ); + GlobalUnlock( hClipboardData ); + + strtok( data, "\n\r\b" ); + } + } + CloseClipboard(); + } + return data; +} + +#define MEM_THRESHOLD 96*1024*1024 + +/* +================== +Sys_LowPhysicalMemory +================== +*/ +qboolean Sys_LowPhysicalMemory( void ) +{ + MEMORYSTATUS stat; + GlobalMemoryStatus (&stat); + return (stat.dwTotalPhys <= MEM_THRESHOLD) ? qtrue : qfalse; +} + +/* +============== +Sys_Basename +============== +*/ +const char *Sys_Basename( char *path ) +{ + static char base[ MAX_OSPATH ] = { 0 }; + int length; + + length = strlen( path ) - 1; + + // Skip trailing slashes + while( length > 0 && path[ length ] == '\\' ) + length--; + + while( length > 0 && path[ length - 1 ] != '\\' ) + length--; + + Q_strncpyz( base, &path[ length ], sizeof( base ) ); + + length = strlen( base ) - 1; + + // Strip trailing slashes + while( length > 0 && base[ length ] == '\\' ) + base[ length-- ] = '\0'; + + return base; +} + +/* +============== +Sys_Dirname +============== +*/ +const char *Sys_Dirname( char *path ) +{ + static char dir[ MAX_OSPATH ] = { 0 }; + int length; + + Q_strncpyz( dir, path, sizeof( dir ) ); + length = strlen( dir ) - 1; + + while( length > 0 && dir[ length ] != '\\' ) + length--; + + dir[ length ] = '\0'; + + return dir; +} + +/* +============== +Sys_Mkdir +============== +*/ +qboolean Sys_Mkdir( const char *path ) +{ + if( !CreateDirectory( path, NULL ) ) + { + if( GetLastError( ) != ERROR_ALREADY_EXISTS ) + return qfalse; + } + + return qtrue; +} + +/* +================== +Sys_Mkfifo +Noop on windows because named pipes do not function the same way +================== +*/ +FILE *Sys_Mkfifo( const char *ospath ) +{ + return NULL; +} + +/* +============== +Sys_Cwd +============== +*/ +char *Sys_Cwd( void ) { + static char cwd[MAX_OSPATH]; + + _getcwd( cwd, sizeof( cwd ) - 1 ); + cwd[MAX_OSPATH-1] = 0; + + return cwd; +} + +/* +============================================================== + +DIRECTORY SCANNING + +============================================================== +*/ + +#define MAX_FOUND_FILES 0x1000 + +/* +============== +Sys_ListFilteredFiles +============== +*/ +void Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **list, int *numfiles ) +{ + char search[MAX_OSPATH], newsubdirs[MAX_OSPATH]; + char filename[MAX_OSPATH]; + intptr_t findhandle; + struct _finddata_t findinfo; + + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + return; + } + + if (strlen(subdirs)) { + Com_sprintf( search, sizeof(search), "%s\\%s\\*", basedir, subdirs ); + } + else { + Com_sprintf( search, sizeof(search), "%s\\*", basedir ); + } + + findhandle = _findfirst (search, &findinfo); + if (findhandle == -1) { + return; + } + + do { + if (findinfo.attrib & _A_SUBDIR) { + if (Q_stricmp(findinfo.name, ".") && Q_stricmp(findinfo.name, "..")) { + if (strlen(subdirs)) { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s\\%s", subdirs, findinfo.name); + } + else { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", findinfo.name); + } + Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles ); + } + } + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + break; + } + Com_sprintf( filename, sizeof(filename), "%s\\%s", subdirs, findinfo.name ); + if (!Com_FilterPath( filter, filename, qfalse )) + continue; + list[ *numfiles ] = CopyString( filename ); + (*numfiles)++; + } while ( _findnext (findhandle, &findinfo) != -1 ); + + _findclose (findhandle); +} + +/* +============== +strgtr +============== +*/ +static qboolean strgtr(const char *s0, const char *s1) +{ + int l0, l1, i; + + l0 = strlen(s0); + l1 = strlen(s1); + + if (l1 s0[i]) { + return qtrue; + } + if (s1[i] < s0[i]) { + return qfalse; + } + } + return qfalse; +} + +/* +============== +Sys_ListFiles +============== +*/ +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ) +{ + char search[MAX_OSPATH]; + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + struct _finddata_t findinfo; + intptr_t findhandle; + int flag; + int i; + + if (filter) { + + nfiles = 0; + Sys_ListFilteredFiles( directory, "", filter, list, &nfiles ); + + list[ nfiles ] = 0; + *numfiles = nfiles; + + if (!nfiles) + return NULL; + + listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; + } + + if ( !extension) { + extension = ""; + } + + // passing a slash as extension will find directories + if ( extension[0] == '/' && extension[1] == 0 ) { + extension = ""; + flag = 0; + } else { + flag = _A_SUBDIR; + } + + Com_sprintf( search, sizeof(search), "%s\\*%s", directory, extension ); + + // search + nfiles = 0; + + findhandle = _findfirst (search, &findinfo); + if (findhandle == -1) { + *numfiles = 0; + return NULL; + } + + do { + if ( (!wantsubs && flag ^ ( findinfo.attrib & _A_SUBDIR )) || (wantsubs && findinfo.attrib & _A_SUBDIR) ) { + if ( nfiles == MAX_FOUND_FILES - 1 ) { + break; + } + list[ nfiles ] = CopyString( findinfo.name ); + nfiles++; + } + } while ( _findnext (findhandle, &findinfo) != -1 ); + + list[ nfiles ] = 0; + + _findclose (findhandle); + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + do { + flag = 0; + for(i=1; i 0 ) + { + Com_Memcpy( p, buffer, size ); + p += size; + } + + *p = '\0'; + + if( OpenClipboard( NULL ) && EmptyClipboard( ) ) + SetClipboardData( CF_TEXT, memoryHandle ); + + GlobalUnlock( clipMemory ); + CloseClipboard( ); + } + } +} + +/* +============== +Sys_Dialog + +Display a win32 dialog box +============== +*/ +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ) +{ + UINT uType; + + switch( type ) + { + default: + case DT_INFO: uType = MB_ICONINFORMATION|MB_OK; break; + case DT_WARNING: uType = MB_ICONWARNING|MB_OK; break; + case DT_ERROR: uType = MB_ICONERROR|MB_OK; break; + case DT_YES_NO: uType = MB_ICONQUESTION|MB_YESNO; break; + case DT_OK_CANCEL: uType = MB_ICONWARNING|MB_OKCANCEL; break; + } + + switch( MessageBox( NULL, message, title, uType ) ) + { + default: + case IDOK: return DR_OK; + case IDCANCEL: return DR_CANCEL; + case IDYES: return DR_YES; + case IDNO: return DR_NO; + } +} + +#ifndef DEDICATED +static qboolean SDL_VIDEODRIVER_externallySet = qfalse; +#endif + +/* +============== +Sys_GLimpSafeInit + +Windows specific "safe" GL implementation initialisation +============== +*/ +void Sys_GLimpSafeInit( void ) +{ +#ifndef DEDICATED + if( !SDL_VIDEODRIVER_externallySet ) + { + // Here, we want to let SDL decide what do to unless + // explicitly requested otherwise + _putenv( "SDL_VIDEODRIVER=" ); + } +#endif +} + +/* +============== +Sys_GLimpInit + +Windows specific GL implementation initialisation +============== +*/ +void Sys_GLimpInit( void ) +{ +#ifndef DEDICATED + if( !SDL_VIDEODRIVER_externallySet ) + { + // It's a little bit weird having in_mouse control the + // video driver, but from ioq3's point of view they're + // virtually the same except for the mouse input anyway + if( Cvar_VariableIntegerValue( "in_mouse" ) == -1 ) + { + // Use the windib SDL backend, which is closest to + // the behaviour of idq3 with in_mouse set to -1 + _putenv( "SDL_VIDEODRIVER=windib" ); + } + else + { + // Use the DirectX SDL backend + _putenv( "SDL_VIDEODRIVER=directx" ); + } + } +#endif +} + +/* +============== +Sys_PlatformInit + +Windows specific initialisation +============== +*/ +void Sys_PlatformInit( void ) +{ +#ifndef DEDICATED + TIMECAPS ptc; + + const char *SDL_VIDEODRIVER = getenv( "SDL_VIDEODRIVER" ); + + if( SDL_VIDEODRIVER ) + { + Com_Printf( "SDL_VIDEODRIVER is externally set to \"%s\", " + "in_mouse -1 will have no effect\n", SDL_VIDEODRIVER ); + SDL_VIDEODRIVER_externallySet = qtrue; + } + else + SDL_VIDEODRIVER_externallySet = qfalse; + + if(timeGetDevCaps(&ptc, sizeof(ptc)) == MMSYSERR_NOERROR) + { + timerResolution = ptc.wPeriodMin; + + if(timerResolution > 1) + { + Com_Printf("Warning: Minimum supported timer resolution is %ums " + "on this system, recommended resolution 1ms\n", timerResolution); + } + + timeBeginPeriod(timerResolution); + } + else + timerResolution = 0; +#endif +} + +/* +============== +Sys_PlatformExit + +Windows specific initialisation +============== +*/ +void Sys_PlatformExit( void ) +{ +#ifndef DEDICATED + if(timerResolution) + timeEndPeriod(timerResolution); +#endif +} + +/* +============== +Sys_SetEnv + +set/unset environment variables (empty value removes it) +============== +*/ +void Sys_SetEnv(const char *name, const char *value) +{ + if(value) + _putenv(va("%s=%s", name, value)); + else + _putenv(va("%s=", name)); +} + +/* +============== +Sys_PID +============== +*/ +int Sys_PID( void ) +{ + return GetCurrentProcessId( ); +} + +/* +============== +Sys_PIDIsRunning +============== +*/ +qboolean Sys_PIDIsRunning( int pid ) +{ + DWORD processes[ 1024 ]; + DWORD numBytes, numProcesses; + int i; + + if( !EnumProcesses( processes, sizeof( processes ), &numBytes ) ) + return qfalse; // Assume it's not running + + numProcesses = numBytes / sizeof( DWORD ); + + // Search for the pid + for( i = 0; i < numProcesses; i++ ) + { + if( processes[ i ] == pid ) + return qtrue; + } + + return qfalse; +} diff --git a/src/sys/win_resource.h b/src/sys/win_resource.h new file mode 100644 index 0000000..8e658d7 --- /dev/null +++ b/src/sys/win_resource.h @@ -0,0 +1,45 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by winquake.rc +// +#define IDS_STRING1 1 +#define IDI_ICON1 1 +#define IDB_BITMAP1 1 +#define IDB_BITMAP2 128 +#define IDC_CURSOR1 129 +#define IDC_CURSOR2 130 +#define IDC_CURSOR3 131 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 132 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/sys/win_resource.rc b/src/sys/win_resource.rc new file mode 100644 index 0000000..6f5d82a --- /dev/null +++ b/src/sys/win_resource.rc @@ -0,0 +1,72 @@ +//Microsoft Developer Studio generated resource script. +// +#include "win_resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON DISCARDABLE "misc/tremulous.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_STRING1 "Tremulous" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + diff --git a/src/tools/asm/README.Id b/src/tools/asm/README.Id new file mode 100644 index 0000000..adacb50 --- /dev/null +++ b/src/tools/asm/README.Id @@ -0,0 +1,10 @@ +2002-10-25 Timothee Besset +If you are looking for a faster version of the q3asm tool, try: +http://www.icculus.org/~phaethon/q3/q3asm-turbo/q3asm-turbo.html + +2001-10-31 Timothee Besset +updated from the $/source/q3asm code +modified for portability and use with >= 1.31 mod source release + +the cmdlib.c cmdlib.h mathlib.h qfiles.h have been copied from +$/source/common diff --git a/src/tools/asm/cmdlib.c b/src/tools/asm/cmdlib.c new file mode 100644 index 0000000..bf50346 --- /dev/null +++ b/src/tools/asm/cmdlib.c @@ -0,0 +1,1141 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cmdlib.c + +#include "cmdlib.h" +#include +#include + +#ifdef WIN32 +#include +#include +#elif defined(NeXT) +#include +#else +#include +#endif + +#define BASEDIRNAME "quake" // assumed to have a 2 or 3 following +#define PATHSEPERATOR '/' + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; +qboolean com_eof; + +qboolean archive; +char archivedir[1024]; + + +/* +=================== +ExpandWildcards + +Mimic unix command line expansion +=================== +*/ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#ifdef _WIN32 +#include "io.h" +void ExpandWildcards( int *argc, char ***argv ) +{ + struct _finddata_t fileinfo; + intptr_t handle; + int i; + char filename[1024]; + char filebase[1024]; + char *path; + + ex_argc = 0; + for (i=0 ; i<*argc ; i++) + { + path = (*argv)[i]; + if ( path[0] == '-' + || ( !strstr(path, "*") && !strstr(path, "?") ) ) + { + ex_argv[ex_argc++] = path; + continue; + } + + handle = _findfirst (path, &fileinfo); + if (handle == -1) + return; + + ExtractFilePath (path, filebase); + + do + { + sprintf (filename, "%s%s", filebase, fileinfo.name); + ex_argv[ex_argc++] = copystring (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); + } + + *argc = ex_argc; + *argv = ex_argv; +} +#else +void ExpandWildcards (int *argc, char ***argv) +{ +} +#endif + +#ifdef WIN_ERROR +#include +/* +================= +Error + +For abnormal program terminations in windowed apps +================= +*/ +void Error( const char *error, ... ) +{ + va_list argptr; + char text[1024]; + char text2[1024]; + int err; + + err = GetLastError (); + + va_start (argptr,error); + vsprintf (text, error,argptr); + va_end (argptr); + + sprintf (text2, "%s\nGetLastError() = %i", text, err); + MessageBox(NULL, text2, "Error", 0 /* MB_OK */ ); + + exit (1); +} + +#else +/* +================= +Error + +For abnormal program terminations in console apps +================= +*/ +void Error( const char *error, ...) +{ + va_list argptr; + + _printf ("\n************ ERROR ************\n"); + + va_start (argptr,error); + vprintf (error,argptr); + va_end (argptr); + _printf ("\r\n"); + + exit (1); +} +#endif + +// only printf if in verbose mode +qboolean verbose = qfalse; +void qprintf( const char *format, ... ) { + va_list argptr; + + if (!verbose) + return; + + va_start (argptr,format); + vprintf (format,argptr); + va_end (argptr); + +} + +#ifdef WIN32 +HWND hwndOut = NULL; +qboolean lookedForServer = qfalse; +UINT wm_BroadcastCommand = -1; +#endif + +void _printf( const char *format, ... ) { + va_list argptr; + char text[4096]; +#ifdef WIN32 + ATOM a; +#endif + va_start (argptr,format); + vsprintf (text, format, argptr); + va_end (argptr); + + printf("%s", text); + +#ifdef WIN32 + if (!lookedForServer) { + lookedForServer = qtrue; + hwndOut = FindWindow(NULL, "Q3Map Process Server"); + if (hwndOut) { + wm_BroadcastCommand = RegisterWindowMessage( "Q3MPS_BroadcastCommand" ); + } + } + if (hwndOut) { + a = GlobalAddAtom(text); + PostMessage(hwndOut, wm_BroadcastCommand, 0, (LPARAM)a); + } +#endif +} + + +/* + +qdir will hold the path up to the quake directory, including the slash + + f:\quake\ + /raid/quake/ + +gamedir will hold qdir + the game directory (id1, id2, etc) + + */ + +char qdir[1024]; +char gamedir[1024]; +char writedir[1024]; + +void SetQdirFromPath( const char *path ) +{ + char temp[1024]; + const char *c; + const char *sep; + int len, count; + + if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) + { // path is partial + Q_getwd (temp); + strcat (temp, path); + path = temp; + } + + // search for "quake2" in path + + len = strlen(BASEDIRNAME); + for (c=path+strlen(path)-1 ; c != path ; c--) + { + int i; + + if (!Q_strncasecmp (c, BASEDIRNAME, len)) + { + // + //strncpy (qdir, path, c+len+2-path); + // the +2 assumes a 2 or 3 following quake which is not the + // case with a retail install + // so we need to add up how much to the next separator + sep = c + len; + count = 1; + while (*sep && *sep != '/' && *sep != '\\') + { + sep++; + count++; + } + strncpy (qdir, path, c+len+count-path); + qprintf ("qdir: %s\n", qdir); + for ( i = 0; i < strlen( qdir ); i++ ) + { + if ( qdir[i] == '\\' ) + qdir[i] = '/'; + } + + c += len+count; + while (*c) + { + if (*c == '/' || *c == '\\') + { + strncpy (gamedir, path, c+1-path); + + for ( i = 0; i < strlen( gamedir ); i++ ) + { + if ( gamedir[i] == '\\' ) + gamedir[i] = '/'; + } + + qprintf ("gamedir: %s\n", gamedir); + + if ( !writedir[0] ) + strcpy( writedir, gamedir ); + else if ( writedir[strlen( writedir )-1] != '/' ) + { + writedir[strlen( writedir )] = '/'; + writedir[strlen( writedir )+1] = 0; + } + + return; + } + c++; + } + Error ("No gamedir in %s", path); + return; + } + } + Error ("SetQdirFromPath: no '%s' in %s", BASEDIRNAME, path); +} + +char *ExpandArg (const char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + Q_getwd (full); + strcat (full, path); + } + else + strcpy (full, path); + return full; +} + +char *ExpandPath (const char *path) +{ + static char full[1024]; + if (!qdir[0]) + Error ("ExpandPath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') { + strcpy( full, path ); + return full; + } + sprintf (full, "%s%s", qdir, path); + return full; +} + +char *ExpandGamePath (const char *path) +{ + static char full[1024]; + if (!qdir[0]) + Error ("ExpandGamePath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') { + strcpy( full, path ); + return full; + } + sprintf (full, "%s%s", gamedir, path); + return full; +} + +char *ExpandPathAndArchive (const char *path) +{ + char *expanded; + char archivename[1024]; + + expanded = ExpandPath (path); + + if (archive) + { + sprintf (archivename, "%s/%s", archivedir, path); + QCopyFile (expanded, archivename); + } + return expanded; +} + + +char *copystring(const char *s) +{ + char *b; + b = malloc(strlen(s)+1); + strcpy (b, s); + return b; +} + + + +/* +================ +I_FloatTime +================ +*/ +double I_FloatTime (void) +{ + time_t t; + + time (&t); + + return t; +#if 0 +// more precise, less portable + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday(&tp, &tzp); + + if (!secbase) + { + secbase = tp.tv_sec; + return tp.tv_usec/1000000.0; + } + + return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; +#endif +} + +void Q_getwd (char *out) +{ + int i = 0; + +#ifdef WIN32 + if (_getcwd (out, 256) == NULL) + strcpy(out, "."); /* shrug */ + strcat (out, "\\"); +#else + if (getcwd (out, 256) == NULL) + strcpy(out, "."); /* shrug */ + strcat (out, "/"); +#endif + + while ( out[i] != 0 ) + { + if ( out[i] == '\\' ) + out[i] = '/'; + i++; + } +} + + +void Q_mkdir (const char *path) +{ +#ifdef WIN32 + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif + if (errno != EEXIST) + Error ("mkdir %s: %s",path, strerror(errno)); +} + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (const char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + com_eof = qtrue; + return NULL; // end of file; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + do + { + c = *data++; + if (c=='\"') + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while (1); + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + + +int Q_strncasecmp (const char *s1, const char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_stricmp (const char *s1, const char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + +char *strupr (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = toupper(*in); + in++; + } + return start; +} + +char *strlower (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = tolower(*in); + in++; + } + return start; +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (const char *check) +{ + int i; + + for (i = 1;i 0) { + nAllocSize += MEM_BLOCKSIZE - nBlock; + } + buffer = malloc (nAllocSize+1); + memset(buffer, 0, nAllocSize+1); + SafeRead (f, buffer, length); + fclose (f); + + *bufferptr = buffer; + return length; +} + + +/* +============== +TryLoadFile + +Allows failure +============== +*/ +int TryLoadFile (const char *filename, void **bufferptr) +{ + FILE *f; + int length; + void *buffer; + + *bufferptr = NULL; + + f = myfopen (filename, "rb"); + if (!f) + return -1; + length = Q_filelength (f); + buffer = malloc (length+1); + ((char *)buffer)[length] = 0; + SafeRead (f, buffer, length); + fclose (f); + + *bufferptr = buffer; + return length; +} + + +/* +============== +SaveFile +============== +*/ +void SaveFile (const char *filename, const void *buffer, int count) +{ + FILE *f; + + f = SafeOpenWrite (filename); + SafeWrite (f, buffer, count); + fclose (f); +} + + + +void DefaultExtension (char *path, const char *extension) +{ + char *src; +// +// if path doesnt have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && *src != '\\' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + + +void DefaultPath (char *path, const char *basepath) +{ + char temp[128]; + + if (path[0] == PATHSEPERATOR) + return; // absolute path location + strcpy (temp,path); + strcpy (path,basepath); + strcat (path,temp); +} + + +void StripFilename (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != PATHSEPERATOR) + length--; + path[length] = 0; +} + +void StripExtension (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '.') + { + length--; + if (path[length] == '/') + return; // no extension + } + if (length) + path[length] = 0; +} + + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash +void ExtractFilePath (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '\\' && *(src-1) != '/') + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +void ExtractFileBase (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != PATHSEPERATOR) + src--; + + while (*src && *src != '.') + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a . or the start +// + while (src != path && *(src-1) != '.') + src--; + if (src == path) + { + *dest = 0; // no extension + return; + } + + strcpy (dest,src); +} + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (const char *hex) +{ + const char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if (*str >= '0' && *str <= '9') + num += *str-'0'; + else if (*str >= 'a' && *str <= 'f') + num += 10 + *str-'a'; + else if (*str >= 'A' && *str <= 'F') + num += 10 + *str-'A'; + else + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (const char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} + + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +short ShortSwap (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +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; +} + +typedef union { + float f; + unsigned int i; +} _FloatByteUnion; + +float FloatSwap (const float *f) { + _FloatByteUnion out; + + out.f = *f; + out.i = LongSwap(out.i); + + return out.f; +} + +//======================================================= + + +// FIXME: byte swap? + +// 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 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; +} +//============================================================================= + +/* +============ +CreatePath +============ +*/ +void CreatePath (const char *path) +{ + const char *ofs; + char c; + char dir[1024]; + +#ifdef _WIN32 + int olddrive = -1; + + if ( path[1] == ':' ) + { + olddrive = _getdrive(); + _chdrive( toupper( path[0] ) - 'A' + 1 ); + } +#endif + + if (path[1] == ':') + path += 2; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + memcpy( dir, path, ofs - path ); + dir[ ofs - path ] = 0; + Q_mkdir( dir ); + } + } + +#ifdef _WIN32 + if ( olddrive != -1 ) + { + _chdrive( olddrive ); + } +#endif +} + + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (const char *from, const char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} diff --git a/src/tools/asm/cmdlib.h b/src/tools/asm/cmdlib.h new file mode 100644 index 0000000..20d06de --- /dev/null +++ b/src/tools/asm/cmdlib.h @@ -0,0 +1,153 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cmdlib.h + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#ifdef _MSC_VER +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncate from double to float + +#pragma check_stack(off) + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + +#pragma intrinsic( memset, memcpy ) + +#endif + +#ifndef __BYTEBOOL__ +#define __BYTEBOOL__ +typedef enum { qfalse, qtrue } qboolean; +typedef unsigned char byte; +#endif + +#define MAX_OS_PATH 1024 +#define MEM_BLOCKSIZE 4096 + +// the dec offsetof macro doesnt work very well... +#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) + + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +char *strupr (char *in); +char *strlower (char *in); +int Q_strncasecmp( const char *s1, const char *s2, int n ); +int Q_stricmp( const char *s1, const char *s2 ); +void Q_getwd( char *out ); + +int Q_filelength (FILE *f); +int FileTime( const char *path ); + +void Q_mkdir( const char *path ); + +extern char qdir[1024]; +extern char gamedir[1024]; +extern char writedir[1024]; +void SetQdirFromPath( const char *path ); +char *ExpandArg( const char *path ); // from cmd line +char *ExpandPath( const char *path ); // from scripts +char *ExpandGamePath (const char *path); +char *ExpandPathAndArchive( const char *path ); + + +double I_FloatTime( void ); + +void Error( const char *error, ... ); +int CheckParm( const char *check ); + +FILE *SafeOpenWrite( const char *filename ); +FILE *SafeOpenRead( const char *filename ); +void SafeRead (FILE *f, void *buffer, int count); +void SafeWrite (FILE *f, const void *buffer, int count); + +int LoadFile( const char *filename, void **bufferptr ); +int LoadFileBlock( const char *filename, void **bufferptr ); +int TryLoadFile( const char *filename, void **bufferptr ); +void SaveFile( const char *filename, const void *buffer, int count ); +qboolean FileExists( const char *filename ); + +void DefaultExtension( char *path, const char *extension ); +void DefaultPath( char *path, const char *basepath ); +void StripFilename( char *path ); +void StripExtension( char *path ); + +void ExtractFilePath( const char *path, char *dest ); +void ExtractFileBase( const char *path, char *dest ); +void ExtractFileExtension( const char *path, char *dest ); + +int ParseNum (const char *str); + +char *COM_Parse (char *data); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring(const char *s); + + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); + +void CreatePath( const char *path ); +void QCopyFile( const char *from, const char *to ); + +extern qboolean archive; +extern char archivedir[1024]; + + +extern qboolean verbose; +void qprintf( const char *format, ... ); +void _printf( const char *format, ... ); + +void ExpandWildcards( int *argc, char ***argv ); + + +// for compression routines +typedef struct +{ + void *data; + int count, width, height; +} cblock_t; + + +#endif diff --git a/src/tools/asm/lib.txt b/src/tools/asm/lib.txt new file mode 100644 index 0000000..737a030 --- /dev/null +++ b/src/tools/asm/lib.txt @@ -0,0 +1,31 @@ + +strlen +strcasecmp +tolower +strcat +strncpy +strcmp +strcpy +strchr + +vsprintf + +memcpy +memset +rand + +atoi +atof + +abs + +floor +fabs +tan +atan +sqrt +log +cos +sin +atan2 + diff --git a/src/tools/asm/mathlib.h b/src/tools/asm/mathlib.h new file mode 100644 index 0000000..bc63a4d --- /dev/null +++ b/src/tools/asm/mathlib.h @@ -0,0 +1,95 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec2_t[3]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#define Q_PI 3.14159265358979323846 +#define DEG2RAD( a ) ( ( (a) * Q_PI ) / 180.0F ) +#define RAD2DEG( a ) ( ( (a) * 180.0f ) / Q_PI ) + +extern vec3_t vec3_origin; + +#define EQUAL_EPSILON 0.001 + +// plane types are used to speed some tests +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 +#define PLANE_NON_AXIAL 3 + +qboolean VectorCompare( const vec3_t v1, const vec3_t v2 ); + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) {c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];} +#define VectorAdd(a,b,c) {c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];} +#define VectorCopy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];} +#define VectorScale(a,b,c) {c[0]=b*a[0];c[1]=b*a[1];c[2]=b*a[2];} +#define VectorClear(x) {x[0] = x[1] = x[2] = 0;} +#define VectorNegate(x) {x[0]=-x[0];x[1]=-x[1];x[2]=-x[2];} +void Vec10Copy( vec_t *in, vec_t *out ); + +vec_t Q_rint (vec_t in); +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); +void _VectorScale (vec3_t v, vec_t scale, vec3_t out); + +double VectorLength( const vec3_t v ); + +void VectorMA( const vec3_t va, double scale, const vec3_t vb, vec3_t vc ); + +void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ); +vec_t VectorNormalize( const vec3_t in, vec3_t out ); +vec_t ColorNormalize( const vec3_t in, vec3_t out ); +void VectorInverse (vec3_t v); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); + +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ); + +void NormalToLatLong( const vec3_t normal, byte bytes[2] ); + +int PlaneTypeForNormal (vec3_t normal); + +#endif diff --git a/src/tools/asm/notes.txt b/src/tools/asm/notes.txt new file mode 100644 index 0000000..63297f3 --- /dev/null +++ b/src/tools/asm/notes.txt @@ -0,0 +1,16 @@ + +don't do any paramter conversion (double to float, etc) + + + +Why? + +Security. +Portability. + +It may be more aproachable. + +can still use regular dlls for development purposes + +lcc +q3asm diff --git a/src/tools/asm/ops.txt b/src/tools/asm/ops.txt new file mode 100644 index 0000000..e897f49 --- /dev/null +++ b/src/tools/asm/ops.txt @@ -0,0 +1,132 @@ +CNSTF, +CNSTI, +CNSTP, +CNSTU, + +ARGB, +ARGF, +ARGI, +ARGP, +ARGU, + +ASGNB, +ASGNF, +ASGNI, +ASGNP, +ASGNU, + +INDIRB, +INDIRF, +INDIRI, +INDIRP, +INDIRU, + +CVFF, +CVFI, + +CVIF, +CVII, +CVIU, + +CVPU, + +CVUI, +CVUP, +CVUU, + +NEGF, +NEGI, + +CALLB, +CALLF, +CALLI, +CALLP, +CALLU, +CALLV, + +RETF, +RETI, +RETP, +RETU, +RETV, + +ADDRGP, + +ADDRFP, + +ADDRLP, + +ADDF, +ADDI, +ADDP, +ADDU, + +SUBF, +SUBI, +SUBP, +SUBU, + +LSHI, +LSHU, + +MODI, +MODU, + +RSHI, +RSHU, + +BANDI, +BANDU, + +BCOMI, +BCOMU, + +BORI, +BORU, + +BXORI, +BXORU, + +DIVF, +DIVI, +DIVU, + +MULF, +MULI, +MULU, + +EQF, +EQI, +EQU, + +GEF, +GEI, +GEU, + +GTF, +GTI, +GTU, + +LEF, +LEI, +LEU, + +LTF, +LTI, +LTU, + +NEF, +NEI, +NEU, + +JUMPV, + +LABELV, + +LOADB, +LOADF, +LOADI, +LOADP, +LOADU, + + diff --git a/src/tools/asm/opstrings.h b/src/tools/asm/opstrings.h new file mode 100644 index 0000000..d2c3210 --- /dev/null +++ b/src/tools/asm/opstrings.h @@ -0,0 +1,176 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +{ "BREAK", OP_BREAK }, + +{ "CNSTF4", OP_CONST }, +{ "CNSTI4", OP_CONST }, +{ "CNSTP4", OP_CONST }, +{ "CNSTU4", OP_CONST }, + +{ "CNSTI2", OP_CONST }, +{ "CNSTU2", OP_CONST }, + +{ "CNSTI1", OP_CONST }, +{ "CNSTU1", OP_CONST }, + +//{ "ARGB", OP_ARG }, +//{ "ARGF", OP_ARG }, +//{ "ARGI", OP_ARG }, +//{ "ARGP", OP_ARG }, +//{ "ARGU", OP_ARG }, + +{ "ASGNB", OP_BLOCK_COPY }, +{ "ASGNF4", OP_STORE4 }, +{ "ASGNI4", OP_STORE4 }, +{ "ASGNP4", OP_STORE4 }, +{ "ASGNU4", OP_STORE4 }, + +{ "ASGNI2", OP_STORE2 }, +{ "ASGNU2", OP_STORE2 }, + +{ "ASGNI1", OP_STORE1 }, +{ "ASGNU1", OP_STORE1 }, + +{ "INDIRB", OP_IGNORE }, // block copy deals with this +{ "INDIRF4", OP_LOAD4 }, +{ "INDIRI4", OP_LOAD4 }, +{ "INDIRP4", OP_LOAD4 }, +{ "INDIRU4", OP_LOAD4 }, + +{ "INDIRI2", OP_LOAD2 }, +{ "INDIRU2", OP_LOAD2 }, + +{ "INDIRI1", OP_LOAD1 }, +{ "INDIRU1", OP_LOAD1 }, + +{ "CVFF4", OP_UNDEF }, +{ "CVFI4", OP_CVFI }, + +{ "CVIF4", OP_CVIF }, +{ "CVII4", OP_SEX8 }, // will be either SEX8 or SEX16 +{ "CVII1", OP_IGNORE }, +{ "CVII2", OP_IGNORE }, +{ "CVIU4", OP_IGNORE }, + +{ "CVPU4", OP_IGNORE }, + +{ "CVUI4", OP_IGNORE }, +{ "CVUP4", OP_IGNORE }, +{ "CVUU4", OP_IGNORE }, + +{ "CVUU1", OP_IGNORE }, + +{ "NEGF4", OP_NEGF }, +{ "NEGI4", OP_NEGI }, + +//{ "CALLB", OP_UNDEF }, +//{ "CALLF", OP_UNDEF }, +//{ "CALLI", OP_UNDEF }, +//{ "CALLP", OP_UNDEF }, +//{ "CALLU", OP_UNDEF }, +//{ "CALLV", OP_CALL }, + +//{ "RETF", OP_UNDEF }, +//{ "RETI", OP_UNDEF }, +//{ "RETP", OP_UNDEF }, +//{ "RETU", OP_UNDEF }, +//{ "RETV", OP_UNDEF }, + +{ "ADDRGP4", OP_CONST }, + +//{ "ADDRFP", OP_PARM }, +//{ "ADDRLP", OP_LOCAL }, + +{ "ADDF4", OP_ADDF }, +{ "ADDI4", OP_ADD }, +{ "ADDP4", OP_ADD }, +{ "ADDP", OP_ADD }, +{ "ADDU4", OP_ADD }, + +{ "SUBF4", OP_SUBF }, +{ "SUBI4", OP_SUB }, +{ "SUBP4", OP_SUB }, +{ "SUBU4", OP_SUB }, + +{ "LSHI4", OP_LSH }, +{ "LSHU4", OP_LSH }, + +{ "MODI4", OP_MODI }, +{ "MODU4", OP_MODU }, + +{ "RSHI4", OP_RSHI }, +{ "RSHU4", OP_RSHU }, + +{ "BANDI4", OP_BAND }, +{ "BANDU4", OP_BAND }, + +{ "BCOMI4", OP_BCOM }, +{ "BCOMU4", OP_BCOM }, + +{ "BORI4", OP_BOR }, +{ "BORU4", OP_BOR }, + +{ "BXORI4", OP_BXOR }, +{ "BXORU4", OP_BXOR }, + +{ "DIVF4", OP_DIVF }, +{ "DIVI4", OP_DIVI }, +{ "DIVU4", OP_DIVU }, + +{ "MULF4", OP_MULF }, +{ "MULI4", OP_MULI }, +{ "MULU4", OP_MULU }, + +{ "EQF4", OP_EQF }, +{ "EQI4", OP_EQ }, +{ "EQU4", OP_EQ }, + +{ "GEF4", OP_GEF }, +{ "GEI4", OP_GEI }, +{ "GEU4", OP_GEU }, + +{ "GTF4", OP_GTF }, +{ "GTI4", OP_GTI }, +{ "GTU4", OP_GTU }, + +{ "LEF4", OP_LEF }, +{ "LEI4", OP_LEI }, +{ "LEU4", OP_LEU }, + +{ "LTF4", OP_LTF }, +{ "LTI4", OP_LTI }, +{ "LTU4", OP_LTU }, + +{ "NEF4", OP_NEF }, +{ "NEI4", OP_NE }, +{ "NEU4", OP_NE }, + +{ "JUMPV", OP_JUMP }, + +{ "LOADB4", OP_UNDEF }, +{ "LOADF4", OP_UNDEF }, +{ "LOADI4", OP_UNDEF }, +{ "LOADP4", OP_UNDEF }, +{ "LOADU4", OP_UNDEF }, + + diff --git a/src/tools/asm/q3asm.c b/src/tools/asm/q3asm.c new file mode 100644 index 0000000..c42b9ac --- /dev/null +++ b/src/tools/asm/q3asm.c @@ -0,0 +1,1647 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../../qcommon/q_platform.h" +#include "cmdlib.h" +#include "mathlib.h" +#include "../../qcommon/qfiles.h" + +/* 19079 total symbols in FI, 2002 Jan 23 */ +#define DEFAULT_HASHTABLE_SIZE 2048 + +char outputFilename[MAX_OS_PATH]; + +// the zero page size is just used for detecting run time faults +#define ZERO_PAGE_SIZE 0 // 256 + +typedef enum { + OP_UNDEF, + + OP_IGNORE, + + OP_BREAK, + + OP_ENTER, + OP_LEAVE, + OP_CALL, + OP_PUSH, + OP_POP, + + OP_CONST, + OP_LOCAL, + + OP_JUMP, + + //------------------- + + OP_EQ, + OP_NE, + + OP_LTI, + OP_LEI, + OP_GTI, + OP_GEI, + + OP_LTU, + OP_LEU, + OP_GTU, + OP_GEU, + + OP_EQF, + OP_NEF, + + OP_LTF, + OP_LEF, + OP_GTF, + OP_GEF, + + //------------------- + + OP_LOAD1, + OP_LOAD2, + OP_LOAD4, + OP_STORE1, + OP_STORE2, + OP_STORE4, // *(stack[top-1]) = stack[yop + OP_ARG, + OP_BLOCK_COPY, + + //------------------- + + OP_SEX8, + OP_SEX16, + + OP_NEGI, + OP_ADD, + OP_SUB, + OP_DIVI, + OP_DIVU, + OP_MODI, + OP_MODU, + OP_MULI, + OP_MULU, + + OP_BAND, + OP_BOR, + OP_BXOR, + OP_BCOM, + + OP_LSH, + OP_RSHI, + OP_RSHU, + + OP_NEGF, + OP_ADDF, + OP_SUBF, + OP_DIVF, + OP_MULF, + + OP_CVIF, + OP_CVFI +} opcode_t; + +typedef struct { + int imageBytes; // after decompression + int entryPoint; + int stackBase; + int stackSize; +} executableHeader_t; + +typedef enum { + CODESEG, + DATASEG, // initialized 32 bit data, will be byte swapped + LITSEG, // strings + BSSSEG, // 0 filled + JTRGSEG, // pseudo-segment that contains only jump table targets + NUM_SEGMENTS +} segmentName_t; + +#define MAX_IMAGE 0x400000 + +typedef struct { + byte image[MAX_IMAGE]; + int imageUsed; + int segmentBase; // only valid on second pass +} segment_t; + +typedef struct symbol_s { + struct symbol_s *next; + int hash; + segment_t *segment; + char *name; + int value; +} symbol_t; + +typedef struct hashchain_s { + void *data; + struct hashchain_s *next; +} hashchain_t; + +typedef struct hashtable_s { + int buckets; + hashchain_t **table; +} hashtable_t; + +int symtablelen = DEFAULT_HASHTABLE_SIZE; +hashtable_t *symtable; +hashtable_t *optable; + +segment_t segment[NUM_SEGMENTS]; +segment_t *currentSegment; + +int passNumber; + +int numSymbols; +int errorCount; + +typedef struct options_s { + qboolean verbose; + qboolean writeMapFile; + qboolean vanillaQ3Compatibility; +} options_t; + +options_t options = { 0 }; + +symbol_t *symbols; +symbol_t *lastSymbol = 0; /* Most recent symbol defined. */ + + +#define MAX_ASM_FILES 256 +int numAsmFiles; +char *asmFiles[MAX_ASM_FILES]; +char *asmFileNames[MAX_ASM_FILES]; + +int currentFileIndex; +char *currentFileName; +int currentFileLine; + +//int stackSize = 16384; +int stackSize = 0x10000; + +// we need to convert arg and ret instructions to +// stores to the local stack frame, so we need to track the +// characteristics of the current functions stack frame +int currentLocals; // bytes of locals needed by this function +int currentArgs; // bytes of largest argument list called from this function +int currentArgOffset; // byte offset in currentArgs to store next arg, reset each call + +#define MAX_LINE_LENGTH 1024 +char lineBuffer[MAX_LINE_LENGTH]; +int lineParseOffset; +char token[MAX_LINE_LENGTH]; + +int instructionCount; + +typedef struct { + char *name; + int opcode; +} sourceOps_t; + +sourceOps_t sourceOps[] = { +#include "opstrings.h" +}; + +#define NUM_SOURCE_OPS ( sizeof( sourceOps ) / sizeof( sourceOps[0] ) ) + +int opcodesHash[ NUM_SOURCE_OPS ]; + + + +static int vreport (const char* fmt, va_list vp) +{ + if (options.verbose != qtrue) + return 0; + return vprintf(fmt, vp); +} + +static int report (const char *fmt, ...) +{ + va_list va; + int retval; + + va_start(va, fmt); + retval = vreport(fmt, va); + va_end(va); + return retval; +} + +/* The chain-and-bucket hash table. -PH */ + +static void hashtable_init (hashtable_t *H, int buckets) +{ + H->buckets = buckets; + H->table = calloc(H->buckets, sizeof(*(H->table))); + return; +} + +static hashtable_t *hashtable_new (int buckets) +{ + hashtable_t *H; + + H = malloc(sizeof(hashtable_t)); + hashtable_init(H, buckets); + return H; +} + +/* No destroy/destructor. No need. */ + +static void hashtable_add (hashtable_t *H, int hashvalue, void *datum) +{ + hashchain_t *hc, **hb; + + hashvalue = (abs(hashvalue) % H->buckets); + hb = &(H->table[hashvalue]); + if (*hb == 0) + { + /* Empty bucket. Create new one. */ + *hb = calloc(1, sizeof(**hb)); + hc = *hb; + } + else + { + /* Get hc to point to last node in chain. */ + for (hc = *hb; hc && hc->next; hc = hc->next); + hc->next = calloc(1, sizeof(*hc)); + hc = hc->next; + } + hc->data = datum; + hc->next = 0; + return; +} + +static hashchain_t *hashtable_get (hashtable_t *H, int hashvalue) +{ + hashvalue = (abs(hashvalue) % H->buckets); + return (H->table[hashvalue]); +} + +static void hashtable_stats (hashtable_t *H) +{ + int len, empties, longest, nodes; + int i; + float meanlen; + hashchain_t *hc; + + report("Stats for hashtable %08X", H); + empties = 0; + longest = 0; + nodes = 0; + for (i = 0; i < H->buckets; i++) + { + if (H->table[i] == 0) + { empties++; continue; } + for (hc = H->table[i], len = 0; hc; hc = hc->next, len++); + if (len > longest) { longest = len; } + nodes += len; + } + meanlen = (float)(nodes) / (H->buckets - empties); +#if 0 +/* Long stats display */ + report(" Total buckets: %d\n", H->buckets); + report(" Total stored nodes: %d\n", nodes); + report(" Longest chain: %d\n", longest); + report(" Empty chains: %d\n", empties); + report(" Mean non-empty chain length: %f\n", meanlen); +#else //0 +/* Short stats display */ + report(", %d buckets, %d nodes", H->buckets, nodes); + report("\n"); + report(" Longest chain: %d, empty chains: %d, mean non-empty: %f", longest, empties, meanlen); +#endif //0 + report("\n"); +} + + +/* Kludge. */ +/* Check if symbol already exists. */ +/* Returns 0 if symbol does NOT already exist, non-zero otherwise. */ +static int hashtable_symbol_exists (hashtable_t *H, int hash, char *sym) +{ + hashchain_t *hc; + symbol_t *s; + + hash = (abs(hash) % H->buckets); + hc = H->table[hash]; + if (hc == 0) + { + /* Empty chain means this symbol has not yet been defined. */ + return 0; + } + for (; hc; hc = hc->next) + { + s = (symbol_t*)hc->data; +// if ((hash == s->hash) && (strcmp(sym, s->name) == 0)) +/* We _already_ know the hash is the same. That's why we're probing! */ + if (strcmp(sym, s->name) == 0) + { + /* Symbol collisions -- symbol already exists. */ + return 1; + } + } + return 0; /* Can't find collision. */ +} + + + + +/* Comparator function for quicksorting. */ +static int symlist_cmp (const void *e1, const void *e2) +{ + const symbol_t *a, *b; + + a = *(const symbol_t **)e1; + b = *(const symbol_t **)e2; +//crumb("Symbol comparison (1) %d to (2) %d\n", a->value, b->value); + return ( a->value - b->value); +} + +/* + Sort the symbols list by using QuickSort (qsort()). + This may take a LOT of memory (a few megabytes?), but memory is cheap these days. + However, qsort(3) already exists, and I'm really lazy. + -PH +*/ +static void sort_symbols () +{ + int i, elems; + symbol_t *s; + symbol_t **symlist; + +//crumb("sort_symbols: Constructing symlist array\n"); + for (elems = 0, s = symbols; s; s = s->next, elems++) /* nop */ ; + symlist = malloc(elems * sizeof(symbol_t*)); + for (i = 0, s = symbols; s; s = s->next, i++) + { + symlist[i] = s; + } +//crumbf("sort_symbols: Quick-sorting %d symbols\n", elems); + qsort(symlist, elems, sizeof(symbol_t*), symlist_cmp); +//crumbf("sort_symbols: Reconstructing symbols list\n"); + s = symbols = symlist[0]; + for (i = 1; i < elems; i++) + { + s->next = symlist[i]; + s = s->next; + } + lastSymbol = s; + s->next = 0; +//crumbf("sort_symbols: verifying..."); fflush(stdout); + for (i = 0, s = symbols; s; s = s->next, i++) /*nop*/ ; +//crumbf(" %d elements\n", i); + free(symlist); /* d'oh. no gc. */ +} + + +#ifdef _MSC_VER +#define INT64 __int64 +#define atoi64 _atoi64 +#else +#define INT64 long long int +#define atoi64 atoll +#endif + +/* + Problem: + BYTE values are specified as signed decimal string. A properly functional + atoip() will cap large signed values at 0x7FFFFFFF. Negative word values are + often specified as very large decimal values by lcc. Therefore, values that + should be between 0x7FFFFFFF and 0xFFFFFFFF come out as 0x7FFFFFFF when using + atoi(). Bad. + + This function is one big evil hack to work around this problem. +*/ +static int atoiNoCap (const char *s) +{ + INT64 l; + union { + unsigned int u; + signed int i; + } retval; + + l = atoi64(s); + /* Now smash to signed 32 bits accordingly. */ + if (l < 0) { + retval.i = (int)l; + } else { + retval.u = (unsigned int)l; + } + return retval.i; /* <- union hackage. I feel dirty with this. -PH */ +} + + + +/* +============= +HashString +============= +*/ +/* Default hash function of Kazlib 1.19, slightly modified. */ +static unsigned int HashString (const char *key) +{ + static unsigned long randbox[] = { + 0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U, + 0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU, + 0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU, + 0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU, + }; + + const char *str = key; + unsigned int acc = 0; + + while (*str) { + acc ^= randbox[(*str + acc) & 0xf]; + acc = (acc << 1) | (acc >> 31); + acc &= 0xffffffffU; + acc ^= randbox[((*str++ >> 4) + acc) & 0xf]; + acc = (acc << 2) | (acc >> 30); + acc &= 0xffffffffU; + } + return abs(acc); +} + + +/* +============ +CodeError +============ +*/ +static void CodeError( char *fmt, ... ) { + va_list argptr; + + errorCount++; + + fprintf( stderr, "%s:%i ", currentFileName, currentFileLine ); + + va_start( argptr,fmt ); + vfprintf( stderr, fmt, argptr ); + va_end( argptr ); +} + +/* +============ +EmitByte +============ +*/ +static void EmitByte( segment_t *seg, int v ) { + if ( seg->imageUsed >= MAX_IMAGE ) { + Error( "MAX_IMAGE" ); + } + seg->image[ seg->imageUsed ] = v; + seg->imageUsed++; +} + +/* +============ +EmitInt +============ +*/ +static void EmitInt( segment_t *seg, int v ) { + if ( seg->imageUsed >= MAX_IMAGE - 4) { + Error( "MAX_IMAGE" ); + } + seg->image[ seg->imageUsed ] = v & 255; + seg->image[ seg->imageUsed + 1 ] = ( v >> 8 ) & 255; + seg->image[ seg->imageUsed + 2 ] = ( v >> 16 ) & 255; + seg->image[ seg->imageUsed + 3 ] = ( v >> 24 ) & 255; + seg->imageUsed += 4; +} + +/* +============ +DefineSymbol + +Symbols can only be defined on pass 0 +============ +*/ +static void DefineSymbol( char *sym, int value ) { + /* Hand optimization by PhaethonH */ + symbol_t *s; + char expanded[MAX_LINE_LENGTH]; + int hash; + + if ( passNumber == 1 ) { + return; + } + + // add the file prefix to local symbols to guarantee unique + if ( sym[0] == '$' ) { + sprintf( expanded, "%s_%i", sym, currentFileIndex ); + sym = expanded; + } + + hash = HashString( sym ); + + if (hashtable_symbol_exists(symtable, hash, sym)) { + CodeError( "Multiple definitions for %s\n", sym ); + return; + } + + s = malloc( sizeof( *s ) ); + s->next = NULL; + s->name = copystring( sym ); + s->hash = hash; + s->value = value; + s->segment = currentSegment; + + hashtable_add(symtable, hash, s); + +/* + Hash table lookup already speeds up symbol lookup enormously. + We postpone sorting until end of pass 0. + Since we're not doing the insertion sort, lastSymbol should always + wind up pointing to the end of list. + This allows constant time for adding to the list. + -PH +*/ + if (symbols == 0) { + lastSymbol = symbols = s; + } else { + lastSymbol->next = s; + lastSymbol = s; + } +} + + +/* +============ +LookupSymbol + +Symbols can only be evaluated on pass 1 +============ +*/ +static int LookupSymbol( char *sym ) { + symbol_t *s; + char expanded[MAX_LINE_LENGTH]; + int hash; + hashchain_t *hc; + + if ( passNumber == 0 ) { + return 0; + } + + // add the file prefix to local symbols to guarantee unique + if ( sym[0] == '$' ) { + sprintf( expanded, "%s_%i", sym, currentFileIndex ); + sym = expanded; + } + + hash = HashString( sym ); + +/* + Hand optimization by PhaethonH + + Using a hash table with chain/bucket for lookups alone sped up q3asm by almost 3x for me. + -PH +*/ + for (hc = hashtable_get(symtable, hash); hc; hc = hc->next) { + s = (symbol_t*)hc->data; /* ugly typecasting, but it's fast! */ + if ( (hash == s->hash) && !strcmp(sym, s->name) ) { + return s->segment->segmentBase + s->value; + } + } + + CodeError( "error: symbol %s undefined\n", sym ); + passNumber = 0; + DefineSymbol( sym, 0 ); // so more errors aren't printed + passNumber = 1; + return 0; +} + + +/* +============== +ExtractLine + +Extracts the next line from the given text block. +If a full line isn't parsed, returns NULL +Otherwise returns the updated parse pointer +=============== +*/ +static char *ExtractLine( char *data ) { +/* Goal: + Given a string `data', extract one text line into buffer `lineBuffer' that + is no longer than MAX_LINE_LENGTH characters long. Return value is + remainder of `data' that isn't part of `lineBuffer'. + -PH +*/ + /* Hand-optimized by PhaethonH */ + char *p, *q; + + currentFileLine++; + + lineParseOffset = 0; + token[0] = 0; + *lineBuffer = 0; + + p = q = data; + if (!*q) { + return NULL; + } + + for ( ; !((*p == 0) || (*p == '\n')); p++) /* nop */ ; + + if ((p - q) >= MAX_LINE_LENGTH) { + CodeError( "MAX_LINE_LENGTH" ); + return data; + } + + memcpy( lineBuffer, data, (p - data) ); + lineBuffer[(p - data)] = 0; + p += (*p == '\n') ? 1 : 0; /* Skip over final newline. */ + return p; +} + + +/* +============== +Parse + +Parse a token out of linebuffer +============== +*/ +static qboolean Parse( void ) { + /* Hand-optimized by PhaethonH */ + const char *p, *q; + + /* Because lineParseOffset is only updated just before exit, this makes this code version somewhat harder to debug under a symbolic debugger. */ + + *token = 0; /* Clear token. */ + + // skip whitespace + for (p = lineBuffer + lineParseOffset; *p && (*p <= ' '); p++) /* nop */ ; + + // skip ; comments + /* die on end-of-string */ + if ((*p == ';') || (*p == 0)) { + lineParseOffset = p - lineBuffer; + return qfalse; + } + + q = p; /* Mark the start of token. */ + /* Find separator first. */ + for ( ; *p > 32; p++) /* nop */ ; /* XXX: unsafe assumptions. */ + /* *p now sits on separator. Mangle other values accordingly. */ + strncpy(token, q, p - q); + token[p - q] = 0; + + lineParseOffset = p - lineBuffer; + + return qtrue; +} + + +/* +============== +ParseValue +============== +*/ +static int ParseValue( void ) { + Parse(); + return atoiNoCap( token ); +} + + +/* +============== +ParseExpression +============== +*/ +static int ParseExpression(void) { + /* Hand optimization, PhaethonH */ + int i, j; + char sym[MAX_LINE_LENGTH]; + int v; + + /* Skip over a leading minus. */ + for ( i = ((token[0] == '-') ? 1 : 0) ; i < MAX_LINE_LENGTH ; i++ ) { + if ( token[i] == '+' || token[i] == '-' || token[i] == 0 ) { + break; + } + } + + memcpy( sym, token, i ); + sym[i] = 0; + + switch (*sym) { /* Resolve depending on first character. */ +/* Optimizing compilers can convert cases into "calculated jumps". I think these are faster. -PH */ + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + v = atoiNoCap(sym); + break; + default: + v = LookupSymbol(sym); + break; + } + + // parse add / subtract offsets + while ( token[i] != 0 ) { + for ( j = i + 1 ; j < MAX_LINE_LENGTH ; j++ ) { + if ( token[j] == '+' || token[j] == '-' || token[j] == 0 ) { + break; + } + } + + memcpy( sym, token+i+1, j-i-1 ); + sym[j-i-1] = 0; + + switch (token[i]) { + case '+': + v += atoiNoCap(sym); + break; + case '-': + v -= atoiNoCap(sym); + break; + } + + i = j; + } + + return v; +} + + +/* +============== +HackToSegment + +BIG HACK: I want to put all 32 bit values in the data +segment so they can be byte swapped, and all char data in the lit +segment, but switch jump tables are emited in the lit segment and +initialized strng variables are put in the data segment. + +I can change segments here, but I also need to fixup the +label that was just defined + +Note that the lit segment is read-write in the VM, so strings +aren't read only as in some architectures. +============== +*/ +static void HackToSegment( segmentName_t seg ) { + if ( currentSegment == &segment[seg] ) { + return; + } + + currentSegment = &segment[seg]; + if ( passNumber == 0 ) { + lastSymbol->segment = currentSegment; + lastSymbol->value = currentSegment->imageUsed; + } +} + + + + + + + +//#define STAT(L) report("STAT " L "\n"); +#define STAT(L) +#define ASM(O) int TryAssemble##O () + + +/* + These clauses were moved out from AssembleLine() to allow reordering of if's. + An optimizing compiler should reconstruct these back into inline code. + -PH +*/ + + // call instructions reset currentArgOffset +ASM(CALL) +{ + if ( !strncmp( token, "CALL", 4 ) ) { +STAT("CALL"); + EmitByte( &segment[CODESEG], OP_CALL ); + instructionCount++; + currentArgOffset = 0; + return 1; + } + return 0; +} + + // arg is converted to a reversed store +ASM(ARG) +{ + if ( !strncmp( token, "ARG", 3 ) ) { +STAT("ARG"); + EmitByte( &segment[CODESEG], OP_ARG ); + instructionCount++; + if ( 8 + currentArgOffset >= 256 ) { + CodeError( "currentArgOffset >= 256" ); + return 1; + } + EmitByte( &segment[CODESEG], 8 + currentArgOffset ); + currentArgOffset += 4; + return 1; + } + return 0; +} + + // ret just leaves something on the op stack +ASM(RET) +{ + if ( !strncmp( token, "RET", 3 ) ) { +STAT("RET"); + EmitByte( &segment[CODESEG], OP_LEAVE ); + instructionCount++; + EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); + return 1; + } + return 0; +} + + // pop is needed to discard the return value of + // a function +ASM(POP) +{ + if ( !strncmp( token, "pop", 3 ) ) { +STAT("POP"); + EmitByte( &segment[CODESEG], OP_POP ); + instructionCount++; + return 1; + } + return 0; +} + + // address of a parameter is converted to OP_LOCAL +ASM(ADDRF) +{ + int v; + if ( !strncmp( token, "ADDRF", 5 ) ) { +STAT("ADDRF"); + instructionCount++; + Parse(); + v = ParseExpression(); + v = 16 + currentArgs + currentLocals + v; + EmitByte( &segment[CODESEG], OP_LOCAL ); + EmitInt( &segment[CODESEG], v ); + return 1; + } + return 0; +} + + // address of a local is converted to OP_LOCAL +ASM(ADDRL) +{ + int v; + if ( !strncmp( token, "ADDRL", 5 ) ) { +STAT("ADDRL"); + instructionCount++; + Parse(); + v = ParseExpression(); + v = 8 + currentArgs + v; + EmitByte( &segment[CODESEG], OP_LOCAL ); + EmitInt( &segment[CODESEG], v ); + return 1; + } + return 0; +} + +ASM(PROC) +{ + char name[1024]; + if ( !strcmp( token, "proc" ) ) { +STAT("PROC"); + Parse(); // function name + strcpy( name, token ); + + DefineSymbol( token, instructionCount ); // segment[CODESEG].imageUsed ); + + currentLocals = ParseValue(); // locals + currentLocals = ( currentLocals + 3 ) & ~3; + currentArgs = ParseValue(); // arg marshalling + currentArgs = ( currentArgs + 3 ) & ~3; + + if ( 8 + currentLocals + currentArgs >= 32767 ) { + CodeError( "Locals > 32k in %s\n", name ); + } + + instructionCount++; + EmitByte( &segment[CODESEG], OP_ENTER ); + EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); + return 1; + } + return 0; +} + + +ASM(ENDPROC) +{ + int v, v2; + if ( !strcmp( token, "endproc" ) ) { +STAT("ENDPROC"); + Parse(); // skip the function name + v = ParseValue(); // locals + v2 = ParseValue(); // arg marshalling + + // all functions must leave something on the opstack + instructionCount++; + EmitByte( &segment[CODESEG], OP_PUSH ); + + instructionCount++; + EmitByte( &segment[CODESEG], OP_LEAVE ); + EmitInt( &segment[CODESEG], 8 + currentLocals + currentArgs ); + + return 1; + } + return 0; +} + + +ASM(ADDRESS) +{ + int v; + if ( !strcmp( token, "address" ) ) { +STAT("ADDRESS"); + Parse(); + v = ParseExpression(); + +/* Addresses are 32 bits wide, and therefore go into data segment. */ + HackToSegment( DATASEG ); + EmitInt( currentSegment, v ); + if( passNumber == 1 && token[ 0 ] == '$' ) // crude test for labels + EmitInt( &segment[ JTRGSEG ], v ); + return 1; + } + return 0; +} + +ASM(EXPORT) +{ + if ( !strcmp( token, "export" ) ) { +STAT("EXPORT"); + return 1; + } + return 0; +} + +ASM(IMPORT) +{ + if ( !strcmp( token, "import" ) ) { +STAT("IMPORT"); + return 1; + } + return 0; +} + +ASM(CODE) +{ + if ( !strcmp( token, "code" ) ) { +STAT("CODE"); + currentSegment = &segment[CODESEG]; + return 1; + } + return 0; +} + +ASM(BSS) +{ + if ( !strcmp( token, "bss" ) ) { +STAT("BSS"); + currentSegment = &segment[BSSSEG]; + return 1; + } + return 0; +} + +ASM(DATA) +{ + if ( !strcmp( token, "data" ) ) { +STAT("DATA"); + currentSegment = &segment[DATASEG]; + return 1; + } + return 0; +} + +ASM(LIT) +{ + if ( !strcmp( token, "lit" ) ) { +STAT("LIT"); + currentSegment = &segment[LITSEG]; + return 1; + } + return 0; +} + +ASM(LINE) +{ + if ( !strcmp( token, "line" ) ) { +STAT("LINE"); + return 1; + } + return 0; +} + +ASM(FILE) +{ + if ( !strcmp( token, "file" ) ) { +STAT("FILE"); + return 1; + } + return 0; +} + +ASM(EQU) +{ + char name[1024]; + if ( !strcmp( token, "equ" ) ) { +STAT("EQU"); + Parse(); + strcpy( name, token ); + Parse(); + DefineSymbol( name, atoiNoCap(token) ); + return 1; + } + return 0; +} + +ASM(ALIGN) +{ + int v; + if ( !strcmp( token, "align" ) ) { +STAT("ALIGN"); + v = ParseValue(); + currentSegment->imageUsed = (currentSegment->imageUsed + v - 1 ) & ~( v - 1 ); + return 1; + } + return 0; +} + +ASM(SKIP) +{ + int v; + if ( !strcmp( token, "skip" ) ) { +STAT("SKIP"); + v = ParseValue(); + currentSegment->imageUsed += v; + return 1; + } + return 0; +} + +ASM(BYTE) +{ + int i, v, v2; + if ( !strcmp( token, "byte" ) ) { +STAT("BYTE"); + v = ParseValue(); + v2 = ParseValue(); + + if ( v == 1 ) { +/* Character (1-byte) values go into lit(eral) segment. */ + HackToSegment( LITSEG ); + } else if ( v == 4 ) { +/* 32-bit (4-byte) values go into data segment. */ + HackToSegment( DATASEG ); + } else if ( v == 2 ) { +/* and 16-bit (2-byte) values will cause q3asm to barf. */ + CodeError( "16 bit initialized data not supported" ); + } + + // emit little endien + for ( i = 0 ; i < v ; i++ ) { + EmitByte( currentSegment, (v2 & 0xFF) ); /* paranoid ANDing -PH */ + v2 >>= 8; + } + return 1; + } + return 0; +} + + // code labels are emited as instruction counts, not byte offsets, + // because the physical size of the code will change with + // different run time compilers and we want to minimize the + // size of the required translation table +ASM(LABEL) +{ + if ( !strncmp( token, "LABEL", 5 ) ) { +STAT("LABEL"); + Parse(); + if ( currentSegment == &segment[CODESEG] ) { + DefineSymbol( token, instructionCount ); + } else { + DefineSymbol( token, currentSegment->imageUsed ); + } + return 1; + } + return 0; +} + + + +/* +============== +AssembleLine + +============== +*/ +static void AssembleLine( void ) { + hashchain_t *hc; + sourceOps_t *op; + int i; + int hash; + + Parse(); + if ( !token[0] ) { + return; + } + + hash = HashString( token ); + +/* + Opcode search using hash table. + Since the opcodes stays mostly fixed, this may benefit even more from a tree. + Always with the tree :) + -PH +*/ + for (hc = hashtable_get(optable, hash); hc; hc = hc->next) { + op = (sourceOps_t*)(hc->data); + i = op - sourceOps; + if ((hash == opcodesHash[i]) && (!strcmp(token, op->name))) { + int opcode; + int expression; + + if ( op->opcode == OP_UNDEF ) { + CodeError( "Undefined opcode: %s\n", token ); + } + if ( op->opcode == OP_IGNORE ) { + return; // we ignore most conversions + } + + // sign extensions need to check next parm + opcode = op->opcode; + if ( opcode == OP_SEX8 ) { + Parse(); + if ( token[0] == '1' ) { + opcode = OP_SEX8; + } else if ( token[0] == '2' ) { + opcode = OP_SEX16; + } else { + CodeError( "Bad sign extension: %s\n", token ); + return; + } + } + + // check for expression + Parse(); + if ( token[0] && op->opcode != OP_CVIF + && op->opcode != OP_CVFI ) { + expression = ParseExpression(); + + // code like this can generate non-dword block copies: + // auto char buf[2] = " "; + // we are just going to round up. This might conceivably + // be incorrect if other initialized chars follow. + if ( opcode == OP_BLOCK_COPY ) { + expression = ( expression + 3 ) & ~3; + } + + EmitByte( &segment[CODESEG], opcode ); + EmitInt( &segment[CODESEG], expression ); + } else { + EmitByte( &segment[CODESEG], opcode ); + } + + instructionCount++; + return; + } + } + +/* This falls through if an assembly opcode is not found. -PH */ + +/* The following should be sorted in sequence of statistical frequency, most frequent first. -PH */ +/* +Empirical frequency statistics from FI 2001.01.23: + 109892 STAT ADDRL + 72188 STAT BYTE + 51150 STAT LINE + 50906 STAT ARG + 43704 STAT IMPORT + 34902 STAT LABEL + 32066 STAT ADDRF + 23704 STAT CALL + 7720 STAT POP + 7256 STAT RET + 5198 STAT ALIGN + 3292 STAT EXPORT + 2878 STAT PROC + 2878 STAT ENDPROC + 2812 STAT ADDRESS + 738 STAT SKIP + 374 STAT EQU + 280 STAT CODE + 176 STAT LIT + 102 STAT FILE + 100 STAT BSS + 68 STAT DATA + + -PH +*/ + +#undef ASM +#define ASM(O) if (TryAssemble##O ()) return; + + ASM(ADDRL) + ASM(BYTE) + ASM(LINE) + ASM(ARG) + ASM(IMPORT) + ASM(LABEL) + ASM(ADDRF) + ASM(CALL) + ASM(POP) + ASM(RET) + ASM(ALIGN) + ASM(EXPORT) + ASM(PROC) + ASM(ENDPROC) + ASM(ADDRESS) + ASM(SKIP) + ASM(EQU) + ASM(CODE) + ASM(LIT) + ASM(FILE) + ASM(BSS) + ASM(DATA) + + CodeError( "Unknown token: %s\n", token ); +} + +/* +============== +InitTables +============== +*/ +void InitTables( void ) { + int i; + + symtable = hashtable_new(symtablelen); + optable = hashtable_new(100); /* There's hardly 100 opcodes anyway. */ + + for ( i = 0 ; i < NUM_SOURCE_OPS ; i++ ) { + opcodesHash[i] = HashString( sourceOps[i].name ); + hashtable_add(optable, opcodesHash[i], sourceOps + i); + } +} + + +/* +============== +WriteMapFile +============== +*/ +static void WriteMapFile( void ) { + FILE *f; + symbol_t *s; + char imageName[MAX_OS_PATH]; + int seg; + + strcpy( imageName, outputFilename ); + StripExtension( imageName ); + strcat( imageName, ".map" ); + + report( "Writing %s...\n", imageName ); + + f = SafeOpenWrite( imageName ); + for ( seg = CODESEG ; seg <= BSSSEG ; seg++ ) { + for ( s = symbols ; s ; s = s->next ) { + if ( s->name[0] == '$' ) { + continue; // skip locals + } + if ( &segment[seg] != s->segment ) { + continue; + } + fprintf( f, "%i %8x %s\n", seg, s->value, s->name ); + } + } + fclose( f ); +} + +/* +=============== +WriteVmFile +=============== +*/ +static void WriteVmFile( void ) { + char imageName[MAX_OS_PATH]; + vmHeader_t header; + FILE *f; + int headerSize; + + report( "%i total errors\n", errorCount ); + + strcpy( imageName, outputFilename ); + StripExtension( imageName ); + strcat( imageName, ".qvm" ); + + remove( imageName ); + + report( "code segment: %7i\n", segment[CODESEG].imageUsed ); + report( "data segment: %7i\n", segment[DATASEG].imageUsed ); + report( "lit segment: %7i\n", segment[LITSEG].imageUsed ); + report( "bss segment: %7i\n", segment[BSSSEG].imageUsed ); + report( "instruction count: %i\n", instructionCount ); + + if ( errorCount != 0 ) { + report( "Not writing a file due to errors\n" ); + return; + } + + if( !options.vanillaQ3Compatibility ) { + header.vmMagic = VM_MAGIC_VER2; + headerSize = sizeof( header ); + } else { + header.vmMagic = VM_MAGIC; + + // Don't write the VM_MAGIC_VER2 bits when maintaining 1.32b compatibility. + // (I know this isn't strictly correct due to padding, but then platforms + // that pad wouldn't be able to write a correct header anyway). Note: if + // vmHeader_t changes, this needs to be adjusted too. + headerSize = sizeof( header ) - sizeof( header.jtrgLength ); + } + + header.instructionCount = instructionCount; + header.codeOffset = headerSize; + header.codeLength = segment[CODESEG].imageUsed; + header.dataOffset = header.codeOffset + segment[CODESEG].imageUsed; + header.dataLength = segment[DATASEG].imageUsed; + header.litLength = segment[LITSEG].imageUsed; + header.bssLength = segment[BSSSEG].imageUsed; + header.jtrgLength = segment[JTRGSEG].imageUsed; + + report( "Writing to %s\n", imageName ); + +#ifdef Q3_BIG_ENDIAN + { + int i; + + // byte swap the header + for ( i = 0 ; i < sizeof( vmHeader_t ) / 4 ; i++ ) { + ((int *)&header)[i] = LittleLong( ((int *)&header)[i] ); + } + } +#endif + + CreatePath( imageName ); + f = SafeOpenWrite( imageName ); + SafeWrite( f, &header, headerSize ); + SafeWrite( f, &segment[CODESEG].image, segment[CODESEG].imageUsed ); + SafeWrite( f, &segment[DATASEG].image, segment[DATASEG].imageUsed ); + SafeWrite( f, &segment[LITSEG].image, segment[LITSEG].imageUsed ); + + if( !options.vanillaQ3Compatibility ) { + SafeWrite( f, &segment[JTRGSEG].image, segment[JTRGSEG].imageUsed ); + } + + fclose( f ); +} + +/* +=============== +Assemble +=============== +*/ +static void Assemble( void ) { + int i; + char filename[MAX_OS_PATH]; + char *ptr; + + report( "outputFilename: %s\n", outputFilename ); + + for ( i = 0 ; i < numAsmFiles ; i++ ) { + strcpy( filename, asmFileNames[ i ] ); + DefaultExtension( filename, ".asm" ); + LoadFile( filename, (void **)&asmFiles[i] ); + } + + // assemble + for ( passNumber = 0 ; passNumber < 2 ; passNumber++ ) { + segment[LITSEG].segmentBase = segment[DATASEG].imageUsed; + segment[BSSSEG].segmentBase = segment[LITSEG].segmentBase + segment[LITSEG].imageUsed; + segment[JTRGSEG].segmentBase = segment[BSSSEG].segmentBase + segment[BSSSEG].imageUsed; + for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) { + segment[i].imageUsed = 0; + } + segment[DATASEG].imageUsed = 4; // skip the 0 byte, so NULL pointers are fixed up properly + instructionCount = 0; + + for ( i = 0 ; i < numAsmFiles ; i++ ) { + currentFileIndex = i; + currentFileName = asmFileNames[ i ]; + currentFileLine = 0; + report("pass %i: %s\n", passNumber, currentFileName ); + fflush( NULL ); + ptr = asmFiles[i]; + while ( ptr ) { + ptr = ExtractLine( ptr ); + AssembleLine(); + } + } + + // align all segment + for ( i = 0 ; i < NUM_SEGMENTS ; i++ ) { + segment[i].imageUsed = (segment[i].imageUsed + 3) & ~3; + } + if (passNumber == 0) { + sort_symbols(); + } + } + + // reserve the stack in bss + DefineSymbol( "_stackStart", segment[BSSSEG].imageUsed ); + segment[BSSSEG].imageUsed += stackSize; + DefineSymbol( "_stackEnd", segment[BSSSEG].imageUsed ); + + // write the image + WriteVmFile(); + + // write the map file even if there were errors + if( options.writeMapFile ) { + WriteMapFile(); + } +} + + +/* +============= +ParseOptionFile + +============= +*/ +static void ParseOptionFile( const char *filename ) { + char expanded[MAX_OS_PATH]; + char *text, *text_p; + + strcpy( expanded, filename ); + DefaultExtension( expanded, ".q3asm" ); + LoadFile( expanded, (void **)&text ); + if ( !text ) { + return; + } + + text_p = text; + + while( ( text_p = COM_Parse( text_p ) ) != 0 ) { + if ( !strcmp( com_token, "-o" ) ) { + // allow output override in option file + text_p = COM_Parse( text_p ); + if ( text_p ) { + strcpy( outputFilename, com_token ); + } + continue; + } + + asmFileNames[ numAsmFiles ] = copystring( com_token ); + numAsmFiles++; + } +} + +static void ShowHelp( char *argv0 ) { + Error("Usage: %s [OPTION]... [FILES]...\n\ +Assemble LCC bytecode assembly to Q3VM bytecode.\n\ +\n\ + -o OUTPUT Write assembled output to file OUTPUT.qvm\n\ + -f LISTFILE Read options and list of files to assemble from LISTFILE.q3asm\n\ + -b BUCKETS Set symbol hash table to BUCKETS buckets\n\ + -m Generate a mapfile for each OUTPUT.qvm\n\ + -v Verbose compilation report\n\ + -vq3 Produce a qvm file compatible with Q3 1.32b\n\ + -h --help -? Show this help\n\ +", argv0); +} + +/* +============== +main +============== +*/ +int main( int argc, char **argv ) { + int i; + double start, end; + +// _chdir( "/quake3/jccode/cgame/lccout" ); // hack for vc profiler + + if ( argc < 2 ) { + ShowHelp( argv[0] ); + } + + start = I_FloatTime (); + + // default filename is "q3asm" + strcpy( outputFilename, "q3asm" ); + numAsmFiles = 0; + + for ( i = 1 ; i < argc ; i++ ) { + if ( argv[i][0] != '-' ) { + break; + } + if( !strcmp( argv[ i ], "-h" ) || + !strcmp( argv[ i ], "--help" ) || + !strcmp( argv[ i ], "-?") ) { + ShowHelp( argv[0] ); + } + + if ( !strcmp( argv[i], "-o" ) ) { + if ( i == argc - 1 ) { + Error( "-o must preceed a filename" ); + } +/* Timbo of Tremulous pointed out -o not working; stock ID q3asm folded in the change. Yay. */ + strcpy( outputFilename, argv[ i+1 ] ); + i++; + continue; + } + + if ( !strcmp( argv[i], "-f" ) ) { + if ( i == argc - 1 ) { + Error( "-f must preceed a filename" ); + } + ParseOptionFile( argv[ i+1 ] ); + i++; + continue; + } + + if (!strcmp(argv[i], "-b")) { + if (i == argc - 1) { + Error("-b requires an argument"); + } + i++; + symtablelen = atoiNoCap(argv[i]); + continue; + } + + if( !strcmp( argv[ i ], "-v" ) ) { +/* Verbosity option added by Timbo, 2002.09.14. +By default (no -v option), q3asm remains silent except for critical errors. +Verbosity turns on all messages, error or not. +Motivation: not wanting to scrollback for pages to find asm error. +*/ + options.verbose = qtrue; + continue; + } + + if( !strcmp( argv[ i ], "-m" ) ) { + options.writeMapFile = qtrue; + continue; + } + + if( !strcmp( argv[ i ], "-vq3" ) ) { + options.vanillaQ3Compatibility = qtrue; + continue; + } + + Error( "Unknown option: %s", argv[i] ); + } + + // the rest of the command line args are asm files + for ( ; i < argc ; i++ ) { + asmFileNames[ numAsmFiles ] = copystring( argv[ i ] ); + numAsmFiles++; + } + // In some case it Segfault without this check + if ( numAsmFiles == 0 ) { + Error( "No file to assemble\n" ); + } + + InitTables(); + Assemble(); + + { + symbol_t *s; + + for ( i = 0, s = symbols ; s ; s = s->next, i++ ) /* nop */ ; + + if (options.verbose) + { + report("%d symbols defined\n", i); + hashtable_stats(symtable); + hashtable_stats(optable); + } + } + + end = I_FloatTime (); + report ("%5.0f seconds elapsed\n", end-start); + + return errorCount; +} + diff --git a/src/tools/lcc/COPYRIGHT b/src/tools/lcc/COPYRIGHT new file mode 100644 index 0000000..961a48f --- /dev/null +++ b/src/tools/lcc/COPYRIGHT @@ -0,0 +1,61 @@ +The authors of this software are Christopher W. Fraser and +David R. Hanson. + +Copyright (c) 1991,1992,1993,1994,1995,1996,1997,1998 by AT&T, +Christopher W. Fraser, and David R. Hanson. All Rights Reserved. + +Permission to use, copy, modify, and distribute this software for any +purpose, subject to the provisions described below, without fee is +hereby granted, provided that this entire notice is included in all +copies of any software that is or includes a copy or modification of +this software and in all copies of the supporting documentation for +such software. + +THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY +REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + + +lcc is not public-domain software, shareware, and it is not protected +by a `copyleft' agreement, like the code from the Free Software +Foundation. + +lcc is available free for your personal research and instructional use +under the `fair use' provisions of the copyright law. You may, however, +redistribute lcc in whole or in part provided you acknowledge its +source and include this CPYRIGHT file. You may, for example, include +the distribution in a CDROM of free software, provided you charge only +for the media, or mirror the distribution files at your site. + +You may not sell lcc or any product derived from it in which it is a +significant part of the value of the product. Using the lcc front end +to build a C syntax checker is an example of this kind of product. + +You may use parts of lcc in products as long as you charge for only +those components that are entirely your own and you acknowledge the use +of lcc clearly in all product documentation and distribution media. You +must state clearly that your product uses or is based on parts of lcc +and that lcc is available free of charge. You must also request that +bug reports on your product be reported to you. Using the lcc front +end to build a C compiler for the Motorola 88000 chip and charging for +and distributing only the 88000 code generator is an example of this +kind of product. + +Using parts of lcc in other products is more problematic. For example, +using parts of lcc in a C++ compiler could save substantial time and +effort and therefore contribute significantly to the profitability of +the product. This kind of use, or any use where others stand to make a +profit from what is primarily our work, requires a license agreement +with Addison-Wesley. Per-copy and unlimited use licenses are +available; for more information, contact + + J. Carter Shanklin + Addison Wesley Longman, Inc. + 2725 Sand Hill Rd. + Menlo Park, CA 94025 + 650/854-0300 x2478 FAX: 650/614-2930 jcs@awl.com +----- +Chris Fraser / cwfraser@microsoft.com +David Hanson / drh@microsoft.com +$Revision: 145 $ $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $ diff --git a/src/tools/lcc/LOG b/src/tools/lcc/LOG new file mode 100644 index 0000000..dd23f62 --- /dev/null +++ b/src/tools/lcc/LOG @@ -0,0 +1,91 @@ +From lcc 4.0 to 4.1: + +Changes: + +See doc/4.html for changes in the code-generation interface. + +Warns about constants that are too large, eg, short x = 70000; + +Warns about expressions that have no effect. + +Unsigned shorts are now used for wide-character constants, and +wchar_t is a typedef for unsigned short. + +More assertions in gen.c to confirm that the register allocator is +configured correctly; ie, that the various masks, wildcards, +clobbers, and targets are internally consistent. Full checking +appears impractical, but there's still more than than there was +before. + +On the SPARC, lcc now emits .type and .size directives +unconditionally. + +On the x86, constants are now emitted into the text segment. + +If the environment variable "LCCDIR" is defined, it gives the directory +that contains the preprocessor, the compiler proper, and the +lcc-specific libraries. + +Under Windows, lcc searches the directories named in the environment +variable "include" for header files. + +Errors fixed: + +Erroneously complained about unknown sizes for some const fields, eg, +typedef struct foo ref; struct foo { const ref *q; int a; }; +f(ref *p, int i) { return p->q[i].a; } + +-A -A erroneously complained about static main's that didn't conform +to the ANSI-mandated "int main(void)" or "int main(int, char **)". + +Silently generated incorrect code for a structure copy with a +post-incremented target, eg, +struct { int x; } data = {1}, copy[2], *q = copy; +main() { *q++ = data; } + +Generated incorrect values in some expressions with constant pointers. + +Silently truncated string literals longer than 4095 characters. + +Failed to emit debugging information for uninitialized globals. + +Failed to diagnose missing sizes in some multi-dimensioned array +declarators, eg, extern int x[][10]; int x[5][]; + +Silently emitted incorrect sizes and initalizations for some +incomplete multi-dimensioned arrays involving pointers and whose size +is determined by the number of initializers. + +Set only the x.name field for some back-end symbols (eg, wildcards), +and the uninitialized name field crashed some debugging output. + +uses() failed to check the register *set* as well as the register +mask. There's no known bug demo, but a wildcard set might be +contrived that would need the test. + +Crashed with -b on some conditional expressions involving calls, eg, +int p; void g(void) { p ? f() : 1; } + +On the MIPS, sometimes generated an incorrect frame size and thus a +crash when floating-point registers were saved. + +On the SPARC, erroneously reused a register variable as a temporary +when the variable is compiler-generated. + +On the SPARC with -b, emitted incorrect code for returning structs. + +On the x86, conversion from float to int rounded instead of truncated +with the default floating-point mode. + +On the x86, eliminate rtargets for kids after the first (see p. 419). + +On the x86, substitute reg for freg, in order to use the common reg +rules. Needed only for debugging output, since we're not using any +float regs as regs at this time. + +On the x86, "double f(); main(){f();}" wasn't popping the FP register stack. + +On the x86, ECX was saved by the callee, when it should have been +saved by the caller. + +$Id: LOG 145 2001-10-17 21:53:10Z timo $ diff --git a/src/tools/lcc/README b/src/tools/lcc/README new file mode 100644 index 0000000..4ba4d3f --- /dev/null +++ b/src/tools/lcc/README @@ -0,0 +1,21 @@ +This hierarchy is the distribution for lcc version 4.1. + +lcc version 3.x is described in the book "A Retargetable C Compiler: +Design and Implementation" (Addison-Wesley, 1995, ISBN 0-8053-1670-1). +There are significant differences between 3.x and 4.x, most notably in +the intermediate code. doc/4.html summarizes the differences. + +VERSION 4.1 IS INCOMPATIBLE WITH EARLIER VERSIONS OF LCC. DO NOT +UNLOAD THIS DISTRIBUTION ON TOP OF A 3.X DISTRIBUTION. + +LOG describes the changes since the last release. + +CPYRIGHT describes the conditions under you can use, copy, modify, and +distribute lcc or works derived from lcc. + +doc/install.html is an HTML file that gives a complete description of +the distribution and installation instructions. + +Chris Fraser / cwfraser@microsoft.com +David Hanson / drh@microsoft.com +$Revision: 145 $ $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $ diff --git a/src/tools/lcc/README.id b/src/tools/lcc/README.id new file mode 100644 index 0000000..6611a37 --- /dev/null +++ b/src/tools/lcc/README.id @@ -0,0 +1,3 @@ +2001-10-31 Timothee Besset +updated from the $/source/lcc code +modified for portability and use with >= 1.31 mod source release diff --git a/src/tools/lcc/cpp/cpp.c b/src/tools/lcc/cpp/cpp.c new file mode 100644 index 0000000..1fcffbc --- /dev/null +++ b/src/tools/lcc/cpp/cpp.c @@ -0,0 +1,327 @@ +#include +#include +#include +#include +#include +#include "cpp.h" + +char rcsid[] = "cpp.c - faked rcsid"; + +#define OUTS 16384 +char outbuf[OUTS]; +char *outp = outbuf; +Source *cursource; +int nerrs; +struct token nltoken = { NL, 0, 0, 0, 1, (uchar*)"\n" }; +char *curtime; +int incdepth; +int ifdepth; +int ifsatisfied[NIF]; +int skipping; + + +int +main(int argc, char **argv) +{ + Tokenrow tr; + time_t t; + char ebuf[BUFSIZ]; + + setbuf(stderr, ebuf); + t = time(NULL); + curtime = ctime(&t); + maketokenrow(3, &tr); + expandlex(); + setup(argc, argv); + fixlex(); + iniths(); + genline(); + process(&tr); + flushout(); + fflush(stderr); + exit(nerrs > 0); + return 0; +} + +void +process(Tokenrow *trp) +{ + int anymacros = 0; + + for (;;) { + if (trp->tp >= trp->lp) { + trp->tp = trp->lp = trp->bp; + outp = outbuf; + anymacros |= gettokens(trp, 1); + trp->tp = trp->bp; + } + if (trp->tp->type == END) { + if (--incdepth>=0) { + if (cursource->ifdepth) + error(ERROR, + "Unterminated conditional in #include"); + unsetsource(); + cursource->line += cursource->lineinc; + trp->tp = trp->lp; + genline(); + continue; + } + if (ifdepth) + error(ERROR, "Unterminated #if/#ifdef/#ifndef"); + break; + } + if (trp->tp->type==SHARP) { + trp->tp += 1; + control(trp); + } else if (!skipping && anymacros) + expandrow(trp, NULL); + if (skipping) + setempty(trp); + puttokens(trp); + anymacros = 0; + cursource->line += cursource->lineinc; + if (cursource->lineinc>1) { + genline(); + } + } +} + +void +control(Tokenrow *trp) +{ + Nlist *np; + Token *tp; + + tp = trp->tp; + if (tp->type!=NAME) { + if (tp->type==NUMBER) + goto kline; + if (tp->type != NL) + error(ERROR, "Unidentifiable control line"); + return; /* else empty line */ + } + if ((np = lookup(tp, 0))==NULL || ((np->flag&ISKW)==0 && !skipping)) { + error(WARNING, "Unknown preprocessor control %t", tp); + return; + } + if (skipping) { + switch (np->val) { + case KENDIF: + if (--ifdepthifdepth; + setempty(trp); + return; + + case KIFDEF: + case KIFNDEF: + case KIF: + if (++ifdepth >= NIF) + error(FATAL, "#if too deeply nested"); + ++cursource->ifdepth; + return; + + case KELIF: + case KELSE: + if (ifdepth<=skipping) + break; + return; + + default: + return; + } + } + switch (np->val) { + case KDEFINE: + dodefine(trp); + break; + + case KUNDEF: + tp += 1; + if (tp->type!=NAME || trp->lp - trp->bp != 4) { + error(ERROR, "Syntax error in #undef"); + break; + } + if ((np = lookup(tp, 0)) != NULL) + np->flag &= ~ISDEFINED; + break; + + case KPRAGMA: + return; + + case KIFDEF: + case KIFNDEF: + case KIF: + if (++ifdepth >= NIF) + error(FATAL, "#if too deeply nested"); + ++cursource->ifdepth; + ifsatisfied[ifdepth] = 0; + if (eval(trp, np->val)) + ifsatisfied[ifdepth] = 1; + else + skipping = ifdepth; + break; + + case KELIF: + if (ifdepth==0) { + error(ERROR, "#elif with no #if"); + return; + } + if (ifsatisfied[ifdepth]==2) + error(ERROR, "#elif after #else"); + if (eval(trp, np->val)) { + if (ifsatisfied[ifdepth]) + skipping = ifdepth; + else { + skipping = 0; + ifsatisfied[ifdepth] = 1; + } + } else + skipping = ifdepth; + break; + + case KELSE: + if (ifdepth==0 || cursource->ifdepth==0) { + error(ERROR, "#else with no #if"); + return; + } + if (ifsatisfied[ifdepth]==2) + error(ERROR, "#else after #else"); + if (trp->lp - trp->bp != 3) + error(ERROR, "Syntax error in #else"); + skipping = ifsatisfied[ifdepth]? ifdepth: 0; + ifsatisfied[ifdepth] = 2; + break; + + case KENDIF: + if (ifdepth==0 || cursource->ifdepth==0) { + error(ERROR, "#endif with no #if"); + return; + } + --ifdepth; + --cursource->ifdepth; + if (trp->lp - trp->bp != 3) + error(WARNING, "Syntax error in #endif"); + break; + + case KWARNING: + trp->tp = tp+1; + error(WARNING, "#warning directive: %r", trp); + break; + + case KERROR: + trp->tp = tp+1; + error(ERROR, "#error directive: %r", trp); + break; + + case KLINE: + trp->tp = tp+1; + expandrow(trp, ""); + tp = trp->bp+2; + kline: + if (tp+1>=trp->lp || tp->type!=NUMBER || tp+3lp + || ((tp+3==trp->lp && ((tp+1)->type!=STRING))||*(tp+1)->t=='L')){ + error(ERROR, "Syntax error in #line"); + return; + } + cursource->line = atol((char*)tp->t)-1; + if (cursource->line<0 || cursource->line>=32768) + error(WARNING, "#line specifies number out of range"); + tp = tp+1; + if (tp+1lp) + cursource->filename=(char*)newstring(tp->t+1,tp->len-2,0); + return; + + case KDEFINED: + error(ERROR, "Bad syntax for control line"); + break; + + case KINCLUDE: + doinclude(trp); + trp->lp = trp->bp; + return; + + case KEVAL: + eval(trp, np->val); + break; + + default: + error(ERROR, "Preprocessor control `%t' not yet implemented", tp); + break; + } + setempty(trp); + return; +} + +void * +domalloc(int size) +{ + void *p = malloc(size); + + if (p==NULL) + error(FATAL, "Out of memory from malloc"); + return p; +} + +void +dofree(void *p) +{ + free(p); +} + +void +error(enum errtype type, char *string, ...) +{ + va_list ap; + char *cp, *ep; + Token *tp; + Tokenrow *trp; + Source *s; + int i; + + fprintf(stderr, "cpp: "); + for (s=cursource; s; s=s->next) + if (*s->filename) + fprintf(stderr, "%s:%d ", s->filename, s->line); + va_start(ap, string); + for (ep=string; *ep; ep++) { + if (*ep=='%') { + switch (*++ep) { + + case 's': + cp = va_arg(ap, char *); + fprintf(stderr, "%s", cp); + break; + case 'd': + i = va_arg(ap, int); + fprintf(stderr, "%d", i); + break; + case 't': + tp = va_arg(ap, Token *); + fprintf(stderr, "%.*s", tp->len, tp->t); + break; + + case 'r': + trp = va_arg(ap, Tokenrow *); + for (tp=trp->tp; tplp&&tp->type!=NL; tp++) { + if (tp>trp->tp && tp->wslen) + fputc(' ', stderr); + fprintf(stderr, "%.*s", tp->len, tp->t); + } + break; + + default: + fputc(*ep, stderr); + break; + } + } else + fputc(*ep, stderr); + } + va_end(ap); + fputc('\n', stderr); + if (type==FATAL) + exit(1); + if (type!=WARNING) + nerrs = 1; + fflush(stderr); +} diff --git a/src/tools/lcc/cpp/cpp.h b/src/tools/lcc/cpp/cpp.h new file mode 100644 index 0000000..87871d9 --- /dev/null +++ b/src/tools/lcc/cpp/cpp.h @@ -0,0 +1,166 @@ +#define INS 32768 /* input buffer */ +#define OBS 4096 /* outbut buffer */ +#define NARG 32 /* Max number arguments to a macro */ +#define NINCLUDE 32 /* Max number of include directories (-I) */ +#define NIF 32 /* depth of nesting of #if */ +#ifndef EOF +#define EOF (-1) +#endif +#ifndef NULL +#define NULL 0 +#endif + +#ifndef __alpha +typedef unsigned char uchar; +#endif + +enum toktype { END, UNCLASS, NAME, NUMBER, STRING, CCON, NL, WS, DSHARP, + EQ, NEQ, LEQ, GEQ, LSH, RSH, LAND, LOR, PPLUS, MMINUS, + ARROW, SBRA, SKET, LP, RP, DOT, AND, STAR, PLUS, MINUS, + TILDE, NOT, SLASH, PCT, LT, GT, CIRC, OR, QUEST, + COLON, ASGN, COMMA, SHARP, SEMIC, CBRA, CKET, + ASPLUS, ASMINUS, ASSTAR, ASSLASH, ASPCT, ASCIRC, ASLSH, + ASRSH, ASOR, ASAND, ELLIPS, + DSHARP1, NAME1, DEFINED, UMINUS }; + +enum kwtype { KIF, KIFDEF, KIFNDEF, KELIF, KELSE, KENDIF, KINCLUDE, KDEFINE, + KUNDEF, KLINE, KWARNING, KERROR, KPRAGMA, KDEFINED, + KLINENO, KFILE, KDATE, KTIME, KSTDC, KEVAL }; + +#define ISDEFINED 01 /* has #defined value */ +#define ISKW 02 /* is PP keyword */ +#define ISUNCHANGE 04 /* can't be #defined in PP */ +#define ISMAC 010 /* builtin macro, e.g. __LINE__ */ + +#define EOB 0xFE /* sentinel for end of input buffer */ +#define EOFC 0xFD /* sentinel for end of input file */ +#define XPWS 1 /* token flag: white space to assure token sep. */ + +typedef struct token { + unsigned char type; + unsigned char flag; + unsigned short hideset; + unsigned int wslen; + unsigned int len; + uchar *t; +} Token; + +typedef struct tokenrow { + Token *tp; /* current one to scan */ + Token *bp; /* base (allocated value) */ + Token *lp; /* last+1 token used */ + int max; /* number allocated */ +} Tokenrow; + +typedef struct source { + char *filename; /* name of file of the source */ + int line; /* current line number */ + int lineinc; /* adjustment for \\n lines */ + uchar *inb; /* input buffer */ + uchar *inp; /* input pointer */ + uchar *inl; /* end of input */ + int fd; /* input source */ + int ifdepth; /* conditional nesting in include */ + struct source *next; /* stack for #include */ +} Source; + +typedef struct nlist { + struct nlist *next; + uchar *name; + int len; + Tokenrow *vp; /* value as macro */ + Tokenrow *ap; /* list of argument names, if any */ + char val; /* value as preprocessor name */ + char flag; /* is defined, is pp name */ +} Nlist; + +typedef struct includelist { + char deleted; + char always; + char *file; +} Includelist; + +#define new(t) (t *)domalloc(sizeof(t)) +#define quicklook(a,b) (namebit[(a)&077] & (1<<((b)&037))) +#define quickset(a,b) namebit[(a)&077] |= (1<<((b)&037)) +extern unsigned long namebit[077+1]; + +enum errtype { WARNING, ERROR, FATAL }; + +void expandlex(void); +void fixlex(void); +void setup(int, char **); +int gettokens(Tokenrow *, int); +int comparetokens(Tokenrow *, Tokenrow *); +Source *setsource(char *, int, char *); +void unsetsource(void); +void puttokens(Tokenrow *); +void process(Tokenrow *); +void *domalloc(int); +void dofree(void *); +void error(enum errtype, char *, ...); +void flushout(void); +int fillbuf(Source *); +int trigraph(Source *); +int foldline(Source *); +Nlist *lookup(Token *, int); +void control(Tokenrow *); +void dodefine(Tokenrow *); +void doadefine(Tokenrow *, int); +void doinclude(Tokenrow *); +void appendDirToIncludeList( char *dir ); +void doif(Tokenrow *, enum kwtype); +void expand(Tokenrow *, Nlist *); +void builtin(Tokenrow *, int); +int gatherargs(Tokenrow *, Tokenrow **, int *); +void substargs(Nlist *, Tokenrow *, Tokenrow **); +void expandrow(Tokenrow *, char *); +void maketokenrow(int, Tokenrow *); +Tokenrow *copytokenrow(Tokenrow *, Tokenrow *); +Token *growtokenrow(Tokenrow *); +Tokenrow *normtokenrow(Tokenrow *); +void adjustrow(Tokenrow *, int); +void movetokenrow(Tokenrow *, Tokenrow *); +void insertrow(Tokenrow *, int, Tokenrow *); +void peektokens(Tokenrow *, char *); +void doconcat(Tokenrow *); +Tokenrow *stringify(Tokenrow *); +int lookuparg(Nlist *, Token *); +long eval(Tokenrow *, int); +void genline(void); +void setempty(Tokenrow *); +void makespace(Tokenrow *); +char *outnum(char *, int); +int digit(int); +uchar *newstring(uchar *, int, int); +int checkhideset(int, Nlist *); +void prhideset(int); +int newhideset(int, Nlist *); +int unionhideset(int, int); +void iniths(void); +void setobjname(char *); +#define rowlen(tokrow) ((tokrow)->lp - (tokrow)->bp) + +char *basepath( char *fname ); + +extern char *outp; +extern Token nltoken; +extern Source *cursource; +extern char *curtime; +extern int incdepth; +extern int ifdepth; +extern int ifsatisfied[NIF]; +extern int Mflag; +extern int skipping; +extern int verbose; +extern int Cplusplus; +extern Nlist *kwdefined; +extern Includelist includelist[NINCLUDE]; +extern char wd[]; + +#ifndef _WIN32 +#include +#else +#include +#endif +#include diff --git a/src/tools/lcc/cpp/eval.c b/src/tools/lcc/cpp/eval.c new file mode 100644 index 0000000..95a9e11 --- /dev/null +++ b/src/tools/lcc/cpp/eval.c @@ -0,0 +1,524 @@ +#include +#include +#include "cpp.h" + +#define NSTAK 32 +#define SGN 0 +#define UNS 1 +#define UND 2 + +#define UNSMARK 0x1000 + +struct value { + long val; + int type; +}; + +/* conversion types */ +#define RELAT 1 +#define ARITH 2 +#define LOGIC 3 +#define SPCL 4 +#define SHIFT 5 +#define UNARY 6 + +/* operator priority, arity, and conversion type, indexed by tokentype */ +struct pri { + char pri; + char arity; + char ctype; +} priority[] = { + { 0, 0, 0 }, /* END */ + { 0, 0, 0 }, /* UNCLASS */ + { 0, 0, 0 }, /* NAME */ + { 0, 0, 0 }, /* NUMBER */ + { 0, 0, 0 }, /* STRING */ + { 0, 0, 0 }, /* CCON */ + { 0, 0, 0 }, /* NL */ + { 0, 0, 0 }, /* WS */ + { 0, 0, 0 }, /* DSHARP */ + { 11, 2, RELAT }, /* EQ */ + { 11, 2, RELAT }, /* NEQ */ + { 12, 2, RELAT }, /* LEQ */ + { 12, 2, RELAT }, /* GEQ */ + { 13, 2, SHIFT }, /* LSH */ + { 13, 2, SHIFT }, /* RSH */ + { 7, 2, LOGIC }, /* LAND */ + { 6, 2, LOGIC }, /* LOR */ + { 0, 0, 0 }, /* PPLUS */ + { 0, 0, 0 }, /* MMINUS */ + { 0, 0, 0 }, /* ARROW */ + { 0, 0, 0 }, /* SBRA */ + { 0, 0, 0 }, /* SKET */ + { 3, 0, 0 }, /* LP */ + { 3, 0, 0 }, /* RP */ + { 0, 0, 0 }, /* DOT */ + { 10, 2, ARITH }, /* AND */ + { 15, 2, ARITH }, /* STAR */ + { 14, 2, ARITH }, /* PLUS */ + { 14, 2, ARITH }, /* MINUS */ + { 16, 1, UNARY }, /* TILDE */ + { 16, 1, UNARY }, /* NOT */ + { 15, 2, ARITH }, /* SLASH */ + { 15, 2, ARITH }, /* PCT */ + { 12, 2, RELAT }, /* LT */ + { 12, 2, RELAT }, /* GT */ + { 9, 2, ARITH }, /* CIRC */ + { 8, 2, ARITH }, /* OR */ + { 5, 2, SPCL }, /* QUEST */ + { 5, 2, SPCL }, /* COLON */ + { 0, 0, 0 }, /* ASGN */ + { 4, 2, 0 }, /* COMMA */ + { 0, 0, 0 }, /* SHARP */ + { 0, 0, 0 }, /* SEMIC */ + { 0, 0, 0 }, /* CBRA */ + { 0, 0, 0 }, /* CKET */ + { 0, 0, 0 }, /* ASPLUS */ + { 0, 0, 0 }, /* ASMINUS */ + { 0, 0, 0 }, /* ASSTAR */ + { 0, 0, 0 }, /* ASSLASH */ + { 0, 0, 0 }, /* ASPCT */ + { 0, 0, 0 }, /* ASCIRC */ + { 0, 0, 0 }, /* ASLSH */ + { 0, 0, 0 }, /* ASRSH */ + { 0, 0, 0 }, /* ASOR */ + { 0, 0, 0 }, /* ASAND */ + { 0, 0, 0 }, /* ELLIPS */ + { 0, 0, 0 }, /* DSHARP1 */ + { 0, 0, 0 }, /* NAME1 */ + { 16, 1, UNARY }, /* DEFINED */ + { 16, 0, UNARY }, /* UMINUS */ +}; + +int evalop(struct pri); +struct value tokval(Token *); +struct value vals[NSTAK], *vp; +enum toktype ops[NSTAK], *op; + +/* + * Evaluate an #if #elif #ifdef #ifndef line. trp->tp points to the keyword. + */ +long +eval(Tokenrow *trp, int kw) +{ + Token *tp; + Nlist *np; + int ntok, rand; + + trp->tp++; + if (kw==KIFDEF || kw==KIFNDEF) { + if (trp->lp - trp->bp != 4 || trp->tp->type!=NAME) { + error(ERROR, "Syntax error in #ifdef/#ifndef"); + return 0; + } + np = lookup(trp->tp, 0); + return (kw==KIFDEF) == (np && np->flag&(ISDEFINED|ISMAC)); + } + ntok = trp->tp - trp->bp; + kwdefined->val = KDEFINED; /* activate special meaning of defined */ + expandrow(trp, ""); + kwdefined->val = NAME; + vp = vals; + op = ops; + *op++ = END; + for (rand=0, tp = trp->bp+ntok; tp < trp->lp; tp++) { + switch(tp->type) { + case WS: + case NL: + continue; + + /* nilary */ + case NAME: + case NAME1: + case NUMBER: + case CCON: + case STRING: + if (rand) + goto syntax; + *vp++ = tokval(tp); + rand = 1; + continue; + + /* unary */ + case DEFINED: + case TILDE: + case NOT: + if (rand) + goto syntax; + *op++ = tp->type; + continue; + + /* unary-binary */ + case PLUS: case MINUS: case STAR: case AND: + if (rand==0) { + if (tp->type==MINUS) + *op++ = UMINUS; + if (tp->type==STAR || tp->type==AND) { + error(ERROR, "Illegal operator * or & in #if/#elsif"); + return 0; + } + continue; + } + /* flow through */ + + /* plain binary */ + case EQ: case NEQ: case LEQ: case GEQ: case LSH: case RSH: + case LAND: case LOR: case SLASH: case PCT: + case LT: case GT: case CIRC: case OR: case QUEST: + case COLON: case COMMA: + if (rand==0) + goto syntax; + if (evalop(priority[tp->type])!=0) + return 0; + *op++ = tp->type; + rand = 0; + continue; + + case LP: + if (rand) + goto syntax; + *op++ = LP; + continue; + + case RP: + if (!rand) + goto syntax; + if (evalop(priority[RP])!=0) + return 0; + if (op<=ops || op[-1]!=LP) { + goto syntax; + } + op--; + continue; + + default: + error(ERROR,"Bad operator (%t) in #if/#elsif", tp); + return 0; + } + } + if (rand==0) + goto syntax; + if (evalop(priority[END])!=0) + return 0; + if (op!=&ops[1] || vp!=&vals[1]) { + error(ERROR, "Botch in #if/#elsif"); + return 0; + } + if (vals[0].type==UND) + error(ERROR, "Undefined expression value"); + return vals[0].val; +syntax: + error(ERROR, "Syntax error in #if/#elsif"); + return 0; +} + +int +evalop(struct pri pri) +{ + struct value v1, v2; + long rv1, rv2; + int rtype, oper; + + /* prevent compiler whining. */ + v1.val = v2.val = 0; + v1.type = v2.type = 0; + + rv2=0; + rtype=0; + while (pri.pri < priority[op[-1]].pri) { + oper = *--op; + if (priority[oper].arity==2) { + v2 = *--vp; + rv2 = v2.val; + } + v1 = *--vp; + rv1 = v1.val; +/*lint -e574 -e644 */ + switch (priority[oper].ctype) { + case 0: + default: + error(WARNING, "Syntax error in #if/#endif"); + return 1; + case ARITH: + case RELAT: + if (v1.type==UNS || v2.type==UNS) + rtype = UNS; + else + rtype = SGN; + if (v1.type==UND || v2.type==UND) + rtype = UND; + if (priority[oper].ctype==RELAT && rtype==UNS) { + oper |= UNSMARK; + rtype = SGN; + } + break; + case SHIFT: + if (v1.type==UND || v2.type==UND) + rtype = UND; + else + rtype = v1.type; + if (rtype==UNS) + oper |= UNSMARK; + break; + case UNARY: + rtype = v1.type; + break; + case LOGIC: + case SPCL: + break; + } + switch (oper) { + case EQ: case EQ|UNSMARK: + rv1 = rv1==rv2; break; + case NEQ: case NEQ|UNSMARK: + rv1 = rv1!=rv2; break; + case LEQ: + rv1 = rv1<=rv2; break; + case GEQ: + rv1 = rv1>=rv2; break; + case LT: + rv1 = rv1rv2; break; + case LEQ|UNSMARK: + rv1 = (unsigned long)rv1<=rv2; break; + case GEQ|UNSMARK: + rv1 = (unsigned long)rv1>=rv2; break; + case LT|UNSMARK: + rv1 = (unsigned long)rv1rv2; break; + case LSH: + rv1 <<= rv2; break; + case LSH|UNSMARK: + rv1 = (unsigned long)rv1<>= rv2; break; + case RSH|UNSMARK: + rv1 = (unsigned long)rv1>>rv2; break; + case LAND: + rtype = UND; + if (v1.type==UND) + break; + if (rv1!=0) { + if (v2.type==UND) + break; + rv1 = rv2!=0; + } else + rv1 = 0; + rtype = SGN; + break; + case LOR: + rtype = UND; + if (v1.type==UND) + break; + if (rv1==0) { + if (v2.type==UND) + break; + rv1 = rv2!=0; + } else + rv1 = 1; + rtype = SGN; + break; + case AND: + rv1 &= rv2; break; + case STAR: + rv1 *= rv2; break; + case PLUS: + rv1 += rv2; break; + case MINUS: + rv1 -= rv2; break; + case UMINUS: + if (v1.type==UND) + rtype = UND; + rv1 = -rv1; break; + case OR: + rv1 |= rv2; break; + case CIRC: + rv1 ^= rv2; break; + case TILDE: + rv1 = ~rv1; break; + case NOT: + rv1 = !rv1; if (rtype!=UND) rtype = SGN; break; + case SLASH: + if (rv2==0) { + rtype = UND; + break; + } + if (rtype==UNS) + rv1 /= (unsigned long)rv2; + else + rv1 /= rv2; + break; + case PCT: + if (rv2==0) { + rtype = UND; + break; + } + if (rtype==UNS) + rv1 %= (unsigned long)rv2; + else + rv1 %= rv2; + break; + case COLON: + if (op[-1] != QUEST) + error(ERROR, "Bad ?: in #if/endif"); + else { + op--; + if ((--vp)->val==0) + v1 = v2; + rtype = v1.type; + rv1 = v1.val; + } + break; + case DEFINED: + break; + default: + error(ERROR, "Eval botch (unknown operator)"); + return 1; + } +/*lint +e574 +e644 */ + v1.val = rv1; + v1.type = rtype; + *vp++ = v1; + } + return 0; +} + +struct value +tokval(Token *tp) +{ + struct value v; + Nlist *np; + int i, base, c; + unsigned long n; + uchar *p; + + v.type = SGN; + v.val = 0; + switch (tp->type) { + + case NAME: + v.val = 0; + break; + + case NAME1: + if ((np = lookup(tp, 0)) != NULL && np->flag&(ISDEFINED|ISMAC)) + v.val = 1; + break; + + case NUMBER: + n = 0; + base = 10; + p = tp->t; + c = p[tp->len]; + p[tp->len] = '\0'; + if (*p=='0') { + base = 8; + if (p[1]=='x' || p[1]=='X') { + base = 16; + p++; + } + p++; + } + for (;; p++) { + if ((i = digit(*p)) < 0) + break; + if (i>=base) + error(WARNING, + "Bad digit in number %t", tp); + n *= base; + n += i; + } + if (n>=0x80000000 && base!=10) + v.type = UNS; + for (; *p; p++) { + if (*p=='u' || *p=='U') + v.type = UNS; + else if (*p=='l' || *p=='L') + ; + else { + error(ERROR, + "Bad number %t in #if/#elsif", tp); + break; + } + } + v.val = n; + tp->t[tp->len] = c; + break; + + case CCON: + n = 0; + p = tp->t; + if (*p=='L') { + p += 1; + error(WARNING, "Wide char constant value undefined"); + } + p += 1; + if (*p=='\\') { + p += 1; + if ((i = digit(*p))>=0 && i<=7) { + n = i; + p += 1; + if ((i = digit(*p))>=0 && i<=7) { + p += 1; + n <<= 3; + n += i; + if ((i = digit(*p))>=0 && i<=7) { + p += 1; + n <<= 3; + n += i; + } + } + } else if (*p=='x') { + p += 1; + while ((i = digit(*p))>=0 && i<=15) { + p += 1; + n <<= 4; + n += i; + } + } else { + static char cvcon[] + = "b\bf\fn\nr\rt\tv\v''\"\"??\\\\"; + for (i=0; i=sizeof(cvcon)) + error(WARNING, + "Undefined escape in character constant"); + } + } else if (*p=='\'') + error(ERROR, "Empty character constant"); + else + n = *p++; + if (*p!='\'') + error(WARNING, "Multibyte character constant undefined"); + else if (n>127) + error(WARNING, "Character constant taken as not signed"); + v.val = n; + break; + + case STRING: + error(ERROR, "String in #if/#elsif"); + break; + } + return v; +} + +int +digit(int i) +{ + if ('0'<=i && i<='9') + i -= '0'; + else if ('a'<=i && i<='f') + i -= 'a'-10; + else if ('A'<=i && i<='F') + i -= 'A'-10; + else + i = -1; + return i; +} diff --git a/src/tools/lcc/cpp/getopt.c b/src/tools/lcc/cpp/getopt.c new file mode 100644 index 0000000..c4d1af7 --- /dev/null +++ b/src/tools/lcc/cpp/getopt.c @@ -0,0 +1,53 @@ +#include +#include +#define EPR fprintf(stderr, +#define ERR(str, chr) if(opterr){EPR "%s%c\n", str, chr);} +int opterr = 1; +int optind = 1; +int optopt; +char *optarg; + +int +lcc_getopt (int argc, char *const argv[], const char *opts) +{ + static int sp = 1; + int c; + char *cp; + + if (sp == 1) { + if (optind >= argc || + argv[optind][0] != '-' || argv[optind][1] == '\0') + return -1; + else if (strcmp(argv[optind], "--") == 0) { + optind++; + return -1; + } + } + optopt = c = argv[optind][sp]; + if (c == ':' || (cp=strchr(opts, c)) == 0) { + ERR (": illegal option -- ", c); + if (argv[optind][++sp] == '\0') { + optind++; + sp = 1; + } + return '?'; + } + if (*++cp == ':') { + if (argv[optind][sp+1] != '\0') + optarg = &argv[optind++][sp+1]; + else if (++optind >= argc) { + ERR (": option requires an argument -- ", c); + sp = 1; + return '?'; + } else + optarg = argv[optind++]; + sp = 1; + } else { + if (argv[optind][++sp] == '\0') { + sp = 1; + optind++; + } + optarg = 0; + } + return c; +} diff --git a/src/tools/lcc/cpp/hideset.c b/src/tools/lcc/cpp/hideset.c new file mode 100644 index 0000000..bd2540d --- /dev/null +++ b/src/tools/lcc/cpp/hideset.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include "cpp.h" + +/* + * A hideset is a null-terminated array of Nlist pointers. + * They are referred to by indices in the hidesets array. + * Hideset 0 is empty. + */ + +#define HSSIZ 32 +typedef Nlist **Hideset; +Hideset *hidesets; +int nhidesets = 0; +int maxhidesets = 3; +int inserths(Hideset, Hideset, Nlist *); + +/* + * Test for membership in a hideset + */ +int +checkhideset(int hs, Nlist *np) +{ + Hideset hsp; + + if (hs>=nhidesets) + abort(); + for (hsp = hidesets[hs]; *hsp; hsp++) { + if (*hsp == np) + return 1; + } + return 0; +} + +/* + * Return the (possibly new) hideset obtained by adding np to hs. + */ +int +newhideset(int hs, Nlist *np) +{ + int i, len; + Nlist *nhs[HSSIZ+3]; + Hideset hs1, hs2; + + len = inserths(nhs, hidesets[hs], np); + for (i=0; i=HSSIZ) + return hs; + if (nhidesets >= maxhidesets) { + maxhidesets = 3*maxhidesets/2+1; + hidesets = (Hideset *)realloc(hidesets, (sizeof (Hideset *))*maxhidesets); + if (hidesets == NULL) + error(FATAL, "Out of memory from realloc"); + } + hs1 = (Hideset)domalloc(len*sizeof(Hideset)); + memmove(hs1, nhs, len*sizeof(Hideset)); + hidesets[nhidesets] = hs1; + return nhidesets++; +} + +int +inserths(Hideset dhs, Hideset shs, Nlist *np) +{ + Hideset odhs = dhs; + + while (*shs && *shs < np) + *dhs++ = *shs++; + if (*shs != np) + *dhs++ = np; + do { + *dhs++ = *shs; + } while (*shs++); + return dhs - odhs; +} + +/* + * Hideset union + */ +int +unionhideset(int hs1, int hs2) +{ + Hideset hp; + + for (hp = hidesets[hs2]; *hp; hp++) + hs1 = newhideset(hs1, *hp); + return hs1; +} + +void +iniths(void) +{ + hidesets = (Hideset *)domalloc(maxhidesets*sizeof(Hideset *)); + hidesets[0] = (Hideset)domalloc(sizeof(Hideset)); + *hidesets[0] = NULL; + nhidesets++; +} + +void +prhideset(int hs) +{ + Hideset np; + + for (np = hidesets[hs]; *np; np++) { + fprintf(stderr, (char*)(*np)->name, (*np)->len); + fprintf(stderr, " "); + } +} diff --git a/src/tools/lcc/cpp/include.c b/src/tools/lcc/cpp/include.c new file mode 100644 index 0000000..331a0b4 --- /dev/null +++ b/src/tools/lcc/cpp/include.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include "cpp.h" + +Includelist includelist[NINCLUDE]; + +extern char *objname; + +void appendDirToIncludeList( char *dir ) +{ + int i; + char *fqdir; + + fqdir = (char *)newstring( (uchar *)includelist[NINCLUDE-1].file, 256, 0 ); + strcat( fqdir, "/" ); + strcat( fqdir, dir ); + + //avoid adding it more than once + for (i=NINCLUDE-2; i>=0; i--) { + if (includelist[i].file && + !strcmp (includelist[i].file, fqdir)) { + return; + } + } + + for (i=NINCLUDE-2; i>=0; i--) { + if (includelist[i].file==NULL) { + includelist[i].always = 1; + includelist[i].file = fqdir; + break; + } + } + if (i<0) + error(FATAL, "Too many -I directives"); +} + +void +doinclude(Tokenrow *trp) +{ + char fname[256], iname[256]; + Includelist *ip; + int angled, len, wlen, fd, i; + + trp->tp += 1; + if (trp->tp>=trp->lp) + goto syntax; + if (trp->tp->type!=STRING && trp->tp->type!=LT) { + len = trp->tp - trp->bp; + expandrow(trp, ""); + trp->tp = trp->bp+len; + } + if (trp->tp->type==STRING) { + len = trp->tp->len-2; + if (len > sizeof(fname) - 1) + len = sizeof(fname) - 1; + strncpy(fname, (char*)trp->tp->t+1, len); + angled = 0; + } else if (trp->tp->type==LT) { + len = 0; + trp->tp++; + while (trp->tp->type!=GT) { + if (trp->tp>trp->lp || len+trp->tp->len+2 >= sizeof(fname)) + goto syntax; + strncpy(fname+len, (char*)trp->tp->t, trp->tp->len); + len += trp->tp->len; + trp->tp++; + } + angled = 1; + } else + goto syntax; + trp->tp += 2; + if (trp->tp < trp->lp || len==0) + goto syntax; + fname[len] = '\0'; + + appendDirToIncludeList( basepath( fname ) ); + + if (fname[0]=='/') { + fd = open(fname, 0); + strcpy(iname, fname); + } else for (fd = -1,i=NINCLUDE-1; i>=0; i--) { + ip = &includelist[i]; + if (ip->file==NULL || ip->deleted || (angled && ip->always==0)) + continue; + if (strlen(fname)+strlen(ip->file)+2 > sizeof(iname)) + continue; + strcpy(iname, ip->file); + strcat(iname, "/"); + strcat(iname, fname); + if ((fd = open(iname, 0)) >= 0) + break; + } + if ( Mflag>1 || (!angled&&Mflag==1) ) { + wlen = write(1,objname,strlen(objname)); + wlen = write(1,iname,strlen(iname)); + wlen = write(1,"\n",1); + } + if (fd >= 0) { + if (++incdepth > 10) + error(FATAL, "#include too deeply nested"); + setsource((char*)newstring((uchar*)iname, strlen(iname), 0), fd, NULL); + genline(); + } else { + trp->tp = trp->bp+2; + error(ERROR, "Could not find include file %r", trp); + } + return; +syntax: + error(ERROR, "Syntax error in #include"); + return; +} + +/* + * Generate a line directive for cursource + */ +void +genline(void) +{ + static Token ta = { UNCLASS }; + static Tokenrow tr = { &ta, &ta, &ta+1, 1 }; + uchar *p; + + ta.t = p = (uchar*)outp; + strcpy((char*)p, "#line "); + p += sizeof("#line ")-1; + p = (uchar*)outnum((char*)p, cursource->line); + *p++ = ' '; *p++ = '"'; + if (cursource->filename[0]!='/' && wd[0]) { + strcpy((char*)p, wd); + p += strlen(wd); + *p++ = '/'; + } + strcpy((char*)p, cursource->filename); + p += strlen((char*)p); + *p++ = '"'; *p++ = '\n'; + ta.len = (char*)p-outp; + outp = (char*)p; + tr.tp = tr.bp; + puttokens(&tr); +} + +void +setobjname(char *f) +{ + int n = strlen(f); + objname = (char*)domalloc(n+5); + strcpy(objname,f); + if(objname[n-2]=='.'){ + strcpy(objname+n-1,"$O: "); + }else{ + strcpy(objname+n,"$O: "); + } +} diff --git a/src/tools/lcc/cpp/lex.c b/src/tools/lcc/cpp/lex.c new file mode 100644 index 0000000..8030354 --- /dev/null +++ b/src/tools/lcc/cpp/lex.c @@ -0,0 +1,580 @@ +#include +#include +#include +#include "cpp.h" + +/* + * lexical FSM encoding + * when in state state, and one of the characters + * in ch arrives, enter nextstate. + * States >= S_SELF are either final, or at least require special action. + * In 'fsm' there is a line for each state X charset X nextstate. + * List chars that overwrite previous entries later (e.g. C_ALPH + * can be overridden by '_' by a later entry; and C_XX is the + * the universal set, and should always be first. + * States above S_SELF are represented in the big table as negative values. + * S_SELF and S_SELFB encode the resulting token type in the upper bits. + * These actions differ in that S_SELF doesn't have a lookahead char, + * S_SELFB does. + * + * The encoding is blown out into a big table for time-efficiency. + * Entries have + * nextstate: 6 bits; ?\ marker: 1 bit; tokentype: 9 bits. + */ + +#define MAXSTATE 32 +#define ACT(tok,act) ((tok<<7)+act) +#define QBSBIT 0100 +#define GETACT(st) (st>>7)&0x1ff + +/* character classes */ +#define C_WS 1 +#define C_ALPH 2 +#define C_NUM 3 +#define C_EOF 4 +#define C_XX 5 + +enum state { + START=0, NUM1, NUM2, NUM3, ID1, ST1, ST2, ST3, COM1, COM2, COM3, COM4, + CC1, CC2, WS1, PLUS1, MINUS1, STAR1, SLASH1, PCT1, SHARP1, + CIRC1, GT1, GT2, LT1, LT2, OR1, AND1, ASG1, NOT1, DOTS1, + S_SELF=MAXSTATE, S_SELFB, S_EOF, S_NL, S_EOFSTR, + S_STNL, S_COMNL, S_EOFCOM, S_COMMENT, S_EOB, S_WS, S_NAME +}; + +int tottok; +int tokkind[256]; +struct fsm { + int state; /* if in this state */ + uchar ch[4]; /* and see one of these characters */ + int nextstate; /* enter this state if +ve */ +}; + +/*const*/ struct fsm fsm[] = { + /* start state */ + {START, { C_XX }, ACT(UNCLASS,S_SELF)}, + {START, { ' ', '\t', '\v' }, WS1}, + {START, { C_NUM }, NUM1}, + {START, { '.' }, NUM3}, + {START, { C_ALPH }, ID1}, + {START, { 'L' }, ST1}, + {START, { '"' }, ST2}, + {START, { '\'' }, CC1}, + {START, { '/' }, COM1}, + {START, { EOFC }, S_EOF}, + {START, { '\n' }, S_NL}, + {START, { '-' }, MINUS1}, + {START, { '+' }, PLUS1}, + {START, { '<' }, LT1}, + {START, { '>' }, GT1}, + {START, { '=' }, ASG1}, + {START, { '!' }, NOT1}, + {START, { '&' }, AND1}, + {START, { '|' }, OR1}, + {START, { '#' }, SHARP1}, + {START, { '%' }, PCT1}, + {START, { '[' }, ACT(SBRA,S_SELF)}, + {START, { ']' }, ACT(SKET,S_SELF)}, + {START, { '(' }, ACT(LP,S_SELF)}, + {START, { ')' }, ACT(RP,S_SELF)}, + {START, { '*' }, STAR1}, + {START, { ',' }, ACT(COMMA,S_SELF)}, + {START, { '?' }, ACT(QUEST,S_SELF)}, + {START, { ':' }, ACT(COLON,S_SELF)}, + {START, { ';' }, ACT(SEMIC,S_SELF)}, + {START, { '{' }, ACT(CBRA,S_SELF)}, + {START, { '}' }, ACT(CKET,S_SELF)}, + {START, { '~' }, ACT(TILDE,S_SELF)}, + {START, { '^' }, CIRC1}, + + /* saw a digit */ + {NUM1, { C_XX }, ACT(NUMBER,S_SELFB)}, + {NUM1, { C_NUM, C_ALPH, '.' }, NUM1}, + {NUM1, { 'E', 'e' }, NUM2}, + {NUM1, { '_' }, ACT(NUMBER,S_SELFB)}, + + /* saw possible start of exponent, digits-e */ + {NUM2, { C_XX }, ACT(NUMBER,S_SELFB)}, + {NUM2, { '+', '-' }, NUM1}, + {NUM2, { C_NUM, C_ALPH }, NUM1}, + {NUM2, { '_' }, ACT(NUMBER,S_SELFB)}, + + /* saw a '.', which could be a number or an operator */ + {NUM3, { C_XX }, ACT(DOT,S_SELFB)}, + {NUM3, { '.' }, DOTS1}, + {NUM3, { C_NUM }, NUM1}, + + {DOTS1, { C_XX }, ACT(UNCLASS, S_SELFB)}, + {DOTS1, { C_NUM }, NUM1}, + {DOTS1, { '.' }, ACT(ELLIPS, S_SELF)}, + + /* saw a letter or _ */ + {ID1, { C_XX }, ACT(NAME,S_NAME)}, + {ID1, { C_ALPH, C_NUM }, ID1}, + + /* saw L (start of wide string?) */ + {ST1, { C_XX }, ACT(NAME,S_NAME)}, + {ST1, { C_ALPH, C_NUM }, ID1}, + {ST1, { '"' }, ST2}, + {ST1, { '\'' }, CC1}, + + /* saw " beginning string */ + {ST2, { C_XX }, ST2}, + {ST2, { '"' }, ACT(STRING, S_SELF)}, + {ST2, { '\\' }, ST3}, + {ST2, { '\n' }, S_STNL}, + {ST2, { EOFC }, S_EOFSTR}, + + /* saw \ in string */ + {ST3, { C_XX }, ST2}, + {ST3, { '\n' }, S_STNL}, + {ST3, { EOFC }, S_EOFSTR}, + + /* saw ' beginning character const */ + {CC1, { C_XX }, CC1}, + {CC1, { '\'' }, ACT(CCON, S_SELF)}, + {CC1, { '\\' }, CC2}, + {CC1, { '\n' }, S_STNL}, + {CC1, { EOFC }, S_EOFSTR}, + + /* saw \ in ccon */ + {CC2, { C_XX }, CC1}, + {CC2, { '\n' }, S_STNL}, + {CC2, { EOFC }, S_EOFSTR}, + + /* saw /, perhaps start of comment */ + {COM1, { C_XX }, ACT(SLASH, S_SELFB)}, + {COM1, { '=' }, ACT(ASSLASH, S_SELF)}, + {COM1, { '*' }, COM2}, + {COM1, { '/' }, COM4}, + + /* saw / then *, start of comment */ + {COM2, { C_XX }, COM2}, + {COM2, { '\n' }, S_COMNL}, + {COM2, { '*' }, COM3}, + {COM2, { EOFC }, S_EOFCOM}, + + /* saw the * possibly ending a comment */ + {COM3, { C_XX }, COM2}, + {COM3, { '\n' }, S_COMNL}, + {COM3, { '*' }, COM3}, + {COM3, { '/' }, S_COMMENT}, + + /* // comment */ + {COM4, { C_XX }, COM4}, + {COM4, { '\n' }, S_NL}, + {COM4, { EOFC }, S_EOFCOM}, + + /* saw white space, eat it up */ + {WS1, { C_XX }, S_WS}, + {WS1, { ' ', '\t', '\v' }, WS1}, + + /* saw -, check --, -=, -> */ + {MINUS1, { C_XX }, ACT(MINUS, S_SELFB)}, + {MINUS1, { '-' }, ACT(MMINUS, S_SELF)}, + {MINUS1, { '=' }, ACT(ASMINUS,S_SELF)}, + {MINUS1, { '>' }, ACT(ARROW,S_SELF)}, + + /* saw +, check ++, += */ + {PLUS1, { C_XX }, ACT(PLUS, S_SELFB)}, + {PLUS1, { '+' }, ACT(PPLUS, S_SELF)}, + {PLUS1, { '=' }, ACT(ASPLUS, S_SELF)}, + + /* saw <, check <<, <<=, <= */ + {LT1, { C_XX }, ACT(LT, S_SELFB)}, + {LT1, { '<' }, LT2}, + {LT1, { '=' }, ACT(LEQ, S_SELF)}, + {LT2, { C_XX }, ACT(LSH, S_SELFB)}, + {LT2, { '=' }, ACT(ASLSH, S_SELF)}, + + /* saw >, check >>, >>=, >= */ + {GT1, { C_XX }, ACT(GT, S_SELFB)}, + {GT1, { '>' }, GT2}, + {GT1, { '=' }, ACT(GEQ, S_SELF)}, + {GT2, { C_XX }, ACT(RSH, S_SELFB)}, + {GT2, { '=' }, ACT(ASRSH, S_SELF)}, + + /* = */ + {ASG1, { C_XX }, ACT(ASGN, S_SELFB)}, + {ASG1, { '=' }, ACT(EQ, S_SELF)}, + + /* ! */ + {NOT1, { C_XX }, ACT(NOT, S_SELFB)}, + {NOT1, { '=' }, ACT(NEQ, S_SELF)}, + + /* & */ + {AND1, { C_XX }, ACT(AND, S_SELFB)}, + {AND1, { '&' }, ACT(LAND, S_SELF)}, + {AND1, { '=' }, ACT(ASAND, S_SELF)}, + + /* | */ + {OR1, { C_XX }, ACT(OR, S_SELFB)}, + {OR1, { '|' }, ACT(LOR, S_SELF)}, + {OR1, { '=' }, ACT(ASOR, S_SELF)}, + + /* # */ + {SHARP1, { C_XX }, ACT(SHARP, S_SELFB)}, + {SHARP1, { '#' }, ACT(DSHARP, S_SELF)}, + + /* % */ + {PCT1, { C_XX }, ACT(PCT, S_SELFB)}, + {PCT1, { '=' }, ACT(ASPCT, S_SELF)}, + + /* * */ + {STAR1, { C_XX }, ACT(STAR, S_SELFB)}, + {STAR1, { '=' }, ACT(ASSTAR, S_SELF)}, + + /* ^ */ + {CIRC1, { C_XX }, ACT(CIRC, S_SELFB)}, + {CIRC1, { '=' }, ACT(ASCIRC, S_SELF)}, + + {-1} +}; + +/* first index is char, second is state */ +/* increase #states to power of 2 to encourage use of shift */ +short bigfsm[256][MAXSTATE]; + +void +expandlex(void) +{ + /*const*/ struct fsm *fp; + int i, j, nstate; + + for (fp = fsm; fp->state>=0; fp++) { + for (i=0; fp->ch[i]; i++) { + nstate = fp->nextstate; + if (nstate >= S_SELF) + nstate = ~nstate; + switch (fp->ch[i]) { + + case C_XX: /* random characters */ + for (j=0; j<256; j++) + bigfsm[j][fp->state] = nstate; + continue; + case C_ALPH: + for (j=0; j<=256; j++) + if (('a'<=j&&j<='z') || ('A'<=j&&j<='Z') + || j=='_') + bigfsm[j][fp->state] = nstate; + continue; + case C_NUM: + for (j='0'; j<='9'; j++) + bigfsm[j][fp->state] = nstate; + continue; + default: + bigfsm[fp->ch[i]][fp->state] = nstate; + } + } + } + /* install special cases for ? (trigraphs), \ (splicing), runes, and EOB */ + for (i=0; i0) + bigfsm[j][i] = ~bigfsm[j][i]; + bigfsm[j][i] &= ~QBSBIT; + } + bigfsm[EOB][i] = ~S_EOB; + if (bigfsm[EOFC][i]>=0) + bigfsm[EOFC][i] = ~S_EOF; + } +} + +void +fixlex(void) +{ + /* do C++ comments? */ + if (Cplusplus==0) + bigfsm['/'][COM1] = bigfsm['x'][COM1]; +} + +/* + * fill in a row of tokens from input, terminated by NL or END + * First token is put at trp->lp. + * Reset is non-zero when the input buffer can be "rewound." + * The value is a flag indicating that possible macros have + * been seen in the row. + */ +int +gettokens(Tokenrow *trp, int reset) +{ + register int c, state, oldstate; + register uchar *ip; + register Token *tp, *maxp; + int runelen; + Source *s = cursource; + int nmac = 0; + + tp = trp->lp; + ip = s->inp; + if (reset) { + s->lineinc = 0; + if (ip>=s->inl) { /* nothing in buffer */ + s->inl = s->inb; + fillbuf(s); + ip = s->inp = s->inb; + } else if (ip >= s->inb+(3*INS/4)) { + memmove(s->inb, ip, 4+s->inl-ip); + s->inl = s->inb+(s->inl-ip); + ip = s->inp = s->inb; + } + } + maxp = &trp->bp[trp->max]; + runelen = 1; + for (;;) { + continue2: + if (tp>=maxp) { + trp->lp = tp; + tp = growtokenrow(trp); + maxp = &trp->bp[trp->max]; + } + tp->type = UNCLASS; + tp->hideset = 0; + tp->t = ip; + tp->wslen = 0; + tp->flag = 0; + state = START; + for (;;) { + oldstate = state; + c = *ip; + if ((state = bigfsm[c][state]) >= 0) { + ip += runelen; + runelen = 1; + continue; + } + state = ~state; + reswitch: + switch (state&0177) { + case S_SELF: + ip += runelen; + runelen = 1; + case S_SELFB: + tp->type = GETACT(state); + tp->len = ip - tp->t; + tp++; + goto continue2; + + case S_NAME: /* like S_SELFB but with nmac check */ + tp->type = NAME; + tp->len = ip - tp->t; + nmac |= quicklook(tp->t[0], tp->len>1?tp->t[1]:0); + tp++; + goto continue2; + + case S_WS: + tp->wslen = ip - tp->t; + tp->t = ip; + state = START; + continue; + + default: + if ((state&QBSBIT)==0) { + ip += runelen; + runelen = 1; + continue; + } + state &= ~QBSBIT; + s->inp = ip; + if (c=='?') { /* check trigraph */ + if (trigraph(s)) { + state = oldstate; + continue; + } + goto reswitch; + } + if (c=='\\') { /* line-folding */ + if (foldline(s)) { + s->lineinc++; + state = oldstate; + continue; + } + goto reswitch; + } + error(WARNING, "Lexical botch in cpp"); + ip += runelen; + runelen = 1; + continue; + + case S_EOB: + s->inp = ip; + fillbuf(cursource); + state = oldstate; + continue; + + case S_EOF: + tp->type = END; + tp->len = 0; + s->inp = ip; + if (tp!=trp->bp && (tp-1)->type!=NL && cursource->fd!=-1) + error(WARNING,"No newline at end of file"); + trp->lp = tp+1; + return nmac; + + case S_STNL: + error(ERROR, "Unterminated string or char const"); + case S_NL: + tp->t = ip; + tp->type = NL; + tp->len = 1; + tp->wslen = 0; + s->lineinc++; + s->inp = ip+1; + trp->lp = tp+1; + return nmac; + + case S_EOFSTR: + error(FATAL, "EOF in string or char constant"); + break; + + case S_COMNL: + s->lineinc++; + state = COM2; + ip += runelen; + runelen = 1; + if (ip >= s->inb+(7*INS/8)) { /* very long comment */ + memmove(tp->t, ip, 4+s->inl-ip); + s->inl -= ip-tp->t; + ip = tp->t+1; + } + continue; + + case S_EOFCOM: + error(WARNING, "EOF inside comment"); + --ip; + case S_COMMENT: + ++ip; + tp->t = ip; + tp->t[-1] = ' '; + tp->wslen = 1; + state = START; + continue; + } + break; + } + ip += runelen; + runelen = 1; + tp->len = ip - tp->t; + tp++; + } +} + +/* have seen ?; handle the trigraph it starts (if any) else 0 */ +int +trigraph(Source *s) +{ + int c; + + while (s->inp+2 >= s->inl && fillbuf(s)!=EOF) + ; + if (s->inp[1]!='?') + return 0; + c = 0; + switch(s->inp[2]) { + case '=': + c = '#'; break; + case '(': + c = '['; break; + case '/': + c = '\\'; break; + case ')': + c = ']'; break; + case '\'': + c = '^'; break; + case '<': + c = '{'; break; + case '!': + c = '|'; break; + case '>': + c = '}'; break; + case '-': + c = '~'; break; + } + if (c) { + *s->inp = c; + memmove(s->inp+1, s->inp+3, s->inl-s->inp+2); + s->inl -= 2; + } + return c; +} + +int +foldline(Source *s) +{ + while (s->inp+1 >= s->inl && fillbuf(s)!=EOF) + ; + if (s->inp[1] == '\n') { + memmove(s->inp, s->inp+2, s->inl-s->inp+3); + s->inl -= 2; + return 1; + } + return 0; +} + +int +fillbuf(Source *s) +{ + int n, nr; + + nr = INS/8; + if ((char *)s->inl+nr > (char *)s->inb+INS) + error(FATAL, "Input buffer overflow"); + if (s->fd<0 || (n=read(s->fd, (char *)s->inl, INS/8)) <= 0) + n = 0; + if ((*s->inp&0xff) == EOB) /* sentinel character appears in input */ + *s->inp = EOFC; + s->inl += n; + s->inl[0] = s->inl[1]= s->inl[2]= s->inl[3] = EOB; + if (n==0) { + s->inl[0] = s->inl[1]= s->inl[2]= s->inl[3] = EOFC; + return EOF; + } + return 0; +} + +/* + * Push down to new source of characters. + * If fd>0 and str==NULL, then from a file `name'; + * if fd==-1 and str, then from the string. + */ +Source * +setsource(char *name, int fd, char *str) +{ + Source *s = new(Source); + int len; + + s->line = 1; + s->lineinc = 0; + s->fd = fd; + s->filename = name; + s->next = cursource; + s->ifdepth = 0; + cursource = s; + /* slop at right for EOB */ + if (str) { + len = strlen(str); + s->inb = domalloc(len+4); + s->inp = s->inb; + strncpy((char *)s->inp, str, len); + } else { + s->inb = domalloc(INS+4); + s->inp = s->inb; + len = 0; + } + s->inl = s->inp+len; + s->inl[0] = s->inl[1] = EOB; + return s; +} + +void +unsetsource(void) +{ + Source *s = cursource; + + if (s->fd>=0) { + close(s->fd); + dofree(s->inb); + } + cursource = s->next; + dofree(s); +} diff --git a/src/tools/lcc/cpp/macro.c b/src/tools/lcc/cpp/macro.c new file mode 100644 index 0000000..49d1129 --- /dev/null +++ b/src/tools/lcc/cpp/macro.c @@ -0,0 +1,515 @@ +#include +#include +#include +#include "cpp.h" + +/* + * do a macro definition. tp points to the name being defined in the line + */ +void +dodefine(Tokenrow *trp) +{ + Token *tp; + Nlist *np; + Tokenrow *def, *args; + + tp = trp->tp+1; + if (tp>=trp->lp || tp->type!=NAME) { + error(ERROR, "#defined token is not a name"); + return; + } + np = lookup(tp, 1); + if (np->flag&ISUNCHANGE) { + error(ERROR, "#defined token %t can't be redefined", tp); + return; + } + /* collect arguments */ + tp += 1; + args = NULL; + if (tplp && tp->type==LP && tp->wslen==0) { + /* macro with args */ + int narg = 0; + tp += 1; + args = new(Tokenrow); + maketokenrow(2, args); + if (tp->type!=RP) { + int err = 0; + for (;;) { + Token *atp; + if (tp->type!=NAME) { + err++; + break; + } + if (narg>=args->max) + growtokenrow(args); + for (atp=args->bp; atplp; atp++) + if (atp->len==tp->len + && strncmp((char*)atp->t, (char*)tp->t, tp->len)==0) + error(ERROR, "Duplicate macro argument"); + *args->lp++ = *tp; + narg++; + tp += 1; + if (tp->type==RP) + break; + if (tp->type!=COMMA) { + err++; + break; + } + tp += 1; + } + if (err) { + error(ERROR, "Syntax error in macro parameters"); + return; + } + } + tp += 1; + } + trp->tp = tp; + if (((trp->lp)-1)->type==NL) + trp->lp -= 1; + def = normtokenrow(trp); + if (np->flag&ISDEFINED) { + if (comparetokens(def, np->vp) + || (np->ap==NULL) != (args==NULL) + || (np->ap && comparetokens(args, np->ap))) + error(ERROR, "Macro redefinition of %t", trp->bp+2); + } + if (args) { + Tokenrow *tap; + tap = normtokenrow(args); + dofree(args->bp); + args = tap; + } + np->ap = args; + np->vp = def; + np->flag |= ISDEFINED; +} + +/* + * Definition received via -D or -U + */ +void +doadefine(Tokenrow *trp, int type) +{ + Nlist *np; + static Token onetoken[1] = {{ NUMBER, 0, 0, 0, 1, (uchar*)"1" }}; + static Tokenrow onetr = { onetoken, onetoken, onetoken+1, 1 }; + + trp->tp = trp->bp; + if (type=='U') { + if (trp->lp-trp->tp != 2 || trp->tp->type!=NAME) + goto syntax; + if ((np = lookup(trp->tp, 0)) == NULL) + return; + np->flag &= ~ISDEFINED; + return; + } + if (trp->tp >= trp->lp || trp->tp->type!=NAME) + goto syntax; + np = lookup(trp->tp, 1); + np->flag |= ISDEFINED; + trp->tp += 1; + if (trp->tp >= trp->lp || trp->tp->type==END) { + np->vp = &onetr; + return; + } + if (trp->tp->type!=ASGN) + goto syntax; + trp->tp += 1; + if ((trp->lp-1)->type == END) + trp->lp -= 1; + np->vp = normtokenrow(trp); + return; +syntax: + error(FATAL, "Illegal -D or -U argument %r", trp); +} + +/* + * Do macro expansion in a row of tokens. + * Flag is NULL if more input can be gathered. + */ +void +expandrow(Tokenrow *trp, char *flag) +{ + Token *tp; + Nlist *np; + + if (flag) + setsource(flag, -1, ""); + for (tp = trp->tp; tplp; ) { + if (tp->type!=NAME + || quicklook(tp->t[0], tp->len>1?tp->t[1]:0)==0 + || (np = lookup(tp, 0))==NULL + || (np->flag&(ISDEFINED|ISMAC))==0 + || (tp->hideset && checkhideset(tp->hideset, np))) { + tp++; + continue; + } + trp->tp = tp; + if (np->val==KDEFINED) { + tp->type = DEFINED; + if ((tp+1)lp && (tp+1)->type==NAME) + (tp+1)->type = NAME1; + else if ((tp+3)lp && (tp+1)->type==LP + && (tp+2)->type==NAME && (tp+3)->type==RP) + (tp+2)->type = NAME1; + else + error(ERROR, "Incorrect syntax for `defined'"); + tp++; + continue; + } + if (np->flag&ISMAC) + builtin(trp, np->val); + else { + expand(trp, np); + } + tp = trp->tp; + } + if (flag) + unsetsource(); +} + +/* + * Expand the macro whose name is np, at token trp->tp, in the tokenrow. + * Return trp->tp at the first token next to be expanded + * (ordinarily the beginning of the expansion) + */ +void +expand(Tokenrow *trp, Nlist *np) +{ + Tokenrow ntr; + int ntokc, narg, i; + Token *tp; + Tokenrow *atr[NARG+1]; + int hs; + + copytokenrow(&ntr, np->vp); /* copy macro value */ + if (np->ap==NULL) /* parameterless */ + ntokc = 1; + else { + ntokc = gatherargs(trp, atr, &narg); + if (narg<0) { /* not actually a call (no '(') */ + trp->tp++; + return; + } + if (narg != rowlen(np->ap)) { + error(ERROR, "Disagreement in number of macro arguments"); + trp->tp->hideset = newhideset(trp->tp->hideset, np); + trp->tp += ntokc; + return; + } + substargs(np, &ntr, atr); /* put args into replacement */ + for (i=0; ibp); + dofree(atr[i]); + } + } + doconcat(&ntr); /* execute ## operators */ + hs = newhideset(trp->tp->hideset, np); + for (tp=ntr.bp; tptype==NAME) { + if (tp->hideset==0) + tp->hideset = hs; + else + tp->hideset = unionhideset(tp->hideset, hs); + } + } + ntr.tp = ntr.bp; + insertrow(trp, ntokc, &ntr); + trp->tp -= rowlen(&ntr); + dofree(ntr.bp); + return; +} + +/* + * Gather an arglist, starting in trp with tp pointing at the macro name. + * Return total number of tokens passed, stash number of args found. + * trp->tp is not changed relative to the tokenrow. + */ +int +gatherargs(Tokenrow *trp, Tokenrow **atr, int *narg) +{ + int parens = 1; + int ntok = 0; + Token *bp, *lp; + Tokenrow ttr; + int ntokp; + int needspace; + + *narg = -1; /* means that there is no macro call */ + /* look for the ( */ + for (;;) { + trp->tp++; + ntok++; + if (trp->tp >= trp->lp) { + gettokens(trp, 0); + if ((trp->lp-1)->type==END) { + trp->lp -= 1; + trp->tp -= ntok; + return ntok; + } + } + if (trp->tp->type==LP) + break; + if (trp->tp->type!=NL) + return ntok; + } + *narg = 0; + ntok++; + ntokp = ntok; + trp->tp++; + /* search for the terminating ), possibly extending the row */ + needspace = 0; + while (parens>0) { + if (trp->tp >= trp->lp) + gettokens(trp, 0); + if (needspace) { + needspace = 0; + makespace(trp); + } + if (trp->tp->type==END) { + trp->lp -= 1; + trp->tp -= ntok; + error(ERROR, "EOF in macro arglist"); + return ntok; + } + if (trp->tp->type==NL) { + trp->tp += 1; + adjustrow(trp, -1); + trp->tp -= 1; + makespace(trp); + needspace = 1; + continue; + } + if (trp->tp->type==LP) + parens++; + else if (trp->tp->type==RP) + parens--; + trp->tp++; + ntok++; + } + trp->tp -= ntok; + /* Now trp->tp won't move underneath us */ + lp = bp = trp->tp+ntokp; + for (; parens>=0; lp++) { + if (lp->type == LP) { + parens++; + continue; + } + if (lp->type==RP) + parens--; + if (lp->type==DSHARP) + lp->type = DSHARP1; /* ## not special in arg */ + if ((lp->type==COMMA && parens==0) || (parens<0 && (lp-1)->type!=LP)) { + if (*narg>=NARG-1) + error(FATAL, "Sorry, too many macro arguments"); + ttr.bp = ttr.tp = bp; + ttr.lp = lp; + atr[(*narg)++] = normtokenrow(&ttr); + bp = lp+1; + } + } + return ntok; +} + +/* + * substitute the argument list into the replacement string + * This would be simple except for ## and # + */ +void +substargs(Nlist *np, Tokenrow *rtr, Tokenrow **atr) +{ + Tokenrow tatr; + Token *tp; + int ntok, argno; + + for (rtr->tp=rtr->bp; rtr->tplp; ) { + if (rtr->tp->type==SHARP) { /* string operator */ + tp = rtr->tp; + rtr->tp += 1; + if ((argno = lookuparg(np, rtr->tp))<0) { + error(ERROR, "# not followed by macro parameter"); + continue; + } + ntok = 1 + (rtr->tp - tp); + rtr->tp = tp; + insertrow(rtr, ntok, stringify(atr[argno])); + continue; + } + if (rtr->tp->type==NAME + && (argno = lookuparg(np, rtr->tp)) >= 0) { + if ((rtr->tp+1)->type==DSHARP + || (rtr->tp!=rtr->bp && (rtr->tp-1)->type==DSHARP)) + insertrow(rtr, 1, atr[argno]); + else { + copytokenrow(&tatr, atr[argno]); + expandrow(&tatr, ""); + insertrow(rtr, 1, &tatr); + dofree(tatr.bp); + } + continue; + } + rtr->tp++; + } +} + +/* + * Evaluate the ## operators in a tokenrow + */ +void +doconcat(Tokenrow *trp) +{ + Token *ltp, *ntp; + Tokenrow ntr; + int len; + + for (trp->tp=trp->bp; trp->tplp; trp->tp++) { + if (trp->tp->type==DSHARP1) + trp->tp->type = DSHARP; + else if (trp->tp->type==DSHARP) { + char tt[128]; + ltp = trp->tp-1; + ntp = trp->tp+1; + if (ltpbp || ntp>=trp->lp) { + error(ERROR, "## occurs at border of replacement"); + continue; + } + len = ltp->len + ntp->len; + strncpy((char*)tt, (char*)ltp->t, ltp->len); + strncpy((char*)tt+ltp->len, (char*)ntp->t, ntp->len); + tt[len] = '\0'; + setsource("<##>", -1, tt); + maketokenrow(3, &ntr); + gettokens(&ntr, 1); + unsetsource(); + if (ntr.lp-ntr.bp!=2 || ntr.bp->type==UNCLASS) + error(WARNING, "Bad token %r produced by ##", &ntr); + ntr.lp = ntr.bp+1; + trp->tp = ltp; + makespace(&ntr); + insertrow(trp, (ntp-ltp)+1, &ntr); + dofree(ntr.bp); + trp->tp--; + } + } +} + +/* + * tp is a potential parameter name of macro mac; + * look it up in mac's arglist, and if found, return the + * corresponding index in the argname array. Return -1 if not found. + */ +int +lookuparg(Nlist *mac, Token *tp) +{ + Token *ap; + + if (tp->type!=NAME || mac->ap==NULL) + return -1; + for (ap=mac->ap->bp; apap->lp; ap++) { + if (ap->len==tp->len && strncmp((char*)ap->t,(char*)tp->t,ap->len)==0) + return ap - mac->ap->bp; + } + return -1; +} + +/* + * Return a quoted version of the tokenrow (from # arg) + */ +#define STRLEN 512 +Tokenrow * +stringify(Tokenrow *vp) +{ + static Token t = { STRING }; + static Tokenrow tr = { &t, &t, &t+1, 1 }; + Token *tp; + uchar s[STRLEN]; + uchar *sp = s, *cp; + int i, instring; + + *sp++ = '"'; + for (tp = vp->bp; tp < vp->lp; tp++) { + instring = tp->type==STRING || tp->type==CCON; + if (sp+2*tp->len >= &s[STRLEN-10]) { + error(ERROR, "Stringified macro arg is too long"); + break; + } + if (tp->wslen && (tp->flag&XPWS)==0) + *sp++ = ' '; + for (i=0, cp=tp->t; ilen; i++) { + if (instring && (*cp=='"' || *cp=='\\')) + *sp++ = '\\'; + *sp++ = *cp++; + } + } + *sp++ = '"'; + *sp = '\0'; + sp = s; + t.len = strlen((char*)sp); + t.t = newstring(sp, t.len, 0); + return &tr; +} + +/* + * expand a builtin name + */ +void +builtin(Tokenrow *trp, int biname) +{ + char *op; + Token *tp; + Source *s; + + tp = trp->tp; + trp->tp++; + /* need to find the real source */ + s = cursource; + while (s && s->fd==-1) + s = s->next; + if (s==NULL) + s = cursource; + /* most are strings */ + tp->type = STRING; + if (tp->wslen) { + *outp++ = ' '; + tp->wslen = 1; + } + op = outp; + *op++ = '"'; + switch (biname) { + + case KLINENO: + tp->type = NUMBER; + op = outnum(op-1, s->line); + break; + + case KFILE: { + char *src = s->filename; + while ((*op++ = *src++) != 0) + if (src[-1] == '\\') + *op++ = '\\'; + op--; + break; + } + + case KDATE: + strncpy(op, curtime+4, 7); + strncpy(op+7, curtime+20, 4); + op += 11; + break; + + case KTIME: + strncpy(op, curtime+11, 8); + op += 8; + break; + + default: + error(ERROR, "cpp botch: unknown internal macro"); + return; + } + if (tp->type==STRING) + *op++ = '"'; + tp->t = (uchar*)outp; + tp->len = op - outp; + outp = op; +} diff --git a/src/tools/lcc/cpp/nlist.c b/src/tools/lcc/cpp/nlist.c new file mode 100644 index 0000000..d3a8357 --- /dev/null +++ b/src/tools/lcc/cpp/nlist.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include "cpp.h" + +extern char *optarg; +extern int optind; +extern int verbose; +extern int Cplusplus; +Nlist *kwdefined; +char wd[128]; + +#define NLSIZE 128 + +static Nlist *nlist[NLSIZE]; + +struct kwtab { + char *kw; + int val; + int flag; +} kwtab[] = { + {"if", KIF, ISKW}, + {"ifdef", KIFDEF, ISKW}, + {"ifndef", KIFNDEF, ISKW}, + {"elif", KELIF, ISKW}, + {"else", KELSE, ISKW}, + {"endif", KENDIF, ISKW}, + {"include", KINCLUDE, ISKW}, + {"define", KDEFINE, ISKW}, + {"undef", KUNDEF, ISKW}, + {"line", KLINE, ISKW}, + {"warning", KWARNING, ISKW}, + {"error", KERROR, ISKW}, + {"pragma", KPRAGMA, ISKW}, + {"eval", KEVAL, ISKW}, + {"defined", KDEFINED, ISDEFINED+ISUNCHANGE}, + {"__LINE__", KLINENO, ISMAC+ISUNCHANGE}, + {"__FILE__", KFILE, ISMAC+ISUNCHANGE}, + {"__DATE__", KDATE, ISMAC+ISUNCHANGE}, + {"__TIME__", KTIME, ISMAC+ISUNCHANGE}, + {"__STDC__", KSTDC, ISUNCHANGE}, + {NULL} +}; + +unsigned long namebit[077+1]; +Nlist *np; + +void +setup_kwtab(void) +{ + struct kwtab *kp; + Nlist *np; + Token t; + static Token deftoken[1] = {{ NAME, 0, 0, 0, 7, (uchar*)"defined" }}; + static Tokenrow deftr = { deftoken, deftoken, deftoken+1, 1 }; + + for (kp=kwtab; kp->kw; kp++) { + t.t = (uchar*)kp->kw; + t.len = strlen(kp->kw); + np = lookup(&t, 1); + np->flag = kp->flag; + np->val = kp->val; + if (np->val == KDEFINED) { + kwdefined = np; + np->val = NAME; + np->vp = &deftr; + np->ap = 0; + } + } +} + +Nlist * +lookup(Token *tp, int install) +{ + unsigned int h; + Nlist *np; + uchar *cp, *cpe; + + h = 0; + for (cp=tp->t, cpe=cp+tp->len; cpt==*np->name && tp->len==np->len + && strncmp((char*)tp->t, (char*)np->name, tp->len)==0) + return np; + np = np->next; + } + if (install) { + np = new(Nlist); + np->vp = NULL; + np->ap = NULL; + np->flag = 0; + np->val = 0; + np->len = tp->len; + np->name = newstring(tp->t, tp->len, 0); + np->next = nlist[h]; + nlist[h] = np; + quickset(tp->t[0], tp->len>1? tp->t[1]:0); + return np; + } + return NULL; +} diff --git a/src/tools/lcc/cpp/tokens.c b/src/tools/lcc/cpp/tokens.c new file mode 100644 index 0000000..ad9f2f7 --- /dev/null +++ b/src/tools/lcc/cpp/tokens.c @@ -0,0 +1,371 @@ +#include +#include +#include +#include "cpp.h" + +static char wbuf[2*OBS]; +static char *wbp = wbuf; + +/* + * 1 for tokens that don't need whitespace when they get inserted + * by macro expansion + */ +static const char wstab[] = { + 0, /* END */ + 0, /* UNCLASS */ + 0, /* NAME */ + 0, /* NUMBER */ + 0, /* STRING */ + 0, /* CCON */ + 1, /* NL */ + 0, /* WS */ + 0, /* DSHARP */ + 0, /* EQ */ + 0, /* NEQ */ + 0, /* LEQ */ + 0, /* GEQ */ + 0, /* LSH */ + 0, /* RSH */ + 0, /* LAND */ + 0, /* LOR */ + 0, /* PPLUS */ + 0, /* MMINUS */ + 0, /* ARROW */ + 1, /* SBRA */ + 1, /* SKET */ + 1, /* LP */ + 1, /* RP */ + 0, /* DOT */ + 0, /* AND */ + 0, /* STAR */ + 0, /* PLUS */ + 0, /* MINUS */ + 0, /* TILDE */ + 0, /* NOT */ + 0, /* SLASH */ + 0, /* PCT */ + 0, /* LT */ + 0, /* GT */ + 0, /* CIRC */ + 0, /* OR */ + 0, /* QUEST */ + 0, /* COLON */ + 0, /* ASGN */ + 1, /* COMMA */ + 0, /* SHARP */ + 1, /* SEMIC */ + 1, /* CBRA */ + 1, /* CKET */ + 0, /* ASPLUS */ + 0, /* ASMINUS */ + 0, /* ASSTAR */ + 0, /* ASSLASH */ + 0, /* ASPCT */ + 0, /* ASCIRC */ + 0, /* ASLSH */ + 0, /* ASRSH */ + 0, /* ASOR */ + 0, /* ASAND */ + 0, /* ELLIPS */ + 0, /* DSHARP1 */ + 0, /* NAME1 */ + 0, /* DEFINED */ + 0, /* UMINUS */ +}; + +void +maketokenrow(int size, Tokenrow *trp) +{ + trp->max = size; + if (size>0) + trp->bp = (Token *)domalloc(size*sizeof(Token)); + else + trp->bp = NULL; + trp->tp = trp->bp; + trp->lp = trp->bp; +} + +Token * +growtokenrow(Tokenrow *trp) +{ + int ncur = trp->tp - trp->bp; + int nlast = trp->lp - trp->bp; + + trp->max = 3*trp->max/2 + 1; + trp->bp = (Token *)realloc(trp->bp, trp->max*sizeof(Token)); + if (trp->bp == NULL) + error(FATAL, "Out of memory from realloc"); + trp->lp = &trp->bp[nlast]; + trp->tp = &trp->bp[ncur]; + return trp->lp; +} + +/* + * Compare a row of tokens, ignoring the content of WS; return !=0 if different + */ +int +comparetokens(Tokenrow *tr1, Tokenrow *tr2) +{ + Token *tp1, *tp2; + + tp1 = tr1->tp; + tp2 = tr2->tp; + if (tr1->lp-tp1 != tr2->lp-tp2) + return 1; + for (; tp1lp ; tp1++, tp2++) { + if (tp1->type != tp2->type + || (tp1->wslen==0) != (tp2->wslen==0) + || tp1->len != tp2->len + || strncmp((char*)tp1->t, (char*)tp2->t, tp1->len)!=0) + return 1; + } + return 0; +} + +/* + * replace ntok tokens starting at dtr->tp with the contents of str. + * tp ends up pointing just beyond the replacement. + * Canonical whitespace is assured on each side. + */ +void +insertrow(Tokenrow *dtr, int ntok, Tokenrow *str) +{ + int nrtok = rowlen(str); + + dtr->tp += ntok; + adjustrow(dtr, nrtok-ntok); + dtr->tp -= ntok; + movetokenrow(dtr, str); + makespace(dtr); + dtr->tp += nrtok; + makespace(dtr); +} + +/* + * make sure there is WS before trp->tp, if tokens might merge in the output + */ +void +makespace(Tokenrow *trp) +{ + uchar *tt; + Token *tp = trp->tp; + + if (tp >= trp->lp) + return; + if (tp->wslen) { + if (tp->flag&XPWS + && (wstab[tp->type] || (trp->tp>trp->bp && wstab[(tp-1)->type]))) { + tp->wslen = 0; + return; + } + tp->t[-1] = ' '; + return; + } + if (wstab[tp->type] || (trp->tp>trp->bp && wstab[(tp-1)->type])) + return; + tt = newstring(tp->t, tp->len, 1); + *tt++ = ' '; + tp->t = tt; + tp->wslen = 1; + tp->flag |= XPWS; +} + +/* + * Copy an entire tokenrow into another, at tp. + * It is assumed that there is enough space. + * Not strictly conforming. + */ +void +movetokenrow(Tokenrow *dtr, Tokenrow *str) +{ + int nby; + + /* nby = sizeof(Token) * (str->lp - str->bp); */ + nby = (char *)str->lp - (char *)str->bp; + memmove(dtr->tp, str->bp, nby); +} + +/* + * Move the tokens in a row, starting at tr->tp, rightward by nt tokens; + * nt may be negative (left move). + * The row may need to be grown. + * Non-strictly conforming because of the (char *), but easily fixed + */ +void +adjustrow(Tokenrow *trp, int nt) +{ + int nby, size; + + if (nt==0) + return; + size = (trp->lp - trp->bp) + nt; + while (size > trp->max) + growtokenrow(trp); + /* nby = sizeof(Token) * (trp->lp - trp->tp); */ + nby = (char *)trp->lp - (char *)trp->tp; + if (nby) + memmove(trp->tp+nt, trp->tp, nby); + trp->lp += nt; +} + +/* + * Copy a row of tokens into the destination holder, allocating + * the space for the contents. Return the destination. + */ +Tokenrow * +copytokenrow(Tokenrow *dtr, Tokenrow *str) +{ + int len = rowlen(str); + + maketokenrow(len, dtr); + movetokenrow(dtr, str); + dtr->lp += len; + return dtr; +} + +/* + * Produce a copy of a row of tokens. Start at trp->tp. + * The value strings are copied as well. The first token + * has WS available. + */ +Tokenrow * +normtokenrow(Tokenrow *trp) +{ + Token *tp; + Tokenrow *ntrp = new(Tokenrow); + int len; + + len = trp->lp - trp->tp; + if (len<=0) + len = 1; + maketokenrow(len, ntrp); + for (tp=trp->tp; tp < trp->lp; tp++) { + *ntrp->lp = *tp; + if (tp->len) { + ntrp->lp->t = newstring(tp->t, tp->len, 1); + *ntrp->lp->t++ = ' '; + if (tp->wslen) + ntrp->lp->wslen = 1; + } + ntrp->lp++; + } + if (ntrp->lp > ntrp->bp) + ntrp->bp->wslen = 0; + return ntrp; +} + +/* + * Debugging + */ +void +peektokens(Tokenrow *trp, char *str) +{ + Token *tp; + + tp = trp->tp; + flushout(); + if (str) + fprintf(stderr, "%s ", str); + if (tpbp || tp>trp->lp) + fprintf(stderr, "(tp offset %ld) ", (long int) (tp - trp->bp)); + for (tp=trp->bp; tplp && tpbp+32; tp++) { + if (tp->type!=NL) { + int c = tp->t[tp->len]; + tp->t[tp->len] = 0; + fprintf(stderr, "%s", tp->t); + tp->t[tp->len] = c; + } + if (tp->type==NAME) { + fprintf(stderr, tp==trp->tp?"{*":"{"); + prhideset(tp->hideset); + fprintf(stderr, "} "); + } else + fprintf(stderr, tp==trp->tp?"{%x*} ":"{%x} ", tp->type); + } + fprintf(stderr, "\n"); + fflush(stderr); +} + +void +puttokens(Tokenrow *trp) +{ + Token *tp; + int len, wlen; + uchar *p; + + if (verbose) + peektokens(trp, ""); + tp = trp->bp; + for (; tplp; tp++) { + len = tp->len+tp->wslen; + p = tp->t-tp->wslen; + while (tplp-1 && p+len == (tp+1)->t - (tp+1)->wslen) { + tp++; + len += tp->wslen+tp->len; + } + if (len>OBS/2) { /* handle giant token */ + if (wbp > wbuf) + wlen = write(1, wbuf, wbp-wbuf); + wlen = write(1, (char *)p, len); + wbp = wbuf; + } else { + memcpy(wbp, p, len); + wbp += len; + } + if (wbp >= &wbuf[OBS]) { + wlen = write(1, wbuf, OBS); + if (wbp > &wbuf[OBS]) + memcpy(wbuf, wbuf+OBS, wbp - &wbuf[OBS]); + wbp -= OBS; + } + } + trp->tp = tp; + if (cursource->fd==0) + flushout(); +} + +void +flushout(void) +{ + int wlen; + if (wbp>wbuf) { + wlen = write(1, wbuf, wbp-wbuf); + wbp = wbuf; + } +} + +/* + * turn a row into just a newline + */ +void +setempty(Tokenrow *trp) +{ + trp->tp = trp->bp; + trp->lp = trp->bp+1; + *trp->bp = nltoken; +} + +/* + * generate a number + */ +char * +outnum(char *p, int n) +{ + if (n>=10) + p = outnum(p, n/10); + *p++ = n%10 + '0'; + return p; +} + +/* + * allocate and initialize a new string from s, of length l, at offset o + * Null terminated. + */ +uchar * +newstring(uchar *s, int l, int o) +{ + uchar *ns = (uchar *)domalloc(l+o+1); + + ns[l+o] = '\0'; + return (uchar*)strncpy((char*)ns+o, (char*)s, l) - o; +} diff --git a/src/tools/lcc/cpp/unix.c b/src/tools/lcc/cpp/unix.c new file mode 100644 index 0000000..17986d8 --- /dev/null +++ b/src/tools/lcc/cpp/unix.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include "cpp.h" + +extern int lcc_getopt(int, char *const *, const char *); +extern char *optarg, rcsid[]; +extern int optind; +int verbose; +int Mflag; /* only print active include files */ +char *objname; /* "src.$O: " */ +int Cplusplus = 1; + +void +setup(int argc, char **argv) +{ + int c, fd, i; + char *fp, *dp; + Tokenrow tr; + extern void setup_kwtab(void); + uchar *includeDirs[ NINCLUDE ] = { 0 }; + int numIncludeDirs = 0; + + setup_kwtab(); + while ((c = lcc_getopt(argc, argv, "MNOVv+I:D:U:F:lg")) != -1) + switch (c) { + case 'N': + for (i=0; i", -1, optarg); + maketokenrow(3, &tr); + gettokens(&tr, 1); + doadefine(&tr, c); + unsetsource(); + break; + case 'M': + Mflag++; + break; + case 'v': + fprintf(stderr, "%s %s\n", argv[0], rcsid); + break; + case 'V': + verbose++; + break; + case '+': + Cplusplus++; + break; + default: + break; + } + dp = "."; + fp = ""; + fd = 0; + if (optind + + + + +The lcc 4.1 Code-Generation Interface + + + + +

The lcc 4.1 Code-Generation Interface

+ +
+ +

Contents

+ + +
  • Introduction
  • +
  • 5.1 Type Metrics
  • +
  • 5.3 Symbols
  • +
  • 5.5 Dag Operators
  • +
  • 5.6 Interface Flags
  • +
  • 5.8 Definitions
  • +
  • 5.9 Constants
  • +
  • 5.12 Upcalls
  • +
    + +

    Introduction

    + +

    Version 4.1 is the latest release of lcc, the ANSI C compiler described in +our book A Retargetable C Compiler: Design and Implementation +(Addison-Wesley, 1995, ISBN 0-8053-1670-1). This document summarizes the differences +between the 4.1 code-generation interface and the 3.x interface described in Chap. 5 of A +Retargetable C Compiler.

    + +

    Previous versions of lcc supported only three sizes of integers, two sizes of floats, +and insisted that pointers fit in unsigned integers (see Sec. 5.1 of A Retargetable +C Compiler). These assumptions simplified the compiler, and were suitable for +32-bit architectures. But on 64-bit architectures, such as the DEC ALPHA, it's natural to +have four sizes of integers and perhaps three sizes of floats, and on 16-bit +architectures, 32-bit pointers don't fit in unsigned integers. Also, the 3.x constaints +limited the use of lcc's back ends for other languages, such as Java.

    + +

    Version 4.x removes all of these restrictions: It supports any number of sizes for +integers and floats, and the size of pointers need not be related to the size of any of +the integer types. The major changes in the code-generation interface are: + +

      +
    • The number of type suffixes has been reduced to 6.
    • +
    • Dag operators are composed of a generic operator, a type suffix, and a size.
    • +
    • Unsigned variants of several operators have been added.
    • +
    • Several interface functions have new signatures.
    • +
    + +

    In addition, version 4.x is written in ANSI C and uses the standard I/O library and +other standard C functions.

    + +

    The sections below parallel the subsections of Chap. 5 of A Retargetable C +Compiler and summarize the differences between the 3.x and 4.x code-generation +interface. Unaffected subsections are omitted. Page citations refer to pages in A +Retargetable C Compiler.

    + +

    5.1 Type Metrics

    + +

    There are now 10 metrics in an interface record:

    + +
    Metrics charmetric;
    +Metrics shortmetric;
    +Metrics intmetric;
    +Metrics longmetric;
    +Metrics longlongmetric;
    +Metrics floatmetric;
    +Metrics doublemetric;
    +Metrics longdoublemetric;
    +Metrics ptrmetric;
    +Metrics structmetric;
    + +

    Each of these specifies the size and alignment of the corresponding type. ptrmetric +describes all pointers.

    + +

    5.3 Symbols

    + +

    The actual value of a constant is stored in the u.c.v field of a symbol, +which holds a Value:

    + +
    typedef union value {
    +	long i;
    +	unsigned long u;
    +	long double d;
    +	void *p;
    +	void (*g)(void);
    +} Value;
    + +

    The value is stored in the appropriate field according to its type, which is given by +the symbol's type field.

    + +

    5.5 Dag Operators

    + +

    The op field a of node structure holds a dag operator, which +consists of a generic operator, a type suffix, and a size indicator. The type suffixes +are:

    + +
    enum {
    +	F=FLOAT,
    +	I=INT,
    +	U=UNSIGNED,
    +	P=POINTER,
    +	V=VOID,
    +	B=STRUCT
    +};
    +
    +#define sizeop(n) ((n)<<10)
    + +

    Given a generic operator o, a type suffix t, and a size s, +a type- and size-specific operator is formed by o+t+sizeop(s). For example, ADD+F+sizeop(4) +forms the operator ADDF4, which denotes the sum of two 4-byte floats. +Similarly, ADD+F+sizeop(8) forms ADDF8, which denotes 8-byte +floating addition. In the 3.x code-generation interface, ADDF and ADDD +denoted these operations. There was no size indicator in the 3.x operators because the +type suffix supplied both a type and a size.

    + +

    Table 5.1 lists each generic operator, its valid type suffixes, and the number of kids +and syms that it uses; multiple values for kids indicate +type-specific variants. The notations in the syms column give the number +of syms values and a one-letter code that suggests their uses: 1V indicates +that syms[0] points to a symbol for a variable, 1C indicates that syms[0] +is a constant, and 1L indicates that syms[0] is a label. For 1S, syms[0] +is a constant whose value is a size in bytes; 2S adds syms[1], which is a +constant whose value is an alignment. For most operators, the type suffix and size +indicator denote the type and size of operation to perform and the type and size of the +result.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Table 5.1|Node Operators.
    symskidsOperatorType SuffixesSizesOperation
    1V0ADDRF...P..paddress of a parameter
    1V0ADDRG...P..paddress of a global
    1V0ADDRL...P..paddress of a local
    1C0CNSTFIUP..fdx csilh pconstant
    |
    1BCOM.IU...ilhbitwise complement
    1S1CVFFI....fdx ilhconvert from float
    1S1CVIFIU...fdx csilh csilhpconvert from signed integer
    1S1CVP..U..pconvert from pointer
    1S1CVU.IUP..csilh pconvert from unsigned integer
    1INDIRFIUP.Bfdx csilh pfetch
    1NEGFI....fdx ilhnegation
    |
    2ADDFIUP..fdx ilh ilhp paddition
    2BAND.IU...ilhbitwise AND
    2BOR.IU...ilhbitwise inclusive OR
    2BXOR.IU...ilhbitwise exclusive OR
    2DIVFIU...fdx ilhdivision
    2LSH.IU...ilhleft shift
    2MOD.IU...ilhmodulus
    2MULFIU...fdx ilhmultiplication
    2RSH.IU...ilhright shift
    2SUBFIUP..fdx ilh ilhp psubtraction
    |
    2S2ASGNFIUP.Bfdx csilh passignment
    1L2EQFIU...fdx ilh ilhpjump if equal
    1L2GEFIU...fdx ilh ilhpjump if greater than or equal
    1L2GTFIU...fdx ilh ilhpjump if greater than
    1L2LEFIU...fdx ilh ilhpjump if less than or equal
    1L2LTFIU...fdx ilh ilhpjump if less than
    1L2NEFIU...fdx ilh ilhpjump if not equal
    2S1ARGFIUP.Bfdx ilh pargument
    11 or 2CALLFIUPVBfdx ilh pfunction call
    1RETFIUPV.fdx ilh preturn from function
    |
    1JUMP....V.unconditional jump
    1L0LABEL....V.label definition
    + +

    The entries in the Sizes column indicate sizes of the operators that +back ends must implement. Letters denote the size of float (f), double (d), long double +(x), character (c), short integer (s), integer (i), long integer (l), "long +long" integer (h) , and pointer (p). These sizes are separated into sets for each +type suffix, except that a single set is used for both I and U when the set for I is +identical to the set for U.

    + +

    The actual values for the size indicators, fdxcsilhp, depend on the target. A +specification like ADDFf denotes the operator ADD+F+sizeop(f), +where "f" is replaced by a target-dependent value, e.g., ADDF4 and ADDF8. +For example, back ends must implement the following CVI and MUL +operators.

    + +
    +

    CVIFf CVIFd CVIFx
    + CVIIc CVIIs CVIIi CVIIl CVIIh
    + CVIUc CVIUs CVIUi CVIUl CVIUh + CVIUp
    +
    + MULFf MULFd MULFx
    + MULIi MULIl MULIh
    + MULUi MULUl MULUh

    +
    + +

    On most platforms, there are fewer than three sizes of floats and six sizes of +integers, and pointers are usually the same size as one of the integers. And lcc doesn't +support the "long long" type, so h is not currently used. So the set of +platform-specific operators is usually smaller than the list above suggests. For example, +the X86, SPARC, and MIPS back ends implement the following CVI and MUL +operators.

    + +
    +

    CVIF4 CVIF8
    + CVII1 CVII2 CVII4
    + CVIU1 CVIU2 CVIU4
    +
    + MULF4 MULF8
    + MULI4
    + MULU4

    +
    + +

    The set of operators is thus target-dependent; for example, ADDI8 appears +only if the target supports an 8-byte integer type. ops.c is +a program that, given a set of sizes, prints the required operators and their values, +e.g.,

    + +
    +
    % ops c=1 s=2 i=4 l=4 h=4 f=4 d=8 x=8 p=4
    +...
    + CVIF4=4225 CVIF8=8321
    + CVII1=1157 CVII2=2181 CVII4=4229
    + CVIU1=1158 CVIU2=2182 CVIU4=4230
    +...
    + MULF4=4561 MULF8=8657
    + MULI4=4565
    + MULU4=4566
    +...
    +131 operators
    +
    + +

    The type suffix for a conversion operator denotes the type of the result and the size +indicator gives the size of the result. For example, CVUI4 converts an +unsigned (U) to a 4-byte signed integer (I4). The syms[0] +field points to a symbol-table entry for a integer constant that gives the size of the +source operand. For example, if syms[0] in a CVUI4 points to a +symbol-table entry for 2, the conversion widens a 2-byte unsigned integer to a 4-byte +signed integer. Conversions that widen unsigned integers zero-extend; those that widen +signed integers sign-extend.

    + +

    The front end composes conversions between types T1 and T2 +by widening T1 to its "supertype", if necessary, converting +that result to T2's supertype, then narrowing the result to T2, +if necessary. The following table lists the supertypes; omitted entries are their own +supertypes.

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Type|Supertype
    signed charint
    signed shortint
    unsigned charint, if sizeof (char) < sizeof (int)
    + unsigned, otherwise
    unsigned shortint, if sizeof (short) < sizeof (int)
    + unsigned, otherwise
    void *an unsigned type as large as a pointer
    +
    + +

    Pointers are converted to an unsigned type of the same size, even when that type is not +one of the integer types.

    + +

    For example, the front end converts a signed short to a float by first converting it to +an int and then to a float. It converts an unsigned short to an int with a single CVUIi +conversion, when shorts are smaller than ints.

    + +

    There are now signed and unsigned variants of ASGN, INDIR, BCOM, +BOR, BXOR, BAND, ARG, CALL, +and RET to simplify code generation on platforms that use different +instructions or register set for signed and unsigned operations. Likewise there are now +pointer variants of ASGN, INDIR, ARG, CALL, +and RET.

    + +

    5.6 Interface Flags

    + +
    unsigned unsigned_char:1;
    + +

    tells the front end whether plain characters are signed or unsigned. If it's zero, char +is a signed type; otherwise, char is an unsigned type.

    + +

    All the interface flags can be set by command-line options, e.g., -Wf-unsigned_char=1 +causes plain characters to be unsigned.

    + +

    5.8 Definitions

    + +

    The front end announces local variables by calling

    + +
    void (*local)(Symbol);
    + +

    It announces temporaries likewise; these have the symbol's temporary flag +set, which indicates that the symbol will be used only in the next call to gen. +If a temporary's u.t.cse field is nonnull, it points to the node that +computes the value assigned to the temporary; see page 346.

    + +

    The front end calls

    + +
    void (*address)(Symbol p, Symbol q, long n);
    + +

    to initialize q to a symbol that represents an address of the form x+n, +where x is the address represented by p and the long integer n +is positive or negative.

    + +

    5.9 Constants

    + +

    The interface function

    + +
    void (*defconst)(int suffix, int size, Value v);
    + +

    initializes constants. defconst emits directives to define a cell and initialize it to +a constant value. v is the constant value, suffix identifies the type of the value, and +size is the size of the value in bytes. The value of suffix indicates which field of v +holds the value, as shown in the following table.

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    suffix|v Field|size
    Fv.dfloat, double, long double
    Iv.isigned char, signed short, signed int, signed long
    Uv.uunsigned char, unsigned short, unsigned int, unsigned long
    Pv.pvoid *
    +
    + +

    defconst must narrow v.x when size is less than sizeof +v.x; e.g., to emit an unsigned char, defconst should emit (unsigned +char)v.i.

    + +

    5.12 Upcalls

    + +

    lcc 4.x uses standard I/O and its I/O functions have been changed accordingly. lcc +reads input from the standard input, emits code to the standard output, and writes +diagnostics to the standard error output. It uses freopen to redirect these +streams to explicit files, when necessary.

    + +

    bp, outflush, and outs have been eliminated.

    + +
    extern void fprint(FILE *f, const char *fmt, ...);
    +extern void  print(const char *fmt, ...);
    + +

    print formatted data to file f (fprint) or the standard +output (print). These functions are like standard C's printf and +fprintf, but support only some of the standard conversion specifiers and do +not support flags, precision, and field-width specifications. They support the following +new conversion specifiers in addition to those described on page 99.

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Specifiers|Corresponding printf Specifiers
    %c%c
    %d %D%d %ld
    %u %U%u %lu
    %x %X%x %lx
    %f %e %g%e %f %g
    %pConverts the corresponding void * argument to unsigned long and prints it with the printf + %#x specifier or just %x when the argument is null.
    %IPrints the number of spaces given by the corresponding argument.
    +
    + +
    #define generic(op)  ((op)&0x3F0)
    +#define specific(op) ((op)&0x3FF)
    + +

    generic(op) returns the generic variant of op; that is, +without its type suffix and size indicator. specific(op) returns the +type-specific variant of op; that is, without its size indicator.

    + +

    newconst has been replaced by

    + +
    extern Symbol intconst(int n);
    + +

    which installs the integer constant n in the symbol table, if necessary, +and returns a pointer to the symbol-table entry.

    + +
    + +
    + Chris Fraser / cwfraser@microsoft.com
    + David Hanson / drh@microsoft.com
    + $Revision: 145 $ $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $ +
    + + diff --git a/src/tools/lcc/doc/bprint.1 b/src/tools/lcc/doc/bprint.1 new file mode 100644 index 0000000..8cf9971 --- /dev/null +++ b/src/tools/lcc/doc/bprint.1 @@ -0,0 +1,83 @@ +.\" $Id: bprint.1 145 2001-10-17 21:53:10Z timo $ +.TH BPRINT 1 "local \- $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $" +.SH NAME +bprint \- expression profiler +.SH SYNOPSIS +.B bprint +[ +.I option ... +] +[ +.I file ... +] +.SH DESCRIPTION +.I bprint +produces on the standard output a listing of the programs compiled by +.I lcc +with the +.B \-b +option. +Executing an +.B a.out +so compiled appends profiling data to +.BR prof.out . +The first token of each expression in the listing is preceded +by the number of times it was executed +enclosed in angle brackets as determined from the data in +.BR prof.out . +.I bprint +interprets the following options. +.TP +.B \-c +Compress the +.B prof.out +file, which otherwise grows with every execution of +.BR a.out . +.TP +.B \-b +Print an annotated listing as described above. +.TP +.B \-n +Include line numbers in the listing. +.TP +.B \-f +Print only the number of invocations of each function. +A second +.B \-f +summarizes call sites instead of callers. +.TP +.BI \-I \*Sdir +specifies additional directories in which to seek +files given in +.B prof.out +that do not begin with `/'. +.PP +If any file names are given, only the requested data for those files are printed +in the order presented. +If no options are given, +.B \-b +is assumed. +.SH FILES +.PP +.ta \w'$LCCDIR/liblcc.{a,lib}XX'u +.nf +prof.out profiling data +$LCCDIR/liblcc.{a,lib} \fIlcc\fP-specific library +.SH "SEE ALSO" +.IR lcc (1), +.IR prof (1) +.SH BUGS +Macros and comments can confuse +.I bprint +because it uses post-expansion source coordinates +to annotate pre-expansion source files. +If +.I bprint +sees that it's about to print a statement count +.I inside +a number or identifier, it moves the count to just +.I before +the token. +.PP +Can't cope with an ill-formed +.BR prof.out . diff --git a/src/tools/lcc/doc/bprint.pdf b/src/tools/lcc/doc/bprint.pdf new file mode 100644 index 0000000..1b11963 Binary files /dev/null and b/src/tools/lcc/doc/bprint.pdf differ diff --git a/src/tools/lcc/doc/install.html b/src/tools/lcc/doc/install.html new file mode 100644 index 0000000..3cc59a8 --- /dev/null +++ b/src/tools/lcc/doc/install.html @@ -0,0 +1,796 @@ + + + + + +Installing lcc + + + + +

    Installing lcc

    + +

    Christopher +W. Fraser and David R. Hanson, Microsoft Research

    + +

    Contents

    + + +
  • Introduction
  • +
  • Installation on UNIX
  • +
  • Building the Driver
  • +
  • Building the Compiler and Accessories
  • +
  • Installation on Windows NT 4.0 and Windows 95/98
  • +
  • Reporting Bugs
  • +
  • Keeping in Touch
  • +
    + +

    Introduction

    + +

    lcc is the ANSI C compiler +described in our book A Retargetable C Compiler: Design and Implementation +(Addison-Wesley, 1995, ISBN 0-8053-1670-1).

    + +

    If you're installing lcc on a UNIX system, read the remainder of this section and +continue with the next section. If you're installing lcc on a Windows NT 4.0 or Windows +95/98 system, and you intend only to use lcc, you can run the InstallShield executable, +which installs the binaries and the documentation. If you want to modify lcc or rebuild +it from the source files, you need the complete distribution, and +you should read the rest of the section, the following three sections, and the Windows NT/95/98 section.

    + +

    Extract the distribution into its own directory. All non-absolute paths below are +relative to this directory. The distribution holds the following subdirectories.

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    srcsource code
    etcdriver, accessories
    libruntime library source code
    cpppreprocessor source code
    lburgcode-generator generator source code
    docthis document, man pages
    include/*/*include files
    tsttest suite
    alpha/*/tstALPHA test outputs
    mips/*/tstMIPS test outputs
    sparc/*/tstSPARC test outputs
    x86/*/tstX86 test outputs
    +
    + +

    doc/install.html is the HTML file for this document. doc/4.html +describes the internal differences between lcc 3.x and 4.1.

    + +

    The installation makefile is designed so that lcc can be installed from a read-only +file system or directory, which is common in networked environments, so the distribution +can be unloaded on a central file server. You will need an existing ANSI/ISO C +compiler to build and install lcc.

    + +

    Installation on UNIX

    + +

    The compilation components (the preprocessor, include files, and compiler proper, etc.) +are installed in a single build directory. On multi-platform systems supported by +a central file server, it's common to store the build directory in a location specific to +the platform and to the version of lcc, and to point a symbolic link to this location. For +example,

    + +
    +
    % ln -s /usr/local/lib/lcc-4.1/sparc-solaris /usr/local/lib/lcc
    +
    + +

    points /usr/local/lib/lcc to a build directory for lcc version 4.1 on the +SPARC under Solaris. Links into /usr/local/lib are created for the programs lcc +and bprint. Thus, a new distribution can be installed by building it in its +own build directory and changing one symbolic link to point to that directory. If these +conventions or their equivalents are followed, the host-specific parts of the driver +program, lcc, can be used unmodified.

    + +

    Installation on a UNIX system involves the following steps. Below, the build directory +is referred to as BUILDDIR. + +

      +
    1. Create the build directory, using a version- and platform-specific naming convention as + suggested above, and record the name of this directory in the BUILDDIR + environment variable:
      +
      % setenv BUILDDIR /usr/local/lib/lcc-4.1/sparc-solaris
      +% mkdir -p $BUILDDIR
      +
      +

      Here and below, commands assume the C shell. Also, you'll need a version of mkdir + that supports the -p option, which creates intermediate directories as + necessary.

      +
    2. +
    3. Copy the man pages to the repository for local man pages, e.g.,
      +
      % cp doc/*.1 /usr/local/man/man1
      +
      +

      Some users copy the man pages to the build directory and create the appropriate + symbolic links, e.g.,

      +
      +
      % cp doc/*.1 $BUILDDIR
      +% ln -s $BUILDDIR/*.1 /usr/local/man/man1
      +
      +
    4. +
    5. Platform-specific include files are in directories named include/target/os. + Create the include directory in the build directory, and copy the include hierarchy for + your platform to this directory, e.g.,
      +
      % mkdir $BUILDDIR/include
      +% cp -p -R include/sparc/solaris/* $BUILDDIR/include
      +
      +

      Again, some users create a symbolic link to the appropriate directory in the + distribution instead of copying the include files. For example, at Princeton, the + distributions are stored under /proj/pkg/lcc, so the included files are + "installed" by creating one symbolic link:

      +
      +
      % ln -s /proj/pkg/lcc/4.1/include/sparc/solaris $BUILDDIR/include
      +
      +

      If you're installing lcc on Linux, you must also plant a symbolic link named gcc + to gcc's library directory, because lcc uses gcc's C preprocessor and most of gcc's header + files:

      +
      +
      % ln -s /usr/lib/gcc-lib/i486-linux/2.7.2.2 $BUILDDIR/gcc
      +
      +

      The library directory shown above may be different on your Linux machine; to determine + the correct directory, browse /usr/lib/gcc-lib, or execute

      +
      +
      % cc -v tst/8q.c
      +
      +

      and examine the diagnostic output. Make sure that $BUILDDIR/gcc/cpp and $BUILDDIR/gcc/include + point to, respectively, gcc's C preprocessor and header files. On Linux, lcc looks for + include files in $BUILDDIR/include, $BUILDDIR/gcc/include, and /usr/include, + in that order; see Building the Driver and etc/linux.c for details.

      +
    6. +
    7. The makefile includes the file named by the CUSTOM + macro; the default is custom.mk, and an empty custom.mk is + included in the distribution. If desired, prepare a site-specification customization file + and define CUSTOM to the path of that file when invoking make in steps 5 and + 6, e.g.,
      +
      make CUSTOM=/users/drh/solaris.mk
      +
      +

      You can, for example, use customization files to record site-specific values for macros + instead of using environment variables, and to record targets for the steps in this list.

      +
    8. +
    9. Build the host-specific driver, creating a custom host-specific part, if necessary. See Building the Driver.
    10. +
    11. Build the preprocessor, compiler proper, library, and other accessories. See Building the Compiler.
    12. +
    13. Plant symbolic links to the build directory and to the installed programs, e.g.,
      +
      % ln -s $BUILDDIR /usr/local/lib/lcc
      +% ln -s /usr/local/lib/{lcc,bprint} /usr/local/bin
      +
      +

      Some users copy bprint and lcc into /usr/local/bin + instead of creating symbolic links. The advantange of creating the links for lcc + and bprint as shown is that, once established, they point indirectly to + whatever /usr/local/lib/lcc points to; installing a new version of lcc, say, + 4.2, can be done by changing /usr/local/lib/lcc to point to the 4.2 build + directory.

      +
    14. +
    + +

    Building the Driver

    + +

    The preprocessor, compiler, assembler, and loader are invoked by a driver program, lcc, +which is similar to cc on most systems. It's described in the man page doc/lcc.1. +The driver is built by combining the host-independent part, etc/lcc.c, +with a small host-specific part. Distributed host-specific parts are named etc/os.c, +where os is the name of the operating system for the host on which lcc +is being installed. If you're following the installations conventions described above, you +can probably use one of the host-specific parts unmodified; otherwise, pick one that is +closely related to your platform, copy it to whatever.c, and edit it +as described below. You should not have to edit etc/lcc.c.

    + +

    We'll use etc/solaris.c as an example in +describing how the host-specific part works. This example illustrates all the important +features. Make sure you have the environment variable BUILDDIR set correctly, +and build the driver with a make command, e.g.,

    + +
    +
    % make HOSTFILE=etc/solaris.c lcc
    +cc -g -c -DTEMPDIR=\"/tmp\" -o /usr/local/lib/lcc-4.1/sparc-solaris/lcc.o etc/lcc.c
    +cc -g -c -o /usr/local/lib/lcc-4.1/sparc-solaris/host.o etc/solaris.c
    +cc -g -o /usr/local/lib/lcc-4.1/sparc-solaris/lcc /usr/local/lib/lcc-4.1/sparc-solaris/lcc.o /usr/local/lib/lcc-4.1/sparc-solaris/host.o
    +
    + +

    The symbolic name HOSTFILE specifies the path to the host-specific part, +either one in the distribution or whatever.c. Some versions of make +may require the -e option in order to read the environment.

    + +

    Here's etc/solaris.c:

    + +
    +
    /* Sparcs running Solaris 2.5.1 at CS Dept., Princeton University */
    +
    +#include <string.h>
    +
    +static char rcsid[] = "$ Id: solaris.c,v 1.10 1998/09/14 20:36:33 drh Exp $";
    +
    +#ifndef LCCDIR
    +#define LCCDIR "/usr/local/lib/lcc/"
    +#endif
    +#ifndef SUNDIR
    +#define SUNDIR "/opt/SUNWspro/SC4.2/lib/"
    +#endif
    +
    +char *suffixes[] = { ".c", ".i", ".s", ".o", ".out", 0 };
    +char inputs[256] = "";
    +char *cpp[] = { LCCDIR "cpp",
    +	"-D__STDC__=1", "-Dsparc", "-D__sparc__", "-Dsun", "-D__sun__", "-Dunix",
    +	"$1", "$2", "$3", 0 };
    +char *include[] = { "-I" LCCDIR "include", "-I/usr/local/include",
    +	"-I/usr/include", 0 };
    +char *com[] = { LCCDIR "rcc", "-target=sparc/solaris",
    +	"$1", "$2", "$3", 0 };
    +char *as[] = { "/usr/ccs/bin/as", "-Qy", "-s", "-o", "$3", "$1", "$2", 0 };
    +char *ld[] = { "/usr/ccs/bin/ld", "-o", "$3", "$1",
    +	SUNDIR "crti.o", SUNDIR "crt1.o",
    +	SUNDIR "values-xa.o", "$2", "",
    +	"-Y", "P," SUNDIR ":/usr/ccs/lib:/usr/lib", "-Qy",
    +	"-L" LCCDIR, "-llcc", "-lm", "-lc", SUNDIR "crtn.o", 0 };
    +
    +extern char *concat(char *, char *);
    +
    +int option(char *arg) {
    +	if (strncmp(arg, "-lccdir=", 8) == 0) {
    +		cpp[0] = concat(&arg[8], "/cpp");
    +		include[0] = concat("-I", concat(&arg[8], "/include"));
    +		ld[12] = concat("-L", &arg[8]);
    +		com[0] = concat(&arg[8], "/rcc");
    +	} else if (strcmp(arg, "-p") == 0) {
    +		ld[5] = SUNDIR "mcrt1.o";
    +		ld[10] = "P," SUNDIR "libp:/usr/ccs/lib/libp:/usr/lib/libp:"
    +			 SUNDIR ":/usr/ccs/lib:/usr/lib";
    +	} else if (strcmp(arg, "-b") == 0)
    +		;
    +	else if (strncmp(arg, "-ld=", 4) == 0)
    +		ld[0] = &arg[4];
    +	else
    +		return 0;
    +	return 1;
    +}
    +
    + +

    LCCDIR defaults to "/usr/local/lib/lcc/" unless +it's defined by a -D option as part of CFLAGS in the make +command, e.g.,

    + +
    +
    % make HOSTFILE=etc/solaris.c CFLAGS='-DLCCDIR=\"/v/lib/lcc/\"' lcc
    +
    + +

    Note the trailing slash; SUNDIR is provided so you can use etc/solaris.c +even if you have a different version of the Sun Pro compiler suite. If you're using the +gcc compiler tools instead of the Sun Pro tools, see etc/gcc-solaris.c.

    + +

    Most of the host-specific code is platform-specific data and templates for the commands +that invoke the preprocessor, compiler, assembler, and loader. The suffixes +array lists the file name suffixes for C source files, preprocessed source files, assembly +language source files, object files, and executable files. suffixes must be +terminated with a null pointer, as shown above. The initialization of suffixes +in etc/solaris.c are the typical ones for UNIX +systems. Each element of suffixes is actually a list of suffixes, separated +by semicolons; etc/win32.c holds an example:

    + +
    +
    char *suffixes[] = { ".c;.C", ".i;.I", ".asm;.ASM;.s;.S", ".obj;.OBJ", ".exe", 0 };
    +
    + +

    When a list is given, the first suffix is used whenever lcc needs to generate a file +name. For example, with etc/win32.c, lcc emits +the generated assembly code into .asm files.

    + +

    The inputs array holds a null-terminated string of directories separated +by colons or semicolons. These are used as the default value of LCCINPUTS, if +the environment variable LCCINPUTS is not set; see the man +page.

    + +

    Each command template is an array of pointers to strings terminated with a null +pointer; the strings are full path names of commands, arguments, or argument placeholders, +which are described below. Commands are executed in a child process, and templates can +contain multiple commands by separating commands with newlines. The driver runs each +command in a new process.

    + +

    The cpp array gives the command for running lcc's preprocessor, cpp. +Literal arguments specified in templates, e.g., "-Dsparc" in the cpp +command above, are passed to the command as given.

    + +

    The strings "$1", "$2", and "$3" +in templates are placeholders for lists of arguments that are substituted in a +copy of the template before the command is executed. $1 is replaced by the options +specified by the user; for the preprocessor, this list always contains at least -D__LCC__. +$2 is replaced by the input files, and $3 is replaced +by the output file.

    + +

    Zero-length arguments after replacement are removed from the argument list before the +command is invoked. So, for example, if the preprocessor is invoked without an output +file, "$3" becomes "", which is removed from +the final argument list.

    + +

    The include array is a list of -I options that specify which +directives should be searched to satisfy include directives. These directories are +searched in the order given. The first directory should be the one to which the ANSI +header files were copied as described in UNIX or Windows +installation instructions. The driver adds these options to cpp's arguments +when it invokes the preprocessor, except when -N is specified.

    + +

    com gives the command for invoking the compiler. This template can appear +as shown above in a custom host-specific part, but the option -target=sparc/solaris +should be edited to the target/os for your platform. If com[1] +includes the string "win32", the driver assumes it's running on +Windows. lcc can generate code for all of the target/os +combinations listed in the file src/bind.c. The -target option +specifies the default combination. The driver's -Wf option can be used to +specify other combinations; the man page elaborates.

    + +

    as gives the command for invoking the assembler. On Linux, you must be +running at least version 2.8.1 of the GNU assembler; earlier versions mis-assemble some +instructions emitted by lcc.

    + +

    ld gives the command for invoking the loader. For the other commands, the +list $2 contains a single file; for ld, $2 contains +all ".o" files and libraries, and $3 is a.out, unless +the -o option is specified. As suggested in the code above, ld +must also specify the appropriate startup code and default libraries, including the lcc +library, liblcc.a.

    + +

    The option function is described below; the minimal option +function just returns 0.

    + +

    You can test lcc with the options -v -v to display the +commands that would be executed, e.g.,

    + +
    +
    % $BUILDDIR/lcc -v -v foo.c baz.c mylib.a -lX11
    +/usr/local/lib/lcc-4.1/lcc $ Id: solaris.c,v 1.10 1998/09/14 20:36:33 drh Exp $
    +foo.c:
    +/usr/local/lib/lcc/cpp -D__STDC__=1 -Dsparc -D__sparc__ -Dsun -D__sun__ -Dunix -D__LCC__ -I/usr/local/lib/lcc/include -I/usr/local/include -I/usr/include foo.c /tmp/lcc266290.i
    +/usr/local/lib/lcc/rcc -target=sparc/solaris -v /tmp/lcc266290.i /tmp/lcc266291.
    +s
    +/usr/ccs/bin/as -Qy -s -o /tmp/lcc266292.o /tmp/lcc266291.s
    +baz.c:
    +/usr/local/lib/lcc/cpp -D__STDC__=1 -Dsparc -D__sparc__ -Dsun -D__sun__ -Dunix -D__LCC__ -I/usr/local/lib/lcc/include -I/usr/local/include -I/usr/include baz.c /tmp/lcc266290.i
    +/usr/local/lib/lcc/rcc -target=sparc/solaris -v /tmp/lcc266290.i /tmp/lcc266291.s
    +/usr/ccs/bin/as -Qy -s -o /tmp/lcc266293.o /tmp/lcc266291.s
    +/usr/ccs/bin/ld -o a.out /opt/SUNWspro/SC4.2/lib/crti.o /opt/SUNWspro/SC4.2/lib/crt1.o /opt/SUNWspro/SC4.2/lib/values-xa.o /tmp/lcc266292.o /tmp/lcc266293.o mylib.a -lX11 -Y P,/opt/SUNWspro/SC4.2/lib/:/usr/ccs/lib:/usr/lib -Qy -L/usr/local/lib/lcc/ -llcc -lm -lc /opt/SUNWspro/SC4.2/lib/crtn.o
    +rm /tmp/lcc266293.o /tmp/lcc266290.i /tmp/lcc266291.s /tmp/lcc266292.o
    +
    + +

    As the output shows, lcc places temporary files in /tmp; if +any of the environment variables TMP, TEMP, and TMPDIR +are set, they override this default (in the order shown) as does the -tempdir=dir +option. The default can be changed by defining TEMPDIR in CFLAGS +when building the driver.

    + +

    The option function is called for the options -Wo, -g, +-p, -pg, and -b because these compiler options +might also affect the loader's arguments. For these options, the driver calls option(arg) +to give the host-specific code an opportunity to edit the ld command, if +necessary. option can change ld, if necessary, and return 1 to +announce its acceptance of the option. If the option is unsupported, option +should return 0.

    + +

    For example, in response to -g, the option function shown +above accepts the option but does nothing else, because the ld and as +commands don't need to be modified on the SPARC. -g will also be added to the +compiler's options by the host-independent part of the driver. The -p causes option +to change the name of the startup code and changed the list of libraries. The -b +option turns on lcc's per-expression profiling, the code for which is in liblcc.a, +so option need no nothing.

    + +

    On SPARCs, the driver also recognizes -Bstatic and -Bdynamic +as linker options. The driver recognizes but ignores "-target name" +option.

    + +

    The option -Woarg causes the driver to pass arg to option. +Such options have no other effect; this mechanism is provided to support system-specific +options that affect the commands executed by the driver. As illustrated above, +host-specific parts should support the -Wo-lccdir=dir option, which +causes lcc's compilation components to be found in dir, because this option is +used by the test scripts, and because the driver simulates a -Wo-lccdir +option with the value of the environment variable LCCDIR, if it's defined. +The code above rebuilds the paths to the include files, preprocessor, compiler, and +library by calling concat, which is defined in etc/lcc.c.

    + +

    Building the Compiler and Accessories

    + +

    To build the rest of compilation components make sure BUILDDIR is set +appropriately and type "make all". This command builds librcc.a +(the compiler's private library), rcc (the compiler proper), lburg +(the code-generator generator), cpp (the preprocessor), liblcc.a +(the runtime library), and bprint (the profile printer), all in BUILDDIR. +There may be warnings, but there should be no errors. If you're using an ANSI/ISO compiler +other than cc, specify its name with the CC= option, e.g., +"make CC=gcc all". If you're running on a DEC ALPHA, use "make +CC='cc -std1' all"; the -std1 option is essential on the ALPHA. +If you're on a DEC 5000 running Ultrix 4.3, use "make CC=c89 all".

    + +

    Once rcc is built with the host C compiler, run the test suite to verify +that rcc is working correctly. If any of the steps below fail, contact us +(see Reporting Bugs). The commands in the makefile run the +shell script src/run.sh on each C program in the test suite, tst/*.c. +It uses the driver, $BUILDDIR/lcc, so you must have the driver in the build +directory before testing rcc. The target/os +combination is read from the variable TARGET, which must be specified when +invoking make:

    + +
    +
    % make TARGET=sparc/solaris test
    +mkdir -p /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/8q.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/array.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/cf.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/cq.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/cvt.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/fields.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/front.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/incr.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/init.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/limits.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/paranoia.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/sort.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/spill.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/stdarg.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/struct.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/switch.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/wf1.s:
    +/usr/local/lib/lcc-4.1/sparc-solaris/rcc -target=sparc/solaris /usr/local/lib/lcc-4.1/sparc-solaris/sparc/solaris/tst/yacc.s:
    +
    + +

    Each line in the output above is of the form

    + +
    +

    $BUILDDIR/rcc -target=target/os$BUILDDIR/target/os/X.s:

    +
    + +

    where X is the base name of the C program X.c in the +test suite. This output identifies the compiler and the target, e.g., "$BUILDDIR/rcc +is generating code for a sparc running the solaris operating +system."

    + +

    For each program in the test suite, src/run.sh compiles the program, drops +the generated assembly language code in BUILDDIR/target/os, +and uses diff to compare the generated assembly code with the expected code +(the code expected for tst/8q.c on the SPARC under Solaris is in sparc/solaris/tst/8q.sbk, +etc.). If there are differences, the script executes the generated code with the input +given in tst (the input for tst/8q.c is in tst/8q.0, +etc.) and compares the output with the expected output (the expected output from tst/8q.c +on the SPARC under Solaris is in sparc/solaris/tst/8q.1bk, etc.). The script +also compares the diagnostics from the compiler with the expected diagnostics.

    + +

    On some systems, there may be a few differences between the generated code and the +expected code. These differences occur because the expected code is generated by cross +compilation and the least significant bits of some floating-point constants differ from +those bits in constants generated on your system. On Linux, there may be differences +because of differences in the header files between our system and yours. There should be +no differences in the output from executing the test programs.

    + +

    Next, run the "triple test", which builds rcc using itself:

    + +
    +
    % make triple
    +/usr/local/lib/lcc-4.1/sparc-solaris/lcc -o /usr/local/lib/lcc-4.1/sparc-solaris/1rcc -d0.6 -Wo-lccdir=/usr/local/lib/lcc-4.1/sparc-solaris -B/usr/local/lib/lcc-4.1/sparc-solaris/  -Isrc src/*.c
    +src/alloc.c:
    +...
    +src/x86.c:
    +/usr/local/lib/lcc-4.1/sparc-solaris/lcc -o /usr/local/lib/lcc-4.1/sparc-solaris/1rcc -d0.6 -Wo-lccdir=/usr/local/lib/lcc-4.1/sparc-solaris -B/usr/local/lib/lcc-4.1/sparc-solaris/  -Isrc src/*.c
    +src/alloc.c:
    +...
    +src/x86.c:
    +strip /usr/local/lib/lcc-4.1/sparc-solaris/[12]rcc
    +dd if=/usr/local/lib/lcc-4.1/sparc-solaris/1rcc of=/usr/local/lib/lcc-4.1/sparc-solaris/rcc1 bs=512 skip=1
    +769+1 records in
    +769+1 records out
    +dd if=/usr/local/lib/lcc-4.1/sparc-solaris/2rcc of=/usr/local/lib/lcc-4.1/sparc-solaris/rcc2 bs=512 skip=1
    +769+1 records in
    +769+1 records out
    +if cmp /usr/local/lib/lcc-4.1/sparc-solaris/rcc[12]; then \
    +        mv /usr/local/lib/lcc-4.1/sparc-solaris/2rcc /usr/local/lib/lcc-4.1/sparc-solaris/rcc; \
    +        rm -f /usr/local/lib/lcc-4.1/sparc-solaris/1rcc /usr/local/lib/lcc-4.1/sparc-solaris/rcc[12]; fi
    +
    + +

    This command builds rcc twice; once using the rcc built by cc +and again using the rcc built by lcc. The resulting binaries are +compared. They should be identical, as shown at the end of the output above. If they +aren't, our compiler is generating incorrect code; contact us.

    + +

    The final version of rcc should also pass the test suite; that is, the +output from

    + +
    +
    % make TARGET=sparc/solaris test
    +
    + +

    should be identical to that from the previous make test.

    + +

    The command "make clean" cleans up, but does not remove rcc, +etc., and "make clobber" cleans up and removes lcc, rcc, +and the other accessories. Test directories under BUILDDIR are not +removed; you'll need to remove these by hand, e.g.,

    + +
    +
    % rm -fr $BUILDDIR/sparc
    +
    + +

    The code generators for the other targets can be tested by specifying the desired target/os +and setting an environment variable that controls what src/run.sh does. For +example, to test the MIPS code generator, type

    + +
    +
    % setenv REMOTEHOST noexecute
    +% make TARGET=mips/irix test
    +
    + +

    As above, src/run.sh compares the MIPS code generated with what's +expected. There should be no differences. Setting REMOTEHOST to noexecute +suppresses the assembly and execution of the generated code. If you set REMOTEHOST +to the name of a MIPS machine to which you can rlogin, src/run.sh +will rcp the generated code to that machine and execute it there, if +necessary. See src/run.sh for the details.

    + +

    You can use lcc as a cross compiler. The options -S and -Wf-target=target/os +generate assembly code for the specified target, which is any of those listed in the file src/bind.c. +For example,

    + +
    +
    % lcc -Wf-target=mips/irix -S tst/8q.c
    +
    + +

    generates MIPS code for tst/8q.c in 8q.s.

    + +

    lcc can also generate code for a "symbolic" target. This target is used +routinely in front-end development, and its output is a printable representation of the +input program, e.g., the dags constructed by the front end are printed, and other +interface functions print their arguments. You can specify this target with the option -Wf-target=symbolic. +For example,

    + +
    +
    % lcc -Wf-target=symbolic -S tst/8q.c
    +
    + +

    generates symbolic output for tst/8q.c in 8q.s. Adding -Wf-html +causes the symbolic target to emit HTML instead of plain text. Finally, the option -Wf-target=null +specifies the "null" target for which lcc emits nothing and thus only checks the +syntax and semantics of its input files.

    + +

    Installation on Windows NT 4.0 or Windows 95/98

    + +

    On Windows NT 4.0 and Windows 95/98, lcc is designed to work with Microsoft's Visual +C++ 5.0 (VC) and Microsoft's Assembler, MASM 6.11d. It uses the VC header files, +libraries, and command-line tools, and it uses MASM to assemble the code it generates. If +you have MASM 6.11, make sure you upgrade to 6.11d, +because earlier 6.11 releases do not generate correct COFF object files.

    + +

    Building the distribution components from the ground up requires Microsoft's Visual +C/C++ 5.0 compiler, Microsoft's make, nmake, and the standard Windows command +interpreter. makefile.nt is written to use only nmake. +As on UNIX systems, the compilation components are installed in a single build +directory, and the top-level programs, lcc.exe and bprint.exe, +are installed in a directory on the PATH. If the conventions used below are followed, the +Windows-specific parts of the driver program, lcc.exe, can be used +unmodified.

    + +

    Building from the source distribution on a Windows system involves the following steps. +Below, the build directory is referred to as BUILDDIR, and the distribution +is in \dist\lcc\4.1. + +

      +
    1. Create the build directory, perhaps using a version- and platform-specific naming + convention as suggested in Installation on UNIX, and record + the name of this directory in the BUILDDIR environment variable:
      +
      C:\dist\lcc\4.1>set BUILDDIR=\progra~1\lcc\4.1\bin
      +C:\dist\lcc\4.1>mkdir %BUILDDIR%
      +
      +

      The default build, or installation, directory is \Program Files\lcc\4.1\bin, + but the nmake commands require that you use the corresponding 8.3 file name, progra~1, + instead of Program Files.

      +
    2. +
    3. etc\win32.c is the Windows-specific part of + the driver. It assumes that environment variable include gives the locations + of the VC header files and that the linker (link.exe) and the assembler (ml.exe) + are on the PATH. It also assumes that the macro LCCDIR gives the build + directory. If necessary, revise a copy of etc\win32.c + to reflect the conventions on your computer (see Building the Driver), + then build the driver, specifying the default temporary directory, if necessary:
      +
      C:\dist\lcc\4.1>nmake -f makefile.nt TEMPDIR=\\temp HOSTFILE=etc/win32.c lcc
      +...
      +        cl -nologo -Zi -MLd -Fd\progra~1\lcc\4.1\bin\ -c -DTEMPDIR=\"\\temp\" -Fo\progra~1\lcc\4.1\bin\lcc.obj etc/lcc.c
      +lcc.c
      +        cl -nologo -Zi -MLd -Fd\progra~1\lcc\4.1\bin\ -c -Fo\progra~1\lcc\4.1\bin\host.obj etc/win32.c
      +win32.c
      +        cl -nologo -Zi -MLd -Fd\progra~1\lcc\4.1\bin\ -Fe\progra~1\lcc\4.1\bin\lcc.exe \progra~1\lcc\4.1\bin\lcc.obj \progra~1\lcc\4.1\bin\host.obj
      +
      +

      If you make a copy of etc\win32.c, specify the path of the copy as the + value of HOSTFILE. For example, if you copy etc\win32.c to BUILDDIR + and edit it, use the command

      +
      +
      C:\dist\lcc\4.1>nmake -f makefile.nt TEMPDIR=\\temp HOSTFILE=%BUILDDIR%\win32.c lcc
      +
      +
    4. +
    5. Build the preprocessor, compiler proper, library, and other accessories (see Building the Compiler):
      +
      C:\dist\lcc\4.1>nmake -f makefile.nt all
      +
      +

      This command uses the VC command-line tools cl and lib to + build bprint.exe, cpp.exe, lburg.exe, liblcc.lib, + librcc.lib, and rcc.exe, all in BUILDDIR. There may + be some warnings, but there should be no warnings.

      +
    6. +
    7. Create a test directory and run the test suite:
      +
      C:\dist\lcc\4.1>mkdir %BUILDDIR%\x86\win32\tst
      +C:\dist\lcc\4.1>nmake -f makefile.nt test
      +
      +

      This command compiles each program in tst, compares the generated + assembly code and diagnostics with the expected assembly code and diagnostics, executes + the program, and compares the output with the expected output (using fc). For + example, when the nmake command compiles tst\8q.c, + it leaves the generated assembly code and diagnostic output in %BUILDDIR%\x86\win32\tst\8q.s + and %BUILDDIR%\x86\win32\tst\8q.2, and it compares them with the expected + results in x86\win32\tst\8q.sbk. It builds the executable program in %BUILDDIR%\x86\win32\tst\8q.exe, + runs it, and redirects the output to %BUILDDIR%\x86\win32\tst\8q.1, which it + compares with x86\win32\tst\8q.1bk. The output from this step is voluminous, + but there should be no differences and no errors.

      +
    8. +
    9. Run the "triple" test, which compiles rcc with itself and + verifies the results:
      +
      C:\dist\lcc\4.1>nmake -f makefile.nt triple
      +...
      +\progra~1\lcc\4.1\bin\x86.c:
      + Assembling: C:/TEMP/lcc2001.asm
      +        fc /b \progra~1\lcc\4.1\bin\1rcc.exe \progra~1\lcc\4.1\bin\2rcc.exe
      +Comparing files \progra~1\lcc\4.1\bin\1rcc.exe and \progra~1\lcc\4.1\bin\2RCC.EXE
      +00000088: B4 D5
      +
      +

      This command builds rcc twice; once using the rcc built by VC + and again using the rcc built by lcc. The resulting binaries are + compared using fc. They should be identical, except for one or two bytes of + timestamp data, as shown at the end of the output above. If they aren't, our compiler is + generating incorrect code; contact us.

      +
    10. +
    11. Copy lcc.exe and bprint.exe to a directory on your PATH, e.g.,
      +
      C:\dist\lcc\4.1>copy %BUILDDIR%\lcc.exe \bin
      +        1 file(s) copied.
      +
      +C:\dist\lcc\4.1>copy %BUILDDIR%\bprint.exe \bin
      +        1 file(s) copied.
      +
      +
    12. +
    13. Finally, clean up:
      +
      C:\dist\lcc\4.1>nmake -f makefile.nt clean
      +
      +

      This command removes the derived files in BUILDDIR, but does not remove rcc.exe, + etc.; "nmake -f makefile.nt clobber" cleans up and removes all + executables and libraries. Test directories under BUILDDIR are not + removed; you'll need to remove these by hand, e.g.,

      +
      +
      C:\dist\lcc\4.1>rmdir %BUILDDIR%\x86 /s
      +\progra~1\lcc\4.1\bin\x86, Are you sure (Y/N)? y
      +
      +
    14. +
    + +

    Reporting Bugs

    + +

    lcc is a large, complex program. We find and repair errors routinely. If you think that +you've found a error, follow the steps below, which are adapted from the instructions in +Chapter 1 of A Retargetable C Compiler: Design and Implementation. + +

      +
    1. If you don't have a source file that displays the error, create one. Most errors are + exposed when programmers try to compile a program they think is valid, so you probably + have a demonstration program already.
    2. +
    3. Preprocess the source file and capture the preprocessor output. Discard the original + code.
    4. +
    5. Prune your source code until it can be pruned no more without sending the error into + hiding. We prune most error demonstrations to fewer than five lines.
    6. +
    7. Confirm that the source file displays the error with the distributed version of + lcc. If you've changed lcc and the error appears only in your version, then you'll have to + chase the error yourself, even if it turns out to be our fault, because we can't work on + your code.
    8. +
    9. Annotate your code with comments that explain why you think that lcc is wrong. If lcc + dies with an assertion failure, please tell us where it died. If lcc crashes, please + report the last part of the call chain if you can. If lcc is rejecting a program you think + is valid, please tell us why you think it's valid, and include supporting page numbers in + the ANSI Standard, Appendix A in The C Programming Language, or the + appropriate section in C: A Reference Manual, 4th edition by S. B. Harbison + and G. L. Steele, Jr. (Prentice Hall, 1995). If lcc silently generates incorrect code for + some construct, please include the corrupt assembly code in the comments and flag the + incorrect instructions if you can.
    10. +
    11. Confirm that your error hasn't been fixed already. The latest version of lcc is always + available for anonymous ftp from ftp.cs.princeton.edu in pub/lcc. A README file there gives + acquistion details, and the LOG file reports what errors + were fixed and when they were fixed. If you report a error that's been fixed, you might + get a canned reply.
    12. +
    13. Send your program by electronic mail to lcc-bugs@cs.princeton.edu. Please + send only valid C programs; put all remarks in C comments so that we can process reports + semiautomatically.
    14. +
    + +

    Keeping in Touch

    + +

    There is an lcc mailing list for general information about lcc. To be added to the +list, send a message with the 1-line body

    + +
    +
    subscribe lcc
    +
    + +

    to majordomo@cs.princeton.edu. This line must appear in the message body; +"Subject:" lines are ignored. To learn more about mailing lists served by majordomo, +send a message with the 1-word body "help" to majordomo@cs.princeton.edu. +Mail sent to lcc@cs.princeton.edu is forwarded to everyone on the mailing +list.

    + +

    There is also an lcc-bugs mailing list for reporting bugs; subscribe to it +by sending a message with the 1-line body

    + +
    +
    subscribe lcc-bugs
    +
    + +

    to majordomo@cs.princeton.edu. Mail addressed to lcc-bugs@cs.princeton.edu +is forwarded to everyone on this list.

    + +
    + +
    + Chris Fraser / cwfraser@microsoft.com
    + David Hanson / drh@microsoft.com
    + $Revision: 145 $ $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $ +
    + + diff --git a/src/tools/lcc/doc/lcc.1 b/src/tools/lcc/doc/lcc.1 new file mode 100644 index 0000000..fe91bca --- /dev/null +++ b/src/tools/lcc/doc/lcc.1 @@ -0,0 +1,605 @@ +.\" $Id: lcc.1 145 2001-10-17 21:53:10Z timo $ +.TH LCC 1 "local \- $Date: 2001-10-17 16:53:10 -0500 (Wed, 17 Oct 2001) $" +.SH NAME +lcc \- ANSI C compiler +.SH SYNOPSIS +.B lcc +[ +.I option +| +.I file +]... +.br +.SH DESCRIPTION +.PP +.I lcc +is an ANSI C compiler for a variety of platforms. +.PP +Arguments whose names end with `.c' (plus `.C' under Windows) are taken to be +C source programs; they are preprocessed, compiled, and +each object program is left on the file +whose name is that of the source with `.o' (UNIX) or `.obj' (Windows) +substituted for the extension. +Arguments whose names end with `.i' are treated similarly, +except they are not preprocessed. +In the same way, +arguments ending with `.s' (plus `.S', `.asm', and `.ASM', under Windows) +are taken to be assembly source programs +and are assembled, producing an object file. +If there are no arguments, +.I lcc +summarizes its options on the standard error. +.PP +.I lcc +deletes an object file if and only if exactly one +source file is mentioned and no other file +(source, object, library) or +.B \-l +option is mentioned. +.PP +If the environment variable +.B LCCINPUTS +is set, +.I lcc +assumes it gives a semicolon- or colon-separated list of directories in which to +look for source and object files whose names do not begin with `/'. +These directories are also added to the list of directories +searched for libraries. +If +.B LCCINPUTS +is defined, it must contain `.' in order for the current directory +to be searched for input files. +.PP +.I lcc +uses ANSI standard header files (see `FILES' below). +Include files not found in the ANSI header files +are taken from the normal default include areas, +which usually includes +.BR /usr/include . +Under Windows, if the environment variable +.B include +is defined, it gives a semicolon-separated list of directories in which to search for +header files. +.PP +.I lcc +interprets the following options; unrecognized options are +taken as loader options (see +.IR ld (1)) +unless +.BR \-c , +.BR \-S , +or +.B \-E +precedes them. +Except for +.BR \-l , +all options are processed before any of the files +and apply to all of the files. +Applicable options are passed to each compilation phase in the order given. +.TP +.B \-c +Suppress the loading phase of the compilation, and force +an object file to be produced even if only one program is compiled. +.TP +.B \-g +Produce additional symbol table information for the local debuggers. +.I lcc +warns when +.B \-g +is unsupported. +.TP +.BI \-Wf\-g n , x +Set the debugging level to +.I n +and emit source code as comments into the generated assembly code; +.I x +must be the assembly language comment character. +If +.I n +is omitted, it defaults to 1, which is similar to +.BR \-g . +Omitting +.BI , x +just sets the debugging level to +.IR n . +.TP +.B \-w +Suppress warning diagnostics, such as those +announcing unreferenced statics, locals, and parameters. +The line +.I +#pragma ref id +simulates a reference to the variable +.IR id . +.TP +.BI \-d n +Generate jump tables for switches whose density is at least +.IR n , +a floating point constant between zero and one. +The default is 0.5. +.TP +.B \-A +Warns about +declarations and casts of function types without prototypes, +assignments between pointers to ints and pointers to enums, and +conversions from pointers to smaller integral types. +A second +.B \-A +warns about +unrecognized control lines, +nonANSI language extensions and source characters in literals, +unreferenced variables and static functions, +declaring arrays of incomplete types, +and exceeding +.I some +ANSI environmental limits, like more than 257 cases in switches. +It also arranges for duplicate global definitions in separately compiled +files to cause loader errors. +.TP +.B \-P +Writes declarations for all defined globals on standard error. +Function declarations include prototypes; +editing this output can simplify conversion to ANSI C. +This output may not correspond to the input when +there are several typedefs for the same type. +.TP +.B \-n +Arrange for the compiler to produce code +that tests for dereferencing zero pointers. +The code reports the offending file and line number and calls +.IR abort (3). +.TP +.B \-O +is ignored. +.TP +.B \-S +Compile the named C programs, and leave the +assembler-language output on corresponding files suffixed `.s' or `.asm'. +.TP +.B \-E +Run only the preprocessor on the named C programs +and unsuffixed file arguments, +and send the result to the standard output. +.TP +.BI \-o " output" +Name the output file +.IR output . +If +.B \-c +or +.B \-S +is specified and there is exactly one source file, +this option names the object or assembly file, respectively. +Otherwise, this option names the final executable +file generated by the loader, and `a.out' (UNIX) or `a.exe' (Windows) is left undisturbed. +.I lcc +warns if +.B \-o +and +.B \-c +or +.B \-S +are given with more than one source file and ignores the +.B \-o +option. +.TP +.BI \-D name=def +Define the +.I name +to the preprocessor, as if by `#define'. +If +.I =def +is omitted, the name is defined as "1". +.TP +.BI \-U name +Remove any initial definition of +.IR name . +.TP +.BI \-I dir +`#include' files +whose names do not begin with `/' are always +sought first in the directory of the +.I file +arguments, then in directories named in +.B \-I +options, then in directories on a standard list. +.TP +.B \-N +Do not search +.I any +of the standard directories for `#include' files. +Only those directories specified by subsequent explicit +.B \-I +options will be searched, in the order given. +.TP +.BI \-B str +Use the compiler +.BI "" str rcc +instead of the default version. +Note that +.I str +often requires a trailing slash. +On Sparcs only, +.B \-Bstatic +and +.BI \-Bdynamic +are passed to the loader; see +.IR ld (1). +.TP +.BI \-Wo\-lccdir= dir +Find the preprocessor, compiler proper, and include directory +in the directory +.I dir/ +or +.I +dir\\. +If the environment variable +.B LCCDIR +is defined, it gives this directory. +.I lcc +warns when this option is unsupported. +.TP +.B \-Wf-unsigned_char=1 +.br +.ns +.TP +.B \-Wf-unsigned_char=0 +makes plain +.B char +an unsigned (1) or signed (0) type; by default, +.B char +is signed. +.TP +.B \-Wf\-wchar_t=unsigned_char +.br +.ns +.TP +.B \-Wf\-wchar_t=unsigned_short +.br +.ns +.TP +.B \-Wf\-wchar_t=unsigned_int +Makes wide characters the type indicated; by default, +wide characters are unsigned short ints, and +.B wchar_t +is a typedef for unsigned short defined in stddef.h. +The definition for +.B wchar_t +in stddef.h must correspond to the type specified. +.TP +.B \-v +Print commands as they are executed; some of the executed +programs are directed to print their version numbers. +More than one occurrence of +.B \-v +causes the commands to be printed, but +.I not +executed. +.TP +.BR \-help " or " \-? +Print a message on the standard error summarizing +.IR lcc 's +options and giving the values of the environment variables +.B LCCINPUTS +and +.BR LCCDIR , +if they are defined. +Under Windows, the values of +.B include +and +.B lib +are also given, if they are defined. +.TP +.B \-b +Produce code that counts the number of times each expression is executed. +If loading takes place, arrange for a +.B prof.out +file to be written when the object program terminates. +A listing annotated with execution counts can then be generated with +.IR bprint (1). +.I lcc +warns when +.B \-b +is unsupported. +.B \-Wf\-C +is similar, but counts only the number of function calls. +.TP +.B \-p +Produce code that counts the number of times each function is called. +If loading takes place, replace the standard startup +function by one that automatically calls +.IR monitor (3) +at the start and arranges to write a +.B mon.out +file when the object program terminates normally. +An execution profile can then be generated with +.IR prof (1). +.I lcc +warns when +.B \-p +is unsupported. +.TP +.B \-pg +Causes the compiler to produce counting code like +.BR \-p , +but invokes a run-time recording mechanism that keeps more +extensive statistics and produces a +.B gmon.out +file at normal termination. +Also, a profiling library is searched, in lieu of the standard C library. +An execution profile can then be generated with +.IR gprof (1). +.I lcc +warns when +.B \-pg +is unsupported. +.TP +.BI \-t name +.br +.ns +.TP +.BI \-t +Produce code to print the name of the function, an activation number, +and the name and value of each argument at function entry. +At function exit, produce code to print +the name of the function, the activation number, and the return value. +By default, +.I printf +does the printing; if +.I name +appears, it does. +For null +.I char* +values, "(null)" is printed. +.BI \-target +.I name +is accepted, but ignored. +.TP +.BI \-tempdir= dir +Store temporary files in the directory +.I dir/ +or +.I +dir\\. +The default is usually +.BR /tmp . +.TP +.BI \-W xarg +pass argument +.I arg +to the program indicated by +.IR x ; +.I x +can be one of +.BR p , +.BR f , +.BR a , +or +.BR l , +which refer, respectively, to the preprocessor, the compiler proper, +the assembler, and the loader. +.I arg +is passed as given; if a +.B \- +is expected, it must be given explicitly. +.BI \-Wo arg +specifies a system-specific option, +.IR arg . +.PP +Other arguments +are taken to be either loader option arguments, or C-compatible +object programs, typically produced by an earlier +.I lcc +run, or perhaps libraries of C-compatible routines. +Duplicate object files are ignored. +These programs, together with the results of any +compilations specified, are loaded (in the order +given) to produce an executable program with name +.BR a.out +(UNIX) or +.BR a.exe +(Windows). +.PP +.I lcc +assigns the most frequently referenced scalar parameters and +locals to registers whenever possible. +For each block, +explicit register declarations are obeyed first; +remaining registers are assigned to automatic locals if they +are `referenced' at least 3 times. +Each top-level occurrence of an identifier +counts as 1 reference. Occurrences in a loop, +either of the then/else arms of an if statement, or a case +in a switch statement each count, respectively, as 10, 1/2, or 1/10 references. +These values are adjusted accordingly for nested control structures. +.B \-Wf\-a +causes +.I lcc +to read a +.B prof.out +file from a previous execution and to use the data therein +to compute reference counts (see +.BR \-b ). +.PP +.I lcc +is a cross compiler; +.BI \-Wf\-target= target/os +causes +.I lcc +to generate code for +.I target +running the operating system denoted by +.IR os . +The supported +.I target/os +combinations may include +.PP +.RS +.ta \w'sparc/solarisxx'u +.nf +alpha/osf ALPHA, OSF 3.2 +mips/irix big-endian MIPS, IRIX 5.2 +mips/ultrix little-endian MIPS, ULTRIX 4.3 +sparc/solaris SPARC, Solaris 2.3 +x86/win32 x86, Windows NT 4.0/Windows 95/98 +x86/linux x86, Linux +symbolic text rendition of the generated code +null no output +.fi +.RE +.PP +For +.BR \-Wf\-target=symbolic , +the option +.B \-Wf-html +causes the text rendition to be emitted as HTML. +.B +.SH LIMITATIONS +.PP +.I lcc +accepts the C programming language +as described in the ANSI standard. +If +.I lcc +is used with the GNU C preprocessor, the +.B \-Wp\-trigraphs +option is required to enable trigraph sequences. +.PP +Plain int bit fields are signed. +Bit fields are aligned like unsigned integers but are otherwise laid out +as by most standard C compilers. +Some compilers, such as the GNU C compiler, +may choose other, incompatible layouts. +.PP +Likewise, calling conventions are intended to be compatible with +the host C compiler, +except possibly for passing and returning structures. +Specifically, +.I lcc +passes and returns structures like host ANSI C compilers +on most targets, but some older host C compilers use different conventions. +Consequently, calls to/from such functions compiled with +older C compilers may not work. +Calling a function that returns +a structure without declaring it as such violates +the ANSI standard and may cause a fault. +.SH FILES +.PP +The file names listed below are +.IR typical , +but vary among installations; installation-dependent variants +can be displayed by running +.I lcc +with the +.B \-v +option. +.PP +.RS +.ta \w'$LCCDIR/liblcc.{a,lib}XX'u +.nf +file.{c,C} input file +file.{s,asm} assembly-language file +file.{o,obj} object file +a.{out,exe} loaded output +/tmp/lcc* temporary files +$LCCDIR/cpp preprocessor +$LCCDIR/rcc compiler +$LCCDIR/liblcc.{a,lib} \fIlcc\fP-specific library +/lib/crt0.o runtime startup (UNIX) +/lib/[gm]crt0.o startups for profiling (UNIX) +/lib/libc.a standard library (UNIX) +$LCCDIR/include ANSI standard headers +/usr/local/include local headers +/usr/include traditional headers +prof.out file produced for \fIbprint\fR(1) +mon.out file produced for \fIprof\fR(1) +gmon.out file produced for \fIgprof\fR(1) +.fi +.RE +.PP +.I lcc +predefines the macro +.B __LCC__ +on all systems. +It may also predefine some installation-dependent symbols; option +.B \-v +exposes them. +.SH "SEE ALSO" +.PP +C. W. Fraser and D. R. Hanson, +.I A Retargetable C Compiler: Design and Implementation, +Addison-Wesley, 1995. ISBN 0-8053-1670-1. +.PP +The World-Wide Web page at http://www.cs.princeton.edu/software/lcc/. +.PP +S. P. Harbison and G. L. Steele, Jr., +.I C: A Reference Manual, +4th ed., Prentice-Hall, 1995. +.PP +B. W. Kernighan and D. M. Ritchie, +.I The C Programming Language, +2nd ed., Prentice-Hall, 1988. +.PP +American National Standards Inst., +.I American National Standard for Information Systems\(emProgramming +.IR Language\(emC , +ANSI X3.159-1989, New York, 1990. +.br +.SH BUGS +Mail bug reports along with the shortest preprocessed program +that exposes them and the details reported by +.IR lcc 's +.B \-v +option to lcc-bugs@princeton.edu. The WWW page at +URL http://www.cs.princeton.edu/software/lcc/ +includes detailed instructions for reporting bugs. +.PP +The ANSI standard headers conform to the specifications in +the Standard, which may be too restrictive for some applications, +but necessary for portability. +Functions given in the ANSI headers may be missing from +some local C libraries (e.g., wide-character functions) +or may not correspond exactly to the local versions; +for example, the ANSI standard +stdio.h +specifies that +.IR printf , +.IR fprintf , +and +.I sprintf +return the number of characters written to the file or array, +but some existing libraries don't implement this convention. +.PP +On the MIPS and SPARC, old-style variadic functions must use +varargs.h +from MIPS or Sun. New-style is recommended. +.PP +With +.BR \-b , +files compiled +.I without +.B \-b +may cause +.I bprint +to print erroneous call graphs. +For example, if +.B f +calls +.B g +calls +.B h +and +.B f +and +.B h +are compiled with +.BR \-b , +but +.B g +is not, +.B bprint +will report that +.B f +called +.BR h . +The total number of calls is correct, however. diff --git a/src/tools/lcc/doc/lcc.pdf b/src/tools/lcc/doc/lcc.pdf new file mode 100644 index 0000000..6134de6 Binary files /dev/null and b/src/tools/lcc/doc/lcc.pdf differ diff --git a/src/tools/lcc/etc/bytecode.c b/src/tools/lcc/etc/bytecode.c new file mode 100644 index 0000000..fe4178f --- /dev/null +++ b/src/tools/lcc/etc/bytecode.c @@ -0,0 +1,66 @@ +/* quake3 bytecode target */ + +#include +#include +#include "../../../qcommon/q_platform.h" + +#ifdef _WIN32 +#define BINEXT ".exe" +#else +#define BINEXT "" +#endif + +char *suffixes[] = { ".c", ".i", ".asm", ".o", ".out", 0 }; +char inputs[256] = ""; +char *cpp[] = { "q3cpp" BINEXT, + "-D__STDC__=1", "-D__STRICT_ANSI__", "-D__signed__=signed", "-DQ3_VM", + "$1", "$2", "$3", 0 }; +char *include[] = { 0 }; +char *com[] = { "q3rcc" BINEXT, "-target=bytecode", "$1", "$2", "$3", 0 }; +char *ld[] = { 0 }; +char *as[] = { 0 }; + +extern char *concat(char *, char *); + +/* +=============== +UpdatePaths + +Updates the paths to q3cpp and q3rcc based on +the directory that contains q3lcc +=============== +*/ +void UpdatePaths( const char *lccBinary ) +{ + char basepath[ 1024 ]; + char *p; + + strncpy( basepath, lccBinary, 1024 ); + p = strrchr( basepath, PATH_SEP ); + + if( p ) + { + *( p + 1 ) = '\0'; + + cpp[ 0 ] = concat( basepath, "q3cpp" BINEXT ); + com[ 0 ] = concat( basepath, "q3rcc" BINEXT ); + } +} + +int option(char *arg) { + if (strncmp(arg, "-lccdir=", 8) == 0) { + cpp[0] = concat(&arg[8], "/q3cpp" BINEXT); + include[0] = concat("-I", concat(&arg[8], "/include")); + com[0] = concat(&arg[8], "/q3rcc" BINEXT); + } else if (strcmp(arg, "-p") == 0 || strcmp(arg, "-pg") == 0) { + fprintf( stderr, "no profiling supported, %s ignored.\n", arg); + } else if (strcmp(arg, "-b") == 0) + ; + else if (strcmp(arg, "-g") == 0) + fprintf( stderr, "no debugging supported, %s ignored.\n", arg); + else if (strncmp(arg, "-ld=", 4) == 0 || strcmp(arg, "-static") == 0) { + fprintf( stderr, "no linking supported, %s ignored.\n", arg); + } else + return 0; + return 1; +} diff --git a/src/tools/lcc/etc/lcc.c b/src/tools/lcc/etc/lcc.c new file mode 100644 index 0000000..a168852 --- /dev/null +++ b/src/tools/lcc/etc/lcc.c @@ -0,0 +1,853 @@ +/* + * lcc [ option ]... [ file | -llib ]... + * front end for the ANSI C compiler + */ +static char rcsid[] = "Id: dummy rcsid"; + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TEMPDIR +#define TEMPDIR "/tmp" +#endif + +typedef struct list *List; +struct list { /* circular list nodes: */ + char *str; /* option or file name */ + List link; /* next list element */ +}; + +static void *alloc(int); +static List append(char *,List); +extern char *basename(char *); +static int callsys(char *[]); +extern char *concat(char *, char *); +static int compile(char *, char *); +static void compose(char *[], List, List, List); +static void error(char *, char *); +static char *exists(char *); +static char *first(char *); +static int filename(char *, char *); +static List find(char *, List); +static void help(void); +static void initinputs(void); +static void interrupt(int); +static void opt(char *); +static List path2list(const char *); +extern int main(int, char *[]); +extern char *replace(const char *, int, int); +static void rm(List); +extern char *strsave(const char *); +extern char *stringf(const char *, ...); +extern int suffix(char *, char *[], int); +extern char *tempname(char *); + +#ifndef __sun +extern int getpid(void); +#endif + +extern char *cpp[], *include[], *com[], *as[],*ld[], inputs[], *suffixes[]; +extern int option(char *); + +static int errcnt; /* number of errors */ +static int Eflag; /* -E specified */ +static int Sflag = 1; /* -S specified */ //for Q3 we always generate asm +static int cflag; /* -c specified */ +static int verbose; /* incremented for each -v */ +static List llist[2]; /* loader files, flags */ +static List alist; /* assembler flags */ +static List clist; /* compiler flags */ +static List plist; /* preprocessor flags */ +static List ilist; /* list of additional includes from LCCINPUTS */ +static List rmlist; /* list of files to remove */ +static char *outfile; /* ld output file or -[cS] object file */ +static int ac; /* argument count */ +static char **av; /* argument vector */ +char *tempdir = TEMPDIR; /* directory for temporary files */ +static char *progname; +static List lccinputs; /* list of input directories */ + +extern void UpdatePaths( const char *lccBinary ); + +int main(int argc, char *argv[]) { + int i, j, nf; + + progname = argv[0]; + + UpdatePaths( progname ); + + ac = argc + 50; + av = alloc(ac*sizeof(char *)); + if (signal(SIGINT, SIG_IGN) != SIG_IGN) + signal(SIGINT, interrupt); + if (signal(SIGTERM, SIG_IGN) != SIG_IGN) + signal(SIGTERM, interrupt); +#ifdef SIGHUP + if (signal(SIGHUP, SIG_IGN) != SIG_IGN) + signal(SIGHUP, interrupt); +#endif + if (getenv("TMP")) + tempdir = getenv("TMP"); + else if (getenv("TEMP")) + tempdir = getenv("TEMP"); + else if (getenv("TMPDIR")) + tempdir = getenv("TMPDIR"); + assert(tempdir); + i = strlen(tempdir); + for (; (i > 0 && tempdir[i-1] == '/') || tempdir[i-1] == '\\'; i--) + tempdir[i-1] = '\0'; + if (argc <= 1) { + help(); + exit(0); + } + plist = append("-D__LCC__", 0); + initinputs(); + if (getenv("LCCDIR")) + option(stringf("-lccdir=%s", getenv("LCCDIR"))); + for (nf = 0, i = j = 1; i < argc; i++) { + if (strcmp(argv[i], "-o") == 0) { + if (++i < argc) { + if (suffix(argv[i], suffixes, 2) >= 0) { + error("-o would overwrite %s", argv[i]); + exit(8); + } + outfile = argv[i]; + continue; + } else { + error("unrecognized option `%s'", argv[i-1]); + exit(8); + } + } else if (strcmp(argv[i], "-target") == 0) { + if (argv[i+1] && *argv[i+1] != '-') + i++; + continue; + } else if (*argv[i] == '-' && argv[i][1] != 'l') { + opt(argv[i]); + continue; + } else if (*argv[i] != '-' && suffix(argv[i], suffixes, 3) >= 0) + nf++; + argv[j++] = argv[i]; + } + if ((cflag || Sflag) && outfile && nf != 1) { + fprintf(stderr, "%s: -o %s ignored\n", progname, outfile); + outfile = 0; + } + argv[j] = 0; + for (i = 0; include[i]; i++) + plist = append(include[i], plist); + if (ilist) { + List b = ilist; + do { + b = b->link; + plist = append(b->str, plist); + } while (b != ilist); + } + ilist = 0; + for (i = 1; argv[i]; i++) + if (*argv[i] == '-') + opt(argv[i]); + else { + char *name = exists(argv[i]); + if (name) { + if (strcmp(name, argv[i]) != 0 + || (nf > 1 && suffix(name, suffixes, 3) >= 0)) + fprintf(stderr, "%s:\n", name); + filename(name, 0); + } else + error("can't find `%s'", argv[i]); + } + if (errcnt == 0 && !Eflag && !Sflag && !cflag && llist[1]) { + compose(ld, llist[0], llist[1], + append(outfile ? outfile : concat("a", first(suffixes[4])), 0)); + if (callsys(av)) + errcnt++; + } + rm(rmlist); + return errcnt ? EXIT_FAILURE : EXIT_SUCCESS; +} + +/* alloc - allocate n bytes or die */ +static void *alloc(int n) { + static char *avail, *limit; + + n = (n + sizeof(char *) - 1)&~(sizeof(char *) - 1); + if (n >= limit - avail) { + avail = malloc(n + 4*1024); + assert(avail); + limit = avail + n + 4*1024; + } + avail += n; + return avail - n; +} + +/* append - append a node with string str onto list, return new list */ +static List append(char *str, List list) { + List p = alloc(sizeof *p); + + p->str = str; + if (list) { + p->link = list->link; + list->link = p; + } else + p->link = p; + return p; +} + +/* basename - return base name for name, e.g. /usr/drh/foo.c => foo */ +char *basename(char *name) { + char *s, *b, *t = 0; + + for (b = s = name; *s; s++) + if (*s == '/' || *s == '\\') { + b = s + 1; + t = 0; + } else if (*s == '.') + t = s; + s = strsave(b); + if (t) + s[t-b] = 0; + return s; +} + +#ifdef WIN32 +#include + +static char *escapeDoubleQuotes(const char *string) { + int stringLength = strlen(string); + int bufferSize = stringLength + 1; + int i, j; + char *newString; + + if (string == NULL) + return NULL; + + for (i = 0; i < stringLength; i++) { + if (string[i] == '"') + bufferSize++; + } + + newString = (char*)malloc(bufferSize); + + if (newString == NULL) + return NULL; + + for (i = 0, j = 0; i < stringLength; i++) { + if (string[i] == '"') + newString[j++] = '\\'; + + newString[j++] = string[i]; + } + + newString[j] = '\0'; + + return newString; +} + +static int spawn(const char *cmdname, char **argv) { + int argc = 0; + char **newArgv = argv; + int i; + intptr_t exitStatus; + + // _spawnvp removes double quotes from arguments, so we + // have to escape them manually + while (*newArgv++ != NULL) + argc++; + + newArgv = (char **)malloc(sizeof(char*) * (argc + 1)); + + for (i = 0; i < argc; i++) + newArgv[i] = escapeDoubleQuotes(argv[i]); + + newArgv[argc] = NULL; + + exitStatus = _spawnvp(_P_WAIT, cmdname, (const char *const *)newArgv); + + for (i = 0; i < argc; i++) + free(newArgv[i]); + + free(newArgv); + return exitStatus; +} + +#else + +#define _P_WAIT 0 +#ifndef __sun +extern int fork(void); +#endif +extern int wait(int *); + +static int spawn(const char *cmdname, char **argv) { + int pid, n, status; + + switch (pid = fork()) { + case -1: + fprintf(stderr, "%s: no more processes\n", progname); + return 100; + case 0: + // TTimo removing hardcoded paths, searching in $PATH + execvp(cmdname, argv); + fprintf(stderr, "%s: ", progname); + perror(cmdname); + fflush(stdout); + exit(100); + } + while ((n = wait(&status)) != pid && n != -1) + ; + if (n == -1) + status = -1; + if (status&0377) { + fprintf(stderr, "%s: fatal error in %s\n", progname, cmdname); + status |= 0400; + } + return (status>>8)&0377; +} +#endif + +/* callsys - execute the command described by av[0...], return status */ +static int callsys(char **av) { + int i, status = 0; + static char **argv; + static int argc; + char *executable; + + for (i = 0; av[i] != NULL; i++) + ; + if (i + 1 > argc) { + argc = i + 1; + if (argv == NULL) + argv = malloc(argc*sizeof *argv); + else + argv = realloc(argv, argc*sizeof *argv); + assert(argv); + } + for (i = 0; status == 0 && av[i] != NULL; ) { + int j = 0; + char *s = NULL; + for ( ; av[i] != NULL && (s = strchr(av[i], '\n')) == NULL; i++) + argv[j++] = av[i]; + if (s != NULL) { + if (s > av[i]) + argv[j++] = stringf("%.*s", s - av[i], av[i]); + if (s[1] != '\0') + av[i] = s + 1; + else + i++; + } + argv[j] = NULL; + executable = strsave( argv[0] ); + argv[0] = stringf( "\"%s\"", argv[0] ); + if (verbose > 0) { + int k; + fprintf(stderr, "%s", argv[0]); + for (k = 1; argv[k] != NULL; k++) + fprintf(stderr, " %s", argv[k]); + fprintf(stderr, "\n"); + } + if (verbose < 2) + status = spawn(executable, argv); + if (status == -1) { + fprintf(stderr, "%s: ", progname); + perror(argv[0]); + } + } + return status; +} + +/* concat - return concatenation of strings s1 and s2 */ +char *concat(char *s1, char *s2) { + int n = strlen(s1); + char *s = alloc(n + strlen(s2) + 1); + + strcpy(s, s1); + strcpy(s + n, s2); + return s; +} + +/* compile - compile src into dst, return status */ +static int compile(char *src, char *dst) { + compose(com, clist, append(src, 0), append(dst, 0)); + return callsys(av); +} + +/* compose - compose cmd into av substituting a, b, c for $1, $2, $3, resp. */ +static void compose(char *cmd[], List a, List b, List c) { + int i, j; + List lists[3]; + + lists[0] = a; + lists[1] = b; + lists[2] = c; + for (i = j = 0; cmd[i]; i++) { + char *s = strchr(cmd[i], '$'); + if (s && isdigit(s[1])) { + int k = s[1] - '0'; + assert(k >=1 && k <= 3); + if ((b = lists[k-1])) { + b = b->link; + av[j] = alloc(strlen(cmd[i]) + strlen(b->str) - 1); + strncpy(av[j], cmd[i], s - cmd[i]); + av[j][s-cmd[i]] = '\0'; + strcat(av[j], b->str); + strcat(av[j++], s + 2); + while (b != lists[k-1]) { + b = b->link; + assert(j < ac); + av[j++] = b->str; + }; + } + } else if (*cmd[i]) { + assert(j < ac); + av[j++] = cmd[i]; + } + } + av[j] = NULL; +} + +/* error - issue error msg according to fmt, bump error count */ +static void error(char *fmt, char *msg) { + fprintf(stderr, "%s: ", progname); + fprintf(stderr, fmt, msg); + fprintf(stderr, "\n"); + errcnt++; +} + +/* exists - if `name' readable return its path name or return null */ +static char *exists(char *name) { + List b; + + if ( (name[0] == '/' || name[0] == '\\' || name[2] == ':') + && access(name, 4) == 0) + return name; + if (!(name[0] == '/' || name[0] == '\\' || name[2] == ':') + && (b = lccinputs)) + do { + b = b->link; + if (b->str[0]) { + char buf[1024]; + sprintf(buf, "%s/%s", b->str, name); + if (access(buf, 4) == 0) + return strsave(buf); + } else if (access(name, 4) == 0) + return name; + } while (b != lccinputs); + if (verbose > 1) + return name; + return 0; +} + +/* first - return first component in semicolon separated list */ +static char *first(char *list) { + char *s = strchr(list, ';'); + + if (s) { + char buf[1024]; + strncpy(buf, list, s-list); + buf[s-list] = '\0'; + return strsave(buf); + } else + return list; +} + +/* filename - process file name argument `name', return status */ +static int filename(char *name, char *base) { + int status = 0; + static char *stemp, *itemp; + + if (base == 0) + base = basename(name); + switch (suffix(name, suffixes, 4)) { + case 0: /* C source files */ + compose(cpp, plist, append(name, 0), 0); + if (Eflag) { + status = callsys(av); + break; + } + if (itemp == NULL) + itemp = tempname(first(suffixes[1])); + compose(cpp, plist, append(name, 0), append(itemp, 0)); + status = callsys(av); + if (status == 0) + return filename(itemp, base); + break; + case 1: /* preprocessed source files */ + if (Eflag) + break; + if (Sflag) + status = compile(name, outfile ? outfile : concat(base, first(suffixes[2]))); + else if ((status = compile(name, stemp?stemp:(stemp=tempname(first(suffixes[2]))))) == 0) + return filename(stemp, base); + break; + case 2: /* assembly language files */ + if (Eflag) + break; + if (!Sflag) { + char *ofile; + if (cflag && outfile) + ofile = outfile; + else if (cflag) + ofile = concat(base, first(suffixes[3])); + else + ofile = tempname(first(suffixes[3])); + compose(as, alist, append(name, 0), append(ofile, 0)); + status = callsys(av); + if (!find(ofile, llist[1])) + llist[1] = append(ofile, llist[1]); + } + break; + case 3: /* object files */ + if (!find(name, llist[1])) + llist[1] = append(name, llist[1]); + break; + default: + if (Eflag) { + compose(cpp, plist, append(name, 0), 0); + status = callsys(av); + } + llist[1] = append(name, llist[1]); + break; + } + if (status) + errcnt++; + return status; +} + +/* find - find 1st occurrence of str in list, return list node or 0 */ +static List find(char *str, List list) { + List b; + + if ((b = list)) + do { + if (strcmp(str, b->str) == 0) + return b; + } while ((b = b->link) != list); + return 0; +} + +/* help - print help message */ +static void help(void) { + static char *msgs[] = { +"", " [ option | file ]...\n", +" except for -l, options are processed left-to-right before files\n", +" unrecognized options are taken to be linker options\n", +"-A warn about nonANSI usage; 2nd -A warns more\n", +"-b emit expression-level profiling code; see bprint(1)\n", +#ifdef sparc +"-Bstatic -Bdynamic specify static or dynamic libraries\n", +#endif +"-Bdir/ use the compiler named `dir/rcc'\n", +"-c compile only\n", +"-dn set switch statement density to `n'\n", +"-Dname -Dname=def define the preprocessor symbol `name'\n", +"-E run only the preprocessor on the named C programs and unsuffixed files\n", +"-g produce symbol table information for debuggers\n", +"-help or -? print this message\n", +"-Idir add `dir' to the beginning of the list of #include directories\n", +"-lx search library `x'\n", +"-N do not search the standard directories for #include files\n", +"-n emit code to check for dereferencing zero pointers\n", +"-O is ignored\n", +"-o file leave the output in `file'\n", +"-P print ANSI-style declarations for globals\n", +"-p -pg emit profiling code; see prof(1) and gprof(1)\n", +"-S compile to assembly language\n", +#ifdef linux +"-static specify static libraries (default is dynamic)\n", +#endif +"-t -tname emit function tracing calls to printf or to `name'\n", +"-target name is ignored\n", +"-tempdir=dir place temporary files in `dir/'", "\n" +"-Uname undefine the preprocessor symbol `name'\n", +"-v show commands as they are executed; 2nd -v suppresses execution\n", +"-w suppress warnings\n", +"-Woarg specify system-specific `arg'\n", +"-W[pfal]arg pass `arg' to the preprocessor, compiler, assembler, or linker\n", + 0 }; + int i; + char *s; + + msgs[0] = progname; + for (i = 0; msgs[i]; i++) { + fprintf(stderr, "%s", msgs[i]); + if (strncmp("-tempdir", msgs[i], 8) == 0 && tempdir) + fprintf(stderr, "; default=%s", tempdir); + } +#define xx(v) if ((s = getenv(#v))) fprintf(stderr, #v "=%s\n", s) + xx(LCCINPUTS); + xx(LCCDIR); +#undef xx +} + +/* initinputs - if LCCINPUTS or include is defined, use them to initialize various lists */ +static void initinputs(void) { + char *s = getenv("LCCINPUTS"); + List b; + + if (s == 0 || (s = inputs)[0] == 0) + s = "."; + if (s) { + lccinputs = path2list(s); + if ((b = lccinputs)) + do { + b = b->link; + if (strcmp(b->str, ".") != 0) { + ilist = append(concat("-I", b->str), ilist); + if (strstr(com[1], "win32") == NULL) + llist[0] = append(concat("-L", b->str), llist[0]); + } else + b->str = ""; + } while (b != lccinputs); + } +} + +/* interrupt - catch interrupt signals */ +static void interrupt(int n) { + rm(rmlist); + exit(n = 100); +} + +/* opt - process option in arg */ +static void opt(char *arg) { + switch (arg[1]) { /* multi-character options */ + case 'W': /* -Wxarg */ + if (arg[2] && arg[3]) + switch (arg[2]) { + case 'o': + if (option(&arg[3])) + return; + break; + case 'p': + plist = append(&arg[3], plist); + return; + case 'f': + if (strcmp(&arg[3], "-C") || option("-b")) { + clist = append(&arg[3], clist); + return; + } + break; /* and fall thru */ + case 'a': + alist = append(&arg[3], alist); + return; + case 'l': + llist[0] = append(&arg[3], llist[0]); + return; + } + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'd': /* -dn */ + arg[1] = 's'; + clist = append(arg, clist); + return; + case 't': /* -t -tname -tempdir=dir */ + if (strncmp(arg, "-tempdir=", 9) == 0) + tempdir = arg + 9; + else + clist = append(arg, clist); + return; + case 'p': /* -p -pg */ + if (option(arg)) + clist = append(arg, clist); + else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'D': /* -Dname -Dname=def */ + case 'U': /* -Uname */ + case 'I': /* -Idir */ + plist = append(arg, plist); + return; + case 'B': /* -Bdir -Bstatic -Bdynamic */ +#ifdef sparc + if (strcmp(arg, "-Bstatic") == 0 || strcmp(arg, "-Bdynamic") == 0) + llist[1] = append(arg, llist[1]); + else +#endif + { + static char *path; + if (path) + error("-B overwrites earlier option", 0); + path = arg + 2; + if (strstr(com[1], "win32") != NULL) + com[0] = concat(replace(path, '/', '\\'), concat("rcc", first(suffixes[4]))); + else + com[0] = concat(path, "rcc"); + if (path[0] == 0) + error("missing directory in -B option", 0); + } + return; + case 'h': + if (strcmp(arg, "-help") == 0) { + static int printed = 0; + case '?': + if (!printed) + help(); + printed = 1; + return; + } +#ifdef linux + case 's': + if (strcmp(arg,"-static") == 0) { + if (!option(arg)) + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + } +#endif + } + if (arg[2] == 0) + switch (arg[1]) { /* single-character options */ + case 'S': + Sflag++; + return; + case 'O': + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'A': case 'n': case 'w': case 'P': + clist = append(arg, clist); + return; + case 'g': case 'b': + if (option(arg)) + clist = append(arg[1] == 'g' ? "-g2" : arg, clist); + else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'G': + if (option(arg)) { + clist = append("-g3", clist); + llist[0] = append("-N", llist[0]); + } else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'E': + Eflag++; + return; + case 'c': + cflag++; + return; + case 'N': + if (strcmp(basename(cpp[0]), "gcc-cpp") == 0) + plist = append("-nostdinc", plist); + include[0] = 0; + ilist = 0; + return; + case 'v': + if (verbose++ == 0) { + if (strcmp(basename(cpp[0]), "gcc-cpp") == 0) + plist = append(arg, plist); + clist = append(arg, clist); + fprintf(stderr, "%s %s\n", progname, rcsid); + } + return; + } + if (cflag || Sflag || Eflag) + fprintf(stderr, "%s: %s ignored\n", progname, arg); + else + llist[1] = append(arg, llist[1]); +} + +/* path2list - convert a colon- or semicolon-separated list to a list */ +static List path2list(const char *path) { + List list = NULL; + char sep = ':'; + + if (path == NULL) + return NULL; + if (strchr(path, ';')) + sep = ';'; + while (*path) { + char *p, buf[512]; + if ((p = strchr(path, sep))) { + assert(p - path < sizeof buf); + strncpy(buf, path, p - path); + buf[p-path] = '\0'; + } else { + assert(strlen(path) < sizeof buf); + strcpy(buf, path); + } + if (!find(buf, list)) + list = append(strsave(buf), list); + if (p == 0) + break; + path = p + 1; + } + return list; +} + +/* replace - copy str, then replace occurrences of from with to, return the copy */ +char *replace(const char *str, int from, int to) { + char *s = strsave(str), *p = s; + + for ( ; (p = strchr(p, from)) != NULL; p++) + *p = to; + return s; +} + +/* rm - remove files in list */ +static void rm(List list) { + if (list) { + List b = list; + if (verbose) + fprintf(stderr, "rm"); + do { + if (verbose) + fprintf(stderr, " %s", b->str); + if (verbose < 2) + remove(b->str); + } while ((b = b->link) != list); + if (verbose) + fprintf(stderr, "\n"); + } +} + +/* strsave - return a saved copy of string str */ +char *strsave(const char *str) { + return strcpy(alloc(strlen(str)+1), str); +} + +/* stringf - format and return a string */ +char *stringf(const char *fmt, ...) { + char buf[1024]; + va_list ap; + int n; + + va_start(ap, fmt); + n = vsprintf(buf, fmt, ap); + va_end(ap); + return strsave(buf); +} + +/* suffix - if one of tails[0..n-1] holds a proper suffix of name, return its index */ +int suffix(char *name, char *tails[], int n) { + int i, len = strlen(name); + + for (i = 0; i < n; i++) { + char *s = tails[i], *t; + for ( ; (t = strchr(s, ';')); s = t + 1) { + int m = t - s; + if (len > m && strncmp(&name[len-m], s, m) == 0) + return i; + } + if (*s) { + int m = strlen(s); + if (len > m && strncmp(&name[len-m], s, m) == 0) + return i; + } + } + return -1; +} + +/* tempname - generate a temporary file name in tempdir with given suffix */ +char *tempname(char *suffix) { + static int n; + char *name = stringf("%s/lcc%d%d%s", tempdir, getpid(), n++, suffix); + + if (strstr(com[1], "win32") != NULL) + name = replace(name, '/', '\\'); + rmlist = append(name, rmlist); + return name; +} diff --git a/src/tools/lcc/lburg/gram.c b/src/tools/lcc/lburg/gram.c new file mode 100644 index 0000000..a1cc890 --- /dev/null +++ b/src/tools/lcc/lburg/gram.c @@ -0,0 +1,682 @@ +#if defined(__STDC__) || defined(__cplusplus) +#define YYCONST const +#define YYPARAMS(x) x +#define YYDEFUN(name, arglist, args) name(args) +#define YYAND , +#define YYPTR void * +#else +#define YYCONST +#define YYPARAMS(x) () +#define YYDEFUN(name, arglist, args) name arglist args; +#define YYAND ; +#define YYPTR char * +#endif +#ifndef lint +YYCONST static char yysccsid[] = "@(#)yaccpar 1.8 (Berkeley +Cygnus.28) 01/20/91"; +#endif +#define YYBYACC 1 +#ifndef YYDONT_INCLUDE_STDIO +#include +#endif +//#ifdef __cplusplus TA stdlib.h applies to C too +#include /* for malloc/realloc/free */ +//#endif +#line 2 "lburg/gram.y" +#include +#include "lburg.h" +/*lint -e616 -e527 -e652 -esym(552,yynerrs) -esym(563,yynewstate,yyerrlab) */ +static int yylineno = 0; +#line 8 "lburg/gram.y" +typedef union { + int n; + char *string; + Tree tree; +} YYSTYPE; +#line 37 "y.tab.c" +#define TERMINAL 257 +#define START 258 +#define PPERCENT 259 +#define ID 260 +#define TEMPLATE 261 +#define CODE 262 +#define INT 263 +#define YYERRCODE 256 +static YYCONST short yylhs[] = { -1, + 0, 0, 4, 4, 6, 6, 6, 6, 7, 7, + 5, 5, 5, 5, 1, 3, 3, 3, 2, +}; +static YYCONST short yylen[] = { 2, + 3, 1, 0, 2, 3, 3, 1, 2, 0, 4, + 0, 7, 2, 3, 1, 1, 4, 6, 1, +}; +static YYCONST short yydefred[] = { 3, + 0, 0, 0, 9, 0, 11, 7, 4, 8, 0, + 15, 0, 0, 0, 5, 6, 0, 13, 0, 0, + 14, 0, 10, 0, 0, 0, 0, 0, 19, 0, + 17, 0, 12, 0, 18, +}; +static YYCONST short yydgoto[] = { 1, + 12, 30, 25, 2, 13, 8, 10, +}; +static YYCONST short yysindex[] = { 0, + 0, -4, -2, 0, -250, 0, 0, 0, 0, -9, + 0, 1, -10, -49, 0, 0, 3, 0, -44, -248, + 0, -244, 0, -22, -242, -244, -245, -37, 0, 10, + 0, -244, 0, -20, 0, +}; +static YYCONST short yyrindex[] = { 0, + 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -39, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, +}; +static YYCONST short yygindex[] = { 0, + 11, 0, -23, 0, 0, 0, 0, +}; +#define YYTABLESIZE 255 +static YYCONST short yytable[] = { 18, + 15, 16, 28, 31, 16, 7, 32, 9, 34, 11, + 16, 20, 21, 22, 23, 24, 29, 26, 27, 33, + 35, 2, 1, 19, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 17, 0, 0, 0, 11, + 14, 3, 4, 5, 6, +}; +static YYCONST short yycheck[] = { 10, + 10, 41, 26, 41, 44, 10, 44, 10, 32, 260, + 10, 61, 10, 58, 263, 260, 262, 40, 261, 10, + 41, 0, 0, 13, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 261, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 256, -1, -1, -1, 260, + 260, 256, 257, 258, 259, +}; +#define YYFINAL 1 +#ifndef YYDEBUG +#define YYDEBUG 0 +#endif +#define YYMAXTOKEN 263 +#if YYDEBUG +static YYCONST char *YYCONST yyname[] = { +"end-of-file",0,0,0,0,0,0,0,0,0,"'\\n'",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,"'('","')'",0,0,"','",0,0,0,0,0,0,0,0,0,0,0,0,0,"':'",0,0, +"'='",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +"TERMINAL","START","PPERCENT","ID","TEMPLATE","CODE","INT", +}; +static YYCONST char *YYCONST yyrule[] = { +"$accept : spec", +"spec : decls PPERCENT rules", +"spec : decls", +"decls :", +"decls : decls decl", +"decl : TERMINAL blist '\\n'", +"decl : START nonterm '\\n'", +"decl : '\\n'", +"decl : error '\\n'", +"blist :", +"blist : blist ID '=' INT", +"rules :", +"rules : rules nonterm ':' tree TEMPLATE cost '\\n'", +"rules : rules '\\n'", +"rules : rules error '\\n'", +"nonterm : ID", +"tree : ID", +"tree : ID '(' tree ')'", +"tree : ID '(' tree ',' tree ')'", +"cost : CODE", +}; +#endif +#define YYLEX yylex() +#define YYEMPTY -1 +#define yyclearin (yychar=(YYEMPTY)) +#define yyerrok (yyerrflag=0) +#ifndef YYINITDEPTH +#define YYINITDEPTH 200 +#endif +#ifdef YYSTACKSIZE +#ifndef YYMAXDEPTH +#define YYMAXDEPTH YYSTACKSIZE +#endif +#else +#ifdef YYMAXDEPTH +#define YYSTACKSIZE YYMAXDEPTH +#else +#define YYSTACKSIZE 500 +#define YYMAXDEPTH 500 +#endif +#endif +#ifndef YYMAXSTACKSIZE +#define YYMAXSTACKSIZE 10000 +#endif +int yydebug; +int yynerrs; +int yyerrflag; +int yychar; +YYSTYPE yyval; +YYSTYPE yylval; +static short *yyss; +static YYSTYPE *yyvs; +static int yystacksize; +#define yyfree(x) free(x) +extern int yylex(); + +static YYPTR +YYDEFUN (yymalloc, (bytes), unsigned bytes) +{ + YYPTR ptr = (YYPTR) malloc (bytes); + if (ptr != 0) return (ptr); + yyerror ("yyparse: memory exhausted"); + return (0); +} + +static YYPTR +YYDEFUN (yyrealloc, (old, bytes), YYPTR old YYAND unsigned bytes) +{ + YYPTR ptr = (YYPTR) realloc (old, bytes); + if (ptr != 0) return (ptr); + yyerror ("yyparse: memory exhausted"); + return (0); +} + +static int +#ifdef __GNUC__ +inline +#endif +yygrow () +{ +#if YYDEBUG + int old_stacksize = yystacksize; +#endif + short *new_yyss; + YYSTYPE *new_yyvs; + + if (yystacksize == YYMAXSTACKSIZE) + return (1); + yystacksize += (yystacksize + 1 ) / 2; + if (yystacksize > YYMAXSTACKSIZE) + yystacksize = YYMAXSTACKSIZE; +#if YYDEBUG + if (yydebug) + printf("yydebug: growing stack size from %d to %d\n", + old_stacksize, yystacksize); +#endif + new_yyss = (short *) yyrealloc ((char *)yyss, yystacksize * sizeof (short)); + if (new_yyss == 0) + return (1); + new_yyvs = (YYSTYPE *) yyrealloc ((char *)yyvs, yystacksize * sizeof (YYSTYPE)); + if (new_yyvs == 0) + { + yyfree (new_yyss); + return (1); + } + yyss = new_yyss; + yyvs = new_yyvs; + return (0); +} +#line 60 "lburg/gram.y" +#include +#include +#include +#include +#include + +int errcnt = 0; +FILE *infp = NULL; +FILE *outfp = NULL; +static char buf[BUFSIZ], *bp = buf; +static int ppercent = 0; +static int code = 0; + +static int get(void) { + if (*bp == 0) { + bp = buf; + *bp = 0; + if (fgets(buf, sizeof buf, infp) == NULL) + return EOF; + yylineno++; + while (buf[0] == '%' && buf[1] == '{' && buf[2] == '\n') { + for (;;) { + if (fgets(buf, sizeof buf, infp) == NULL) { + yywarn("unterminated %{...%}\n"); + return EOF; + } + yylineno++; + if (strcmp(buf, "%}\n") == 0) + break; + fputs(buf, outfp); + } + if (fgets(buf, sizeof buf, infp) == NULL) + return EOF; + yylineno++; + } + } + return *bp++; +} + +void yyerror(char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + if (yylineno > 0) + fprintf(stderr, "line %d: ", yylineno); + vfprintf(stderr, fmt, ap); + if (fmt[strlen(fmt)-1] != '\n') + fprintf(stderr, "\n"); + errcnt++; + va_end(ap); +} + +int yylex(void) { + int c; + + if (code) { + char *p; + bp += strspn(bp, " \t\f"); + p = strchr(bp, '\n'); + if (p == NULL) + p = strchr(bp, '\n'); + while (p > bp && isspace(p[-1])) + p--; + yylval.string = alloc(p - bp + 1); + strncpy(yylval.string, bp, p - bp); + yylval.string[p - bp] = 0; + bp = p; + code--; + return CODE; + } + while ((c = get()) != EOF) { + switch (c) { + case ' ': case '\f': case '\t': + continue; + case '\n': + case '(': case ')': case ',': + case ':': case '=': + return c; + } + if (c == '%' && *bp == '%') { + bp++; + return ppercent++ ? 0 : PPERCENT; + } else if (c == '%' && strncmp(bp, "term", 4) == 0 + && isspace(bp[4])) { + bp += 4; + return TERMINAL; + } else if (c == '%' && strncmp(bp, "start", 5) == 0 + && isspace(bp[5])) { + bp += 5; + return START; + } else if (c == '"') { + char *p = strchr(bp, '"'); + if (p == NULL) { + yyerror("missing \" in assembler template\n"); + p = strchr(bp, '\n'); + if (p == NULL) + p = strchr(bp, '\0'); + } + assert(p); + yylval.string = alloc(p - bp + 1); + strncpy(yylval.string, bp, p - bp); + yylval.string[p - bp] = 0; + bp = *p == '"' ? p + 1 : p; + code++; + return TEMPLATE; + } else if (isdigit(c)) { + int n = 0; + do { + int d = c - '0'; + if (n > (INT_MAX - d)/10) + yyerror("integer greater than %d\n", INT_MAX); + else + n = 10*n + d; + c = get(); + } while (c != EOF && isdigit(c)); + bp--; + yylval.n = n; + return INT; + } else if (isalpha(c)) { + char *p = bp - 1; + while (isalpha(*bp) || isdigit(*bp) || *bp == '_') + bp++; + yylval.string = alloc(bp - p + 1); + strncpy(yylval.string, p, bp - p); + yylval.string[bp - p] = 0; + return ID; + } else if (isprint(c)) + yyerror("invalid character `%c'\n", c); + else + yyerror("invalid character `\\%03o'\n", (unsigned char)c); + } + return 0; +} + +void yywarn(char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + if (yylineno > 0) + fprintf(stderr, "line %d: ", yylineno); + fprintf(stderr, "warning: "); + vfprintf(stderr, fmt, ap); +} +#line 403 "y.tab.c" +#define YYABORT goto yyabort +#define YYACCEPT goto yyaccept +#define YYERROR goto yyerrlab + +#if YYDEBUG +#ifdef __cplusplus +extern "C" char *getenv(); +#else +extern char *getenv(); +#endif +#endif + +int +yyparse() +{ + register int yym, yyn, yystate; + register YYSTYPE *yyvsp; + register short *yyssp; + short *yysse; +#if YYDEBUG + register YYCONST char *yys; + + if (yys = getenv("YYDEBUG")) + { + yyn = *yys; + if (yyn >= '0' && yyn <= '9') + yydebug = yyn - '0'; + } +#endif + + yynerrs = 0; + yyerrflag = 0; + yychar = (-1); + + if (yyss == 0) + { + yyss = (short *) yymalloc (YYSTACKSIZE * sizeof (short)); + if (yyss == 0) + goto yyabort; + yyvs = (YYSTYPE *) yymalloc (YYSTACKSIZE * sizeof (YYSTYPE)); + if (yyvs == 0) + { + yyfree (yyss); + goto yyabort; + } + yystacksize = YYSTACKSIZE; + } + yysse = yyss + yystacksize - 1; + yyssp = yyss; + yyvsp = yyvs; + *yyssp = yystate = 0; + goto yyloop; + +yypush_lex: + yyval = yylval; + yystate = yytable[yyn]; +yypush: + if (yyssp >= yysse) + { + int depth = yyssp - yyss; + if (yygrow() != 0) + goto yyoverflow; + yysse = yyss + yystacksize -1; + yyssp = depth + yyss; + yyvsp = depth + yyvs; + } + *++yyssp = yystate; + *++yyvsp = yyval; + +yyloop: + if ((yyn = yydefred[yystate])) goto yyreduce; + yyn = yysindex[yystate]; + if (yychar < 0) + { + if ((yychar = yylex()) < 0) yychar = 0; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYMAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("yydebug: state %d, reading %d (%s)\n", yystate, + yychar, yys); + } +#endif + } + if (yyn != 0 + && ((yyn += yychar), ((unsigned)yyn <= (unsigned)YYTABLESIZE)) + && yycheck[yyn] == yychar) + { +#if YYDEBUG + if (yydebug) + printf("yydebug: state %d, shifting to state %d\n", + yystate, yytable[yyn]); +#endif + if (yyerrflag > 0) --yyerrflag; + yychar = (-1); + goto yypush_lex; + } + yyn = yyrindex[yystate]; + if (yyn != 0 + && ((yyn += yychar), ((unsigned)yyn <= (unsigned)YYTABLESIZE)) + && yycheck[yyn] == yychar) + { + yyn = yytable[yyn]; + goto yyreduce; + } + if (yyerrflag) goto yyinrecovery; +#ifdef lint + goto yynewerror; +yynewerror: +#endif + yyerror("syntax error"); +#ifdef lint + goto yyerrlab; +yyerrlab: +#endif + ++yynerrs; +yyinrecovery: + if (yyerrflag < 3) + { + yyerrflag = 3; + for (;;) + { + yyn = yysindex[*yyssp]; + if (yyn != 0 + && ((yyn += YYERRCODE), ((unsigned)yyn <= (unsigned)YYTABLESIZE)) + && yycheck[yyn] == YYERRCODE) + { +#if YYDEBUG + if (yydebug) + printf("yydebug: state %d, error recovery shifting\ + to state %d\n", *yyssp, yytable[yyn]); +#endif + goto yypush_lex; + } + else + { +#if YYDEBUG + if (yydebug) + printf("yydebug: error recovery discarding state %d\n", + *yyssp); +#endif + if (yyssp <= yyss) goto yyabort; + --yyssp; + --yyvsp; + } + } + } + else + { + if (yychar == 0) goto yyabort; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYMAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("yydebug: state %d, error recovery discards token %d (%s)\n", + yystate, yychar, yys); + } +#endif + yychar = (-1); + goto yyloop; + } +yyreduce: +#if YYDEBUG + if (yydebug) + printf("yydebug: state %d, reducing by rule %d (%s)\n", + yystate, yyn, yyrule[yyn]); +#endif + yym = yylen[yyn]; + yyval = yyvsp[1-yym]; + switch (yyn) + { +case 1: +#line 22 "lburg/gram.y" +{ yylineno = 0; } +break; +case 2: +#line 23 "lburg/gram.y" +{ yylineno = 0; } +break; +case 6: +#line 31 "lburg/gram.y" +{ + if (nonterm(yyvsp[-1].string)->number != 1) + yyerror("redeclaration of the start symbol\n"); + } +break; +case 8: +#line 36 "lburg/gram.y" +{ yyerrok; } +break; +case 10: +#line 40 "lburg/gram.y" +{ term(yyvsp[-2].string, yyvsp[0].n); } +break; +case 12: +#line 44 "lburg/gram.y" +{ rule(yyvsp[-5].string, yyvsp[-3].tree, yyvsp[-2].string, yyvsp[-1].string); } +break; +case 14: +#line 46 "lburg/gram.y" +{ yyerrok; } +break; +case 15: +#line 49 "lburg/gram.y" +{ nonterm(yyval.string = yyvsp[0].string); } +break; +case 16: +#line 52 "lburg/gram.y" +{ yyval.tree = tree(yyvsp[0].string, 0, 0); } +break; +case 17: +#line 53 "lburg/gram.y" +{ yyval.tree = tree(yyvsp[-3].string, yyvsp[-1].tree, 0); } +break; +case 18: +#line 54 "lburg/gram.y" +{ yyval.tree = tree(yyvsp[-5].string, yyvsp[-3].tree, yyvsp[-1].tree); } +break; +case 19: +#line 57 "lburg/gram.y" +{ if (*yyvsp[0].string == 0) yyval.string = "0"; } +break; +#line 630 "y.tab.c" + } + yyssp -= yym; + yyvsp -= yym; + yym = yylhs[yyn]; + yystate = *yyssp; + if (yystate == 0 && yym == 0) + { +#if YYDEBUG + if (yydebug) + printf("yydebug: after reduction, shifting from state 0 to\ + state %d\n", YYFINAL); +#endif + yystate = YYFINAL; + *++yyssp = YYFINAL; + *++yyvsp = yyval; + if (yychar < 0) + { + if ((yychar = yylex()) < 0) yychar = 0; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYMAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("yydebug: state %d, reading %d (%s)\n", + YYFINAL, yychar, yys); + } +#endif + } + if (yychar == 0) goto yyaccept; + goto yyloop; + } + yyn = yygindex[yym]; + if (yyn != 0 + && ((yyn += yystate), ((unsigned)yyn <= (unsigned)YYTABLESIZE)) + && yycheck[yyn] == yystate) + yystate = yytable[yyn]; + else + yystate = yydgoto[yym]; +#if YYDEBUG + if (yydebug) + printf("yydebug: after reduction, shifting from state %d \ +to state %d\n", *yyssp, yystate); +#endif + goto yypush; +yyoverflow: + yyerror("yacc stack overflow"); +yyabort: + return (1); +yyaccept: + return (0); +} diff --git a/src/tools/lcc/lburg/gram.y b/src/tools/lcc/lburg/gram.y new file mode 100644 index 0000000..1ecd8a9 --- /dev/null +++ b/src/tools/lcc/lburg/gram.y @@ -0,0 +1,202 @@ +%{ +#include +#include "lburg.h" +static char rcsid[] = "$Id: gram.y 145 2001-10-17 21:53:10Z timo $"; +/*lint -e616 -e527 -e652 -esym(552,yynerrs) -esym(563,yynewstate,yyerrlab) */ +static int yylineno = 0; +%} +%union { + int n; + char *string; + Tree tree; +} +%term TERMINAL +%term START +%term PPERCENT + +%token ID TEMPLATE CODE +%token INT +%type nonterm cost +%type tree +%% +spec : decls PPERCENT rules { yylineno = 0; } + | decls { yylineno = 0; } + ; + +decls : /* lambda */ + | decls decl + ; + +decl : TERMINAL blist '\n' + | START nonterm '\n' { + if (nonterm($2)->number != 1) + yyerror("redeclaration of the start symbol\n"); + } + | '\n' + | error '\n' { yyerrok; } + ; + +blist : /* lambda */ + | blist ID '=' INT { term($2, $4); } + ; + +rules : /* lambda */ + | rules nonterm ':' tree TEMPLATE cost '\n' { rule($2, $4, $5, $6); } + | rules '\n' + | rules error '\n' { yyerrok; } + ; + +nonterm : ID { nonterm($$ = $1); } + ; + +tree : ID { $$ = tree($1, 0, 0); } + | ID '(' tree ')' { $$ = tree($1, $3, 0); } + | ID '(' tree ',' tree ')' { $$ = tree($1, $3, $5); } + ; + +cost : CODE { if (*$1 == 0) $$ = "0"; } + ; +%% +#include +#include +#include +#include +#include + +int errcnt = 0; +FILE *infp = NULL; +FILE *outfp = NULL; +static char buf[BUFSIZ], *bp = buf; +static int ppercent = 0; +static int code = 0; + +static int get(void) { + if (*bp == 0) { + bp = buf; + *bp = 0; + if (fgets(buf, sizeof buf, infp) == NULL) + return EOF; + yylineno++; + while (buf[0] == '%' && buf[1] == '{' && buf[2] == '\n') { + for (;;) { + if (fgets(buf, sizeof buf, infp) == NULL) { + yywarn("unterminated %{...%}\n"); + return EOF; + } + yylineno++; + if (strcmp(buf, "%}\n") == 0) + break; + fputs(buf, outfp); + } + if (fgets(buf, sizeof buf, infp) == NULL) + return EOF; + yylineno++; + } + } + return *bp++; +} + +void yyerror(char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + if (yylineno > 0) + fprintf(stderr, "line %d: ", yylineno); + vfprintf(stderr, fmt, ap); + if (fmt[strlen(fmt)-1] != '\n') + fprintf(stderr, "\n"); + errcnt++; + va_end(ap); +} + +int yylex(void) { + int c; + + if (code) { + char *p; + bp += strspn(bp, " \t\f"); + p = strchr(bp, '\n'); + if (p == NULL) + p = strchr(bp, '\n'); + while (p > bp && isspace(p[-1])) + p--; + yylval.string = alloc(p - bp + 1); + strncpy(yylval.string, bp, p - bp); + yylval.string[p - bp] = 0; + bp = p; + code--; + return CODE; + } + while ((c = get()) != EOF) { + switch (c) { + case ' ': case '\f': case '\t': + continue; + case '\n': + case '(': case ')': case ',': + case ':': case '=': + return c; + } + if (c == '%' && *bp == '%') { + bp++; + return ppercent++ ? 0 : PPERCENT; + } else if (c == '%' && strncmp(bp, "term", 4) == 0 + && isspace(bp[4])) { + bp += 4; + return TERMINAL; + } else if (c == '%' && strncmp(bp, "start", 5) == 0 + && isspace(bp[5])) { + bp += 5; + return START; + } else if (c == '"') { + char *p = strchr(bp, '"'); + if (p == NULL) { + yyerror("missing \" in assembler template\n"); + p = strchr(bp, '\n'); + if (p == NULL) + p = strchr(bp, '\0'); + } + assert(p); + yylval.string = alloc(p - bp + 1); + strncpy(yylval.string, bp, p - bp); + yylval.string[p - bp] = 0; + bp = *p == '"' ? p + 1 : p; + code++; + return TEMPLATE; + } else if (isdigit(c)) { + int n = 0; + do { + int d = c - '0'; + if (n > (INT_MAX - d)/10) + yyerror("integer greater than %d\n", INT_MAX); + else + n = 10*n + d; + c = get(); + } while (c != EOF && isdigit(c)); + bp--; + yylval.n = n; + return INT; + } else if (isalpha(c)) { + char *p = bp - 1; + while (isalpha(*bp) || isdigit(*bp) || *bp == '_') + bp++; + yylval.string = alloc(bp - p + 1); + strncpy(yylval.string, p, bp - p); + yylval.string[bp - p] = 0; + return ID; + } else if (isprint(c)) + yyerror("invalid character `%c'\n", c); + else + yyerror("invalid character `\\%03o'\n", (unsigned char)c); + } + return 0; +} + +void yywarn(char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + if (yylineno > 0) + fprintf(stderr, "line %d: ", yylineno); + fprintf(stderr, "warning: "); + vfprintf(stderr, fmt, ap); +} diff --git a/src/tools/lcc/lburg/lburg.1 b/src/tools/lcc/lburg/lburg.1 new file mode 100644 index 0000000..8cf7250 --- /dev/null +++ b/src/tools/lcc/lburg/lburg.1 @@ -0,0 +1,179 @@ +.TH LBURG 1 "local \- 11/30/94" +.\" $Id: lburg.1 145 2001-10-17 21:53:10Z timo $ +.SH NAME +lburg \- lcc's code-generator generator +.SH SYNOPSIS +.B lburg +[ +.I option +]... +[ [ +.I input +] +.I output +] +.br +.SH DESCRIPTION +.PP +.I lburg +reads an lcc-style BURG specification from +.I input +and writes a pattern-matching code generator to +.IR output . +If +.I input +is `\-' or is omitted, +.I lburg +reads the standard input; +If +.I output +is `\-' or is omitted, +.I lburg +writes to the standard output. +.PP +.I lburg +accepts specifications that conform to the following EBNF grammar. +Terminals are enclosed in single quotes or are +given in uppercase, all other symbols are nonterminals or English phrases, +{X} denotes zero or more instances of X, and [X] denotes an optional X. +.PP +.nf +.RS +.ft CW +spec: `%{' configuration `%}' { dcl } `%%' { rule } + [ `%%' C code ] + +dcl: `%start' nonterm + `%term' { ID `=' INT } + +rule: nonterm `:' tree template [ C expression ] + +tree: term `(' tree `,' tree `)' + term `(' tree `)' + term + nonterm + +nonterm: ID + +template: `"' { any character except double quote } `"' +.RE +.fi +.PP +Specifications are structurally similar to +.IR yacc 's. +Text between +`\f(CW%{\fP' +and +`\f(CW%}\fP' +is called the configuration section; there may be several such segments. +All are concatenated and copied verbatim into the head of the output. +Text after the second +`\f(CW%%\fP', +if any, is also copied verbatim into the output, at the end. +.PP +Specifications consist of declarations, a +`\f(CW%%\fP' +separator, and rules. +Input is line-oriented; each declaration and rule must appear on a separate line, +and declarations must begin in column 1. +Declarations declare terminals \(em the operators in subject +trees \(em and associate a unique, positive external symbol +number with each one. +Nonterminals are declared by their presence +on the left side of rules. The +\f(CW%start\fP +declaration optionally declares a nonterminal as the start symbol. +In the grammar above, +\f(CWterm\fP +and +\f(CWnonterm\fP +denote identifiers that are terminals and nonterminals. +.PP +Rules define tree patterns in a fully parenthesized prefix +form. Every nonterminal denotes a tree. +Each operator has a fixed +arity, which is inferred from the rules in which it is used. +A chain rule is a rule whose pattern is another nonterminal. +If no start symbol is declared, the nonterminal defined by the first rule is used. +.PP +Each rule ends with an expression that computes the cost of matching +that rule; omitted costs +default to zero. Costs of chain rules must be constants. +.PP +The configuration section configures the output +for the trees being parsed and the client's environment. +As shown, this section must define +\f(CWNODEPTR_TYPE\fP +to be a visible typedef symbol for a pointer to a +node in the subject tree. +The labeller invokes +\f(CWOP_LABEL(p)\fP, +\f(CWLEFT\_CHILD(p)\fP, and +\f(CWRIGHT\_CHILD(p)\fP +to read the operator and children from the node pointed to by \f(CWp\fP. +If the configuration section defines these operations as macros, they are implemented in-line; +otherwise, they must be implemented as functions. +.PP +The matcher +computes and stores a single integral state in each node of the subject tree. +The configuration section must define a macro +\f(CWSTATE_LABEL(p)\fP +to access the state field of the node pointed to +by \f(CWp\fP. It must be large enough to hold a pointer, and +a macro is required because it is used as an lvalue. +.PP +.SH OPTIONS +.TP +.BI \-p \ prefix +.br +.ns +.TP +.BI \-p prefix +Use +.I prefix +as the disambiquating prefix for visible names and fields. +The default is `\f(CW_\fP'. +.TP +.B \-T +Arrange for +.sp +.nf +.ft CW + void _trace(NODEPTR_TYPE p, int eruleno, + int cost, int bestcost); +.sp +.fi +.ft R +to be called at each successful match. +\f(CWp\fP +identifies the node and +\f(CWeruleno\fP +identifies the matching rule; the rules are numbered +beginning at 1 in the order they appear in the input. +\f(CWcost\fP +is the cost of the match and +\f(CWbestcost\fP +is the cost of the best previous match. The current match +wins only if +\f(CWcost\fP +is less than \f(CWbestcost\fP. +32767 represents the infinite cost of no previous match. +\f(CW_trace\fP must be declared in the configuration section. +.SH "SEE ALSO" +.IR lcc (1) +.PP +C. W. Fraser and D. R. Hanson, +.IR A Retargetable C Compiler: Design and Implementation , +Benjamin/Cummings, Redwood City, CA, 1995, +ISBN 0-8053-1670-1. Chapter 14. +.PP +C. W. Fraser, D. R. Hanson and T. A. Proebsting, +`Engineering a simple, efficient code generator generator,' +.I +ACM Letters on Programming Languages and Systems +.BR 1 , +3 (Sep. 1992), 213-226. +.br +.SH BUGS +Mail bug reports along with the shortest input +that exposes them to drh@cs.princeton.edu. diff --git a/src/tools/lcc/lburg/lburg.c b/src/tools/lcc/lburg/lburg.c new file mode 100644 index 0000000..c43c96a --- /dev/null +++ b/src/tools/lcc/lburg/lburg.c @@ -0,0 +1,671 @@ +#include +#include +#include +#include +#include +#include +#include +#include "lburg.h" + +static char rcsid[] = "lburg.c - faked rcsid"; + +static char *prefix = ""; +static int Tflag = 0; +static int ntnumber = 0; +static Nonterm start = 0; +static Term terms; +static Nonterm nts; +static Rule rules; +static int nrules; +static struct block { + struct block *link; +} *memlist; /* list of allocated blocks */ + +static char *stringf(char *fmt, ...); +static void print(char *fmt, ...); +static void ckreach(Nonterm p); +static void emitclosure(Nonterm nts); +static void emitcost(Tree t, char *v); +static void emitdefs(Nonterm nts, int ntnumber); +static void emitheader(void); +static void emitkids(Rule rules, int nrules); +static void emitnts(Rule rules, int nrules); +static void emitrecalc(char *pre, Term root, Term kid); +static void emitrecord(char *pre, Rule r, char *c, int cost); +static void emitrule(Nonterm nts); +static void emitlabel(Term terms, Nonterm start, int ntnumber); +static void emitstring(Rule rules); +static void emitstruct(Nonterm nts, int ntnumber); +static void emittest(Tree t, char *v, char *suffix); + +int main(int argc, char *argv[]) { + int c, i; + Nonterm p; + + for (i = 1; i < argc; i++) + if (strcmp(argv[i], "-T") == 0) + Tflag = 1; + else if (strncmp(argv[i], "-p", 2) == 0 && argv[i][2]) + prefix = &argv[i][2]; + else if (strncmp(argv[i], "-p", 2) == 0 && i + 1 < argc) + prefix = argv[++i]; + else if (*argv[i] == '-' && argv[i][1]) { + yyerror("usage: %s [-T | -p prefix]... [ [ input ] output ] \n", + argv[0]); + exit(1); + } else if (infp == NULL) { + if (strcmp(argv[i], "-") == 0) + infp = stdin; + else if ((infp = fopen(argv[i], "r")) == NULL) { + yyerror("%s: can't read `%s'\n", argv[0], argv[i]); + exit(1); + } + } else if (outfp == NULL) { + if (strcmp(argv[i], "-") == 0) + outfp = stdout; + if ((outfp = fopen(argv[i], "w")) == NULL) { + yyerror("%s: can't write `%s'\n", argv[0], argv[i]); + exit(1); + } + } + if (infp == NULL) + infp = stdin; + if (outfp == NULL) + outfp = stdout; + yyparse(); + if (start) + ckreach(start); + for (p = nts; p; p = p->link) { + if (p->rules == NULL) + yyerror("undefined nonterminal `%s'\n", p->name); + if (!p->reached) + yyerror("can't reach nonterminal `%s'\n", p->name); + } + emitheader(); + emitdefs(nts, ntnumber); + emitstruct(nts, ntnumber); + emitnts(rules, nrules); + emitstring(rules); + emitrule(nts); + emitclosure(nts); + if (start) + emitlabel(terms, start, ntnumber); + emitkids(rules, nrules); + if (!feof(infp)) + while ((c = getc(infp)) != EOF) + putc(c, outfp); + while (memlist) { /* for purify */ + struct block *q = memlist->link; + free(memlist); + memlist = q; + } + return errcnt > 0; +} + +/* alloc - allocate nbytes or issue fatal error */ +void *alloc(int nbytes) { + struct block *p = calloc(1, sizeof *p + nbytes); + + if (p == NULL) { + yyerror("out of memory\n"); + exit(1); + } + p->link = memlist; + memlist = p; + return p + 1; +} + +/* stringf - format and save a string */ +static char *stringf(char *fmt, ...) { + va_list ap; + char buf[512]; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + return strcpy(alloc(strlen(buf) + 1), buf); +} + +struct entry { + union { + char *name; + struct term t; + struct nonterm nt; + } sym; + struct entry *link; +} *table[211]; +#define HASHSIZE (sizeof table/sizeof table[0]) + +/* hash - return hash number for str */ +static unsigned hash(char *str) { + unsigned h = 0; + + while (*str) + h = (h<<1) + *str++; + return h; +} + +/* lookup - lookup symbol name */ +static void *lookup(char *name) { + struct entry *p = table[hash(name)%HASHSIZE]; + + for ( ; p; p = p->link) + if (strcmp(name, p->sym.name) == 0) + return &p->sym; + return 0; +} + +/* install - install symbol name */ +static void *install(char *name) { + struct entry *p = alloc(sizeof *p); + int i = hash(name)%HASHSIZE; + + p->sym.name = name; + p->link = table[i]; + table[i] = p; + return &p->sym; +} + +/* nonterm - create a new terminal id, if necessary */ +Nonterm nonterm(char *id) { + Nonterm p = lookup(id), *q = &nts; + + if (p && p->kind == NONTERM) + return p; + if (p && p->kind == TERM) + yyerror("`%s' is a terminal\n", id); + p = install(id); + p->kind = NONTERM; + p->number = ++ntnumber; + if (p->number == 1) + start = p; + while (*q && (*q)->number < p->number) + q = &(*q)->link; + assert(*q == 0 || (*q)->number != p->number); + p->link = *q; + *q = p; + return p; +} + +/* term - create a new terminal id with external symbol number esn */ +Term term(char *id, int esn) { + Term p = lookup(id), *q = &terms; + + if (p) + yyerror("redefinition of terminal `%s'\n", id); + else + p = install(id); + p->kind = TERM; + p->esn = esn; + p->arity = -1; + while (*q && (*q)->esn < p->esn) + q = &(*q)->link; + if (*q && (*q)->esn == p->esn) + yyerror("duplicate external symbol number `%s=%d'\n", + p->name, p->esn); + p->link = *q; + *q = p; + return p; +} + +/* tree - create & initialize a tree node with the given fields */ +Tree tree(char *id, Tree left, Tree right) { + Tree t = alloc(sizeof *t); + Term p = lookup(id); + int arity = 0; + + if (left && right) + arity = 2; + else if (left) + arity = 1; + if (p == NULL && arity > 0) { + yyerror("undefined terminal `%s'\n", id); + p = term(id, -1); + } else if (p == NULL && arity == 0) + p = (Term)nonterm(id); + else if (p && p->kind == NONTERM && arity > 0) { + yyerror("`%s' is a nonterminal\n", id); + p = term(id, -1); + } + if (p->kind == TERM && p->arity == -1) + p->arity = arity; + if (p->kind == TERM && arity != p->arity) + yyerror("inconsistent arity for terminal `%s'\n", id); + t->op = p; + t->nterms = p->kind == TERM; + if ((t->left = left) != NULL) + t->nterms += left->nterms; + if ((t->right = right) != NULL) + t->nterms += right->nterms; + return t; +} + +/* rule - create & initialize a rule with the given fields */ +Rule rule(char *id, Tree pattern, char *template, char *code) { + Rule r = alloc(sizeof *r), *q; + Term p = pattern->op; + char *end; + + r->lhs = nonterm(id); + r->packed = ++r->lhs->lhscount; + for (q = &r->lhs->rules; *q; q = &(*q)->decode) + ; + *q = r; + r->pattern = pattern; + r->ern = ++nrules; + r->template = template; + r->code = code; + r->cost = strtol(code, &end, 10); + if (*end) { + r->cost = -1; + r->code = stringf("(%s)", code); + } + if (p->kind == TERM) { + for (q = &p->rules; *q; q = &(*q)->next) + ; + *q = r; + } else if (pattern->left == NULL && pattern->right == NULL) { + Nonterm p = pattern->op; + r->chain = p->chain; + p->chain = r; + if (r->cost == -1) + yyerror("illegal nonconstant cost `%s'\n", code); + } + for (q = &rules; *q; q = &(*q)->link) + ; + r->link = *q; + *q = r; + return r; +} + +/* print - formatted output */ +static void print(char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + for ( ; *fmt; fmt++) + if (*fmt == '%') + switch (*++fmt) { + case 'd': fprintf(outfp, "%d", va_arg(ap, int)); break; + case 's': fputs(va_arg(ap, char *), outfp); break; + case 'P': fprintf(outfp, "%s_", prefix); break; + case 'T': { + Tree t = va_arg(ap, Tree); + print("%S", t->op); + if (t->left && t->right) + print("(%T,%T)", t->left, t->right); + else if (t->left) + print("(%T)", t->left); + break; + } + case 'R': { + Rule r = va_arg(ap, Rule); + print("%S: %T", r->lhs, r->pattern); + break; + } + case 'S': fputs(va_arg(ap, Term)->name, outfp); break; + case '1': case '2': case '3': case '4': case '5': { + int n = *fmt - '0'; + while (n-- > 0) + putc('\t', outfp); + break; + } + default: putc(*fmt, outfp); break; + } + else + putc(*fmt, outfp); + va_end(ap); +} + +/* reach - mark all nonterminals in tree t as reachable */ +static void reach(Tree t) { + Nonterm p = t->op; + + if (p->kind == NONTERM) + if (!p->reached) + ckreach(p); + if (t->left) + reach(t->left); + if (t->right) + reach(t->right); +} + +/* ckreach - mark all nonterminals reachable from p */ +static void ckreach(Nonterm p) { + Rule r; + + p->reached = 1; + for (r = p->rules; r; r = r->decode) + reach(r->pattern); +} + +/* emitcase - emit one case in function state */ +static void emitcase(Term p, int ntnumber) { + Rule r; + + print("%1case %d: /* %S */\n", p->esn, p); + switch (p->arity) { + case 0: case -1: + break; + case 1: + print("%2%Plabel(LEFT_CHILD(a));\n"); + break; + case 2: + print("%2%Plabel(LEFT_CHILD(a));\n"); + print("%2%Plabel(RIGHT_CHILD(a));\n"); + break; + default: assert(0); + } + for (r = p->rules; r; r = r->next) { + char *indent = "\t\t\0"; + switch (p->arity) { + case 0: case -1: + print("%2/* %R */\n", r); + if (r->cost == -1) { + print("%2c = %s;\n", r->code); + emitrecord("\t\t", r, "c", 0); + } else + emitrecord("\t\t", r, r->code, 0); + break; + case 1: + if (r->pattern->nterms > 1) { + print("%2if (%1/* %R */\n", r); + emittest(r->pattern->left, "LEFT_CHILD(a)", " "); + print("%2) {\n"); + indent = "\t\t\t"; + } else + print("%2/* %R */\n", r); + if (r->pattern->nterms == 2 && r->pattern->left + && r->pattern->right == NULL) + emitrecalc(indent, r->pattern->op, r->pattern->left->op); + print("%sc = ", indent); + emitcost(r->pattern->left, "LEFT_CHILD(a)"); + print("%s;\n", r->code); + emitrecord(indent, r, "c", 0); + if (indent[2]) + print("%2}\n"); + break; + case 2: + if (r->pattern->nterms > 1) { + print("%2if (%1/* %R */\n", r); + emittest(r->pattern->left, "LEFT_CHILD(a)", + r->pattern->right->nterms ? " && " : " "); + emittest(r->pattern->right, "RIGHT_CHILD(a)", " "); + print("%2) {\n"); + indent = "\t\t\t"; + } else + print("%2/* %R */\n", r); + print("%sc = ", indent); + emitcost(r->pattern->left, "LEFT_CHILD(a)"); + emitcost(r->pattern->right, "RIGHT_CHILD(a)"); + print("%s;\n", r->code); + emitrecord(indent, r, "c", 0); + if (indent[2]) + print("%2}\n"); + break; + default: assert(0); + } + } + print("%2break;\n"); +} + +/* emitclosure - emit the closure functions */ +static void emitclosure(Nonterm nts) { + Nonterm p; + + for (p = nts; p; p = p->link) + if (p->chain) + print("static void %Pclosure_%S(NODEPTR_TYPE, int);\n", p); + print("\n"); + for (p = nts; p; p = p->link) + if (p->chain) { + Rule r; + print("static void %Pclosure_%S(NODEPTR_TYPE a, int c) {\n" +"%1struct %Pstate *p = STATE_LABEL(a);\n", p); + for (r = p->chain; r; r = r->chain) + emitrecord("\t", r, "c", r->cost); + print("}\n\n"); + } +} + +/* emitcost - emit cost computation for tree t */ +static void emitcost(Tree t, char *v) { + Nonterm p = t->op; + + if (p->kind == TERM) { + if (t->left) + emitcost(t->left, stringf("LEFT_CHILD(%s)", v)); + if (t->right) + emitcost(t->right, stringf("RIGHT_CHILD(%s)", v)); + } else + print("((struct %Pstate *)(%s->x.state))->cost[%P%S_NT] + ", v, p); +} + +/* emitdefs - emit nonterminal defines and data structures */ +static void emitdefs(Nonterm nts, int ntnumber) { + Nonterm p; + + for (p = nts; p; p = p->link) + print("#define %P%S_NT %d\n", p, p->number); + print("\n"); + print("static char *%Pntname[] = {\n%10,\n"); + for (p = nts; p; p = p->link) + print("%1\"%S\",\n", p); + print("%10\n};\n\n"); +} + +/* emitheader - emit initial definitions */ +static void emitheader(void) { + time_t timer = time(NULL); + + print("/*\ngenerated at %sby %s\n*/\n", ctime(&timer), rcsid); + print("static void %Pkids(NODEPTR_TYPE, int, NODEPTR_TYPE[]);\n"); + print("static void %Plabel(NODEPTR_TYPE);\n"); + print("static int %Prule(void*, int);\n\n"); +} + +/* computekids - compute paths to kids in tree t */ +static char *computekids(Tree t, char *v, char *bp, int *ip) { + Term p = t->op; + + if (p->kind == NONTERM) { + sprintf(bp, "\t\tkids[%d] = %s;\n", (*ip)++, v); + bp += strlen(bp); + } else if (p->arity > 0) { + bp = computekids(t->left, stringf("LEFT_CHILD(%s)", v), bp, ip); + if (p->arity == 2) + bp = computekids(t->right, stringf("RIGHT_CHILD(%s)", v), bp, ip); + } + return bp; +} + +/* emitkids - emit _kids */ +static void emitkids(Rule rules, int nrules) { + int i; + Rule r, *rc = alloc((nrules + 1 + 1)*sizeof *rc); + char **str = alloc((nrules + 1 + 1)*sizeof *str); + + for (i = 0, r = rules; r; r = r->link) { + int j = 0; + char buf[1024], *bp = buf; + *computekids(r->pattern, "p", bp, &j) = 0; + for (j = 0; str[j] && strcmp(str[j], buf); j++) + ; + if (str[j] == NULL) + str[j] = strcpy(alloc(strlen(buf) + 1), buf); + r->kids = rc[j]; + rc[j] = r; + } + print("static void %Pkids(NODEPTR_TYPE p, int eruleno, NODEPTR_TYPE kids[]) {\n" +"%1if (!p)\n%2fatal(\"%Pkids\", \"Null tree\\n\", 0);\n" +"%1if (!kids)\n%2fatal(\"%Pkids\", \"Null kids\\n\", 0);\n" +"%1switch (eruleno) {\n"); + for (i = 0; (r = rc[i]) != NULL; i++) { + for ( ; r; r = r->kids) + print("%1case %d: /* %R */\n", r->ern, r); + print("%s%2break;\n", str[i]); + } + print("%1default:\n%2fatal(\"%Pkids\", \"Bad rule number %%d\\n\", eruleno);\n%1}\n}\n\n"); +} + +/* emitlabel - emit label function */ +static void emitlabel(Term terms, Nonterm start, int ntnumber) { + int i; + Term p; + + print("static void %Plabel(NODEPTR_TYPE a) {\n%1int c;\n" +"%1struct %Pstate *p;\n\n" +"%1if (!a)\n%2fatal(\"%Plabel\", \"Null tree\\n\", 0);\n"); + print("%1STATE_LABEL(a) = p = allocate(sizeof *p, FUNC);\n" +"%1p->rule._stmt = 0;\n"); + for (i = 1; i <= ntnumber; i++) + print("%1p->cost[%d] =\n", i); + print("%20x7fff;\n%1switch (OP_LABEL(a)) {\n"); + for (p = terms; p; p = p->link) + emitcase(p, ntnumber); + print("%1default:\n" +"%2fatal(\"%Plabel\", \"Bad terminal %%d\\n\", OP_LABEL(a));\n%1}\n}\n\n"); +} + +/* computents - fill in bp with _nts vector for tree t */ +static char *computents(Tree t, char *bp) { + if (t) { + Nonterm p = t->op; + if (p->kind == NONTERM) { + sprintf(bp, "%s_%s_NT, ", prefix, p->name); + bp += strlen(bp); + } else + bp = computents(t->right, computents(t->left, bp)); + } + return bp; +} + +/* emitnts - emit _nts ragged array */ +static void emitnts(Rule rules, int nrules) { + Rule r; + int i, j, *nts = alloc((nrules + 1)*sizeof *nts); + char **str = alloc((nrules + 1)*sizeof *str); + + for (i = 0, r = rules; r; r = r->link) { + char buf[1024]; + *computents(r->pattern, buf) = 0; + for (j = 0; str[j] && strcmp(str[j], buf); j++) + ; + if (str[j] == NULL) { + print("static short %Pnts_%d[] = { %s0 };\n", j, buf); + str[j] = strcpy(alloc(strlen(buf) + 1), buf); + } + nts[i++] = j; + } + print("\nstatic short *%Pnts[] = {\n"); + for (i = j = 0, r = rules; r; r = r->link) { + for ( ; j < r->ern; j++) + print("%10,%1/* %d */\n", j); + print("%1%Pnts_%d,%1/* %d */\n", nts[i++], j++); + } + print("};\n\n"); +} + +/* emitrecalc - emit code that tests for recalculation of INDIR?(VREGP) */ +static void emitrecalc(char *pre, Term root, Term kid) { + if (root->kind == TERM && strncmp(root->name, "INDIR", 5) == 0 + && kid->kind == TERM && strcmp(kid->name, "VREGP" ) == 0) { + Nonterm p; + print("%sif (mayrecalc(a)) {\n", pre); + print("%s%1struct %Pstate *q = a->syms[RX]->u.t.cse->x.state;\n", pre); + for (p = nts; p; p = p->link) { + print("%s%1if (q->cost[%P%S_NT] == 0) {\n", pre, p); + print("%s%2p->cost[%P%S_NT] = 0;\n", pre, p); + print("%s%2p->rule.%P%S = q->rule.%P%S;\n", pre, p, p); + print("%s%1}\n", pre); + } + print("%s}\n", pre); + } +} + +/* emitrecord - emit code that tests for a winning match of rule r */ +static void emitrecord(char *pre, Rule r, char *c, int cost) { + if (Tflag) + print("%s%Ptrace(a, %d, %s + %d, p->cost[%P%S_NT]);\n", + pre, r->ern, c, cost, r->lhs); + print("%sif (", pre); + print("%s + %d < p->cost[%P%S_NT]) {\n" +"%s%1p->cost[%P%S_NT] = %s + %d;\n%s%1p->rule.%P%S = %d;\n", + c, cost, r->lhs, pre, r->lhs, c, cost, pre, r->lhs, + r->packed); + if (r->lhs->chain) + print("%s%1%Pclosure_%S(a, %s + %d);\n", pre, r->lhs, c, cost); + print("%s}\n", pre); +} + +/* emitrule - emit decoding vectors and _rule */ +static void emitrule(Nonterm nts) { + Nonterm p; + + for (p = nts; p; p = p->link) { + Rule r; + print("static short %Pdecode_%S[] = {\n%10,\n", p); + for (r = p->rules; r; r = r->decode) + print("%1%d,\n", r->ern); + print("};\n\n"); + } + print("static int %Prule(void *state, int goalnt) {\n" +"%1if (goalnt < 1 || goalnt > %d)\n%2fatal(\"%Prule\", \"Bad goal nonterminal %%d\\n\", goalnt);\n" +"%1if (!state)\n%2return 0;\n%1switch (goalnt) {\n", ntnumber); + for (p = nts; p; p = p->link) + print("%1case %P%S_NT:" +"%1return %Pdecode_%S[((struct %Pstate *)state)->rule.%P%S];\n", p, p, p); + print("%1default:\n%2fatal(\"%Prule\", \"Bad goal nonterminal %%d\\n\", goalnt);\n%2return 0;\n%1}\n}\n\n"); +} + +/* emitstring - emit arrays of templates, instruction flags, and rules */ +static void emitstring(Rule rules) { + Rule r; + + print("static char *%Ptemplates[] = {\n"); + print("/* 0 */%10,\n"); + for (r = rules; r; r = r->link) + print("/* %d */%1\"%s\",%1/* %R */\n", r->ern, r->template, r); + print("};\n"); + print("\nstatic char %Pisinstruction[] = {\n"); + print("/* 0 */%10,\n"); + for (r = rules; r; r = r->link) { + int len = strlen(r->template); + print("/* %d */%1%d,%1/* %s */\n", r->ern, + len >= 2 && r->template[len-2] == '\\' && r->template[len-1] == 'n', + r->template); + } + print("};\n"); + print("\nstatic char *%Pstring[] = {\n"); + print("/* 0 */%10,\n"); + for (r = rules; r; r = r->link) + print("/* %d */%1\"%R\",\n", r->ern, r); + print("};\n\n"); +} + +/* emitstruct - emit the definition of the state structure */ +static void emitstruct(Nonterm nts, int ntnumber) { + print("struct %Pstate {\n%1short cost[%d];\n%1struct {\n", ntnumber + 1); + for ( ; nts; nts = nts->link) { + int n = 1, m = nts->lhscount; + while ((m >>= 1) != 0) + n++; + print("%2unsigned int %P%S:%d;\n", nts, n); + } + print("%1} rule;\n};\n\n"); +} + +/* emittest - emit clause for testing a match */ +static void emittest(Tree t, char *v, char *suffix) { + Term p = t->op; + + if (p->kind == TERM) { + print("%3%s->op == %d%s/* %S */\n", v, p->esn, + t->nterms > 1 ? " && " : suffix, p); + if (t->left) + emittest(t->left, stringf("LEFT_CHILD(%s)", v), + t->right && t->right->nterms ? " && " : suffix); + if (t->right) + emittest(t->right, stringf("RIGHT_CHILD(%s)", v), suffix); + } +} diff --git a/src/tools/lcc/lburg/lburg.h b/src/tools/lcc/lburg/lburg.h new file mode 100644 index 0000000..b67e802 --- /dev/null +++ b/src/tools/lcc/lburg/lburg.h @@ -0,0 +1,65 @@ +#ifndef BURG_INCLUDED +#define BURG_INCLUDED + +/* iburg.c: */ +extern void *alloc(int nbytes); + +typedef enum { TERM=1, NONTERM } Kind; +typedef struct rule *Rule; +typedef struct term *Term; +struct term { /* terminals: */ + char *name; /* terminal name */ + Kind kind; /* TERM */ + int esn; /* external symbol number */ + int arity; /* operator arity */ + Term link; /* next terminal in esn order */ + Rule rules; /* rules whose pattern starts with term */ +}; + +typedef struct nonterm *Nonterm; +struct nonterm { /* nonterminals: */ + char *name; /* nonterminal name */ + Kind kind; /* NONTERM */ + int number; /* identifying number */ + int lhscount; /* # times nt appears in a rule lhs */ + int reached; /* 1 iff reached from start nonterminal */ + Rule rules; /* rules w/nonterminal on lhs */ + Rule chain; /* chain rules w/nonterminal on rhs */ + Nonterm link; /* next terminal in number order */ +}; +extern Nonterm nonterm(char *id); +extern Term term(char *id, int esn); + +typedef struct tree *Tree; +struct tree { /* tree patterns: */ + void *op; /* a terminal or nonterminal */ + Tree left, right; /* operands */ + int nterms; /* number of terminal nodes in this tree */ +}; +extern Tree tree(char *op, Tree left, Tree right); + +struct rule { /* rules: */ + Nonterm lhs; /* lefthand side nonterminal */ + Tree pattern; /* rule pattern */ + int ern; /* external rule number */ + int packed; /* packed external rule number */ + int cost; /* cost, if a constant */ + char *code; /* cost, if an expression */ + char *template; /* assembler template */ + Rule link; /* next rule in ern order */ + Rule next; /* next rule with same pattern root */ + Rule chain; /* next chain rule with same rhs */ + Rule decode; /* next rule with same lhs */ + Rule kids; /* next rule with same _kids pattern */ +}; +extern Rule rule(char *id, Tree pattern, char *template, char *code); + +/* gram.y: */ +void yyerror(char *fmt, ...); +int yyparse(void); +void yywarn(char *fmt, ...); +extern int errcnt; +extern FILE *infp; +extern FILE *outfp; + +#endif diff --git a/src/tools/lcc/src/alloc.c b/src/tools/lcc/src/alloc.c new file mode 100644 index 0000000..e0566df --- /dev/null +++ b/src/tools/lcc/src/alloc.c @@ -0,0 +1,94 @@ +#include "c.h" +struct block { + struct block *next; + char *limit; + char *avail; +}; +union align { + long l; + char *p; + double d; + int (*f)(void); +}; +union header { + struct block b; + union align a; +}; +#ifdef PURIFY +union header *arena[3]; + +void *allocate(unsigned long n, unsigned a) { + union header *new = malloc(sizeof *new + n); + + assert(a < NELEMS(arena)); + if (new == NULL) { + error("insufficient memory\n"); + exit(1); + } + new->b.next = (void *)arena[a]; + arena[a] = new; + return new + 1; +} + +void deallocate(unsigned a) { + union header *p, *q; + + assert(a < NELEMS(arena)); + for (p = arena[a]; p; p = q) { + q = (void *)p->b.next; + free(p); + } + arena[a] = NULL; +} + +void *newarray(unsigned long m, unsigned long n, unsigned a) { + return allocate(m*n, a); +} +#else +static struct block + first[] = { { NULL }, { NULL }, { NULL } }, + *arena[] = { &first[0], &first[1], &first[2] }; +static struct block *freeblocks; + +void *allocate(unsigned long n, unsigned a) { + struct block *ap; + + assert(a < NELEMS(arena)); + assert(n > 0); + ap = arena[a]; + n = roundup(n, sizeof (union align)); + while (n > ap->limit - ap->avail) { + if ((ap->next = freeblocks) != NULL) { + freeblocks = freeblocks->next; + ap = ap->next; + } else + { + unsigned m = sizeof (union header) + n + roundup(10*1024, sizeof (union align)); + ap->next = malloc(m); + ap = ap->next; + if (ap == NULL) { + error("insufficient memory\n"); + exit(1); + } + ap->limit = (char *)ap + m; + } + ap->avail = (char *)((union header *)ap + 1); + ap->next = NULL; + arena[a] = ap; + + } + ap->avail += n; + return ap->avail - n; +} + +void *newarray(unsigned long m, unsigned long n, unsigned a) { + return allocate(m*n, a); +} +void deallocate(unsigned a) { + assert(a < NELEMS(arena)); + arena[a]->next = freeblocks; + freeblocks = first[a].next; + first[a].next = NULL; + arena[a] = &first[a]; +} +#endif diff --git a/src/tools/lcc/src/bind.c b/src/tools/lcc/src/bind.c new file mode 100644 index 0000000..bc7b983 --- /dev/null +++ b/src/tools/lcc/src/bind.c @@ -0,0 +1,8 @@ +#include "c.h" +extern Interface nullIR; +extern Interface bytecodeIR; +Binding bindings[] = { + { "null", &nullIR }, + { "bytecode", &bytecodeIR }, + { NULL, NULL }, +}; diff --git a/src/tools/lcc/src/bytecode.c b/src/tools/lcc/src/bytecode.c new file mode 100644 index 0000000..e83429f --- /dev/null +++ b/src/tools/lcc/src/bytecode.c @@ -0,0 +1,367 @@ +#include "c.h" +#define I(f) b_##f + + +static void I(segment)(int n) { + static int cseg; + + if (cseg != n) + switch (cseg = n) { + case CODE: print("code\n"); return; + case DATA: print("data\n"); return; + case BSS: print("bss\n"); return; + case LIT: print("lit\n"); return; + default: assert(0); + } +} + +static void I(address)(Symbol q, Symbol p, long n) { + q->x.name = stringf("%s%s%D", p->x.name, n > 0 ? "+" : "", n); +} + +static void I(defaddress)(Symbol p) { + print("address %s\n", p->x.name); +} + +static void I(defconst)(int suffix, int size, Value v) { + switch (suffix) { + case I: + if (size > sizeof (int)) + print("byte %d %D\n", size, v.i); + else + print("byte %d %d\n", size, v.i); + return; + case U: + if (size > sizeof (unsigned)) + print("byte %d %U\n", size, v.u); + else + print("byte %d %u\n", size, v.u); + return; + case P: print("byte %d %U\n", size, (unsigned long)v.p); return; + case F: + if (size == 4) { + floatint_t fi; + fi.f = v.d; + print("byte 4 %u\n", fi.ui); + } else { + unsigned *p = (unsigned *)&v.d; + print("byte 4 %u\n", p[swap]); + print("byte 4 %u\n", p[1 - swap]); + } + return; + } + assert(0); +} + +static void I(defstring)(int len, char *str) { + char *s; + + for (s = str; s < str + len; s++) + print("byte 1 %d\n", (*s)&0377); +} + +static void I(defsymbol)(Symbol p) { + if (p->scope == CONSTANTS) + switch (optype(ttob(p->type))) { + case I: p->x.name = stringf("%D", p->u.c.v.i); break; + case U: p->x.name = stringf("%U", p->u.c.v.u); break; + case P: p->x.name = stringf("%U", p->u.c.v.p); break; + case F: + { // JDC: added this to get inline floats + floatint_t temp; + + temp.f = p->u.c.v.d; + p->x.name = stringf("%U", temp.ui ); + } + break;// JDC: added this + default: assert(0); + } + else if (p->scope >= LOCAL && p->sclass == STATIC) + p->x.name = stringf("$%d", genlabel(1)); + else if (p->scope == LABELS || p->generated) + p->x.name = stringf("$%s", p->name); + else + p->x.name = p->name; +} + +static void dumptree(Node p) { + switch (specific(p->op)) { + case ASGN+B: + assert(p->kids[0]); + assert(p->kids[1]); + assert(p->syms[0]); + dumptree(p->kids[0]); + dumptree(p->kids[1]); + print("%s %d\n", opname(p->op), p->syms[0]->u.c.v.u); + return; + case RET+V: + assert(!p->kids[0]); + assert(!p->kids[1]); + print("%s\n", opname(p->op)); + return; + } + switch (generic(p->op)) { + case CNST: case ADDRG: case ADDRF: case ADDRL: case LABEL: + assert(!p->kids[0]); + assert(!p->kids[1]); + assert(p->syms[0] && p->syms[0]->x.name); + print("%s %s\n", opname(p->op), p->syms[0]->x.name); + return; + case CVF: case CVI: case CVP: case CVU: + assert(p->kids[0]); + assert(!p->kids[1]); + assert(p->syms[0]); + dumptree(p->kids[0]); + print("%s %d\n", opname(p->op), p->syms[0]->u.c.v.i); + return; + case ARG: case BCOM: case NEG: case INDIR: case JUMP: case RET: + assert(p->kids[0]); + assert(!p->kids[1]); + dumptree(p->kids[0]); + print("%s\n", opname(p->op)); + return; + case CALL: + assert(p->kids[0]); + assert(!p->kids[1]); + assert(optype(p->op) != B); + dumptree(p->kids[0]); + print("%s\n", opname(p->op)); + if ( !p->count ) { printf("pop\n"); }; // JDC + return; + case ASGN: case BOR: case BAND: case BXOR: case RSH: case LSH: + case ADD: case SUB: case DIV: case MUL: case MOD: + assert(p->kids[0]); + assert(p->kids[1]); + dumptree(p->kids[0]); + dumptree(p->kids[1]); + print("%s\n", opname(p->op)); + return; + case EQ: case NE: case GT: case GE: case LE: case LT: + assert(p->kids[0]); + assert(p->kids[1]); + assert(p->syms[0]); + assert(p->syms[0]->x.name); + dumptree(p->kids[0]); + dumptree(p->kids[1]); + print("%s %s\n", opname(p->op), p->syms[0]->x.name); + return; + } + assert(0); +} + +static void I(emit)(Node p) { + for (; p; p = p->link) + dumptree(p); +} + +static void I(export)(Symbol p) { + print("export %s\n", p->x.name); +} + +static void I(function)(Symbol f, Symbol caller[], Symbol callee[], int ncalls) { + int i; + + (*IR->segment)(CODE); + offset = 0; + for (i = 0; caller[i] && callee[i]; i++) { + offset = roundup(offset, caller[i]->type->align); + caller[i]->x.name = callee[i]->x.name = stringf("%d", offset); + caller[i]->x.offset = callee[i]->x.offset = offset; + offset += caller[i]->type->size; + } + maxargoffset = maxoffset = argoffset = offset = 0; + gencode(caller, callee); + print("proc %s %d %d\n", f->x.name, maxoffset, maxargoffset); + emitcode(); + print("endproc %s %d %d\n", f->x.name, maxoffset, maxargoffset); + +} + +static void gen02(Node p) { + assert(p); + if (generic(p->op) == ARG) { + assert(p->syms[0]); + argoffset += (p->syms[0]->u.c.v.i < 4 ? 4 : p->syms[0]->u.c.v.i); + } else if (generic(p->op) == CALL) { + maxargoffset = (argoffset > maxargoffset ? argoffset : maxargoffset); + argoffset = 0; + } +} + +static void gen01(Node p) { + if (p) { + gen01(p->kids[0]); + gen01(p->kids[1]); + gen02(p); + } +} + +static Node I(gen)(Node p) { + Node q; + + assert(p); + for (q = p; q; q = q->link) + gen01(q); + return p; +} + +static void I(global)(Symbol p) { + print("align %d\n", p->type->align > 4 ? 4 : p->type->align); + print("LABELV %s\n", p->x.name); +} + +static void I(import)(Symbol p) { + print("import %s\n", p->x.name); +} + +static void I(local)(Symbol p) { + offset = roundup(offset, p->type->align); + p->x.name = stringf("%d", offset); + p->x.offset = offset; + offset += p->type->size; +} + +static void I(progbeg)(int argc, char *argv[]) {} + +static void I(progend)(void) {} + +static void I(space)(int n) { + print("skip %d\n", n); +} + +//======================================================== + +// JDC: hacked up to get interleaved source lines in asm code +static char *sourceFile; +static char *sourcePtr; +static int sourceLine; + +static int filelength( FILE *f ) { + int pos; + int end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} + +static void LoadSourceFile( const char *filename ) { + FILE *f; + int length; + + f = fopen( filename, "r" ); + if ( !f ) { + print( ";couldn't open %s\n", filename ); + sourceFile = NULL; + return; + } + length = filelength( f ); + sourceFile = malloc( length + 1 ); + if ( sourceFile ) { + size_t size; + size = fread( sourceFile, length, 1, f ); + sourceFile[length] = 0; + } + + fclose( f ); + sourceLine = 1; + sourcePtr = sourceFile; +} + +static void PrintToSourceLine( int line ) { + int c; + + if ( !sourceFile ) { + return; + } + while ( sourceLine <= line ) { + int i; + + for ( i = 0 ; sourcePtr[i] && sourcePtr[i] != '\n' ; i++ ) { + } + c = sourcePtr[i]; + if ( c == '\n' ) { + sourcePtr[i] = 0; + } + print( ";%d:%s\n", sourceLine, sourcePtr ); + if ( c == 0 ) { + sourcePtr += i; // end of file + } else { + sourcePtr += i+1; + } + sourceLine++; + } +} + +static void I(stabline)(Coordinate *cp) { + static char *prevfile; + static int prevline; + + if (cp->file && (prevfile == NULL || strcmp(prevfile, cp->file) != 0)) { + print("file \"%s\"\n", prevfile = cp->file); + prevline = 0; + if ( sourceFile ) { + free( sourceFile ); + sourceFile = NULL; + } + // load the new source file + LoadSourceFile( cp->file ); + } + if (cp->y != prevline) { + print("line %d\n", prevline = cp->y); + PrintToSourceLine( cp->y ); + } +} + +//======================================================== + +#define b_blockbeg blockbeg +#define b_blockend blockend + +Interface bytecodeIR = { + {1, 1, 0}, /* char */ + {2, 2, 0}, /* short */ + {4, 4, 0}, /* int */ + {4, 4, 0}, /* long */ + {4, 4, 0}, /* long long */ + {4, 4, 0}, /* float */ // JDC: use inline floats + {4, 4, 0}, /* double */ // JDC: don't ever emit 8 byte double code + {4, 4, 0}, /* long double */ // JDC: don't ever emit 8 byte double code + {4, 4, 0}, /* T* */ + {0, 4, 0}, /* struct */ + 0, /* little_endian */ + 0, /* mulops_calls */ + 0, /* wants_callb */ + 0, /* wants_argb */ + 1, /* left_to_right */ + 0, /* wants_dag */ + 0, /* unsigned_char */ + I(address), + I(blockbeg), + I(blockend), + I(defaddress), + I(defconst), + I(defstring), + I(defsymbol), + I(emit), + I(export), + I(function), + I(gen), + I(global), + I(import), + I(local), + I(progbeg), + I(progend), + I(segment), + I(space), + 0, /* I(stabblock) */ + 0, /* I(stabend) */ + 0, /* I(stabfend) */ + 0, /* I(stabinit) */ + I(stabline), + 0, /* I(stabsym) */ + 0, /* I(stabtype) */ +}; diff --git a/src/tools/lcc/src/c.h b/src/tools/lcc/src/c.h new file mode 100644 index 0000000..68c8f62 --- /dev/null +++ b/src/tools/lcc/src/c.h @@ -0,0 +1,729 @@ +#include +#include +#include +#include +#include +#include + +#define NEW(p,a) ((p) = allocate(sizeof *(p), (a))) +#define NEW0(p,a) memset(NEW((p),(a)), 0, sizeof *(p)) +#define isaddrop(op) (specific(op)==ADDRG+P || specific(op)==ADDRL+P \ + || specific(op)==ADDRF+P) + +#define MAXLINE 512 +#define BUFSIZE 4096 + +#define istypename(t,tsym) (kind[t] == CHAR \ + || (t == ID && tsym && tsym->sclass == TYPEDEF)) +#define sizeop(n) ((n)<<10) +#define generic(op) ((op)&0x3F0) +#define specific(op) ((op)&0x3FF) +#define opindex(op) (((op)>>4)&0x3F) +#define opkind(op) ((op)&~0x3F0) +#define opsize(op) ((op)>>10) +#define optype(op) ((op)&0xF) +#ifdef __LCC__ +#ifndef __STDC__ +#define __STDC__ +#endif +#endif +#define NELEMS(a) ((int)(sizeof (a)/sizeof ((a)[0]))) +#undef roundup +#define roundup(x,n) (((x)+((n)-1))&(~((n)-1))) +#define mkop(op,ty) (specific((op) + ttob(ty))) + +#define extend(x,ty) ((x)&(1<<(8*(ty)->size-1)) ? (x)|((~0UL)<<(8*(ty)->size-1)) : (x)&ones(8*(ty)->size)) +#define ones(n) ((n)>=8*sizeof (unsigned long) ? ~0UL : ~((~0UL)<<(n))) + +#define isqual(t) ((t)->op >= CONST) +#define unqual(t) (isqual(t) ? (t)->type : (t)) + +#define isvolatile(t) ((t)->op == VOLATILE \ + || (t)->op == CONST+VOLATILE) +#define isconst(t) ((t)->op == CONST \ + || (t)->op == CONST+VOLATILE) +#define isarray(t) (unqual(t)->op == ARRAY) +#define isstruct(t) (unqual(t)->op == STRUCT \ + || unqual(t)->op == UNION) +#define isunion(t) (unqual(t)->op == UNION) +#define isfunc(t) (unqual(t)->op == FUNCTION) +#define isptr(t) (unqual(t)->op == POINTER) +#define ischar(t) ((t)->size == 1 && isint(t)) +#define isint(t) (unqual(t)->op == INT \ + || unqual(t)->op == UNSIGNED) +#define isfloat(t) (unqual(t)->op == FLOAT) +#define isarith(t) (unqual(t)->op <= UNSIGNED) +#define isunsigned(t) (unqual(t)->op == UNSIGNED) +#define isscalar(t) (unqual(t)->op <= POINTER \ + || unqual(t)->op == ENUM) +#define isenum(t) (unqual(t)->op == ENUM) +#define fieldsize(p) (p)->bitsize +#define fieldright(p) ((p)->lsb - 1) +#define fieldleft(p) (8*(p)->type->size - \ + fieldsize(p) - fieldright(p)) +#define fieldmask(p) (~(~(unsigned)0<mulops_calls \ + && (generic(op)==DIV||generic(op)==MOD||generic(op)==MUL) \ + && ( optype(op)==U || optype(op)==I))) +static Node forest; +static struct dag { + struct node node; + struct dag *hlink; +} *buckets[16]; +int nodecount; +static Tree firstarg; +int assignargs = 1; +int prunetemps = -1; +static Node *tail; + +static int depth = 0; +static Node replace(Node); +static Node prune(Node); +static Node asgnnode(Symbol, Node); +static struct dag *dagnode(int, Node, Node, Symbol); +static Symbol equated(Symbol); +static void fixup(Node); +static void labelnode(int); +static void list(Node); +static void kill(Symbol); +static Node node(int, Node, Node, Symbol); +static void printdag1(Node, int, int); +static void printnode(Node, int, int); +static void reset(void); +static Node tmpnode(Node); +static void typestab(Symbol, void *); +static Node undag(Node); +static Node visit(Node, int); +static void unlist(void); +void walk(Tree tp, int tlab, int flab) { + listnodes(tp, tlab, flab); + if (forest) { + Node list = forest->link; + forest->link = NULL; + if (!IR->wants_dag) + list = undag(list); + code(Gen)->u.forest = list; + forest = NULL; + } + reset(); + deallocate(STMT); +} + +static Node node(int op, Node l, Node r, Symbol sym) { + int i; + struct dag *p; + + i = (opindex(op)^((unsigned long)sym>>2))&(NELEMS(buckets)-1); + for (p = buckets[i]; p; p = p->hlink) + if (p->node.op == op && p->node.syms[0] == sym + && p->node.kids[0] == l && p->node.kids[1] == r) + return &p->node; + p = dagnode(op, l, r, sym); + p->hlink = buckets[i]; + buckets[i] = p; + ++nodecount; + return &p->node; +} +static struct dag *dagnode(int op, Node l, Node r, Symbol sym) { + struct dag *p; + + NEW0(p, FUNC); + p->node.op = op; + if ((p->node.kids[0] = l) != NULL) + ++l->count; + if ((p->node.kids[1] = r) != NULL) + ++r->count; + p->node.syms[0] = sym; + return p; +} +Node newnode(int op, Node l, Node r, Symbol sym) { + return &dagnode(op, l, r, sym)->node; +} +static void kill(Symbol p) { + int i; + struct dag **q; + + for (i = 0; i < NELEMS(buckets); i++) + for (q = &buckets[i]; *q; ) + if (generic((*q)->node.op) == INDIR && + (!isaddrop((*q)->node.kids[0]->op) + || (*q)->node.kids[0]->syms[0] == p)) { + *q = (*q)->hlink; + --nodecount; + } else + q = &(*q)->hlink; +} +static void reset(void) { + if (nodecount > 0) + memset(buckets, 0, sizeof buckets); + nodecount = 0; +} +Node listnodes(Tree tp, int tlab, int flab) { + Node p = NULL, l, r; + int op; + + assert(tlab || flab || (tlab == 0 && flab == 0)); + if (tp == NULL) + return NULL; + if (tp->node) + return tp->node; + op = tp->op + sizeop(tp->type->size); + switch (generic(tp->op)) { + case AND: { if (depth++ == 0) reset(); + if (flab) { + listnodes(tp->kids[0], 0, flab); + listnodes(tp->kids[1], 0, flab); + } else { + listnodes(tp->kids[0], 0, flab = genlabel(1)); + listnodes(tp->kids[1], tlab, 0); + labelnode(flab); + } + depth--; } break; + case OR: { if (depth++ == 0) + reset(); + if (tlab) { + listnodes(tp->kids[0], tlab, 0); + listnodes(tp->kids[1], tlab, 0); + } else { + tlab = genlabel(1); + listnodes(tp->kids[0], tlab, 0); + listnodes(tp->kids[1], 0, flab); + labelnode(tlab); + } + depth--; + } break; + case NOT: { return listnodes(tp->kids[0], flab, tlab); } + case COND: { Tree q = tp->kids[1]; + assert(tlab == 0 && flab == 0); + if (tp->u.sym) + addlocal(tp->u.sym); + flab = genlabel(2); + listnodes(tp->kids[0], 0, flab); + assert(q && q->op == RIGHT); + reset(); + listnodes(q->kids[0], 0, 0); + if (forest->op == LABEL+V) { + equatelab(forest->syms[0], findlabel(flab + 1)); + unlist(); + } + list(jump(flab + 1)); + labelnode(flab); + listnodes(q->kids[1], 0, 0); + if (forest->op == LABEL+V) { + equatelab(forest->syms[0], findlabel(flab + 1)); + unlist(); + } + labelnode(flab + 1); + + if (tp->u.sym) + p = listnodes(idtree(tp->u.sym), 0, 0); } break; + case CNST: { Type ty = unqual(tp->type); + assert(ty->u.sym); + if (tlab || flab) { + assert(ty == inttype); + if (tlab && tp->u.v.i != 0) + list(jump(tlab)); + else if (flab && tp->u.v.i == 0) + list(jump(flab)); + } + else if (ty->u.sym->addressed) + p = listnodes(cvtconst(tp), 0, 0); + else + p = node(op, NULL, NULL, constant(ty, tp->u.v)); } break; + case RIGHT: { if ( tp->kids[0] && tp->kids[1] + && generic(tp->kids[1]->op) == ASGN + && ((generic(tp->kids[0]->op) == INDIR + && tp->kids[0]->kids[0] == tp->kids[1]->kids[0]) + || (tp->kids[0]->op == FIELD + && tp->kids[0] == tp->kids[1]->kids[0]))) { + assert(tlab == 0 && flab == 0); + if (generic(tp->kids[0]->op) == INDIR) { + p = listnodes(tp->kids[0], 0, 0); + list(p); + listnodes(tp->kids[1], 0, 0); + } + else { + assert(generic(tp->kids[0]->kids[0]->op) == INDIR); + list(listnodes(tp->kids[0]->kids[0], 0, 0)); + p = listnodes(tp->kids[0], 0, 0); + listnodes(tp->kids[1], 0, 0); + } + } else if (tp->kids[1]) { + listnodes(tp->kids[0], 0, 0); + p = listnodes(tp->kids[1], tlab, flab); + } else + p = listnodes(tp->kids[0], tlab, flab); } break; + case JUMP: { assert(tlab == 0 && flab == 0); + assert(tp->u.sym == 0); + assert(tp->kids[0]); + l = listnodes(tp->kids[0], 0, 0); + list(newnode(JUMP+V, l, NULL, NULL)); + reset(); } break; + case CALL: { Tree save = firstarg; + firstarg = NULL; + assert(tlab == 0 && flab == 0); + if (tp->op == CALL+B && !IR->wants_callb) { + Tree arg0 = tree(ARG+P, tp->kids[1]->type, + tp->kids[1], NULL); + if (IR->left_to_right) + firstarg = arg0; + l = listnodes(tp->kids[0], 0, 0); + if (!IR->left_to_right || firstarg) { + firstarg = NULL; + listnodes(arg0, 0, 0); + } + p = newnode(CALL+V, l, NULL, NULL); + } else { + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + p = newnode(tp->op == CALL+B ? tp->op : op, l, r, NULL); + } + NEW0(p->syms[0], FUNC); + assert(isptr(tp->kids[0]->type)); + assert(isfunc(tp->kids[0]->type->type)); + p->syms[0]->type = tp->kids[0]->type->type; + list(p); + reset(); + cfunc->u.f.ncalls++; + firstarg = save; + } break; + case ARG: { assert(tlab == 0 && flab == 0); + if (IR->left_to_right) + listnodes(tp->kids[1], 0, 0); + if (firstarg) { + Tree arg = firstarg; + firstarg = NULL; + listnodes(arg, 0, 0); + } + l = listnodes(tp->kids[0], 0, 0); + list(newnode(tp->op == ARG+B ? tp->op : op, l, NULL, NULL)); + forest->syms[0] = intconst(tp->type->size); + forest->syms[1] = intconst(tp->type->align); + if (!IR->left_to_right) + listnodes(tp->kids[1], 0, 0); } break; + case EQ: case NE: case GT: case GE: case LE: + case LT: { assert(tp->u.sym == 0); + assert(errcnt || tlab || flab); + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + assert(errcnt || opkind(l->op) == opkind(r->op)); + assert(errcnt || optype(op) == optype(l->op)); + if (tlab) + assert(flab == 0), + list(newnode(generic(tp->op) + opkind(l->op), l, r, findlabel(tlab))); + else if (flab) { + switch (generic(tp->op)) { + case EQ: op = NE; break; + case NE: op = EQ; break; + case GT: op = LE; break; + case LT: op = GE; break; + case GE: op = LT; break; + case LE: op = GT; break; + default: assert(0); + } + list(newnode(op + opkind(l->op), l, r, findlabel(flab))); + } + if (forest && forest->syms[0]) + forest->syms[0]->ref++; } break; + case ASGN: { assert(tlab == 0 && flab == 0); + if (tp->kids[0]->op == FIELD) { + Tree x = tp->kids[0]->kids[0]; + Field f = tp->kids[0]->u.field; + assert(generic(x->op) == INDIR); + reset(); + l = listnodes(lvalue(x), 0, 0); + if (fieldsize(f) < 8*f->type->size) { + unsigned int fmask = fieldmask(f); + unsigned int mask = fmask<kids[1]; + if ((q->op == CNST+I && q->u.v.i == 0) + || (q->op == CNST+U && q->u.v.u == 0)) + q = bittree(BAND, x, cnsttree(unsignedtype, (unsigned long)~mask)); + else if ((q->op == CNST+I && (q->u.v.i&fmask) == fmask) + || (q->op == CNST+U && (q->u.v.u&fmask) == fmask)) + q = bittree(BOR, x, cnsttree(unsignedtype, (unsigned long)mask)); + else { + listnodes(q, 0, 0); + q = bittree(BOR, + bittree(BAND, rvalue(lvalue(x)), + cnsttree(unsignedtype, (unsigned long)~mask)), + bittree(BAND, shtree(LSH, cast(q, unsignedtype), + cnsttree(unsignedtype, (unsigned long)fieldright(f))), + cnsttree(unsignedtype, (unsigned long)mask))); + } + r = listnodes(q, 0, 0); + op = ASGN + ttob(q->type); + } else { + r = listnodes(tp->kids[1], 0, 0); + op = ASGN + ttob(tp->kids[1]->type); + } + } else { + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + } + list(newnode(tp->op == ASGN+B ? tp->op : op, l, r, NULL)); + forest->syms[0] = intconst(tp->kids[1]->type->size); + forest->syms[1] = intconst(tp->kids[1]->type->align); + if (isaddrop(tp->kids[0]->op) + && !tp->kids[0]->u.sym->computed) + kill(tp->kids[0]->u.sym); + else + reset(); + p = listnodes(tp->kids[1], 0, 0); } break; + case BOR: case BAND: case BXOR: + case ADD: case SUB: case RSH: + case LSH: { assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + p = node(op, l, r, NULL); } break; + case DIV: case MUL: + case MOD: { assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + r = listnodes(tp->kids[1], 0, 0); + p = node(op, l, r, NULL); + if (IR->mulops_calls && isint(tp->type)) { + list(p); + cfunc->u.f.ncalls++; + } } break; + case RET: { assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + list(newnode(op, l, NULL, NULL)); } break; + case CVF: case CVI: case CVP: + case CVU: { assert(tlab == 0 && flab == 0); + assert(optype(tp->kids[0]->op) != optype(tp->op) || tp->kids[0]->type->size != tp->type->size); + l = listnodes(tp->kids[0], 0, 0); + p = node(op, l, NULL, intconst(tp->kids[0]->type->size)); + } break; + case BCOM: + case NEG: { assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + p = node(op, l, NULL, NULL); } break; + case INDIR: { Type ty = tp->kids[0]->type; + assert(tlab == 0 && flab == 0); + l = listnodes(tp->kids[0], 0, 0); + if (isptr(ty)) + ty = unqual(ty)->type; + if (isvolatile(ty) + || (isstruct(ty) && unqual(ty)->u.sym->u.s.vfields)) + p = newnode(tp->op == INDIR+B ? tp->op : op, l, NULL, NULL); + else + p = node(tp->op == INDIR+B ? tp->op : op, l, NULL, NULL); } break; + case FIELD: { Tree q = tp->kids[0]; + if (tp->type == inttype) { + long n = fieldleft(tp->u.field); + q = shtree(RSH, + shtree(LSH, q, cnsttree(inttype, n)), + cnsttree(inttype, n + fieldright(tp->u.field))); + } else if (fieldsize(tp->u.field) < 8*tp->u.field->type->size) + q = bittree(BAND, + shtree(RSH, q, cnsttree(inttype, (long)fieldright(tp->u.field))), + cnsttree(unsignedtype, (unsigned long)fieldmask(tp->u.field))); + assert(tlab == 0 && flab == 0); + p = listnodes(q, 0, 0); } break; + case ADDRG: + case ADDRF: { assert(tlab == 0 && flab == 0); + p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); + } break; + case ADDRL: { assert(tlab == 0 && flab == 0); + if (tp->u.sym->temporary) + addlocal(tp->u.sym); + p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); } break; + default:assert(0); + } + tp->node = p; + return p; +} +static void list(Node p) { + if (p && p->link == NULL) { + if (forest) { + p->link = forest->link; + forest->link = p; + } else + p->link = p; + forest = p; + } +} +static void labelnode(int lab) { + assert(lab); + if (forest && forest->op == LABEL+V) + equatelab(findlabel(lab), forest->syms[0]); + else + list(newnode(LABEL+V, NULL, NULL, findlabel(lab))); + reset(); +} +static void unlist(void) { + Node p; + + assert(forest); + assert(forest != forest->link); + p = forest->link; + while (p->link != forest) + p = p->link; + p->link = forest->link; + forest = p; +} +Tree cvtconst(Tree p) { + Symbol q = constant(p->type, p->u.v); + Tree e; + + if (q->u.c.loc == NULL) + q->u.c.loc = genident(STATIC, p->type, GLOBAL); + if (isarray(p->type)) { + e = simplify(ADDRG, atop(p->type), NULL, NULL); + e->u.sym = q->u.c.loc; + } else + e = idtree(q->u.c.loc); + return e; +} +void gencode(Symbol caller[], Symbol callee[]) { + Code cp; + Coordinate save; + + if (prunetemps == -1) + prunetemps = !IR->wants_dag; + save = src; + if (assignargs) { + int i; + Symbol p, q; + cp = codehead.next->next; + codelist = codehead.next; + for (i = 0; (p = callee[i]) != NULL + && (q = caller[i]) != NULL; i++) + if (p->sclass != q->sclass || p->type != q->type) + walk(asgn(p, idtree(q)), 0, 0); + codelist->next = cp; + cp->prev = codelist; + } + if (glevel && IR->stabsym) { + int i; + Symbol p, q; + for (i = 0; (p = callee[i]) != NULL + && (q = caller[i]) != NULL; i++) { + (*IR->stabsym)(p); + if (p->sclass != q->sclass || p->type != q->type) + (*IR->stabsym)(q); + } + swtoseg(CODE); + } + cp = codehead.next; + for ( ; errcnt <= 0 && cp; cp = cp->next) + switch (cp->kind) { + case Address: (*IR->address)(cp->u.addr.sym, cp->u.addr.base, + cp->u.addr.offset); break; + case Blockbeg: { + Symbol *p = cp->u.block.locals; + (*IR->blockbeg)(&cp->u.block.x); + for ( ; *p; p++) + if ((*p)->ref != 0.0) + (*IR->local)(*p); + else if (glevel) (*IR->local)(*p); + } + break; + case Blockend: (*IR->blockend)(&cp->u.begin->u.block.x); break; + case Defpoint: src = cp->u.point.src; break; + case Gen: case Jump: + case Label: if (prunetemps) + cp->u.forest = prune(cp->u.forest); + fixup(cp->u.forest); + cp->u.forest = (*IR->gen)(cp->u.forest); break; + case Local: (*IR->local)(cp->u.var); break; + case Switch: break; + default: assert(0); + } + src = save; +} +static void fixup(Node p) { + for ( ; p; p = p->link) + switch (generic(p->op)) { + case JUMP: + if (specific(p->kids[0]->op) == ADDRG+P) + p->kids[0]->syms[0] = + equated(p->kids[0]->syms[0]); + break; + case LABEL: assert(p->syms[0] == equated(p->syms[0])); break; + case EQ: case GE: case GT: case LE: case LT: case NE: + assert(p->syms[0]); + p->syms[0] = equated(p->syms[0]); + } +} +static Symbol equated(Symbol p) { + { Symbol q; for (q = p->u.l.equatedto; q; q = q->u.l.equatedto) assert(p != q); } + while (p->u.l.equatedto) + p = p->u.l.equatedto; + return p; +} +void emitcode(void) { + Code cp; + Coordinate save; + + save = src; + cp = codehead.next; + for ( ; errcnt <= 0 && cp; cp = cp->next) + switch (cp->kind) { + case Address: break; + case Blockbeg: if (glevel && IR->stabblock) { + (*IR->stabblock)('{', cp->u.block.level - LOCAL, cp->u.block.locals); + swtoseg(CODE); + } + break; + case Blockend: if (glevel && IR->stabblock) { + Code bp = cp->u.begin; + foreach(bp->u.block.identifiers, bp->u.block.level, typestab, NULL); + foreach(bp->u.block.types, bp->u.block.level, typestab, NULL); + (*IR->stabblock)('}', bp->u.block.level - LOCAL, bp->u.block.locals); + swtoseg(CODE); + } + break; + case Defpoint: src = cp->u.point.src; + if (glevel > 0 && IR->stabline) { + (*IR->stabline)(&cp->u.point.src); swtoseg(CODE); } break; + case Gen: case Jump: + case Label: if (cp->u.forest) + (*IR->emit)(cp->u.forest); break; + case Local: if (glevel && IR->stabsym) { + (*IR->stabsym)(cp->u.var); + swtoseg(CODE); + } break; + case Switch: { int i; + defglobal(cp->u.swtch.table, LIT); + (*IR->defaddress)(equated(cp->u.swtch.labels[0])); + for (i = 1; i < cp->u.swtch.size; i++) { + long k = cp->u.swtch.values[i-1]; + while (++k < cp->u.swtch.values[i]) + assert(k < LONG_MAX), + (*IR->defaddress)(equated(cp->u.swtch.deflab)); + (*IR->defaddress)(equated(cp->u.swtch.labels[i])); + } + swtoseg(CODE); + } break; + default: assert(0); + } + src = save; +} + +static Node undag(Node forest) { + Node p; + + tail = &forest; + for (p = forest; p; p = p->link) + if (generic(p->op) == INDIR) { + assert(p->count >= 1); + visit(p, 1); + if (p->syms[2]) { + assert(p->syms[2]->u.t.cse); + p->syms[2]->u.t.cse = NULL; + addlocal(p->syms[2]); + } + } else if (iscall(p->op) && p->count >= 1) + visit(p, 1); + else { + assert(p->count == 0), + visit(p, 1); + *tail = p; + tail = &p->link; + } + *tail = NULL; + return forest; +} +static Node replace(Node p) { + if (p && ( generic(p->op) == INDIR + && generic(p->kids[0]->op) == ADDRL + && p->kids[0]->syms[0]->temporary + && p->kids[0]->syms[0]->u.t.replace)) { + p = p->kids[0]->syms[0]->u.t.cse; + if (generic(p->op) == INDIR && isaddrop(p->kids[0]->op)) + p = newnode(p->op, newnode(p->kids[0]->op, NULL, NULL, + p->kids[0]->syms[0]), NULL, NULL); + else if (generic(p->op) == ADDRG) + p = newnode(p->op, NULL, NULL, p->syms[0]); + else + assert(0); + p->count = 1; + } else if (p) { + p->kids[0] = replace(p->kids[0]); + p->kids[1] = replace(p->kids[1]); + } + return p; +} +static Node prune(Node forest) { + Node p, *tail = &forest; + int count = 0; + + for (p = forest; p; p = p->link) { + if (count > 0) { + p->kids[0] = replace(p->kids[0]); + p->kids[1] = replace(p->kids[1]); + } + if (( generic(p->op) == ASGN + && generic(p->kids[0]->op) == ADDRL + && p->kids[0]->syms[0]->temporary + && p->kids[0]->syms[0]->u.t.cse == p->kids[1])) { + Symbol tmp = p->kids[0]->syms[0]; + if (!tmp->defined) + (*IR->local)(tmp); + tmp->defined = 1; + if (( generic(p->kids[1]->op) == INDIR + && isaddrop(p->kids[1]->kids[0]->op) + && p->kids[1]->kids[0]->syms[0]->sclass == REGISTER) + || (( generic(p->kids[1]->op) == INDIR + && isaddrop(p->kids[1]->kids[0]->op)) && tmp->sclass == AUTO) + || (generic(p->kids[1]->op) == ADDRG && tmp->sclass == AUTO)) { + tmp->u.t.replace = 1; + count++; + continue; /* and omit the assignment */ + } + } + /* keep the assignment and other roots */ + *tail = p; + tail = &(*tail)->link; + } + assert(*tail == NULL); + return forest; +} +static Node visit(Node p, int listed) { + if (p) { + if (p->syms[2]) + p = tmpnode(p); + else if ((p->count <= 1 && !iscall(p->op)) + || (p->count == 0 && iscall(p->op))) { + p->kids[0] = visit(p->kids[0], 0); + p->kids[1] = visit(p->kids[1], 0); + } + + else if (specific(p->op) == ADDRL+P || specific(p->op) == ADDRF+P) { + assert(!listed); + p = newnode(p->op, NULL, NULL, p->syms[0]); + p->count = 1; + } + else if (p->op == INDIR+B) { + p = newnode(p->op, p->kids[0], NULL, NULL); + p->count = 1; + p->kids[0] = visit(p->kids[0], 0); + p->kids[1] = visit(p->kids[1], 0); + } + else { + p->kids[0] = visit(p->kids[0], 0); + p->kids[1] = visit(p->kids[1], 0); + p->syms[2] = temporary(REGISTER, btot(p->op, opsize(p->op))); + assert(!p->syms[2]->defined); + p->syms[2]->ref = 1; + p->syms[2]->u.t.cse = p; + + *tail = asgnnode(p->syms[2], p); + tail = &(*tail)->link; + if (!listed) + p = tmpnode(p); + }; + } + return p; +} +static Node tmpnode(Node p) { + Symbol tmp = p->syms[2]; + + assert(tmp); + if (--p->count == 0) + p->syms[2] = NULL; + p = newnode(INDIR + ttob(tmp->type), + newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), NULL, NULL); + p->count = 1; + return p; +} +static Node asgnnode(Symbol tmp, Node p) { + p = newnode(ASGN + ttob(tmp->type), + newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), p, NULL); + p->syms[0] = intconst(tmp->type->size); + p->syms[1] = intconst(tmp->type->align); + return p; +} +/* printdag - print dag p on fd, or the node list if p == 0 */ +void printdag(Node p, int fd) { + FILE *f = fd == 1 ? stdout : stderr; + + printed(0); + if (p == 0) { + if ((p = forest) != NULL) + do { + p = p->link; + printdag1(p, fd, 0); + } while (p != forest); + } else if (*printed(nodeid((Tree)p))) + fprint(f, "node'%d printed above\n", nodeid((Tree)p)); + else + printdag1(p, fd, 0); +} + +/* printdag1 - recursively print dag p */ +static void printdag1(Node p, int fd, int lev) { + int id, i; + + if (p == 0 || *printed(id = nodeid((Tree)p))) + return; + *printed(id) = 1; + for (i = 0; i < NELEMS(p->kids); i++) + printdag1(p->kids[i], fd, lev + 1); + printnode(p, fd, lev); +} + +/* printnode - print fields of dag p */ +static void printnode(Node p, int fd, int lev) { + if (p) { + FILE *f = fd == 1 ? stdout : stderr; + int i, id = nodeid((Tree)p); + fprint(f, "%c%d%s", lev == 0 ? '\'' : '#', id, + &" "[id < 10 ? 0 : id < 100 ? 1 : 2]); + fprint(f, "%s count=%d", opname(p->op), p->count); + for (i = 0; i < NELEMS(p->kids) && p->kids[i]; i++) + fprint(f, " #%d", nodeid((Tree)p->kids[i])); + if (generic(p->op) == CALL && p->syms[0] && p->syms[0]->type) + fprint(f, " {%t}", p->syms[0]->type); + else + for (i = 0; i < NELEMS(p->syms) && p->syms[i]; i++) + if (p->syms[i]->name) + fprint(f, " %s", p->syms[i]->name); + else + fprint(f, " %p", p->syms[i]); + fprint(f, "\n"); + } +} + +/* typestab - emit stab entries for p */ +static void typestab(Symbol p, void *cl) { + if (!isfunc(p->type) && (p->sclass == EXTERN || p->sclass == STATIC) && IR->stabsym) + (*IR->stabsym)(p); + else if ((p->sclass == TYPEDEF || p->sclass == 0) && IR->stabtype) + (*IR->stabtype)(p); +} + diff --git a/src/tools/lcc/src/dagcheck.md b/src/tools/lcc/src/dagcheck.md new file mode 100644 index 0000000..292dbee --- /dev/null +++ b/src/tools/lcc/src/dagcheck.md @@ -0,0 +1,210 @@ +%{ +#include "c.h" +typedef Node NODEPTR_TYPE; +#define OP_LABEL(p) (specific((p)->op)) +#define LEFT_CHILD(p) ((p)->kids[0]) +#define RIGHT_CHILD(p) ((p)->kids[1]) +#define STATE_LABEL(p) ((p)->x.state) +#define PANIC error +%} +%term CNSTF=17 CNSTI=21 CNSTP=23 CNSTU=22 +%term ARGB=41 ARGF=33 ARGI=37 ARGP=39 ARGU=38 +%term ASGNB=57 ASGNF=49 ASGNI=53 ASGNP=55 ASGNU=54 +%term INDIRB=73 INDIRF=65 INDIRI=69 INDIRP=71 INDIRU=70 +%term CVFF=113 CVFI=117 +%term CVIF=129 CVII=133 CVIU=134 +%term CVPP=151 CVPU=150 +%term CVUI=181 CVUP=183 CVUU=182 +%term NEGF=193 NEGI=197 +%term CALLB=217 CALLF=209 CALLI=213 CALLP=215 CALLU=214 CALLV=216 +%term RETF=241 RETI=245 RETP=247 RETU=246 RETV=248 +%term ADDRGP=263 +%term ADDRFP=279 +%term ADDRLP=295 +%term ADDF=305 ADDI=309 ADDP=311 ADDU=310 +%term SUBF=321 SUBI=325 SUBP=327 SUBU=326 +%term LSHI=341 LSHU=342 +%term MODI=357 MODU=358 +%term RSHI=373 RSHU=374 +%term BANDI=389 BANDU=390 +%term BCOMI=405 BCOMU=406 +%term BORI=421 BORU=422 +%term BXORI=437 BXORU=438 +%term DIVF=449 DIVI=453 DIVU=454 +%term MULF=465 MULI=469 MULU=470 +%term EQF=481 EQI=485 EQU=486 +%term GEF=497 GEI=501 GEU=502 +%term GTF=513 GTI=517 GTU=518 +%term LEF=529 LEI=533 LEU=534 +%term LTF=545 LTI=549 LTU=550 +%term NEF=561 NEI=565 NEU=566 +%term JUMPV=584 +%term LABELV=600 +%% +stmt: INDIRB(P) "" +stmt: INDIRF(P) "" +stmt: INDIRI(P) "" +stmt: INDIRU(P) "" +stmt: INDIRP(P) "" +stmt: CALLF(P) "" +stmt: CALLI(P) "" +stmt: CALLU(P) "" +stmt: CALLP(P) "" +stmt: V "" +bogus: I "" 1 +bogus: U "" 1 +bogus: P "" 1 +bogus: F "" 1 +bogus: B "" 1 +bogus: V "" 1 +I: bogus "" 1 +U: bogus "" 1 +P: bogus "" 1 +F: bogus "" 1 +B: bogus "" 1 +V: bogus "" 1 +F: CNSTF "" +I: CNSTI "" +P: CNSTP "" +U: CNSTU "" +V: ARGB(B) "" +V: ARGF(F) "" +V: ARGI(I) "" +V: ARGU(U) "" +V: ARGP(P) "" +V: ASGNB(P,B) "" +V: ASGNF(P,F) "" +V: ASGNI(P,I) "" +V: ASGNU(P,U) "" +V: ASGNP(P,P) "" +B: INDIRB(P) "" +F: INDIRF(P) "" +I: INDIRI(P) "" +U: INDIRU(P) "" +P: INDIRP(P) "" +I: CVII(I) "" +I: CVUI(U) "" +I: CVFI(F) "" +U: CVIU(I) "" +U: CVUU(U) "" +U: CVPU(P) "" +F: CVIF(I) "" +F: CVFF(F) "" +P: CVUP(U) "" +P: CVPP(P) "" +F: NEGF(F) "" +I: NEGI(I) "" +V: CALLB(P,P) "" +F: CALLF(P) "" +I: CALLI(P) "" +U: CALLU(P) "" +P: CALLP(P) "" +V: CALLV(P) "" +V: RETF(F) "" +V: RETI(I) "" +V: RETU(U) "" +V: RETP(P) "" +V: RETV "" +P: ADDRGP "" +P: ADDRFP "" +P: ADDRLP "" +F: ADDF(F,F) "" +I: ADDI(I,I) "" +P: ADDP(P,I) "" +P: ADDP(I,P) "" +P: ADDP(U,P) "" +P: ADDP(P,U) "" +U: ADDU(U,U) "" +F: SUBF(F,F) "" +I: SUBI(I,I) "" +P: SUBP(P,I) "" +P: SUBP(P,U) "" +U: SUBU(U,U) "" +I: LSHI(I,I) "" +U: LSHU(U,I) "" +I: MODI(I,I) "" +U: MODU(U,U) "" +I: RSHI(I,I) "" +U: RSHU(U,I) "" +U: BANDU(U,U) "" +I: BANDI(I,I) "" +U: BCOMU(U) "" +I: BCOMI(I) "" +I: BORI(I,I) "" +U: BORU(U,U) "" +U: BXORU(U,U) "" +I: BXORI(I,I) "" +F: DIVF(F,F) "" +I: DIVI(I,I) "" +U: DIVU(U,U) "" +F: MULF(F,F) "" +I: MULI(I,I) "" +U: MULU(U,U) "" +V: EQF(F,F) "" +V: EQI(I,I) "" +V: EQU(U,U) "" +V: GEF(F,F) "" +V: GEI(I,I) "" +V: GEU(U,U) "" +V: GTF(F,F) "" +V: GTI(I,I) "" +V: GTU(U,U) "" +V: LEF(F,F) "" +V: LEI(I,I) "" +V: LEU(U,U) "" +V: LTF(F,F) "" +V: LTI(I,I) "" +V: LTU(U,U) "" +V: NEF(F,F) "" +V: NEI(I,I) "" +V: NEU(U,U) "" +V: JUMPV(P) "" +V: LABELV "" +%% + +static void reduce(NODEPTR_TYPE p, int goalnt) { + int i, sz = opsize(p->op), rulenumber = _rule(p->x.state, goalnt); + short *nts = _nts[rulenumber]; + NODEPTR_TYPE kids[10]; + + assert(rulenumber); + _kids(p, rulenumber, kids); + for (i = 0; nts[i]; i++) + reduce(kids[i], nts[i]); + switch (optype(p->op)) { +#define xx(ty) if (sz == ty->size) return + case I: + case U: + xx(chartype); + xx(shorttype); + xx(inttype); + xx(longtype); + xx(longlong); + break; + case F: + xx(floattype); + xx(doubletype); + xx(longdouble); + break; + case P: + xx(voidptype); + xx(funcptype); + break; + case V: + case B: if (sz == 0) return; +#undef xx + } + printdag(p, 2); + assert(0); +} + +void check(Node p) { + struct _state { short cost[1]; }; + + _label(p); + if (((struct _state *)p->x.state)->cost[1] > 0) { + printdag(p, 2); + assert(0); + } + reduce(p, 1); +} diff --git a/src/tools/lcc/src/decl.c b/src/tools/lcc/src/decl.c new file mode 100644 index 0000000..132241e --- /dev/null +++ b/src/tools/lcc/src/decl.c @@ -0,0 +1,1162 @@ +#include "c.h" + + +#define add(x,n) (x > inttype->u.sym->u.limits.max.i-(n) ? (overflow=1,x) : x+(n)) +#define chkoverflow(x,n) ((void)add(x,n)) +#define bits2bytes(n) (((n) + 7)/8) +static int regcount; + +static List autos, registers; +Symbol cfunc; /* current function */ +Symbol retv; /* return value location for structs */ + +static void checkref(Symbol, void *); +static Symbol dclglobal(int, char *, Type, Coordinate *); +static Symbol dcllocal(int, char *, Type, Coordinate *); +static Symbol dclparam(int, char *, Type, Coordinate *); +static Type dclr(Type, char **, Symbol **, int); +static Type dclr1(char **, Symbol **, int); +static void decl(Symbol (*)(int, char *, Type, Coordinate *)); +extern void doconst(Symbol, void *); +static void doglobal(Symbol, void *); +static void doextern(Symbol, void *); +static void exitparams(Symbol []); +static void fields(Type); +static void funcdefn(int, char *, Type, Symbol [], Coordinate); +static void initglobal(Symbol, int); +static void oldparam(Symbol, void *); +static Symbol *parameters(Type); +static Type specifier(int *); +static Type structdcl(int); +static Type tnode(int, Type); +void program(void) { + int n; + + level = GLOBAL; + for (n = 0; t != EOI; n++) + if (kind[t] == CHAR || kind[t] == STATIC + || t == ID || t == '*' || t == '(') { + decl(dclglobal); + deallocate(STMT); + if (!(glevel >= 3 || xref)) + deallocate(FUNC); + } else if (t == ';') { + warning("empty declaration\n"); + t = gettok(); + } else { + error("unrecognized declaration\n"); + t = gettok(); + } + if (n == 0) + warning("empty input file\n"); +} +static Type specifier(int *sclass) { + int cls, cons, sign, size, type, vol; + Type ty = NULL; + + cls = vol = cons = sign = size = type = 0; + if (sclass == NULL) + cls = AUTO; + for (;;) { + int *p, tt = t; + switch (t) { + case AUTO: + case REGISTER: if (level <= GLOBAL && cls == 0) + error("invalid use of `%k'\n", t); + p = &cls; t = gettok(); break; + case STATIC: case EXTERN: + case TYPEDEF: p = &cls; t = gettok(); break; + case CONST: p = &cons; t = gettok(); break; + case VOLATILE: p = &vol; t = gettok(); break; + case SIGNED: + case UNSIGNED: p = &sign; t = gettok(); break; + case LONG: if (size == LONG) { + size = 0; + tt = LONG+LONG; + } + p = &size; t = gettok(); break; + case SHORT: p = &size; t = gettok(); break; + case VOID: case CHAR: case INT: case FLOAT: + case DOUBLE: p = &type; ty = tsym->type; + t = gettok(); break; + case ENUM: p = &type; ty = enumdcl(); break; + case STRUCT: + case UNION: p = &type; ty = structdcl(t); break; + case ID: + if (istypename(t, tsym) && type == 0 + && sign == 0 && size == 0) { + use(tsym, src); + ty = tsym->type; + if (isqual(ty) + && ty->size != ty->type->size) { + ty = unqual(ty); + if (isconst(tsym->type)) + ty = qual(CONST, ty); + if (isvolatile(tsym->type)) + ty = qual(VOLATILE, ty); + tsym->type = ty; + } + p = &type; + t = gettok(); + } else + p = NULL; + break; + default: p = NULL; + } + if (p == NULL) + break; + if (*p) + error("invalid use of `%k'\n", tt); + *p = tt; + } + if (sclass) + *sclass = cls; + if (type == 0) { + type = INT; + ty = inttype; + } + if ((size == SHORT && type != INT) + || (size == LONG+LONG && type != INT) + || (size == LONG && type != INT && type != DOUBLE) + || (sign && type != INT && type != CHAR)) + error("invalid type specification\n"); + if (type == CHAR && sign) + ty = sign == UNSIGNED ? unsignedchar : signedchar; + else if (size == SHORT) + ty = sign == UNSIGNED ? unsignedshort : shorttype; + else if (size == LONG && type == DOUBLE) + ty = longdouble; + else if (size == LONG+LONG) { + ty = sign == UNSIGNED ? unsignedlonglong : longlong; + if (Aflag >= 1) + warning("`%t' is a non-ANSI type\n", ty); + } else if (size == LONG) + ty = sign == UNSIGNED ? unsignedlong : longtype; + else if (sign == UNSIGNED && type == INT) + ty = unsignedtype; + if (cons == CONST) + ty = qual(CONST, ty); + if (vol == VOLATILE) + ty = qual(VOLATILE, ty); + return ty; +} +static void decl(Symbol (*dcl)(int, char *, Type, Coordinate *)) { + int sclass; + Type ty, ty1; + static char stop[] = { CHAR, STATIC, ID, 0 }; + + ty = specifier(&sclass); + if (t == ID || t == '*' || t == '(' || t == '[') { + char *id; + Coordinate pos; + id = NULL; + pos = src; + if (level == GLOBAL) { + Symbol *params = NULL; + ty1 = dclr(ty, &id, ¶ms, 0); + if (params && id && isfunc(ty1) + && (t == '{' || istypename(t, tsym) + || (kind[t] == STATIC && t != TYPEDEF))) { + if (sclass == TYPEDEF) { + error("invalid use of `typedef'\n"); + sclass = EXTERN; + } + if (ty1->u.f.oldstyle) + exitscope(); + funcdefn(sclass, id, ty1, params, pos); + return; + } else if (params) + exitparams(params); + } else + ty1 = dclr(ty, &id, NULL, 0); + for (;;) { + if (Aflag >= 1 && !hasproto(ty1)) + warning("missing prototype\n"); + if (id == NULL) + error("missing identifier\n"); + else if (sclass == TYPEDEF) + { + Symbol p = lookup(id, identifiers); + if (p && p->scope == level) + error("redeclaration of `%s'\n", id); + p = install(id, &identifiers, level, + level < LOCAL ? PERM : FUNC); + p->type = ty1; + p->sclass = TYPEDEF; + p->src = pos; + } + else + (void)(*dcl)(sclass, id, ty1, &pos); + if (t != ',') + break; + t = gettok(); + id = NULL; + pos = src; + ty1 = dclr(ty, &id, NULL, 0); + } + } else if (ty == NULL + || !(isenum(ty) || + (isstruct(ty) && (*unqual(ty)->u.sym->name < '1' || *unqual(ty)->u.sym->name > '9')))) + error("empty declaration\n"); + test(';', stop); +} +static Symbol dclglobal(int sclass, char *id, Type ty, Coordinate *pos) { + Symbol p; + + if (sclass == 0) + sclass = AUTO; + else if (sclass != EXTERN && sclass != STATIC) { + error("invalid storage class `%k' for `%t %s'\n", + sclass, ty, id); + sclass = AUTO; + } + p = lookup(id, identifiers); + if (p && p->scope == GLOBAL) { + if (p->sclass != TYPEDEF && eqtype(ty, p->type, 1)) + ty = compose(ty, p->type); + else + error("redeclaration of `%s' previously declared at %w\n", p->name, &p->src); + + if (!isfunc(ty) && p->defined && t == '=') + error("redefinition of `%s' previously defined at %w\n", p->name, &p->src); + + if ((p->sclass == EXTERN && sclass == STATIC) + || (p->sclass == STATIC && sclass == AUTO) + || (p->sclass == AUTO && sclass == STATIC)) + warning("inconsistent linkage for `%s' previously declared at %w\n", p->name, &p->src); + + } + if (p == NULL || p->scope != GLOBAL) { + Symbol q = lookup(id, externals); + if (q) { + if (sclass == STATIC || !eqtype(ty, q->type, 1)) + warning("declaration of `%s' does not match previous declaration at %w\n", id, &q->src); + + p = relocate(id, externals, globals); + p->sclass = sclass; + } else { + p = install(id, &globals, GLOBAL, PERM); + p->sclass = sclass; + (*IR->defsymbol)(p); + } + if (p->sclass != STATIC) { + static int nglobals; + nglobals++; + if (Aflag >= 2 && nglobals == 512) + warning("more than 511 external identifiers\n"); + } + } else if (p->sclass == EXTERN) + p->sclass = sclass; + p->type = ty; + p->src = *pos; + if (t == '=' && isfunc(p->type)) { + error("illegal initialization for `%s'\n", p->name); + t = gettok(); + initializer(p->type, 0); + } else if (t == '=') { + initglobal(p, 0); + if (glevel > 0 && IR->stabsym) { + (*IR->stabsym)(p); swtoseg(p->u.seg); } + } else if (p->sclass == STATIC && !isfunc(p->type) + && p->type->size == 0) + error("undefined size for `%t %s'\n", p->type, p->name); + return p; +} +static void initglobal(Symbol p, int flag) { + Type ty; + + if (t == '=' || flag) { + if (p->sclass == STATIC) { + for (ty = p->type; isarray(ty); ty = ty->type) + ; + defglobal(p, isconst(ty) ? LIT : DATA); + } else + defglobal(p, DATA); + if (t == '=') + t = gettok(); + ty = initializer(p->type, 0); + if (isarray(p->type) && p->type->size == 0) + p->type = ty; + if (p->sclass == EXTERN) + p->sclass = AUTO; + } +} +void defglobal(Symbol p, int seg) { + p->u.seg = seg; + swtoseg(p->u.seg); + if (p->sclass != STATIC) + (*IR->export)(p); + (*IR->global)(p); + p->defined = 1; +} + +static Type dclr(Type basety, char **id, Symbol **params, int abstract) { + Type ty = dclr1(id, params, abstract); + + for ( ; ty; ty = ty->type) + switch (ty->op) { + case POINTER: + basety = ptr(basety); + break; + case FUNCTION: + basety = func(basety, ty->u.f.proto, + ty->u.f.oldstyle); + break; + case ARRAY: + basety = array(basety, ty->size, 0); + break; + case CONST: case VOLATILE: + basety = qual(ty->op, basety); + break; + default: assert(0); + } + if (Aflag >= 2 && basety->size > 32767) + warning("more than 32767 bytes in `%t'\n", basety); + return basety; +} +static Type tnode(int op, Type type) { + Type ty; + + NEW0(ty, STMT); + ty->op = op; + ty->type = type; + return ty; +} +static Type dclr1(char **id, Symbol **params, int abstract) { + Type ty = NULL; + + switch (t) { + case ID: if (id) + *id = token; + else + error("extraneous identifier `%s'\n", token); + t = gettok(); break; + case '*': t = gettok(); if (t == CONST || t == VOLATILE) { + Type ty1; + ty1 = ty = tnode(t, NULL); + while ((t = gettok()) == CONST || t == VOLATILE) + ty1 = tnode(t, ty1); + ty->type = dclr1(id, params, abstract); + ty = ty1; + } else + ty = dclr1(id, params, abstract); + ty = tnode(POINTER, ty); break; + case '(': t = gettok(); if (abstract + && (t == REGISTER || istypename(t, tsym) || t == ')')) { + Symbol *args; + ty = tnode(FUNCTION, ty); + enterscope(); + if (level > PARAM) + enterscope(); + args = parameters(ty); + exitparams(args); + } else { + ty = dclr1(id, params, abstract); + expect(')'); + if (abstract && ty == NULL + && (id == NULL || *id == NULL)) + return tnode(FUNCTION, NULL); + } break; + case '[': break; + default: return ty; + } + while (t == '(' || t == '[') + switch (t) { + case '(': t = gettok(); { Symbol *args; + ty = tnode(FUNCTION, ty); + enterscope(); + if (level > PARAM) + enterscope(); + args = parameters(ty); + if (params && *params == NULL) + *params = args; + else + exitparams(args); + } + break; + case '[': t = gettok(); { int n = 0; + if (kind[t] == ID) { + n = intexpr(']', 1); + if (n <= 0) { + error("`%d' is an illegal array size\n", n); + n = 1; + } + } else + expect(']'); + ty = tnode(ARRAY, ty); + ty->size = n; } break; + default: assert(0); + } + return ty; +} +static Symbol *parameters(Type fty) { + List list = NULL; + Symbol *params; + + if (kind[t] == STATIC || istypename(t, tsym)) { + int n = 0; + Type ty1 = NULL; + for (;;) { + Type ty; + int sclass = 0; + char *id = NULL; + if (ty1 && t == ELLIPSIS) { + static struct symbol sentinel; + if (sentinel.type == NULL) { + sentinel.type = voidtype; + sentinel.defined = 1; + } + if (ty1 == voidtype) + error("illegal formal parameter types\n"); + list = append(&sentinel, list); + t = gettok(); + break; + } + if (!istypename(t, tsym) && t != REGISTER) + error("missing parameter type\n"); + n++; + ty = dclr(specifier(&sclass), &id, NULL, 1); + if ( (ty == voidtype && (ty1 || id)) + || ty1 == voidtype) + error("illegal formal parameter types\n"); + if (id == NULL) + id = stringd(n); + if (ty != voidtype) + list = append(dclparam(sclass, id, ty, &src), list); + if (Aflag >= 1 && !hasproto(ty)) + warning("missing prototype\n"); + if (ty1 == NULL) + ty1 = ty; + if (t != ',') + break; + t = gettok(); + } + fty->u.f.proto = newarray(length(list) + 1, + sizeof (Type *), PERM); + params = ltov(&list, FUNC); + for (n = 0; params[n]; n++) + fty->u.f.proto[n] = params[n]->type; + fty->u.f.proto[n] = NULL; + fty->u.f.oldstyle = 0; + } else { + if (t == ID) + for (;;) { + Symbol p; + if (t != ID) { + error("expecting an identifier\n"); + break; + } + p = dclparam(0, token, inttype, &src); + p->defined = 0; + list = append(p, list); + t = gettok(); + if (t != ',') + break; + t = gettok(); + } + params = ltov(&list, FUNC); + fty->u.f.proto = NULL; + fty->u.f.oldstyle = 1; + } + if (t != ')') { + static char stop[] = { CHAR, STATIC, IF, ')', 0 }; + expect(')'); + skipto('{', stop); + } + if (t == ')') + t = gettok(); + return params; +} +static void exitparams(Symbol params[]) { + assert(params); + if (params[0] && !params[0]->defined) + error("extraneous old-style parameter list\n"); + if (level > PARAM) + exitscope(); + exitscope(); +} + +static Symbol dclparam(int sclass, char *id, Type ty, Coordinate *pos) { + Symbol p; + + if (isfunc(ty)) + ty = ptr(ty); + else if (isarray(ty)) + ty = atop(ty); + if (sclass == 0) + sclass = AUTO; + else if (sclass != REGISTER) { + error("invalid storage class `%k' for `%t%s\n", + sclass, ty, stringf(id ? " %s'" : "' parameter", id)); + sclass = AUTO; + } else if (isvolatile(ty) || isstruct(ty)) { + warning("register declaration ignored for `%t%s\n", + ty, stringf(id ? " %s'" : "' parameter", id)); + sclass = AUTO; + } + + p = lookup(id, identifiers); + if (p && p->scope == level) + error("duplicate declaration for `%s' previously declared at %w\n", id, &p->src); + + else + p = install(id, &identifiers, level, FUNC); + p->sclass = sclass; + p->src = *pos; + p->type = ty; + p->defined = 1; + if (t == '=') { + error("illegal initialization for parameter `%s'\n", id); + t = gettok(); + (void)expr1(0); + } + return p; +} +static Type structdcl(int op) { + char *tag; + Type ty; + Symbol p; + Coordinate pos; + + t = gettok(); + pos = src; + if (t == ID) { + tag = token; + t = gettok(); + } else + tag = ""; + if (t == '{') { + static char stop[] = { IF, ',', 0 }; + ty = newstruct(op, tag); + ty->u.sym->src = pos; + ty->u.sym->defined = 1; + t = gettok(); + if (istypename(t, tsym)) + fields(ty); + else + error("invalid %k field declarations\n", op); + test('}', stop); + } + else if (*tag && (p = lookup(tag, types)) != NULL + && p->type->op == op) { + ty = p->type; + if (t == ';' && p->scope < level) + ty = newstruct(op, tag); + } + else { + if (*tag == 0) + error("missing %k tag\n", op); + ty = newstruct(op, tag); + } + if (*tag && xref) + use(ty->u.sym, pos); + return ty; +} +static void fields(Type ty) { + { int n = 0; + while (istypename(t, tsym)) { + static char stop[] = { IF, CHAR, '}', 0 }; + Type ty1 = specifier(NULL); + for (;;) { + Field p; + char *id = NULL; + Type fty = dclr(ty1, &id, NULL, 0); + p = newfield(id, ty, fty); + if (Aflag >= 1 && !hasproto(p->type)) + warning("missing prototype\n"); + if (t == ':') { + if (unqual(p->type) != inttype + && unqual(p->type) != unsignedtype) { + error("`%t' is an illegal bit-field type\n", + p->type); + p->type = inttype; + } + t = gettok(); + p->bitsize = intexpr(0, 0); + if (p->bitsize > 8*inttype->size || p->bitsize < 0) { + error("`%d' is an illegal bit-field size\n", + p->bitsize); + p->bitsize = 8*inttype->size; + } else if (p->bitsize == 0 && id) { + warning("extraneous 0-width bit field `%t %s' ignored\n", p->type, id); + + p->name = stringd(genlabel(1)); + } + p->lsb = 1; + } + else { + if (id == NULL) + error("field name missing\n"); + else if (isfunc(p->type)) + error("`%t' is an illegal field type\n", p->type); + else if (p->type->size == 0) + error("undefined size for field `%t %s'\n", + p->type, id); + } + if (isconst(p->type)) + ty->u.sym->u.s.cfields = 1; + if (isvolatile(p->type)) + ty->u.sym->u.s.vfields = 1; + n++; + if (Aflag >= 2 && n == 128) + warning("more than 127 fields in `%t'\n", ty); + if (t != ',') + break; + t = gettok(); + } + test(';', stop); + } } + { int bits = 0, off = 0, overflow = 0; + Field p, *q = &ty->u.sym->u.s.flist; + ty->align = IR->structmetric.align; + for (p = *q; p; p = p->link) { + int a = p->type->align ? p->type->align : 1; + if (p->lsb) + a = unsignedtype->align; + if (ty->op == UNION) + off = bits = 0; + else if (p->bitsize == 0 || bits == 0 + || bits - 1 + p->bitsize > 8*unsignedtype->size) { + off = add(off, bits2bytes(bits-1)); + bits = 0; + chkoverflow(off, a - 1); + off = roundup(off, a); + } + if (a > ty->align) + ty->align = a; + p->offset = off; + + if (p->lsb) { + if (bits == 0) + bits = 1; + if (IR->little_endian) + p->lsb = bits; + else + p->lsb = 8*unsignedtype->size - bits + 1 + - p->bitsize + 1; + bits += p->bitsize; + } else + off = add(off, p->type->size); + if (off + bits2bytes(bits-1) > ty->size) + ty->size = off + bits2bytes(bits-1); + if (p->name == NULL + || !('1' <= *p->name && *p->name <= '9')) { + *q = p; + q = &p->link; + } + } + *q = NULL; + chkoverflow(ty->size, ty->align - 1); + ty->size = roundup(ty->size, ty->align); + if (overflow) { + error("size of `%t' exceeds %d bytes\n", ty, inttype->u.sym->u.limits.max.i); + ty->size = inttype->u.sym->u.limits.max.i&(~(ty->align - 1)); + } } +} +static void funcdefn(int sclass, char *id, Type ty, Symbol params[], Coordinate pt) { + int i, n; + Symbol *callee, *caller, p; + Type rty = freturn(ty); + + if (isstruct(rty) && rty->size == 0) + error("illegal use of incomplete type `%t'\n", rty); + for (n = 0; params[n]; n++) + ; + if (n > 0 && params[n-1]->name == NULL) + params[--n] = NULL; + if (Aflag >= 2 && n > 31) + warning("more than 31 parameters in function `%s'\n", id); + if (ty->u.f.oldstyle) { + if (Aflag >= 1) + warning("old-style function definition for `%s'\n", id); + caller = params; + callee = newarray(n + 1, sizeof *callee, FUNC); + memcpy(callee, caller, (n+1)*sizeof *callee); + enterscope(); + assert(level == PARAM); + while (kind[t] == STATIC || istypename(t, tsym)) + decl(dclparam); + foreach(identifiers, PARAM, oldparam, callee); + + for (i = 0; (p = callee[i]) != NULL; i++) { + if (!p->defined) + callee[i] = dclparam(0, p->name, inttype, &p->src); + *caller[i] = *p; + caller[i]->sclass = AUTO; + caller[i]->type = promote(p->type); + } + p = lookup(id, identifiers); + if (p && p->scope == GLOBAL && isfunc(p->type) + && p->type->u.f.proto) { + Type *proto = p->type->u.f.proto; + for (i = 0; caller[i] && proto[i]; i++) { + Type ty = unqual(proto[i]); + if (eqtype(isenum(ty) ? ty->type : ty, + unqual(caller[i]->type), 1) == 0) + break; + else if (isenum(ty) && !isenum(unqual(caller[i]->type))) + warning("compatibility of `%t' and `%t' is compiler dependent\n", + proto[i], caller[i]->type); + } + if (proto[i] || caller[i]) + error("conflicting argument declarations for function `%s'\n", id); + + } + else { + Type *proto = newarray(n + 1, sizeof *proto, PERM); + if (Aflag >= 1) + warning("missing prototype for `%s'\n", id); + for (i = 0; i < n; i++) + proto[i] = caller[i]->type; + proto[i] = NULL; + ty = func(rty, proto, 1); + } + } else { + callee = params; + caller = newarray(n + 1, sizeof *caller, FUNC); + for (i = 0; (p = callee[i]) != NULL && p->name; i++) { + NEW(caller[i], FUNC); + *caller[i] = *p; + if (isint(p->type)) + caller[i]->type = promote(p->type); + caller[i]->sclass = AUTO; + if ('1' <= *p->name && *p->name <= '9') + error("missing name for parameter %d to function `%s'\n", i + 1, id); + + } + caller[i] = NULL; + } + for (i = 0; (p = callee[i]) != NULL; i++) + if (p->type->size == 0) { + error("undefined size for parameter `%t %s'\n", + p->type, p->name); + caller[i]->type = p->type = inttype; + } + if (Aflag >= 2 && sclass != STATIC && strcmp(id, "main") == 0) { + if (ty->u.f.oldstyle) + warning("`%t %s()' is a non-ANSI definition\n", rty, id); + else if (!(rty == inttype + && ((n == 0 && callee[0] == NULL) + || (n == 2 && callee[0]->type == inttype + && isptr(callee[1]->type) && callee[1]->type->type == charptype + && !variadic(ty))))) + warning("`%s' is a non-ANSI definition\n", typestring(ty, id)); + } + p = lookup(id, identifiers); + if (p && isfunc(p->type) && p->defined) + error("redefinition of `%s' previously defined at %w\n", + p->name, &p->src); + cfunc = dclglobal(sclass, id, ty, &pt); + cfunc->u.f.label = genlabel(1); + cfunc->u.f.callee = callee; + cfunc->u.f.pt = src; + cfunc->defined = 1; + if (xref) + use(cfunc, cfunc->src); + if (Pflag) + printproto(cfunc, cfunc->u.f.callee); + if (ncalled >= 0) + ncalled = findfunc(cfunc->name, pt.file); + labels = table(NULL, LABELS); + stmtlabs = table(NULL, LABELS); + refinc = 1.0; + regcount = 0; + codelist = &codehead; + codelist->next = NULL; + if (!IR->wants_callb && isstruct(rty)) + retv = genident(AUTO, ptr(rty), PARAM); + compound(0, NULL, 0); + + { + Code cp; + for (cp = codelist; cp->kind < Label; cp = cp->prev) + ; + if (cp->kind != Jump) { + if (rty != voidtype) { + warning("missing return value\n"); + retcode(cnsttree(inttype, 0L)); + } else + retcode(NULL); + } + } + definelab(cfunc->u.f.label); + if (events.exit) + apply(events.exit, cfunc, NULL); + walk(NULL, 0, 0); + exitscope(); + assert(level == PARAM); + foreach(identifiers, level, checkref, NULL); + if (!IR->wants_callb && isstruct(rty)) { + Symbol *a; + a = newarray(n + 2, sizeof *a, FUNC); + a[0] = retv; + memcpy(&a[1], callee, (n+1)*sizeof *callee); + callee = a; + a = newarray(n + 2, sizeof *a, FUNC); + NEW(a[0], FUNC); + *a[0] = *retv; + memcpy(&a[1], caller, (n+1)*sizeof *callee); + caller = a; + } + if (!IR->wants_argb) + for (i = 0; caller[i]; i++) + if (isstruct(caller[i]->type)) { + caller[i]->type = ptr(caller[i]->type); + callee[i]->type = ptr(callee[i]->type); + caller[i]->structarg = callee[i]->structarg = 1; + } + if (glevel > 1) for (i = 0; callee[i]; i++) callee[i]->sclass = AUTO; + if (cfunc->sclass != STATIC) + (*IR->export)(cfunc); + if (glevel && IR->stabsym) { + swtoseg(CODE); (*IR->stabsym)(cfunc); } + swtoseg(CODE); + (*IR->function)(cfunc, caller, callee, cfunc->u.f.ncalls); + if (glevel && IR->stabfend) + (*IR->stabfend)(cfunc, lineno); + foreach(stmtlabs, LABELS, checklab, NULL); + exitscope(); + expect('}'); + labels = stmtlabs = NULL; + retv = NULL; + cfunc = NULL; +} +static void oldparam(Symbol p, void *cl) { + int i; + Symbol *callee = cl; + + for (i = 0; callee[i]; i++) + if (p->name == callee[i]->name) { + callee[i] = p; + return; + } + error("declared parameter `%s' is missing\n", p->name); +} +void compound(int loop, struct swtch *swp, int lev) { + Code cp; + int nregs; + + walk(NULL, 0, 0); + cp = code(Blockbeg); + enterscope(); + assert(level >= LOCAL); + if (level == LOCAL && events.entry) + apply(events.entry, cfunc, NULL); + definept(NULL); + expect('{'); + autos = registers = NULL; + if (level == LOCAL && IR->wants_callb + && isstruct(freturn(cfunc->type))) { + retv = genident(AUTO, ptr(freturn(cfunc->type)), level); + retv->defined = 1; + retv->ref = 1; + registers = append(retv, registers); + } + while (kind[t] == CHAR || kind[t] == STATIC + || (istypename(t, tsym) && getchr() != ':')) + decl(dcllocal); + { + int i; + Symbol *a = ltov(&autos, STMT); + nregs = length(registers); + for (i = 0; a[i]; i++) + registers = append(a[i], registers); + cp->u.block.locals = ltov(®isters, FUNC); + } + if (events.blockentry) + apply(events.blockentry, cp->u.block.locals, NULL); + while (kind[t] == IF || kind[t] == ID) + statement(loop, swp, lev); + walk(NULL, 0, 0); + foreach(identifiers, level, checkref, NULL); + { + int i = nregs, j; + Symbol p; + for ( ; (p = cp->u.block.locals[i]) != NULL; i++) { + for (j = i; j > nregs + && cp->u.block.locals[j-1]->ref < p->ref; j--) + cp->u.block.locals[j] = cp->u.block.locals[j-1]; + cp->u.block.locals[j] = p; + } + } + if (events.blockexit) + apply(events.blockexit, cp->u.block.locals, NULL); + cp->u.block.level = level; + cp->u.block.identifiers = identifiers; + cp->u.block.types = types; + code(Blockend)->u.begin = cp; + if (reachable(Gen)) + definept(NULL); + if (level > LOCAL) { + exitscope(); + expect('}'); + } +} +static void checkref(Symbol p, void *cl) { + if (p->scope >= PARAM + && (isvolatile(p->type) || isfunc(p->type))) + p->addressed = 1; + if (Aflag >= 2 && p->defined && p->ref == 0) { + if (p->sclass == STATIC) + warning("static `%t %s' is not referenced\n", + p->type, p->name); + else if (p->scope == PARAM) + warning("parameter `%t %s' is not referenced\n", + p->type, p->name); + else if (p->scope >= LOCAL && p->sclass != EXTERN) + warning("local `%t %s' is not referenced\n", + p->type, p->name); + } + if (p->sclass == AUTO + && ((p->scope == PARAM && regcount == 0) + || p->scope >= LOCAL) + && !p->addressed && isscalar(p->type) && p->ref >= 3.0) + p->sclass = REGISTER; + if (level == GLOBAL && p->sclass == STATIC && !p->defined + && isfunc(p->type) && p->ref) + error("undefined static `%t %s'\n", p->type, p->name); + assert(!(level == GLOBAL && p->sclass == STATIC && !p->defined && !isfunc(p->type))); +} +static Symbol dcllocal(int sclass, char *id, Type ty, Coordinate *pos) { + Symbol p, q; + + if (sclass == 0) + sclass = isfunc(ty) ? EXTERN : AUTO; + else if (isfunc(ty) && sclass != EXTERN) { + error("invalid storage class `%k' for `%t %s'\n", + sclass, ty, id); + sclass = EXTERN; + } else if (sclass == REGISTER + && (isvolatile(ty) || isstruct(ty) || isarray(ty))) { + warning("register declaration ignored for `%t %s'\n", + ty, id); + sclass = AUTO; + } + q = lookup(id, identifiers); + if ((q && q->scope >= level) + || (q && q->scope == PARAM && level == LOCAL)) { + if (sclass == EXTERN && q->sclass == EXTERN + && eqtype(q->type, ty, 1)) + ty = compose(ty, q->type); + else + error("redeclaration of `%s' previously declared at %w\n", q->name, &q->src); + } + + assert(level >= LOCAL); + p = install(id, &identifiers, level, sclass == STATIC || sclass == EXTERN ? PERM : FUNC); + p->type = ty; + p->sclass = sclass; + p->src = *pos; + switch (sclass) { + case EXTERN: q = lookup(id, globals); + if (q == NULL || q->sclass == TYPEDEF || q->sclass == ENUM) { + q = lookup(id, externals); + if (q == NULL) { + q = install(p->name, &externals, GLOBAL, PERM); + q->type = p->type; + q->sclass = EXTERN; + q->src = src; + (*IR->defsymbol)(q); + } + } + if (!eqtype(p->type, q->type, 1)) + warning("declaration of `%s' does not match previous declaration at %w\n", q->name, &q->src); + + p->u.alias = q; break; + case STATIC: (*IR->defsymbol)(p); + initglobal(p, 0); + if (!p->defined) { + if (p->type->size > 0) { + defglobal(p, BSS); + (*IR->space)(p->type->size); + } else + error("undefined size for `%t %s'\n", + p->type, p->name); + } + p->defined = 1; break; + case REGISTER: registers = append(p, registers); + regcount++; + p->defined = 1; + break; + case AUTO: autos = append(p, autos); + p->defined = 1; break; + default: assert(0); + } + if (t == '=') { + Tree e; + if (sclass == EXTERN) + error("illegal initialization of `extern %s'\n", id); + t = gettok(); + definept(NULL); + if (isscalar(p->type) + || (isstruct(p->type) && t != '{')) { + if (t == '{') { + t = gettok(); + e = expr1(0); + expect('}'); + } else + e = expr1(0); + } else { + Symbol t1; + Type ty = p->type, ty1 = ty; + while (isarray(ty1)) + ty1 = ty1->type; + if (!isconst(ty) && (!isarray(ty) || !isconst(ty1))) + ty = qual(CONST, ty); + t1 = genident(STATIC, ty, GLOBAL); + initglobal(t1, 1); + if (isarray(p->type) && p->type->size == 0 + && t1->type->size > 0) + p->type = array(p->type->type, + t1->type->size/t1->type->type->size, 0); + e = idtree(t1); + } + walk(root(asgn(p, e)), 0, 0); + p->ref = 1; + } + if (!isfunc(p->type) && p->defined && p->type->size <= 0) + error("undefined size for `%t %s'\n", p->type, id); + return p; +} +void finalize(void) { + foreach(externals, GLOBAL, doextern, NULL); + foreach(identifiers, GLOBAL, doglobal, NULL); + foreach(identifiers, GLOBAL, checkref, NULL); + foreach(constants, CONSTANTS, doconst, NULL); +} +static void doextern(Symbol p, void *cl) { + (*IR->import)(p); +} +static void doglobal(Symbol p, void *cl) { + if (!p->defined && (p->sclass == EXTERN + || (isfunc(p->type) && p->sclass == AUTO))) + (*IR->import)(p); + else if (!p->defined && !isfunc(p->type) + && (p->sclass == AUTO || p->sclass == STATIC)) { + if (isarray(p->type) + && p->type->size == 0 && p->type->type->size > 0) + p->type = array(p->type->type, 1, 0); + if (p->type->size > 0) { + defglobal(p, BSS); + (*IR->space)(p->type->size); + if (glevel > 0 && IR->stabsym) + (*IR->stabsym)(p); + } else + error("undefined size for `%t %s'\n", + p->type, p->name); + p->defined = 1; + } + if (Pflag + && !isfunc(p->type) + && !p->generated && p->sclass != EXTERN) + printdecl(p, p->type); +} +void doconst(Symbol p, void *cl) { + if (p->u.c.loc) { + assert(p->u.c.loc->u.seg == 0); + defglobal(p->u.c.loc, LIT); + if (isarray(p->type) && p->type->type == widechar) { + unsigned int *s = p->u.c.v.p; + int n = p->type->size/widechar->size; + while (n-- > 0) { + Value v; + v.u = *s++; + (*IR->defconst)(widechar->op, widechar->size, v); + } + } else if (isarray(p->type)) + (*IR->defstring)(p->type->size, p->u.c.v.p); + else + (*IR->defconst)(p->type->op, p->type->size, p->u.c.v); + p->u.c.loc = NULL; + } +} +void checklab(Symbol p, void *cl) { + if (!p->defined) + error("undefined label `%s'\n", p->name); + p->defined = 1; +} + +Type enumdcl(void) { + char *tag; + Type ty; + Symbol p = {0}; + Coordinate pos; + + t = gettok(); + pos = src; + if (t == ID) { + tag = token; + t = gettok(); + } else + tag = ""; + if (t == '{') { + static char follow[] = { IF, 0 }; + int n = 0; + long k = -1; + List idlist = 0; + ty = newstruct(ENUM, tag); + t = gettok(); + if (t != ID) + error("expecting an enumerator identifier\n"); + while (t == ID) { + char *id = token; + Coordinate s; + if (tsym && tsym->scope == level) + error("redeclaration of `%s' previously declared at %w\n", + token, &tsym->src); + s = src; + t = gettok(); + if (t == '=') { + t = gettok(); + k = intexpr(0, 0); + } else { + if (k == inttype->u.sym->u.limits.max.i) + error("overflow in value for enumeration constant `%s'\n", id); + k++; + } + p = install(id, &identifiers, level, level < LOCAL ? PERM : FUNC); + p->src = s; + p->type = ty; + p->sclass = ENUM; + p->u.value = k; + idlist = append(p, idlist); + n++; + if (Aflag >= 2 && n == 128) + warning("more than 127 enumeration constants in `%t'\n", ty); + if (t != ',') + break; + t = gettok(); + if (Aflag >= 2 && t == '}') + warning("non-ANSI trailing comma in enumerator list\n"); + } + test('}', follow); + ty->type = inttype; + ty->size = ty->type->size; + ty->align = ty->type->align; + ty->u.sym->u.idlist = ltov(&idlist, PERM); + ty->u.sym->defined = 1; + } else if ((p = lookup(tag, types)) != NULL && p->type->op == ENUM) { + ty = p->type; + if (t == ';') + error("empty declaration\n"); + } else { + error("unknown enumeration `%s'\n", tag); + ty = newstruct(ENUM, tag); + ty->type = inttype; + } + if (*tag && xref) + use(p, pos); + return ty; +} + +Type typename(void) { + Type ty = specifier(NULL); + + if (t == '*' || t == '(' || t == '[') { + ty = dclr(ty, NULL, NULL, 1); + if (Aflag >= 1 && !hasproto(ty)) + warning("missing prototype\n"); + } + return ty; +} + diff --git a/src/tools/lcc/src/enode.c b/src/tools/lcc/src/enode.c new file mode 100644 index 0000000..760096d --- /dev/null +++ b/src/tools/lcc/src/enode.c @@ -0,0 +1,545 @@ +#include "c.h" + + +static Tree addtree(int, Tree, Tree); +static Tree andtree(int, Tree, Tree); +static Tree cmptree(int, Tree, Tree); +static int compatible(Type, Type); +static int isnullptr(Tree e); +static Tree multree(int, Tree, Tree); +static Tree subtree(int, Tree, Tree); +#define isvoidptr(ty) \ + (isptr(ty) && unqual(ty->type) == voidtype) + +Tree (*optree[])(int, Tree, Tree) = { +#define xx(a,b,c,d,e,f,g) e, +#define yy(a,b,c,d,e,f,g) e, +#include "token.h" +}; +Tree call(Tree f, Type fty, Coordinate src) { + int n = 0; + Tree args = NULL, r = NULL, e; + Type *proto, rty = unqual(freturn(fty)); + Symbol t3 = NULL; + + if (fty->u.f.oldstyle) + proto = NULL; + else + proto = fty->u.f.proto; + if (hascall(f)) + r = f; + if (isstruct(rty)) + { + t3 = temporary(AUTO, unqual(rty)); + if (rty->size == 0) + error("illegal use of incomplete type `%t'\n", rty); + } + if (t != ')') + for (;;) { + Tree q = pointer(expr1(0)); + if (proto && *proto && *proto != voidtype) + { + Type aty; + q = value(q); + aty = assign(*proto, q); + if (aty) + q = cast(q, aty); + else + error("type error in argument %d to %s; found `%t' expected `%t'\n", n + 1, funcname(f), + + q->type, *proto); + if ((isint(q->type) || isenum(q->type)) + && q->type->size != inttype->size) + q = cast(q, promote(q->type)); + ++proto; + } + else + { + if (!fty->u.f.oldstyle && *proto == NULL) + error("too many arguments to %s\n", funcname(f)); + q = value(q); + if (isarray(q->type) || q->type->size == 0) + error("type error in argument %d to %s; `%t' is illegal\n", n + 1, funcname(f), q->type); + + else + q = cast(q, promote(q->type)); + } + if (!IR->wants_argb && isstruct(q->type)) { + if (iscallb(q)) + q = addrof(q); + else { + Symbol t1 = temporary(AUTO, unqual(q->type)); + q = asgn(t1, q); + q = tree(RIGHT, ptr(t1->type), + root(q), lvalue(idtree(t1))); + } + } + if (q->type->size == 0) + q->type = inttype; + if (hascall(q)) + r = r ? tree(RIGHT, voidtype, r, q) : q; + args = tree(mkop(ARG, q->type), q->type, q, args); + n++; + if (Aflag >= 2 && n == 32) + warning("more than 31 arguments in a call to %s\n", + funcname(f)); + if (t != ',') + break; + t = gettok(); + } + expect(')'); + if (proto && *proto && *proto != voidtype) + error("insufficient number of arguments to %s\n", + funcname(f)); + if (r) + args = tree(RIGHT, voidtype, r, args); + e = calltree(f, rty, args, t3); + if (events.calls) + apply(events.calls, &src, &e); + return e; +} +Tree calltree(Tree f, Type ty, Tree args, Symbol t3) { + Tree p; + + if (args) + f = tree(RIGHT, f->type, args, f); + if (isstruct(ty)) + assert(t3), + p = tree(RIGHT, ty, + tree(CALL+B, ty, f, addrof(idtree(t3))), + idtree(t3)); + else { + Type rty = ty; + if (isenum(ty)) + rty = unqual(ty)->type; + if (!isfloat(rty)) + rty = promote(rty); + p = tree(mkop(CALL, rty), rty, f, NULL); + if (isptr(ty) || p->type->size > ty->size) + p = cast(p, ty); + } + return p; +} +Tree vcall(Symbol func, Type ty, ...) { + va_list ap; + Tree args = NULL, e, f = pointer(idtree(func)), r = NULL; + + assert(isfunc(func->type)); + if (ty == NULL) + ty = freturn(func->type); + va_start(ap, ty); + while ((e = va_arg(ap, Tree)) != NULL) { + if (hascall(e)) + r = r == NULL ? e : tree(RIGHT, voidtype, r, e); + args = tree(mkop(ARG, e->type), e->type, e, args); + } + va_end(ap); + if (r != NULL) + args = tree(RIGHT, voidtype, r, args); + return calltree(f, ty, args, NULL); +} +int iscallb(Tree e) { + return e->op == RIGHT && e->kids[0] && e->kids[1] + && e->kids[0]->op == CALL+B + && e->kids[1]->op == INDIR+B + && isaddrop(e->kids[1]->kids[0]->op) + && e->kids[1]->kids[0]->u.sym->temporary; +} + +static Tree addtree(int op, Tree l, Tree r) { + Type ty = inttype; + + if (isarith(l->type) && isarith(r->type)) { + ty = binary(l->type, r->type); + l = cast(l, ty); + r = cast(r, ty); + } else if (isptr(l->type) && isint(r->type)) + return addtree(ADD, r, l); + else if ( isptr(r->type) && isint(l->type) + && !isfunc(r->type->type)) + { + long n; + ty = unqual(r->type); + n = unqual(ty->type)->size; + if (n == 0) + error("unknown size for type `%t'\n", ty->type); + l = cast(l, promote(l->type)); + if (n > 1) + l = multree(MUL, cnsttree(signedptr, n), l); + if (YYcheck && !isaddrop(r->op)) /* omit */ + return nullcall(ty, YYcheck, r, l); /* omit */ + return simplify(ADD, ty, l, r); + } + + else + typeerror(op, l, r); + return simplify(op, ty, l, r); +} + +Tree cnsttree(Type ty, ...) { + Tree p = tree(mkop(CNST,ty), ty, NULL, NULL); + va_list ap; + + va_start(ap, ty); + switch (ty->op) { + case INT: p->u.v.i = va_arg(ap, long); break; + case UNSIGNED:p->u.v.u = va_arg(ap, unsigned long)&ones(8*ty->size); break; + case FLOAT: p->u.v.d = va_arg(ap, double); break; + case POINTER: p->u.v.p = va_arg(ap, void *); break; + default: assert(0); + } + va_end(ap); + return p; +} + +Tree consttree(unsigned n, Type ty) { + if (isarray(ty)) + ty = atop(ty); + else assert(isint(ty)); + return cnsttree(ty, (unsigned long)n); +} +static Tree cmptree(int op, Tree l, Tree r) { + Type ty; + + if (isarith(l->type) && isarith(r->type)) { + ty = binary(l->type, r->type); + l = cast(l, ty); + r = cast(r, ty); + } else if (compatible(l->type, r->type)) { + ty = unsignedptr; + l = cast(l, ty); + r = cast(r, ty); + } else { + ty = unsignedtype; + typeerror(op, l, r); + } + return simplify(mkop(op,ty), inttype, l, r); +} +static int compatible(Type ty1, Type ty2) { + return isptr(ty1) && !isfunc(ty1->type) + && isptr(ty2) && !isfunc(ty2->type) + && eqtype(unqual(ty1->type), unqual(ty2->type), 0); +} +static int isnullptr(Tree e) { + Type ty = unqual(e->type); + + return generic(e->op) == CNST + && ((ty->op == INT && e->u.v.i == 0) + || (ty->op == UNSIGNED && e->u.v.u == 0) + || (isvoidptr(ty) && e->u.v.p == NULL)); +} +Tree eqtree(int op, Tree l, Tree r) { + Type xty = l->type, yty = r->type; + + if ((isptr(xty) && isnullptr(r)) + || (isptr(xty) && !isfunc(xty->type) && isvoidptr(yty)) + || (isptr(xty) && isptr(yty) + && eqtype(unqual(xty->type), unqual(yty->type), 1))) { + Type ty = unsignedptr; + l = cast(l, ty); + r = cast(r, ty); + return simplify(mkop(op,ty), inttype, l, r); + } + if ((isptr(yty) && isnullptr(l)) + || (isptr(yty) && !isfunc(yty->type) && isvoidptr(xty))) + return eqtree(op, r, l); + return cmptree(op, l, r); +} + +Type assign(Type xty, Tree e) { + Type yty = unqual(e->type); + + xty = unqual(xty); + if (isenum(xty)) + xty = xty->type; + if (xty->size == 0 || yty->size == 0) + return NULL; + if ( (isarith(xty) && isarith(yty)) + || (isstruct(xty) && xty == yty)) + return xty; + if (isptr(xty) && isnullptr(e)) + return xty; + if (((isvoidptr(xty) && isptr(yty)) + || (isptr(xty) && isvoidptr(yty))) + && ( (isconst(xty->type) || !isconst(yty->type)) + && (isvolatile(xty->type) || !isvolatile(yty->type)))) + return xty; + + if ((isptr(xty) && isptr(yty) + && eqtype(unqual(xty->type), unqual(yty->type), 1)) + && ( (isconst(xty->type) || !isconst(yty->type)) + && (isvolatile(xty->type) || !isvolatile(yty->type)))) + return xty; + if (isptr(xty) && isptr(yty) + && ( (isconst(xty->type) || !isconst(yty->type)) + && (isvolatile(xty->type) || !isvolatile(yty->type)))) { + Type lty = unqual(xty->type), rty = unqual(yty->type); + if ((isenum(lty) && rty == inttype) + || (isenum(rty) && lty == inttype)) { + if (Aflag >= 1) + warning("assignment between `%t' and `%t' is compiler-dependent\n", + xty, yty); + return xty; + } + } + return NULL; +} +Tree asgntree(int op, Tree l, Tree r) { + Type aty, ty; + + r = pointer(r); + ty = assign(l->type, r); + if (ty) + r = cast(r, ty); + else { + typeerror(ASGN, l, r); + if (r->type == voidtype) + r = retype(r, inttype); + ty = r->type; + } + if (l->op != FIELD) + l = lvalue(l); + aty = l->type; + if (isptr(aty)) + aty = unqual(aty)->type; + if ( isconst(aty) + || (isstruct(aty) && unqual(aty)->u.sym->u.s.cfields)) { + if (isaddrop(l->op) + && !l->u.sym->computed && !l->u.sym->generated) + error("assignment to const identifier `%s'\n", + l->u.sym->name); + else + error("assignment to const location\n"); + } + if (l->op == FIELD) { + long n = 8*l->u.field->type->size - fieldsize(l->u.field); + if (n > 0 && isunsigned(l->u.field->type)) + r = bittree(BAND, r, + cnsttree(r->type, (unsigned long)fieldmask(l->u.field))); + else if (n > 0) { + if (r->op == CNST+I) { + n = r->u.v.i; + if (n&(1<<(fieldsize(l->u.field)-1))) + n |= ~0UL<u.field); + r = cnsttree(r->type, n); + } else + r = shtree(RSH, + shtree(LSH, r, cnsttree(inttype, n)), + cnsttree(inttype, n)); + } + } + if (isstruct(ty) && isaddrop(l->op) && iscallb(r)) + return tree(RIGHT, ty, + tree(CALL+B, ty, r->kids[0]->kids[0], l), + idtree(l->u.sym)); + return tree(mkop(op,ty), ty, l, r); +} +Tree condtree(Tree e, Tree l, Tree r) { + Symbol t1; + Type ty, xty = l->type, yty = r->type; + Tree p; + + if (isarith(xty) && isarith(yty)) + ty = binary(xty, yty); + else if (eqtype(xty, yty, 1)) + ty = unqual(xty); + else if (isptr(xty) && isnullptr(r)) + ty = xty; + else if (isnullptr(l) && isptr(yty)) + ty = yty; + else if ((isptr(xty) && !isfunc(xty->type) && isvoidptr(yty)) + || (isptr(yty) && !isfunc(yty->type) && isvoidptr(xty))) + ty = voidptype; + else if ((isptr(xty) && isptr(yty) + && eqtype(unqual(xty->type), unqual(yty->type), 1))) + ty = xty; + else { + typeerror(COND, l, r); + return consttree(0, inttype); + } + if (isptr(ty)) { + ty = unqual(unqual(ty)->type); + if ((isptr(xty) && isconst(unqual(xty)->type)) + || (isptr(yty) && isconst(unqual(yty)->type))) + ty = qual(CONST, ty); + if ((isptr(xty) && isvolatile(unqual(xty)->type)) + || (isptr(yty) && isvolatile(unqual(yty)->type))) + ty = qual(VOLATILE, ty); + ty = ptr(ty); + } + switch (e->op) { + case CNST+I: return cast(e->u.v.i != 0 ? l : r, ty); + case CNST+U: return cast(e->u.v.u != 0 ? l : r, ty); + case CNST+P: return cast(e->u.v.p != 0 ? l : r, ty); + case CNST+F: return cast(e->u.v.d != 0.0 ? l : r, ty); + } + if (ty != voidtype && ty->size > 0) { + t1 = genident(REGISTER, unqual(ty), level); + /* t1 = temporary(REGISTER, unqual(ty)); */ + l = asgn(t1, l); + r = asgn(t1, r); + } else + t1 = NULL; + p = tree(COND, ty, cond(e), + tree(RIGHT, ty, root(l), root(r))); + p->u.sym = t1; + return p; +} +/* addrof - address of p */ +Tree addrof(Tree p) { + Tree q = p; + + for (;;) + switch (generic(q->op)) { + case RIGHT: + assert(q->kids[0] || q->kids[1]); + q = q->kids[1] ? q->kids[1] : q->kids[0]; + continue; + case ASGN: + q = q->kids[1]; + continue; + case COND: { + Symbol t1 = q->u.sym; + q->u.sym = 0; + q = idtree(t1); + /* fall thru */ + } + case INDIR: + if (p == q) + return q->kids[0]; + q = q->kids[0]; + return tree(RIGHT, q->type, root(p), q); + default: + error("addressable object required\n"); + return value(p); + } +} + +/* andtree - construct tree for l [&& ||] r */ +static Tree andtree(int op, Tree l, Tree r) { + if (!isscalar(l->type) || !isscalar(r->type)) + typeerror(op, l, r); + return simplify(op, inttype, cond(l), cond(r)); +} + +/* asgn - generate tree for assignment of expr e to symbol p sans qualifiers */ +Tree asgn(Symbol p, Tree e) { + if (isarray(p->type)) + e = tree(ASGN+B, p->type, idtree(p), + tree(INDIR+B, e->type, e, NULL)); + else { + Type ty = p->type; + p->type = unqual(p->type); + if (isstruct(p->type) && p->type->u.sym->u.s.cfields) { + p->type->u.sym->u.s.cfields = 0; + e = asgntree(ASGN, idtree(p), e); + p->type->u.sym->u.s.cfields = 1; + } else + e = asgntree(ASGN, idtree(p), e); + p->type = ty; + } + return e; +} + +/* bittree - construct tree for l [& | ^ %] r */ +Tree bittree(int op, Tree l, Tree r) { + Type ty = inttype; + + if (isint(l->type) && isint(r->type)) { + ty = binary(l->type, r->type); + l = cast(l, ty); + r = cast(r, ty); + } else + typeerror(op, l, r); + return simplify(op, ty, l, r); +} + +/* multree - construct tree for l [* /] r */ +static Tree multree(int op, Tree l, Tree r) { + Type ty = inttype; + + if (isarith(l->type) && isarith(r->type)) { + ty = binary(l->type, r->type); + l = cast(l, ty); + r = cast(r, ty); + } else + typeerror(op, l, r); + return simplify(op, ty, l, r); +} + +/* shtree - construct tree for l [>> <<] r */ +Tree shtree(int op, Tree l, Tree r) { + Type ty = inttype; + + if (isint(l->type) && isint(r->type)) { + ty = promote(l->type); + l = cast(l, ty); + r = cast(r, inttype); + } else + typeerror(op, l, r); + return simplify(op, ty, l, r); +} + +/* subtree - construct tree for l - r */ +static Tree subtree(int op, Tree l, Tree r) { + long n; + Type ty = inttype; + + if (isarith(l->type) && isarith(r->type)) { + ty = binary(l->type, r->type); + l = cast(l, ty); + r = cast(r, ty); + } else if (isptr(l->type) && !isfunc(l->type->type) && isint(r->type)) { + ty = unqual(l->type); + n = unqual(ty->type)->size; + if (n == 0) + error("unknown size for type `%t'\n", ty->type); + r = cast(r, promote(r->type)); + if (n > 1) + r = multree(MUL, cnsttree(signedptr, n), r); + if (isunsigned(r->type)) + r = cast(r, unsignedptr); + else + r = cast(r, signedptr); + return simplify(SUB+P, ty, l, r); + } else if (compatible(l->type, r->type)) { + ty = unqual(l->type); + n = unqual(ty->type)->size; + if (n == 0) + error("unknown size for type `%t'\n", ty->type); + l = simplify(SUB+U, unsignedptr, + cast(l, unsignedptr), cast(r, unsignedptr)); + return simplify(DIV+I, longtype, + cast(l, longtype), cnsttree(longtype, n)); + } else + typeerror(op, l, r); + return simplify(op, ty, l, r); +} + +/* typeerror - issue "operands of op have illegal types `l' and `r'" */ +void typeerror(int op, Tree l, Tree r) { + int i; + static struct { int op; char *name; } ops[] = { + {ASGN, "="}, {INDIR, "*"}, {NEG, "-"}, + {ADD, "+"}, {SUB, "-"}, {LSH, "<<"}, + {MOD, "%"}, {RSH, ">>"}, {BAND, "&"}, + {BCOM, "~"}, {BOR, "|"}, {BXOR, "^"}, + {DIV, "/"}, {MUL, "*"}, {EQ, "=="}, + {GE, ">="}, {GT, ">"}, {LE, "<="}, + {LT, "<"}, {NE, "!="}, {AND, "&&"}, + {NOT, "!"}, {OR, "||"}, {COND, "?:"}, + {0, 0} + }; + + op = generic(op); + for (i = 0; ops[i].op; i++) + if (op == ops[i].op) + break; + assert(ops[i].name); + if (r) + error("operands of %s have illegal types `%t' and `%t'\n", + ops[i].name, l->type, r->type); + else + error("operand of unary %s has illegal type `%t'\n", ops[i].name, + l->type); +} diff --git a/src/tools/lcc/src/error.c b/src/tools/lcc/src/error.c new file mode 100644 index 0000000..2187c10 --- /dev/null +++ b/src/tools/lcc/src/error.c @@ -0,0 +1,137 @@ +#include "c.h" + + +static void printtoken(void); +int errcnt = 0; +int errlimit = 20; +char kind[] = { +#define xx(a,b,c,d,e,f,g) f, +#define yy(a,b,c,d,e,f,g) f, +#include "token.h" +}; +int wflag; /* != 0 to suppress warning messages */ + +void test(int tok, char set[]) { + if (t == tok) + t = gettok(); + else { + expect(tok); + skipto(tok, set); + if (t == tok) + t = gettok(); + } +} +void expect(int tok) { + if (t == tok) + t = gettok(); + else { + error("syntax error; found"); + printtoken(); + fprint(stderr, " expecting `%k'\n", tok); + } +} +void error(const char *fmt, ...) { + va_list ap; + + if (errcnt++ >= errlimit) { + errcnt = -1; + error("too many errors\n"); + exit(1); + } + va_start(ap, fmt); + if (firstfile != file && firstfile && *firstfile) + fprint(stderr, "%s: ", firstfile); + fprint(stderr, "%w: ", &src); + vfprint(stderr, NULL, fmt, ap); + va_end(ap); +} + +void skipto(int tok, char set[]) { + int n; + char *s; + + assert(set); + for (n = 0; t != EOI && t != tok; t = gettok()) { + for (s = set; *s && kind[t] != *s; s++) + ; + if (kind[t] == *s) + break; + if (n++ == 0) + error("skipping"); + if (n <= 8) + printtoken(); + else if (n == 9) + fprint(stderr, " ..."); + } + if (n > 8) { + fprint(stderr, " up to"); + printtoken(); + } + if (n > 0) + fprint(stderr, "\n"); +} +/* fatal - issue fatal error message and exit */ +int fatal(const char *name, const char *fmt, int n) { + print("\n"); + errcnt = -1; + error("compiler error in %s--", name); + fprint(stderr, fmt, n); + exit(EXIT_FAILURE); + return 0; +} + +/* printtoken - print current token preceeded by a space */ +static void printtoken(void) { + switch (t) { + case ID: fprint(stderr, " `%s'", token); break; + case ICON: + fprint(stderr, " `%s'", vtoa(tsym->type, tsym->u.c.v)); + break; + case SCON: { + int i, n; + if (ischar(tsym->type->type)) { + char *s = tsym->u.c.v.p; + n = tsym->type->size; + fprint(stderr, " \""); + for (i = 0; i < 20 && i < n && *s; s++, i++) + if (*s < ' ' || *s >= 0177) + fprint(stderr, "\\%o", *s); + else + fprint(stderr, "%c", *s); + } else { /* wchar_t string */ + unsigned int *s = tsym->u.c.v.p; + assert(tsym->type->type->size == widechar->size); + n = tsym->type->size/widechar->size; + fprint(stderr, " L\""); + for (i = 0; i < 20 && i < n && *s; s++, i++) + if (*s < ' ' || *s >= 0177) + fprint(stderr, "\\x%x", *s); + else + fprint(stderr, "%c", *s); + } + if (i < n) + fprint(stderr, " ..."); + else + fprint(stderr, "\""); + break; + } + case FCON: + fprint(stderr, " `%S'", token, (char*)cp - token); + break; + case '`': case '\'': fprint(stderr, " \"%k\"", t); break; + default: fprint(stderr, " `%k'", t); + } +} + +/* warning - issue warning error message */ +void warning(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + if (wflag == 0) { + errcnt--; + error("warning: "); + vfprint(stderr, NULL, fmt, ap); + } + va_end(ap); +} diff --git a/src/tools/lcc/src/event.c b/src/tools/lcc/src/event.c new file mode 100644 index 0000000..4549e3f --- /dev/null +++ b/src/tools/lcc/src/event.c @@ -0,0 +1,28 @@ +#include "c.h" + + +struct entry { + Apply func; + void *cl; +}; + +Events events; +void attach(Apply func, void *cl, List *list) { + struct entry *p; + + NEW(p, PERM); + p->func = func; + p->cl = cl; + *list = append(p, *list); +} +void apply(List event, void *arg1, void *arg2) { + if (event) { + List lp = event; + do { + struct entry *p = lp->x; + (*p->func)(p->cl, arg1, arg2); + lp = lp->link; + } while (lp != event); + } +} + diff --git a/src/tools/lcc/src/expr.c b/src/tools/lcc/src/expr.c new file mode 100644 index 0000000..b8cb08b --- /dev/null +++ b/src/tools/lcc/src/expr.c @@ -0,0 +1,711 @@ +#include "c.h" + + +static char prec[] = { +#define xx(a,b,c,d,e,f,g) c, +#define yy(a,b,c,d,e,f,g) c, +#include "token.h" +}; +static int oper[] = { +#define xx(a,b,c,d,e,f,g) d, +#define yy(a,b,c,d,e,f,g) d, +#include "token.h" +}; +float refinc = 1.0; +static Tree expr2(void); +static Tree expr3(int); +static Tree nullcheck(Tree); +static Tree postfix(Tree); +static Tree unary(void); +static Tree primary(void); +static Type super(Type ty); + +static Type super(Type ty) { + switch (ty->op) { + case INT: + if (ty->size < inttype->size) + return inttype; + break; + case UNSIGNED: + if (ty->size < unsignedtype->size) + return unsignedtype; + break; + case POINTER: + return unsignedptr; + } + return ty; +} +Tree expr(int tok) { + static char stop[] = { IF, ID, '}', 0 }; + Tree p = expr1(0); + + while (t == ',') { + Tree q; + t = gettok(); + q = pointer(expr1(0)); + p = tree(RIGHT, q->type, root(value(p)), q); + } + if (tok) + test(tok, stop); + return p; +} +Tree expr0(int tok) { + return root(expr(tok)); +} +Tree expr1(int tok) { + static char stop[] = { IF, ID, 0 }; + Tree p = expr2(); + + if (t == '=' + || (prec[t] >= 6 && prec[t] <= 8) + || (prec[t] >= 11 && prec[t] <= 13)) { + int op = t; + t = gettok(); + if (oper[op] == ASGN) + p = asgntree(ASGN, p, value(expr1(0))); + else + { + expect('='); + p = incr(op, p, expr1(0)); + } + } + if (tok) + test(tok, stop); + return p; +} +Tree incr(int op, Tree v, Tree e) { + return asgntree(ASGN, v, (*optree[op])(oper[op], v, e)); +} +static Tree expr2(void) { + Tree p = expr3(4); + + if (t == '?') { + Tree l, r; + Coordinate pts[2]; + if (Aflag > 1 && isfunc(p->type)) + warning("%s used in a conditional expression\n", + funcname(p)); + p = pointer(p); + t = gettok(); + pts[0] = src; + l = pointer(expr(':')); + pts[1] = src; + r = pointer(expr2()); + if (events.points) + { + apply(events.points, &pts[0], &l); + apply(events.points, &pts[1], &r); + } + p = condtree(p, l, r); + } + return p; +} +Tree value(Tree p) { + int op = generic(rightkid(p)->op); + + if (p->type != voidtype + && (op==AND || op==OR || op==NOT || op==EQ || op==NE + || op== LE || op==LT || op== GE || op==GT)) + p = condtree(p, consttree(1, inttype), + consttree(0, inttype)); + return p; +} +static Tree expr3(int k) { + int k1; + Tree p = unary(); + + for (k1 = prec[t]; k1 >= k; k1--) + while (prec[t] == k1 && *cp != '=') { + Tree r; + Coordinate pt; + int op = t; + t = gettok(); + pt = src; + p = pointer(p); + if (op == ANDAND || op == OROR) { + r = pointer(expr3(k1)); + if (events.points) + apply(events.points, &pt, &r); + } else + r = pointer(expr3(k1 + 1)); + p = (*optree[op])(oper[op], p, r); + } + return p; +} +static Tree unary(void) { + Tree p; + + switch (t) { + case '*': t = gettok(); p = unary(); p = pointer(p); + if (isptr(p->type) + && (isfunc(p->type->type) || isarray(p->type->type))) + p = retype(p, p->type->type); + else { + if (YYnull) + p = nullcheck(p); + p = rvalue(p); + } break; + case '&': t = gettok(); p = unary(); if (isarray(p->type) || isfunc(p->type)) + p = retype(p, ptr(p->type)); + else + p = lvalue(p); + if (isaddrop(p->op) && p->u.sym->sclass == REGISTER) + error("invalid operand of unary &; `%s' is declared register\n", p->u.sym->name); + + else if (isaddrop(p->op)) + p->u.sym->addressed = 1; + break; + case '+': t = gettok(); p = unary(); p = pointer(p); + if (isarith(p->type)) + p = cast(p, promote(p->type)); + else + typeerror(ADD, p, NULL); break; + case '-': t = gettok(); p = unary(); p = pointer(p); + if (isarith(p->type)) { + Type ty = promote(p->type); + p = cast(p, ty); + if (isunsigned(ty)) { + warning("unsigned operand of unary -\n"); + p = simplify(ADD, ty, simplify(BCOM, ty, p, NULL), cnsttree(ty, 1UL)); + } else + p = simplify(NEG, ty, p, NULL); + } else + typeerror(SUB, p, NULL); break; + case '~': t = gettok(); p = unary(); p = pointer(p); + if (isint(p->type)) { + Type ty = promote(p->type); + p = simplify(BCOM, ty, cast(p, ty), NULL); + } else + typeerror(BCOM, p, NULL); break; + case '!': t = gettok(); p = unary(); p = pointer(p); + if (isscalar(p->type)) + p = simplify(NOT, inttype, cond(p), NULL); + else + typeerror(NOT, p, NULL); break; + case INCR: t = gettok(); p = unary(); p = incr(INCR, pointer(p), consttree(1, inttype)); break; + case DECR: t = gettok(); p = unary(); p = incr(DECR, pointer(p), consttree(1, inttype)); break; + case TYPECODE: case SIZEOF: { int op = t; + Type ty; + p = NULL; + t = gettok(); + if (t == '(') { + t = gettok(); + if (istypename(t, tsym)) { + ty = typename(); + expect(')'); + } else { + p = postfix(expr(')')); + ty = p->type; + } + } else { + p = unary(); + ty = p->type; + } + assert(ty); + if (op == TYPECODE) + p = cnsttree(inttype, (long)ty->op); + else { + if (isfunc(ty) || ty->size == 0) + error("invalid type argument `%t' to `sizeof'\n", ty); + else if (p && rightkid(p)->op == FIELD) + error("`sizeof' applied to a bit field\n"); + p = cnsttree(unsignedlong, (unsigned long)ty->size); + } } break; + case '(': + t = gettok(); + if (istypename(t, tsym)) { + Type ty, ty1 = typename(), pty; + expect(')'); + ty = unqual(ty1); + if (isenum(ty)) { + Type ty2 = ty->type; + if (isconst(ty1)) + ty2 = qual(CONST, ty2); + if (isvolatile(ty1)) + ty2 = qual(VOLATILE, ty2); + ty1 = ty2; + ty = ty->type; + } + p = pointer(unary()); + pty = p->type; + if (isenum(pty)) + pty = pty->type; + if ((isarith(pty) && isarith(ty)) + || (isptr(pty) && isptr(ty))) { + explicitCast++; + p = cast(p, ty); + explicitCast--; + } else if ((isptr(pty) && isint(ty)) + || (isint(pty) && isptr(ty))) { + if (Aflag >= 1 && ty->size < pty->size) + warning("conversion from `%t' to `%t' is compiler dependent\n", p->type, ty); + + p = cast(p, ty); + } else if (ty != voidtype) { + error("cast from `%t' to `%t' is illegal\n", + p->type, ty1); + ty1 = inttype; + } + if (generic(p->op) == INDIR || ty->size == 0) + p = tree(RIGHT, ty1, NULL, p); + else + p = retype(p, ty1); + } else + p = postfix(expr(')')); + break; + default: + p = postfix(primary()); + } + return p; +} + +static Tree postfix(Tree p) { + for (;;) + switch (t) { + case INCR: p = tree(RIGHT, p->type, + tree(RIGHT, p->type, + p, + incr(t, p, consttree(1, inttype))), + p); + t = gettok(); break; + case DECR: p = tree(RIGHT, p->type, + tree(RIGHT, p->type, + p, + incr(t, p, consttree(1, inttype))), + p); + t = gettok(); break; + case '[': { + Tree q; + t = gettok(); + q = expr(']'); + if (YYnull) { + if (isptr(p->type)) + p = nullcheck(p); + else if (isptr(q->type)) + q = nullcheck(q); + } + p = (*optree['+'])(ADD, pointer(p), pointer(q)); + if (isptr(p->type) && isarray(p->type->type)) + p = retype(p, p->type->type); + else + p = rvalue(p); + } break; + case '(': { + Type ty; + Coordinate pt; + p = pointer(p); + if (isptr(p->type) && isfunc(p->type->type)) + ty = p->type->type; + else { + error("found `%t' expected a function\n", p->type); + ty = func(voidtype, NULL, 1); + p = retype(p, ptr(ty)); + } + pt = src; + t = gettok(); + p = call(p, ty, pt); + } break; + case '.': t = gettok(); + if (t == ID) { + if (isstruct(p->type)) { + Tree q = addrof(p); + p = field(q, token); + q = rightkid(q); + if (isaddrop(q->op) && q->u.sym->temporary) + p = tree(RIGHT, p->type, p, NULL); + } else + error("left operand of . has incompatible type `%t'\n", + p->type); + t = gettok(); + } else + error("field name expected\n"); break; + case DEREF: t = gettok(); + p = pointer(p); + if (t == ID) { + if (isptr(p->type) && isstruct(p->type->type)) { + if (YYnull) + p = nullcheck(p); + p = field(p, token); + } else + error("left operand of -> has incompatible type `%t'\n", p->type); + + t = gettok(); + } else + error("field name expected\n"); break; + default: + return p; + } +} +static Tree primary(void) { + Tree p; + + assert(t != '('); + switch (t) { + case ICON: + case FCON: p = tree(mkop(CNST,tsym->type), tsym->type, NULL, NULL); + p->u.v = tsym->u.c.v; + break; + case SCON: if (ischar(tsym->type->type)) + tsym->u.c.v.p = stringn(tsym->u.c.v.p, tsym->type->size); + else + tsym->u.c.v.p = memcpy(allocate(tsym->type->size, PERM), tsym->u.c.v.p, tsym->type->size); + tsym = constant(tsym->type, tsym->u.c.v); + if (tsym->u.c.loc == NULL) + tsym->u.c.loc = genident(STATIC, tsym->type, GLOBAL); + p = idtree(tsym->u.c.loc); break; + case ID: if (tsym == NULL) + { + Symbol p = install(token, &identifiers, level, FUNC); + p->src = src; + if (getchr() == '(') { + Symbol q = lookup(token, externals); + p->type = func(inttype, NULL, 1); + p->sclass = EXTERN; + if (Aflag >= 1) + warning("missing prototype\n"); + if (q && !eqtype(q->type, p->type, 1)) + warning("implicit declaration of `%s' does not match previous declaration at %w\n", q->name, &q->src); + + if (q == NULL) { + q = install(p->name, &externals, GLOBAL, PERM); + q->type = p->type; + q->sclass = EXTERN; + q->src = src; + (*IR->defsymbol)(q); + } + p->u.alias = q; + } else { + error("undeclared identifier `%s'\n", p->name); + p->sclass = AUTO; + p->type = inttype; + if (p->scope == GLOBAL) + (*IR->defsymbol)(p); + else + addlocal(p); + } + t = gettok(); + if (xref) + use(p, src); + return idtree(p); + } + if (xref) + use(tsym, src); + if (tsym->sclass == ENUM) + p = consttree(tsym->u.value, inttype); + else { + if (tsym->sclass == TYPEDEF) + error("illegal use of type name `%s'\n", tsym->name); + p = idtree(tsym); + } break; + case FIRSTARG: + if (level > PARAM && cfunc && cfunc->u.f.callee[0]) + p = idtree(cfunc->u.f.callee[0]); + else { + error("illegal use of `%k'\n", FIRSTARG); + p = cnsttree(inttype, 0L); + } + break; + default: + error("illegal expression\n"); + p = cnsttree(inttype, 0L); + } + t = gettok(); + return p; +} +Tree idtree(Symbol p) { + int op; + Tree e; + Type ty = p->type ? unqual(p->type) : voidptype; + + if (p->scope == GLOBAL || p->sclass == STATIC) + op = ADDRG; + else if (p->scope == PARAM) { + op = ADDRF; + if (isstruct(p->type) && !IR->wants_argb) + { + e = tree(mkop(op,voidptype), ptr(ptr(p->type)), NULL, NULL); + e->u.sym = p; + return rvalue(rvalue(e)); + } + } else if (p->sclass == EXTERN) { + assert(p->u.alias); + p = p->u.alias; + op = ADDRG; + } else + op = ADDRL; + p->ref += refinc; + if (isarray(ty)) + e = tree(mkop(op,voidptype), p->type, NULL, NULL); + else if (isfunc(ty)) + e = tree(mkop(op,funcptype), p->type, NULL, NULL); + else + e = tree(mkop(op,voidptype), ptr(p->type), NULL, NULL); + e->u.sym = p; + if (isptr(e->type)) + e = rvalue(e); + return e; +} + +Tree rvalue(Tree p) { + Type ty = deref(p->type); + + ty = unqual(ty); + return tree(mkop(INDIR,ty), ty, p, NULL); +} +Tree lvalue(Tree p) { + if (generic(p->op) != INDIR) { + error("lvalue required\n"); + return value(p); + } else if (unqual(p->type) == voidtype) + warning("`%t' used as an lvalue\n", p->type); + return p->kids[0]; +} +Tree retype(Tree p, Type ty) { + Tree q; + + if (p->type == ty) + return p; + q = tree(p->op, ty, p->kids[0], p->kids[1]); + q->node = p->node; + q->u = p->u; + return q; +} +Tree rightkid(Tree p) { + while (p && p->op == RIGHT) + if (p->kids[1]) + p = p->kids[1]; + else if (p->kids[0]) + p = p->kids[0]; + else + assert(0); + assert(p); + return p; +} +int hascall(Tree p) { + if (p == 0) + return 0; + if (generic(p->op) == CALL || (IR->mulops_calls && + (p->op == DIV+I || p->op == MOD+I || p->op == MUL+I + || p->op == DIV+U || p->op == MOD+U || p->op == MUL+U))) + return 1; + return hascall(p->kids[0]) || hascall(p->kids[1]); +} +Type binary(Type xty, Type yty) { +#define xx(t) if (xty == t || yty == t) return t + xx(longdouble); + xx(doubletype); + xx(floattype); + xx(unsignedlonglong); + xx(longlong); + xx(unsignedlong); + if ((xty == longtype && yty == unsignedtype) + || (xty == unsignedtype && yty == longtype)) { + if (longtype->size > unsignedtype->size) + return longtype; + else + return unsignedlong; + } + xx(longtype); + xx(unsignedtype); + return inttype; +#undef xx +} +Tree pointer(Tree p) { + if (isarray(p->type)) + /* assert(p->op != RIGHT || p->u.sym == NULL), */ + p = retype(p, atop(p->type)); + else if (isfunc(p->type)) + p = retype(p, ptr(p->type)); + return p; +} +Tree cond(Tree p) { + int op = generic(rightkid(p)->op); + + if (op == AND || op == OR || op == NOT + || op == EQ || op == NE + || op == LE || op == LT || op == GE || op == GT) + return p; + p = pointer(p); + return (*optree[NEQ])(NE, p, consttree(0, inttype)); +} +Tree cast(Tree p, Type type) { + Type src, dst; + + p = value(p); + if (p->type == type) + return p; + dst = unqual(type); + src = unqual(p->type); + if (src->op != dst->op || src->size != dst->size) { + switch (src->op) { + case INT: + if (src->size < inttype->size) + p = simplify(CVI, inttype, p, NULL); + break; + case UNSIGNED: + if (src->size < inttype->size) + p = simplify(CVU, inttype, p, NULL); + else if (src->size < unsignedtype->size) + p = simplify(CVU, unsignedtype, p, NULL); + break; + case ENUM: + p = retype(p, inttype); + break; + case POINTER: + if (isint(dst) && src->size > dst->size) + warning("conversion from `%t' to `%t' is undefined\n", p->type, type); + p = simplify(CVP, super(src), p, NULL); + break; + case FLOAT: + break; + default: assert(0); + } + { + src = unqual(p->type); + dst = super(dst); + if (src->op != dst->op) + switch (src->op) { + case INT: + p = simplify(CVI, dst, p, NULL); + break; + case UNSIGNED: + if (isfloat(dst)) { + Type ssrc = signedint(src); + Tree two = cnsttree(longdouble, (double)2.0); + p = (*optree['+'])(ADD, + (*optree['*'])(MUL, + two, + simplify(CVU, ssrc, + simplify(RSH, src, + p, consttree(1, inttype)), NULL)), + simplify(CVU, ssrc, + simplify(BAND, src, + p, consttree(1, unsignedtype)), NULL)); + } else + p = simplify(CVU, dst, p, NULL); + break; + case FLOAT: + if (isunsigned(dst)) { + Type sdst = signedint(dst); + Tree c = cast(cnsttree(longdouble, (double)sdst->u.sym->u.limits.max.i + 1), src); + p = condtree( + simplify(GE, src, p, c), + (*optree['+'])(ADD, + cast(cast(simplify(SUB, src, p, c), sdst), dst), + cast(cnsttree(unsignedlong, (unsigned long)sdst->u.sym->u.limits.max.i + 1), dst)), + simplify(CVF, sdst, p, NULL)); + } else + p = simplify(CVF, dst, p, NULL); + break; + default: assert(0); + } + dst = unqual(type); + } + } + src = unqual(p->type); + switch (src->op) { + case INT: + if (src->op != dst->op || src->size != dst->size) + p = simplify(CVI, dst, p, NULL); + break; + case UNSIGNED: + if (src->op != dst->op || src->size != dst->size) + p = simplify(CVU, dst, p, NULL); + break; + case FLOAT: + if (src->op != dst->op || src->size != dst->size) + p = simplify(CVF, dst, p, NULL); + break; + case POINTER: + if (src->op != dst->op) + p = simplify(CVP, dst, p, NULL); + else { + if ((isfunc(src->type) && !isfunc(dst->type)) + || (!isfunc(src->type) && isfunc(dst->type))) + warning("conversion from `%t' to `%t' is compiler dependent\n", p->type, type); + + if (src->size != dst->size) + p = simplify(CVP, dst, p, NULL); + } + break; + default: assert(0); + } + return retype(p, type); +} +Tree field(Tree p, const char *name) { + Field q; + Type ty1, ty = p->type; + + if (isptr(ty)) + ty = deref(ty); + ty1 = ty; + ty = unqual(ty); + if ((q = fieldref(name, ty)) != NULL) { + if (isarray(q->type)) { + ty = q->type->type; + if (isconst(ty1) && !isconst(ty)) + ty = qual(CONST, ty); + if (isvolatile(ty1) && !isvolatile(ty)) + ty = qual(VOLATILE, ty); + ty = array(ty, q->type->size/ty->size, q->type->align); + } else { + ty = q->type; + if (isconst(ty1) && !isconst(ty)) + ty = qual(CONST, ty); + if (isvolatile(ty1) && !isvolatile(ty)) + ty = qual(VOLATILE, ty); + ty = ptr(ty); + } + if (YYcheck && !isaddrop(p->op) && q->offset > 0) /* omit */ + p = nullcall(ty, YYcheck, p, consttree(q->offset, inttype)); /* omit */ + else /* omit */ + p = simplify(ADD+P, ty, p, consttree(q->offset, inttype)); + + if (q->lsb) { + p = tree(FIELD, ty->type, rvalue(p), NULL); + p->u.field = q; + } else if (!isarray(q->type)) + p = rvalue(p); + + } else { + error("unknown field `%s' of `%t'\n", name, ty); + p = rvalue(retype(p, ptr(inttype))); + } + return p; +} +/* funcname - return name of function f or a function' */ +char *funcname(Tree f) { + if (isaddrop(f->op)) + return stringf("`%s'", f->u.sym->name); + return "a function"; +} +static Tree nullcheck(Tree p) { + if (!needconst && YYnull && isptr(p->type)) { + p = value(p); + if (strcmp(YYnull->name, "_YYnull") == 0) { + Symbol t1 = temporary(REGISTER, voidptype); + p = tree(RIGHT, p->type, + tree(OR, voidtype, + cond(asgn(t1, cast(p, voidptype))), + vcall(YYnull, voidtype, (file && *file ? pointer(idtree(mkstr(file)->u.c.loc)) : cnsttree(voidptype, NULL)), cnsttree(inttype, (long)lineno) , NULL)), + idtree(t1)); + } + + else + p = nullcall(p->type, YYnull, p, cnsttree(inttype, 0L)); + + } + return p; +} +Tree nullcall(Type pty, Symbol f, Tree p, Tree e) { + Type ty; + + if (isarray(pty)) + return retype(nullcall(atop(pty), f, p, e), pty); + ty = unqual(unqual(p->type)->type); + return vcall(f, pty, + p, e, + cnsttree(inttype, (long)ty->size), + cnsttree(inttype, (long)ty->align), + (file && *file ? pointer(idtree(mkstr(file)->u.c.loc)) : cnsttree(voidptype, NULL)), cnsttree(inttype, (long)lineno) , NULL); +} diff --git a/src/tools/lcc/src/gen.c b/src/tools/lcc/src/gen.c new file mode 100644 index 0000000..4ee170d --- /dev/null +++ b/src/tools/lcc/src/gen.c @@ -0,0 +1,830 @@ +#include "c.h" + + +#define readsreg(p) \ + (generic((p)->op)==INDIR && (p)->kids[0]->op==VREG+P) +#define setsrc(d) ((d) && (d)->x.regnode && \ + (d)->x.regnode->set == src->x.regnode->set && \ + (d)->x.regnode->mask&src->x.regnode->mask) + +#define relink(a, b) ((b)->x.prev = (a), (a)->x.next = (b)) + +static Symbol askfixedreg(Symbol); +static Symbol askreg(Symbol, unsigned*); +static void blkunroll(int, int, int, int, int, int, int[]); +static void docall(Node); +static void dumpcover(Node, int, int); +static void dumpregs(char *, char *, char *); +static void dumprule(int); +static void dumptree(Node); +static unsigned emitasm(Node, int); +static void genreload(Node, Symbol, int); +static void genspill(Symbol, Node, Symbol); +static Symbol getreg(Symbol, unsigned*, Node); +static int getrule(Node, int); +static void linearize(Node, Node); +static int moveself(Node); +static void prelabel(Node); +static Node* prune(Node, Node*); +static void putreg(Symbol); +static void ralloc(Node); +static void reduce(Node, int); +static int reprune(Node*, int, int, Node); +static int requate(Node); +static Node reuse(Node, int); +static void rewrite(Node); +static Symbol spillee(Symbol, unsigned mask[], Node); +static void spillr(Symbol, Node); +static int uses(Node, Regnode); + +int offset; + +int maxoffset; + +int framesize; +int argoffset; + +int maxargoffset; + +int dalign, salign; +int bflag = 0; /* omit */ +int dflag = 0; + +int swap; + +unsigned (*emitter)(Node, int) = emitasm; +static char NeedsReg[] = { + 0, /* unused */ + 1, /* CNST */ + 0, 0, /* ARG ASGN */ + 1, /* INDIR */ + 0, 0, 1, 1, /* - - CVF CVI */ + 1, 0, 1, 1, /* CVP - CVU NEG */ + 1, /* CALL */ + 1, /* LOAD */ + 0, /* RET */ + 1, 1, 1, /* ADDRG ADDRF ADDRL */ + 1, 1, 1, 1, 1, /* ADD SUB LSH MOD RSH */ + 1, 1, 1, 1, /* BAND BCOM BOR BXOR */ + 1, 1, /* DIV MUL */ + 0, 0, 0, 0, 0, 0, /* EQ GE GT LE LT NE */ + 0, 0 /* JUMP LABEL */ +}; +Node head; + +unsigned freemask[2]; +unsigned usedmask[2]; +unsigned tmask[2]; +unsigned vmask[2]; +Symbol mkreg(char *fmt, int n, int mask, int set) { + Symbol p; + + NEW0(p, PERM); + p->name = p->x.name = stringf(fmt, n); + NEW0(p->x.regnode, PERM); + p->x.regnode->number = n; + p->x.regnode->mask = mask<x.regnode->set = set; + return p; +} +Symbol mkwildcard(Symbol *syms) { + Symbol p; + + NEW0(p, PERM); + p->name = p->x.name = "wildcard"; + p->x.wildcard = syms; + return p; +} +void mkauto(Symbol p) { + assert(p->sclass == AUTO); + offset = roundup(offset + p->type->size, p->type->align); + p->x.offset = -offset; + p->x.name = stringd(-offset); +} +void blockbeg(Env *e) { + e->offset = offset; + e->freemask[IREG] = freemask[IREG]; + e->freemask[FREG] = freemask[FREG]; +} +void blockend(Env *e) { + if (offset > maxoffset) + maxoffset = offset; + offset = e->offset; + freemask[IREG] = e->freemask[IREG]; + freemask[FREG] = e->freemask[FREG]; +} +int mkactual(int align, int size) { + int n = roundup(argoffset, align); + + argoffset = n + size; + return n; +} +static void docall(Node p) { + p->syms[1] = p->syms[0]; + p->syms[0] = intconst(argoffset); + if (argoffset > maxargoffset) + maxargoffset = argoffset; + argoffset = 0; +} +void blkcopy(int dreg, int doff, int sreg, int soff, int size, int tmp[]) { + assert(size >= 0); + if (size == 0) + return; + else if (size <= 2) + blkunroll(size, dreg, doff, sreg, soff, size, tmp); + else if (size == 3) { + blkunroll(2, dreg, doff, sreg, soff, 2, tmp); + blkunroll(1, dreg, doff+2, sreg, soff+2, 1, tmp); + } + else if (size <= 16) { + blkunroll(4, dreg, doff, sreg, soff, size&~3, tmp); + blkcopy(dreg, doff+(size&~3), + sreg, soff+(size&~3), size&3, tmp); + } + else + (*IR->x.blkloop)(dreg, doff, sreg, soff, size, tmp); +} +static void blkunroll(int k, int dreg, int doff, int sreg, int soff, int size, int tmp[]) { + int i; + + assert(IR->x.max_unaligned_load); + if (k > IR->x.max_unaligned_load + && (k > salign || k > dalign)) + k = IR->x.max_unaligned_load; + for (i = 0; i+k < size; i += 2*k) { + (*IR->x.blkfetch)(k, soff+i, sreg, tmp[0]); + (*IR->x.blkfetch)(k, soff+i+k, sreg, tmp[1]); + (*IR->x.blkstore)(k, doff+i, dreg, tmp[0]); + (*IR->x.blkstore)(k, doff+i+k, dreg, tmp[1]); + } + if (i < size) { + (*IR->x.blkfetch)(k, i+soff, sreg, tmp[0]); + (*IR->x.blkstore)(k, i+doff, dreg, tmp[0]); + } +} +void parseflags(int argc, char *argv[]) { + int i; + + for (i = 0; i < argc; i++) + if (strcmp(argv[i], "-d") == 0) + dflag = 1; + else if (strcmp(argv[i], "-b") == 0) /* omit */ + bflag = 1; /* omit */ +} +static int getrule(Node p, int nt) { + int rulenum; + + assert(p); + rulenum = (*IR->x._rule)(p->x.state, nt); + if (!rulenum) { + fprint(stderr, "(%x->op=%s at %w is corrupt.)\n", p, opname(p->op), &src); + assert(0); + } + return rulenum; +} +static void reduce(Node p, int nt) { + int rulenum, i; + short *nts; + Node kids[10]; + + p = reuse(p, nt); + rulenum = getrule(p, nt); + nts = IR->x._nts[rulenum]; + (*IR->x._kids)(p, rulenum, kids); + for (i = 0; nts[i]; i++) + reduce(kids[i], nts[i]); + if (IR->x._isinstruction[rulenum]) { + assert(p->x.inst == 0 || p->x.inst == nt); + p->x.inst = nt; + if (p->syms[RX] && p->syms[RX]->temporary) { + debug(fprint(stderr, "(using %s)\n", p->syms[RX]->name)); + p->syms[RX]->x.usecount++; + } + } +} +static Node reuse(Node p, int nt) { + struct _state { + short cost[1]; + }; + Symbol r = p->syms[RX]; + + if (generic(p->op) == INDIR && p->kids[0]->op == VREG+P + && r->u.t.cse && p->x.mayrecalc + && ((struct _state*)r->u.t.cse->x.state)->cost[nt] == 0) + return r->u.t.cse; + else + return p; +} + +int mayrecalc(Node p) { + int op; + + assert(p && p->syms[RX]); + if (p->syms[RX]->u.t.cse == NULL) + return 0; + op = generic(p->syms[RX]->u.t.cse->op); + if (op == CNST || op == ADDRF || op == ADDRG || op == ADDRL) { + p->x.mayrecalc = 1; + return 1; + } else + return 0; +} +static Node *prune(Node p, Node pp[]) { + if (p == NULL) + return pp; + p->x.kids[0] = p->x.kids[1] = p->x.kids[2] = NULL; + if (p->x.inst == 0) + return prune(p->kids[1], prune(p->kids[0], pp)); + else if (p->syms[RX] && p->syms[RX]->temporary + && p->syms[RX]->x.usecount < 2) { + p->x.inst = 0; + debug(fprint(stderr, "(clobbering %s)\n", p->syms[RX]->name)); + return prune(p->kids[1], prune(p->kids[0], pp)); + } + else { + prune(p->kids[1], prune(p->kids[0], &p->x.kids[0])); + *pp = p; + return pp + 1; + } +} + +#define ck(i) return (i) ? 0 : LBURG_MAX + +int range(Node p, int lo, int hi) { + Symbol s = p->syms[0]; + + switch (specific(p->op)) { + case ADDRF+P: + case ADDRL+P: ck(s->x.offset >= lo && s->x.offset <= hi); + case CNST+I: ck(s->u.c.v.i >= lo && s->u.c.v.i <= hi); + case CNST+U: ck(s->u.c.v.u >= lo && s->u.c.v.u <= hi); + case CNST+P: ck(s->u.c.v.p == 0 && lo <= 0 && hi >= 0); + } + return LBURG_MAX; +} +static void dumptree(Node p) { + if (p->op == VREG+P && p->syms[0]) { + fprint(stderr, "VREGP(%s)", p->syms[0]->name); + return; + } else if (generic(p->op) == LOAD) { + fprint(stderr, "LOAD("); + dumptree(p->kids[0]); + fprint(stderr, ")"); + return; + } + fprint(stderr, "%s(", opname(p->op)); + switch (generic(p->op)) { + case CNST: case LABEL: + case ADDRG: case ADDRF: case ADDRL: + if (p->syms[0]) + fprint(stderr, "%s", p->syms[0]->name); + break; + case RET: + if (p->kids[0]) + dumptree(p->kids[0]); + break; + case CVF: case CVI: case CVP: case CVU: case JUMP: + case ARG: case BCOM: case NEG: case INDIR: + dumptree(p->kids[0]); + break; + case CALL: + if (optype(p->op) != B) { + dumptree(p->kids[0]); + break; + } + /* else fall thru */ + case EQ: case NE: case GT: case GE: case LE: case LT: + case ASGN: case BOR: case BAND: case BXOR: case RSH: case LSH: + case ADD: case SUB: case DIV: case MUL: case MOD: + dumptree(p->kids[0]); + fprint(stderr, ", "); + dumptree(p->kids[1]); + break; + default: assert(0); + } + fprint(stderr, ")"); +} +static void dumpcover(Node p, int nt, int in) { + int rulenum, i; + short *nts; + Node kids[10]; + + p = reuse(p, nt); + rulenum = getrule(p, nt); + nts = IR->x._nts[rulenum]; + fprint(stderr, "dumpcover(%x) = ", p); + for (i = 0; i < in; i++) + fprint(stderr, " "); + dumprule(rulenum); + (*IR->x._kids)(p, rulenum, kids); + for (i = 0; nts[i]; i++) + dumpcover(kids[i], nts[i], in+1); +} + +static void dumprule(int rulenum) { + assert(rulenum); + fprint(stderr, "%s / %s", IR->x._string[rulenum], + IR->x._templates[rulenum]); + if (!IR->x._isinstruction[rulenum]) + fprint(stderr, "\n"); +} +static unsigned emitasm(Node p, int nt) { + int rulenum; + short *nts; + char *fmt; + Node kids[10]; + + p = reuse(p, nt); + rulenum = getrule(p, nt); + nts = IR->x._nts[rulenum]; + fmt = IR->x._templates[rulenum]; + assert(fmt); + if (IR->x._isinstruction[rulenum] && p->x.emitted) + print("%s", p->syms[RX]->x.name); + else if (*fmt == '#') + (*IR->x.emit2)(p); + else { + if (*fmt == '?') { + fmt++; + assert(p->kids[0]); + if (p->syms[RX] == p->x.kids[0]->syms[RX]) + while (*fmt++ != '\n') + ; + } + for ((*IR->x._kids)(p, rulenum, kids); *fmt; fmt++) + if (*fmt != '%') + (void)putchar(*fmt); + else if (*++fmt == 'F') + print("%d", framesize); + else if (*fmt >= '0' && *fmt <= '9') + emitasm(kids[*fmt - '0'], nts[*fmt - '0']); + else if (*fmt >= 'a' && *fmt < 'a' + NELEMS(p->syms)) + fputs(p->syms[*fmt - 'a']->x.name, stdout); + else + (void)putchar(*fmt); + } + return 0; +} +void emit(Node p) { + for (; p; p = p->x.next) { + assert(p->x.registered); + if ((p->x.equatable && requate(p)) || moveself(p)) + ; + else + (*emitter)(p, p->x.inst); + p->x.emitted = 1; + } +} +static int moveself(Node p) { + return p->x.copy + && p->syms[RX]->x.name == p->x.kids[0]->syms[RX]->x.name; +} +int move(Node p) { + p->x.copy = 1; + return 1; +} +static int requate(Node q) { + Symbol src = q->x.kids[0]->syms[RX]; + Symbol tmp = q->syms[RX]; + Node p; + int n = 0; + + debug(fprint(stderr, "(requate(%x): tmp=%s src=%s)\n", q, tmp->x.name, src->x.name)); + for (p = q->x.next; p; p = p->x.next) + if (p->x.copy && p->syms[RX] == src + && p->x.kids[0]->syms[RX] == tmp) + debug(fprint(stderr, "(requate arm 0 at %x)\n", p)), + p->syms[RX] = tmp; + else if (setsrc(p->syms[RX]) && !moveself(p) && !readsreg(p)) + return 0; + else if (p->x.spills) + return 0; + else if (generic(p->op) == CALL && p->x.next) + return 0; + else if (p->op == LABEL+V && p->x.next) + return 0; + else if (p->syms[RX] == tmp && readsreg(p)) + debug(fprint(stderr, "(requate arm 5 at %x)\n", p)), + n++; + else if (p->syms[RX] == tmp) + break; + debug(fprint(stderr, "(requate arm 7 at %x)\n", p)); + assert(n > 0); + for (p = q->x.next; p; p = p->x.next) + if (p->syms[RX] == tmp && readsreg(p)) { + p->syms[RX] = src; + if (--n <= 0) + break; + } + return 1; +} +static void prelabel(Node p) { + if (p == NULL) + return; + prelabel(p->kids[0]); + prelabel(p->kids[1]); + if (NeedsReg[opindex(p->op)]) + setreg(p, (*IR->x.rmap)(opkind(p->op))); + switch (generic(p->op)) { + case ADDRF: case ADDRL: + if (p->syms[0]->sclass == REGISTER) + p->op = VREG+P; + break; + case INDIR: + if (p->kids[0]->op == VREG+P) + setreg(p, p->kids[0]->syms[0]); + break; + case ASGN: + if (p->kids[0]->op == VREG+P) + rtarget(p, 1, p->kids[0]->syms[0]); + break; + case CVI: case CVU: case CVP: + if (optype(p->op) != F + && opsize(p->op) <= p->syms[0]->u.c.v.i) + p->op = LOAD + opkind(p->op); + break; + } + (IR->x.target)(p); +} +void setreg(Node p, Symbol r) { + p->syms[RX] = r; +} +void rtarget(Node p, int n, Symbol r) { + Node q = p->kids[n]; + + assert(q); + assert(r); + assert(r->sclass == REGISTER || !r->x.wildcard); + assert(q->syms[RX]); + if (r != q->syms[RX] && !q->syms[RX]->x.wildcard) { + q = newnode(LOAD + opkind(q->op), + q, NULL, q->syms[0]); + if (r->u.t.cse == p->kids[n]) + r->u.t.cse = q; + p->kids[n] = p->x.kids[n] = q; + q->x.kids[0] = q->kids[0]; + } + setreg(q, r); + debug(fprint(stderr, "(targeting %x->x.kids[%d]=%x to %s)\n", p, n, p->kids[n], r->x.name)); +} +static void rewrite(Node p) { + assert(p->x.inst == 0); + prelabel(p); + debug(dumptree(p)); + debug(fprint(stderr, "\n")); + (*IR->x._label)(p); + debug(dumpcover(p, 1, 0)); + reduce(p, 1); +} +Node gen(Node forest) { + int i; + struct node sentinel; + Node dummy, p; + + head = forest; + for (p = forest; p; p = p->link) { + assert(p->count == 0); + if (generic(p->op) == CALL) + docall(p); + else if ( generic(p->op) == ASGN + && generic(p->kids[1]->op) == CALL) + docall(p->kids[1]); + else if (generic(p->op) == ARG) + (*IR->x.doarg)(p); + rewrite(p); + p->x.listed = 1; + } + for (p = forest; p; p = p->link) + prune(p, &dummy); + relink(&sentinel, &sentinel); + for (p = forest; p; p = p->link) + linearize(p, &sentinel); + forest = sentinel.x.next; + assert(forest); + sentinel.x.next->x.prev = NULL; + sentinel.x.prev->x.next = NULL; + for (p = forest; p; p = p->x.next) + for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) { + assert(p->x.kids[i]->syms[RX]); + if (p->x.kids[i]->syms[RX]->temporary) { + p->x.kids[i]->x.prevuse = + p->x.kids[i]->syms[RX]->x.lastuse; + p->x.kids[i]->syms[RX]->x.lastuse = p->x.kids[i]; + } + } + for (p = forest; p; p = p->x.next) { + ralloc(p); + if (p->x.listed && NeedsReg[opindex(p->op)] + && (*IR->x.rmap)(opkind(p->op))) { + assert(generic(p->op) == CALL || generic(p->op) == LOAD); + putreg(p->syms[RX]); + } + } + return forest; +} +int notarget(Node p) { + return p->syms[RX]->x.wildcard ? 0 : LBURG_MAX; +} +static void putreg(Symbol r) { + assert(r && r->x.regnode); + freemask[r->x.regnode->set] |= r->x.regnode->mask; + debug(dumpregs("(freeing %s)\n", r->x.name, NULL)); +} +static Symbol askfixedreg(Symbol s) { + Regnode r = s->x.regnode; + int n = r->set; + + if (r->mask&~freemask[n]) + return NULL; + else { + freemask[n] &= ~r->mask; + usedmask[n] |= r->mask; + return s; + } +} +static Symbol askreg(Symbol rs, unsigned rmask[]) { + int i; + + if (rs->x.wildcard == NULL) + return askfixedreg(rs); + for (i = 31; i >= 0; i--) { + Symbol r = rs->x.wildcard[i]; + if (r != NULL + && !(r->x.regnode->mask&~rmask[r->x.regnode->set]) + && askfixedreg(r)) + return r; + } + return NULL; +} + +static Symbol getreg(Symbol s, unsigned mask[], Node p) { + Symbol r = askreg(s, mask); + if (r == NULL) { + r = spillee(s, mask, p); + assert(r && r->x.regnode); + spill(r->x.regnode->mask, r->x.regnode->set, p); + r = askreg(s, mask); + } + assert(r && r->x.regnode); + r->x.regnode->vbl = NULL; + return r; +} +int askregvar(Symbol p, Symbol regs) { + Symbol r; + + assert(p); + if (p->sclass != REGISTER) + return 0; + else if (!isscalar(p->type)) { + p->sclass = AUTO; + return 0; + } + else if (p->temporary) { + p->x.name = "?"; + return 1; + } + else if ((r = askreg(regs, vmask)) != NULL) { + p->x.regnode = r->x.regnode; + p->x.regnode->vbl = p; + p->x.name = r->x.name; + debug(dumpregs("(allocating %s to symbol %s)\n", p->x.name, p->name)); + return 1; + } + else { + p->sclass = AUTO; + return 0; + } +} +static void linearize(Node p, Node next) { + int i; + + for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) + linearize(p->x.kids[i], next); + relink(next->x.prev, p); + relink(p, next); + debug(fprint(stderr, "(listing %x)\n", p)); +} +static void ralloc(Node p) { + int i; + unsigned mask[2]; + + mask[0] = tmask[0]; + mask[1] = tmask[1]; + assert(p); + debug(fprint(stderr, "(rallocing %x)\n", p)); + for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) { + Node kid = p->x.kids[i]; + Symbol r = kid->syms[RX]; + assert(r && kid->x.registered); + if (r->sclass != REGISTER && r->x.lastuse == kid) + putreg(r); + } + if (!p->x.registered && NeedsReg[opindex(p->op)] + && (*IR->x.rmap)(opkind(p->op))) { + Symbol sym = p->syms[RX], set = sym; + assert(sym); + if (sym->temporary) + set = (*IR->x.rmap)(opkind(p->op)); + assert(set); + if (set->sclass != REGISTER) { + Symbol r; + if (*IR->x._templates[getrule(p, p->x.inst)] == '?') + for (i = 1; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) { + Symbol r = p->x.kids[i]->syms[RX]; + assert(p->x.kids[i]->x.registered); + assert(r && r->x.regnode); + assert(sym->x.wildcard || sym != r); + mask[r->x.regnode->set] &= ~r->x.regnode->mask; + } + r = getreg(set, mask, p); + if (sym->temporary) { + Node q; + r->x.lastuse = sym->x.lastuse; + for (q = sym->x.lastuse; q; q = q->x.prevuse) { + q->syms[RX] = r; + q->x.registered = 1; + if (sym->u.t.cse && q->x.copy) + q->x.equatable = 1; + } + } else { + p->syms[RX] = r; + r->x.lastuse = p; + } + debug(dumpregs("(allocating %s to node %x)\n", r->x.name, (char *) p)); + } + } + p->x.registered = 1; + (*IR->x.clobber)(p); +} +static Symbol spillee(Symbol set, unsigned mask[], Node here) { + Symbol bestreg = NULL; + int bestdist = -1, i; + + assert(set); + if (!set->x.wildcard) + bestreg = set; + else { + for (i = 31; i >= 0; i--) { + Symbol ri = set->x.wildcard[i]; + if ( + ri != NULL && + ri->x.lastuse && + (ri->x.regnode->mask&tmask[ri->x.regnode->set]&mask[ri->x.regnode->set]) + ) { + Regnode rn = ri->x.regnode; + Node q = here; + int dist = 0; + for (; q && !uses(q, rn); q = q->x.next) + dist++; + if (q && dist > bestdist) { + bestdist = dist; + bestreg = ri; + } + } + } + } + assert(bestreg); /* Must be able to spill something. Reconfigure the register allocator + to ensure that we can allocate a register for all nodes without spilling + the node's necessary input regs. */ + assert(bestreg->x.regnode->vbl == NULL); /* Can't spill register variables because + the reload site might be in other blocks. Reconfigure the register allocator + to ensure that this register is never allocated to a variable. */ + return bestreg; +} +static int uses(Node p, Regnode rn) { + int i; + + for (i = 0; i < NELEMS(p->x.kids); i++) + if ( + p->x.kids[i] && + p->x.kids[i]->x.registered && + rn->set == p->x.kids[i]->syms[RX]->x.regnode->set && + (rn->mask&p->x.kids[i]->syms[RX]->x.regnode->mask) + ) + return 1; + return 0; +} +static void spillr(Symbol r, Node here) { + int i; + Symbol tmp; + Node p = r->x.lastuse; + assert(p); + while (p->x.prevuse) + assert(r == p->syms[RX]), + p = p->x.prevuse; + assert(p->x.registered && !readsreg(p)); + tmp = newtemp(AUTO, optype(p->op), opsize(p->op)); + genspill(r, p, tmp); + for (p = here->x.next; p; p = p->x.next) + for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) { + Node k = p->x.kids[i]; + if (k->x.registered && k->syms[RX] == r) + genreload(p, tmp, i); + } + putreg(r); +} +static void genspill(Symbol r, Node last, Symbol tmp) { + Node p, q; + Symbol s; + unsigned ty; + + debug(fprint(stderr, "(spilling %s to local %s)\n", r->x.name, tmp->x.name)); + debug(fprint(stderr, "(genspill: ")); + debug(dumptree(last)); + debug(fprint(stderr, ")\n")); + ty = opkind(last->op); + NEW0(s, FUNC); + s->sclass = REGISTER; + s->name = s->x.name = r->x.name; + s->x.regnode = r->x.regnode; + q = newnode(ADDRL+P + sizeop(IR->ptrmetric.size), NULL, NULL, s); + q = newnode(INDIR + ty, q, NULL, NULL); + p = newnode(ADDRL+P + sizeop(IR->ptrmetric.size), NULL, NULL, tmp); + p = newnode(ASGN + ty, p, q, NULL); + p->x.spills = 1; + rewrite(p); + prune(p, &q); + q = last->x.next; + linearize(p, q); + for (p = last->x.next; p != q; p = p->x.next) { + ralloc(p); + assert(!p->x.listed || !NeedsReg[opindex(p->op)] || !(*IR->x.rmap)(opkind(p->op))); + } +} + +static void genreload(Node p, Symbol tmp, int i) { + Node q; + int ty; + + debug(fprint(stderr, "(replacing %x with a reload from %s)\n", p->x.kids[i], tmp->x.name)); + debug(fprint(stderr, "(genreload: ")); + debug(dumptree(p->x.kids[i])); + debug(fprint(stderr, ")\n")); + ty = opkind(p->x.kids[i]->op); + q = newnode(ADDRL+P + sizeop(IR->ptrmetric.size), NULL, NULL, tmp); + p->x.kids[i] = newnode(INDIR + ty, q, NULL, NULL); + rewrite(p->x.kids[i]); + prune(p->x.kids[i], &q); + reprune(&p->kids[1], reprune(&p->kids[0], 0, i, p), i, p); + prune(p, &q); + linearize(p->x.kids[i], p); +} +static int reprune(Node *pp, int k, int n, Node p) { + struct node x, *q = *pp; + + if (q == NULL || k > n) + return k; + else if (q->x.inst == 0) + return reprune(&q->kids[1], + reprune(&q->kids[0], k, n, p), n, p); + if (k == n) { + debug(fprint(stderr, "(reprune changes %x from %x to %x)\n", pp, *pp, p->x.kids[n])); + *pp = p->x.kids[n]; + x = *p; + (IR->x.target)(&x); + } + return k + 1; +} +void spill(unsigned mask, int n, Node here) { + int i; + Node p; + + here->x.spills = 1; + usedmask[n] |= mask; + if (mask&~freemask[n]) { + + assert( /* It makes no sense for a node to clobber() its target. */ + here->x.registered == 0 || /* call isn't coming through clobber() */ + here->syms[RX] == NULL || + here->syms[RX]->x.regnode == NULL || + here->syms[RX]->x.regnode->set != n || + (here->syms[RX]->x.regnode->mask&mask) == 0 + ); + + for (p = here; p; p = p->x.next) + for (i = 0; i < NELEMS(p->x.kids) && p->x.kids[i]; i++) { + Symbol r = p->x.kids[i]->syms[RX]; + assert(r); + if (p->x.kids[i]->x.registered && r->x.regnode->set == n + && r->x.regnode->mask&mask) + spillr(r, here); + } + } +} +static void dumpregs(char *msg, char *a, char *b) { + fprint(stderr, msg, a, b); + fprint(stderr, "(free[0]=%x)\n", freemask[0]); + fprint(stderr, "(free[1]=%x)\n", freemask[1]); +} + +int getregnum(Node p) { + assert(p && p->syms[RX] && p->syms[RX]->x.regnode); + return p->syms[RX]->x.regnode->number; +} + + +unsigned regloc(Symbol p) { + assert(p && p->sclass == REGISTER && p->sclass == REGISTER && p->x.regnode); + return p->x.regnode->set<<8 | p->x.regnode->number; +} + diff --git a/src/tools/lcc/src/init.c b/src/tools/lcc/src/init.c new file mode 100644 index 0000000..172d7c0 --- /dev/null +++ b/src/tools/lcc/src/init.c @@ -0,0 +1,318 @@ +#include "c.h" + + +static int curseg; /* current segment */ + +/* defpointer - initialize a pointer to p or to 0 if p==0 */ +void defpointer(Symbol p) { + if (p) { + (*IR->defaddress)(p); + p->ref++; + } else { + static Value v; + (*IR->defconst)(P, voidptype->size, v); + } +} + +/* genconst - generate/check constant expression e; return size */ +static int genconst(Tree e, int def) { + for (;;) + switch (generic(e->op)) { + case ADDRG: + if (def) + (*IR->defaddress)(e->u.sym); + return e->type->size; + case CNST: + if (e->op == CNST+P && isarray(e->type)) { + e = cvtconst(e); + continue; + } + if (def) + (*IR->defconst)(e->type->op, e->type->size, e->u.v); + return e->type->size; + case RIGHT: + assert(e->kids[0] || e->kids[1]); + if (e->kids[1] && e->kids[0]) + error("initializer must be constant\n"); + e = e->kids[1] ? e->kids[1] : e->kids[0]; + continue; + case CVP: + if (isarith(e->type)) + error("cast from `%t' to `%t' is illegal in constant expressions\n", + e->kids[0]->type, e->type); + /* fall thru */ + case CVI: case CVU: case CVF: + e = e->kids[0]; + continue; + default: + error("initializer must be constant\n"); + if (def) + genconst(consttree(0, inttype), def); + return inttype->size; + } +} + +/* initvalue - evaluate a constant expression for a value of integer type ty */ +static Tree initvalue(Type ty) { + Type aty; + Tree e; + + needconst++; + e = expr1(0); + if ((aty = assign(ty, e)) != NULL) + e = cast(e, aty); + else { + error("invalid initialization type; found `%t' expected `%t'\n", + e->type, ty); + e = retype(consttree(0, inttype), ty); + } + needconst--; + if (generic(e->op) != CNST) { + error("initializer must be constant\n"); + e = retype(consttree(0, inttype), ty); + } + return e; +} + +/* initarray - initialize array of ty of <= len bytes; if len == 0, go to } */ +static int initarray(int len, Type ty, int lev) { + int n = 0; + + do { + initializer(ty, lev); + n += ty->size; + if ((len > 0 && n >= len) || t != ',') + break; + t = gettok(); + } while (t != '}'); + return n; +} + +/* initchar - initialize array of <= len ty characters; if len == 0, go to } */ +static int initchar(int len, Type ty) { + int n = 0; + char buf[16], *s = buf; + + do { + *s++ = initvalue(ty)->u.v.i; + if (++n%inttype->size == 0) { + (*IR->defstring)(inttype->size, buf); + s = buf; + } + if ((len > 0 && n >= len) || t != ',') + break; + t = gettok(); + } while (t != '}'); + if (s > buf) + (*IR->defstring)(s - buf, buf); + return n; +} + +/* initend - finish off an initialization at level lev; accepts trailing comma */ +static void initend(int lev, char follow[]) { + if (lev == 0 && t == ',') + t = gettok(); + test('}', follow); +} + +/* initfields - initialize <= an unsigned's worth of bit fields in fields p to q */ +static int initfields(Field p, Field q) { + unsigned int bits = 0; + int i, n = 0; + + do { + i = initvalue(inttype)->u.v.i; + if (fieldsize(p) < 8*p->type->size) { + if ((p->type == inttype && + (i < -(int)(fieldmask(p)>>1)-1 || i > (int)(fieldmask(p)>>1))) + || (p->type == unsignedtype && (i&~fieldmask(p)) != 0)) + warning("initializer exceeds bit-field width\n"); + i &= fieldmask(p); + } + bits |= i<little_endian) { + if (fieldsize(p) + fieldright(p) > n) + n = fieldsize(p) + fieldright(p); + } else { + if (fieldsize(p) + fieldleft(p) > n) + n = fieldsize(p) + fieldleft(p); + } + if (p->link == q) + break; + p = p->link; + } while (t == ',' && (t = gettok()) != 0); + n = (n + 7)/8; + for (i = 0; i < n; i++) { + Value v; + if (IR->little_endian) { + v.u = (unsigned char)bits; + bits >>= 8; + } else { /* a big endian */ + v.u = (unsigned char)(bits>>(8*(unsignedtype->size - 1))); + bits <<= 8; + } + (*IR->defconst)(U, unsignedchar->size, v); + } + return n; +} + +/* initstruct - initialize a struct ty of <= len bytes; if len == 0, go to } */ +static int initstruct(int len, Type ty, int lev) { + int a, n = 0; + Field p = ty->u.sym->u.s.flist; + + do { + if (p->offset > n) { + (*IR->space)(p->offset - n); + n += p->offset - n; + } + if (p->lsb) { + Field q = p; + while (q->link && q->link->offset == p->offset) + q = q->link; + n += initfields(p, q->link); + p = q; + } else { + initializer(p->type, lev); + n += p->type->size; + } + if (p->link) { + p = p->link; + a = p->type->align; + } else + a = ty->align; + if (a && n%a) { + (*IR->space)(a - n%a); + n = roundup(n, a); + } + if ((len > 0 && n >= len) || t != ',') + break; + t = gettok(); + } while (t != '}'); + return n; +} + +/* initializer - constexpr | { constexpr ( , constexpr )* [ , ] } */ +Type initializer(Type ty, int lev) { + int n = 0; + Tree e; + Type aty = NULL; + static char follow[] = { IF, CHAR, STATIC, 0 }; + + ty = unqual(ty); + if (isscalar(ty)) { + needconst++; + if (t == '{') { + t = gettok(); + e = expr1(0); + initend(lev, follow); + } else + e = expr1(0); + e = pointer(e); + if ((aty = assign(ty, e)) != NULL) + e = cast(e, aty); + else + error("invalid initialization type; found `%t' expected `%t'\n", + e->type, ty); + n = genconst(e, 1); + deallocate(STMT); + needconst--; + } + if ((isunion(ty) || isstruct(ty)) && ty->size == 0) { + static char follow[] = { CHAR, STATIC, 0 }; + error("cannot initialize undefined `%t'\n", ty); + skipto(';', follow); + return ty; + } else if (isunion(ty)) { + if (t == '{') { + t = gettok(); + n = initstruct(ty->u.sym->u.s.flist->type->size, ty, lev + 1); + initend(lev, follow); + } else { + if (lev == 0) + error("missing { in initialization of `%t'\n", ty); + n = initstruct(ty->u.sym->u.s.flist->type->size, ty, lev + 1); + } + } else if (isstruct(ty)) { + if (t == '{') { + t = gettok(); + n = initstruct(0, ty, lev + 1); + test('}', follow); + } else if (lev > 0) + n = initstruct(ty->size, ty, lev + 1); + else { + error("missing { in initialization of `%t'\n", ty); + n = initstruct(ty->u.sym->u.s.flist->type->size, ty, lev + 1); + } + } + if (isarray(ty)) + aty = unqual(ty->type); + if (isarray(ty) && ischar(aty)) { + if (t == SCON) { + if (ty->size > 0 && ty->size == tsym->type->size - 1) + tsym->type = array(chartype, ty->size, 0); + n = tsym->type->size; + (*IR->defstring)(tsym->type->size, tsym->u.c.v.p); + t = gettok(); + } else if (t == '{') { + t = gettok(); + if (t == SCON) { + ty = initializer(ty, lev + 1); + initend(lev, follow); + return ty; + } + n = initchar(0, aty); + test('}', follow); + } else if (lev > 0 && ty->size > 0) + n = initchar(ty->size, aty); + else { /* eg, char c[] = 0; */ + error("missing { in initialization of `%t'\n", ty); + n = initchar(1, aty); + } + } else if (isarray(ty)) { + if (t == SCON && aty == widechar) { + int i; + unsigned int *s = tsym->u.c.v.p; + if (ty->size > 0 && ty->size == tsym->type->size - widechar->size) + tsym->type = array(widechar, ty->size/widechar->size, 0); + n = tsym->type->size; + for (i = 0; i < n; i += widechar->size) { + Value v; + v.u = *s++; + (*IR->defconst)(widechar->op, widechar->size, v); + } + t = gettok(); + } else if (t == '{') { + t = gettok(); + if (t == SCON && aty == widechar) { + ty = initializer(ty, lev + 1); + initend(lev, follow); + return ty; + } + n = initarray(0, aty, lev + 1); + test('}', follow); + } else if (lev > 0 && ty->size > 0) + n = initarray(ty->size, aty, lev + 1); + else { + error("missing { in initialization of `%t'\n", ty); + n = initarray(aty->size, aty, lev + 1); + } + } + if (ty->size) { + if (n > ty->size) + error("too many initializers\n"); + else if (n < ty->size) + (*IR->space)(ty->size - n); + } else if (isarray(ty) && ty->type->size > 0) + ty = array(ty->type, n/ty->type->size, 0); + else + ty->size = n; + return ty; +} + +/* swtoseg - switch to segment seg, if necessary */ +void swtoseg(int seg) { + if (curseg != seg) + (*IR->segment)(seg); + curseg = seg; +} diff --git a/src/tools/lcc/src/inits.c b/src/tools/lcc/src/inits.c new file mode 100644 index 0000000..c42f61e --- /dev/null +++ b/src/tools/lcc/src/inits.c @@ -0,0 +1,7 @@ +void init(int argc, char *argv[]) { + {extern void input_init(int, char *[]); input_init(argc, argv);} + {extern void main_init(int, char *[]); main_init(argc, argv);} + {extern void prof_init(int, char *[]); prof_init(argc, argv);} + {extern void trace_init(int, char *[]); trace_init(argc, argv);} + {extern void type_init(int, char *[]); type_init(argc, argv);} +} diff --git a/src/tools/lcc/src/input.c b/src/tools/lcc/src/input.c new file mode 100644 index 0000000..c2a084e --- /dev/null +++ b/src/tools/lcc/src/input.c @@ -0,0 +1,135 @@ +#include "c.h" + + +static void pragma(void); +static void resynch(void); + +static int bsize; +static unsigned char buffer[MAXLINE+1 + BUFSIZE+1]; +unsigned char *cp; /* current input character */ +char *file; /* current input file name */ +char *firstfile; /* first input file */ +unsigned char *limit; /* points to last character + 1 */ +char *line; /* current line */ +int lineno; /* line number of current line */ + +void nextline(void) { + do { + if (cp >= limit) { + fillbuf(); + if (cp >= limit) + cp = limit; + if (cp == limit) + return; + } else { + lineno++; + for (line = (char *)cp; *cp==' ' || *cp=='\t'; cp++) + ; + if (*cp == '#') { + resynch(); + nextline(); + } + } + } while (*cp == '\n' && cp == limit); +} +void fillbuf(void) { + if (bsize == 0) + return; + if (cp >= limit) + cp = &buffer[MAXLINE+1]; + else + { + int n = limit - cp; + unsigned char *s = &buffer[MAXLINE+1] - n; + assert(s >= buffer); + line = (char *)s - ((char *)cp - line); + while (cp < limit) + *s++ = *cp++; + cp = &buffer[MAXLINE+1] - n; + } + if (feof(stdin)) + bsize = 0; + else + bsize = fread(&buffer[MAXLINE+1], 1, BUFSIZE, stdin); + if (bsize < 0) { + error("read error\n"); + exit(EXIT_FAILURE); + } + limit = &buffer[MAXLINE+1+bsize]; + *limit = '\n'; +} +void input_init(int argc, char *argv[]) { + static int inited; + + if (inited) + return; + inited = 1; + main_init(argc, argv); + limit = cp = &buffer[MAXLINE+1]; + bsize = -1; + lineno = 0; + file = NULL; + fillbuf(); + if (cp >= limit) + cp = limit; + nextline(); +} + +/* pragma - handle #pragma ref id... */ +static void pragma(void) { + if ((t = gettok()) == ID && strcmp(token, "ref") == 0) + for (;;) { + while (*cp == ' ' || *cp == '\t') + cp++; + if (*cp == '\n' || *cp == 0) + break; + if ((t = gettok()) == ID && tsym) { + tsym->ref++; + use(tsym, src); + } + } +} + +/* resynch - set line number/file name in # n [ "file" ] and #pragma ... */ +static void resynch(void) { + for (cp++; *cp == ' ' || *cp == '\t'; ) + cp++; + if (limit - cp < MAXLINE) + fillbuf(); + if (strncmp((char *)cp, "pragma", 6) == 0) { + cp += 6; + pragma(); + } else if (*cp >= '0' && *cp <= '9') { + line: for (lineno = 0; *cp >= '0' && *cp <= '9'; ) + lineno = 10*lineno + *cp++ - '0'; + lineno--; + while (*cp == ' ' || *cp == '\t') + cp++; + if (*cp == '"') { + file = (char *)++cp; + while (*cp && *cp != '"' && *cp != '\n') + cp++; + file = stringn(file, (char *)cp - file); + if (*cp == '\n') + warning("missing \" in preprocessor line\n"); + if (firstfile == 0) + firstfile = file; + } + } else if (strncmp((char *)cp, "line", 4) == 0) { + for (cp += 4; *cp == ' ' || *cp == '\t'; ) + cp++; + if (*cp >= '0' && *cp <= '9') + goto line; + if (Aflag >= 2) + warning("unrecognized control line\n"); + } else if (Aflag >= 2 && *cp != '\n') + warning("unrecognized control line\n"); + while (*cp) + if (*cp++ == '\n') { + if (cp == limit + 1) + nextline(); + else + break; + } +} + diff --git a/src/tools/lcc/src/lex.c b/src/tools/lcc/src/lex.c new file mode 100644 index 0000000..ec2f1ec --- /dev/null +++ b/src/tools/lcc/src/lex.c @@ -0,0 +1,923 @@ +#include "c.h" +#include +#include + + +#define MAXTOKEN 32 + +enum { BLANK=01, NEWLINE=02, LETTER=04, + DIGIT=010, HEX=020, OTHER=040 }; + +static unsigned char map[256] = { /* 000 nul */ 0, + /* 001 soh */ 0, + /* 002 stx */ 0, + /* 003 etx */ 0, + /* 004 eot */ 0, + /* 005 enq */ 0, + /* 006 ack */ 0, + /* 007 bel */ 0, + /* 010 bs */ 0, + /* 011 ht */ BLANK, + /* 012 nl */ NEWLINE, + /* 013 vt */ BLANK, + /* 014 ff */ BLANK, + /* 015 cr */ 0, + /* 016 so */ 0, + /* 017 si */ 0, + /* 020 dle */ 0, + /* 021 dc1 */ 0, + /* 022 dc2 */ 0, + /* 023 dc3 */ 0, + /* 024 dc4 */ 0, + /* 025 nak */ 0, + /* 026 syn */ 0, + /* 027 etb */ 0, + /* 030 can */ 0, + /* 031 em */ 0, + /* 032 sub */ 0, + /* 033 esc */ 0, + /* 034 fs */ 0, + /* 035 gs */ 0, + /* 036 rs */ 0, + /* 037 us */ 0, + /* 040 sp */ BLANK, + /* 041 ! */ OTHER, + /* 042 " */ OTHER, + /* 043 # */ OTHER, + /* 044 $ */ 0, + /* 045 % */ OTHER, + /* 046 & */ OTHER, + /* 047 ' */ OTHER, + /* 050 ( */ OTHER, + /* 051 ) */ OTHER, + /* 052 * */ OTHER, + /* 053 + */ OTHER, + /* 054 , */ OTHER, + /* 055 - */ OTHER, + /* 056 . */ OTHER, + /* 057 / */ OTHER, + /* 060 0 */ DIGIT, + /* 061 1 */ DIGIT, + /* 062 2 */ DIGIT, + /* 063 3 */ DIGIT, + /* 064 4 */ DIGIT, + /* 065 5 */ DIGIT, + /* 066 6 */ DIGIT, + /* 067 7 */ DIGIT, + /* 070 8 */ DIGIT, + /* 071 9 */ DIGIT, + /* 072 : */ OTHER, + /* 073 ; */ OTHER, + /* 074 < */ OTHER, + /* 075 = */ OTHER, + /* 076 > */ OTHER, + /* 077 ? */ OTHER, + /* 100 @ */ 0, + /* 101 A */ LETTER|HEX, + /* 102 B */ LETTER|HEX, + /* 103 C */ LETTER|HEX, + /* 104 D */ LETTER|HEX, + /* 105 E */ LETTER|HEX, + /* 106 F */ LETTER|HEX, + /* 107 G */ LETTER, + /* 110 H */ LETTER, + /* 111 I */ LETTER, + /* 112 J */ LETTER, + /* 113 K */ LETTER, + /* 114 L */ LETTER, + /* 115 M */ LETTER, + /* 116 N */ LETTER, + /* 117 O */ LETTER, + /* 120 P */ LETTER, + /* 121 Q */ LETTER, + /* 122 R */ LETTER, + /* 123 S */ LETTER, + /* 124 T */ LETTER, + /* 125 U */ LETTER, + /* 126 V */ LETTER, + /* 127 W */ LETTER, + /* 130 X */ LETTER, + /* 131 Y */ LETTER, + /* 132 Z */ LETTER, + /* 133 [ */ OTHER, + /* 134 \ */ OTHER, + /* 135 ] */ OTHER, + /* 136 ^ */ OTHER, + /* 137 _ */ LETTER, + /* 140 ` */ 0, + /* 141 a */ LETTER|HEX, + /* 142 b */ LETTER|HEX, + /* 143 c */ LETTER|HEX, + /* 144 d */ LETTER|HEX, + /* 145 e */ LETTER|HEX, + /* 146 f */ LETTER|HEX, + /* 147 g */ LETTER, + /* 150 h */ LETTER, + /* 151 i */ LETTER, + /* 152 j */ LETTER, + /* 153 k */ LETTER, + /* 154 l */ LETTER, + /* 155 m */ LETTER, + /* 156 n */ LETTER, + /* 157 o */ LETTER, + /* 160 p */ LETTER, + /* 161 q */ LETTER, + /* 162 r */ LETTER, + /* 163 s */ LETTER, + /* 164 t */ LETTER, + /* 165 u */ LETTER, + /* 166 v */ LETTER, + /* 167 w */ LETTER, + /* 170 x */ LETTER, + /* 171 y */ LETTER, + /* 172 z */ LETTER, + /* 173 { */ OTHER, + /* 174 | */ OTHER, + /* 175 } */ OTHER, + /* 176 ~ */ OTHER, }; +static struct symbol tval; +static char cbuf[BUFSIZE+1]; +static unsigned int wcbuf[BUFSIZE+1]; + +Coordinate src; /* current source coordinate */ +int t; +char *token; /* current token */ +Symbol tsym; /* symbol table entry for current token */ + +static void *cput(int c, void *cl); +static void *wcput(int c, void *cl); +static void *scon(int q, void *put(int c, void *cl), void *cl); +static int backslash(int q); +static Symbol fcon(void); +static Symbol icon(unsigned long, int, int); +static void ppnumber(char *); + +int gettok(void) { + for (;;) { + register unsigned char *rcp = cp; + while (map[*rcp]&BLANK) + rcp++; + if (limit - rcp < MAXTOKEN) { + cp = rcp; + fillbuf(); + rcp = cp; + } + src.file = file; + src.x = (char *)rcp - line; + src.y = lineno; + cp = rcp + 1; + switch (*rcp++) { + case '/': if (*rcp == '*') { + int c = 0; + for (rcp++; *rcp != '/' || c != '*'; ) + if (map[*rcp]&NEWLINE) { + if (rcp < limit) + c = *rcp; + cp = rcp + 1; + nextline(); + rcp = cp; + if (rcp == limit) + break; + } else + c = *rcp++; + if (rcp < limit) + rcp++; + else + error("unclosed comment\n"); + cp = rcp; + continue; + } + return '/'; + case '<': + if (*rcp == '=') return cp++, LEQ; + if (*rcp == '<') return cp++, LSHIFT; + return '<'; + case '>': + if (*rcp == '=') return cp++, GEQ; + if (*rcp == '>') return cp++, RSHIFT; + return '>'; + case '-': + if (*rcp == '>') return cp++, DEREF; + if (*rcp == '-') return cp++, DECR; + return '-'; + case '=': return *rcp == '=' ? cp++, EQL : '='; + case '!': return *rcp == '=' ? cp++, NEQ : '!'; + case '|': return *rcp == '|' ? cp++, OROR : '|'; + case '&': return *rcp == '&' ? cp++, ANDAND : '&'; + case '+': return *rcp == '+' ? cp++, INCR : '+'; + case ';': case ',': case ':': + case '*': case '~': case '%': case '^': case '?': + case '[': case ']': case '{': case '}': case '(': case ')': + return rcp[-1]; + case '\n': case '\v': case '\r': case '\f': + nextline(); + if (cp == limit) { + tsym = NULL; + return EOI; + } + continue; + + case 'i': + if (rcp[0] == 'f' + && !(map[rcp[1]]&(DIGIT|LETTER))) { + cp = rcp + 1; + return IF; + } + if (rcp[0] == 'n' + && rcp[1] == 't' + && !(map[rcp[2]]&(DIGIT|LETTER))) { + cp = rcp + 2; + tsym = inttype->u.sym; + return INT; + } + goto id; + case 'h': case 'j': case 'k': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + id: + if (limit - rcp < MAXLINE) { + cp = rcp - 1; + fillbuf(); + rcp = ++cp; + } + assert(cp == rcp); + token = (char *)rcp - 1; + while (map[*rcp]&(DIGIT|LETTER)) + rcp++; + token = stringn(token, (char *)rcp - token); + tsym = lookup(token, identifiers); + cp = rcp; + return ID; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + unsigned long n = 0; + if (limit - rcp < MAXLINE) { + cp = rcp - 1; + fillbuf(); + rcp = ++cp; + } + assert(cp == rcp); + token = (char *)rcp - 1; + if (*token == '0' && (*rcp == 'x' || *rcp == 'X')) { + int d, overflow = 0; + while (*++rcp) { + if (map[*rcp]&DIGIT) + d = *rcp - '0'; + else if (*rcp >= 'a' && *rcp <= 'f') + d = *rcp - 'a' + 10; + else if (*rcp >= 'A' && *rcp <= 'F') + d = *rcp - 'A' + 10; + else + break; + if (n&~(~0UL >> 4)) + overflow = 1; + else + n = (n<<4) + d; + } + if ((char *)rcp - token <= 2) + error("invalid hexadecimal constant `%S'\n", token, (char *)rcp-token); + cp = rcp; + tsym = icon(n, overflow, 16); + } else if (*token == '0') { + int err = 0, overflow = 0; + for ( ; map[*rcp]&DIGIT; rcp++) { + if (*rcp == '8' || *rcp == '9') + err = 1; + if (n&~(~0UL >> 3)) + overflow = 1; + else + n = (n<<3) + (*rcp - '0'); + } + if (*rcp == '.' || *rcp == 'e' || *rcp == 'E') { + cp = rcp; + tsym = fcon(); + return FCON; + } + cp = rcp; + tsym = icon(n, overflow, 8); + if (err) + error("invalid octal constant `%S'\n", token, (char*)cp-token); + } else { + int overflow = 0; + for (n = *token - '0'; map[*rcp]&DIGIT; ) { + int d = *rcp++ - '0'; + if (n > (ULONG_MAX - d)/10) + overflow = 1; + else + n = 10*n + d; + } + if (*rcp == '.' || *rcp == 'e' || *rcp == 'E') { + cp = rcp; + tsym = fcon(); + return FCON; + } + cp = rcp; + tsym = icon(n, overflow, 10); + } + return ICON; + } + case '.': + if (rcp[0] == '.' && rcp[1] == '.') { + cp += 2; + return ELLIPSIS; + } + if ((map[*rcp]&DIGIT) == 0) + return '.'; + if (limit - rcp < MAXLINE) { + cp = rcp - 1; + fillbuf(); + rcp = ++cp; + } + assert(cp == rcp); + cp = rcp - 1; + token = (char *)cp; + tsym = fcon(); + return FCON; + case 'L': + if (*rcp == '\'') { + unsigned int *s = scon(*cp, wcput, wcbuf); + if (s - wcbuf > 2) + warning("excess characters in wide-character literal ignored\n"); + tval.type = widechar; + tval.u.c.v.u = wcbuf[0]; + tsym = &tval; + return ICON; + } else if (*rcp == '"') { + unsigned int *s = scon(*cp, wcput, wcbuf); + tval.type = array(widechar, s - wcbuf, 0); + tval.u.c.v.p = wcbuf; + tsym = &tval; + return SCON; + } else + goto id; + case '\'': { + char *s = scon(*--cp, cput, cbuf); + if (s - cbuf > 2) + warning("excess characters in multibyte character literal ignored\n"); + tval.type = inttype; + if (chartype->op == INT) + tval.u.c.v.i = extend(cbuf[0], chartype); + else + tval.u.c.v.i = cbuf[0]&0xFF; + tsym = &tval; + return ICON; + } + case '"': { + char *s = scon(*--cp, cput, cbuf); + tval.type = array(chartype, s - cbuf, 0); + tval.u.c.v.p = cbuf; + tsym = &tval; + return SCON; + } + case 'a': + if (rcp[0] == 'u' + && rcp[1] == 't' + && rcp[2] == 'o' + && !(map[rcp[3]]&(DIGIT|LETTER))) { + cp = rcp + 3; + return AUTO; + } + goto id; + case 'b': + if (rcp[0] == 'r' + && rcp[1] == 'e' + && rcp[2] == 'a' + && rcp[3] == 'k' + && !(map[rcp[4]]&(DIGIT|LETTER))) { + cp = rcp + 4; + return BREAK; + } + goto id; + case 'c': + if (rcp[0] == 'a' + && rcp[1] == 's' + && rcp[2] == 'e' + && !(map[rcp[3]]&(DIGIT|LETTER))) { + cp = rcp + 3; + return CASE; + } + if (rcp[0] == 'h' + && rcp[1] == 'a' + && rcp[2] == 'r' + && !(map[rcp[3]]&(DIGIT|LETTER))) { + cp = rcp + 3; + tsym = chartype->u.sym; + return CHAR; + } + if (rcp[0] == 'o' + && rcp[1] == 'n' + && rcp[2] == 's' + && rcp[3] == 't' + && !(map[rcp[4]]&(DIGIT|LETTER))) { + cp = rcp + 4; + return CONST; + } + if (rcp[0] == 'o' + && rcp[1] == 'n' + && rcp[2] == 't' + && rcp[3] == 'i' + && rcp[4] == 'n' + && rcp[5] == 'u' + && rcp[6] == 'e' + && !(map[rcp[7]]&(DIGIT|LETTER))) { + cp = rcp + 7; + return CONTINUE; + } + goto id; + case 'd': + if (rcp[0] == 'e' + && rcp[1] == 'f' + && rcp[2] == 'a' + && rcp[3] == 'u' + && rcp[4] == 'l' + && rcp[5] == 't' + && !(map[rcp[6]]&(DIGIT|LETTER))) { + cp = rcp + 6; + return DEFAULT; + } + if (rcp[0] == 'o' + && rcp[1] == 'u' + && rcp[2] == 'b' + && rcp[3] == 'l' + && rcp[4] == 'e' + && !(map[rcp[5]]&(DIGIT|LETTER))) { + cp = rcp + 5; + tsym = doubletype->u.sym; + return DOUBLE; + } + if (rcp[0] == 'o' + && !(map[rcp[1]]&(DIGIT|LETTER))) { + cp = rcp + 1; + return DO; + } + goto id; + case 'e': + if (rcp[0] == 'l' + && rcp[1] == 's' + && rcp[2] == 'e' + && !(map[rcp[3]]&(DIGIT|LETTER))) { + cp = rcp + 3; + return ELSE; + } + if (rcp[0] == 'n' + && rcp[1] == 'u' + && rcp[2] == 'm' + && !(map[rcp[3]]&(DIGIT|LETTER))) { + cp = rcp + 3; + return ENUM; + } + if (rcp[0] == 'x' + && rcp[1] == 't' + && rcp[2] == 'e' + && rcp[3] == 'r' + && rcp[4] == 'n' + && !(map[rcp[5]]&(DIGIT|LETTER))) { + cp = rcp + 5; + return EXTERN; + } + goto id; + case 'f': + if (rcp[0] == 'l' + && rcp[1] == 'o' + && rcp[2] == 'a' + && rcp[3] == 't' + && !(map[rcp[4]]&(DIGIT|LETTER))) { + cp = rcp + 4; + tsym = floattype->u.sym; + return FLOAT; + } + if (rcp[0] == 'o' + && rcp[1] == 'r' + && !(map[rcp[2]]&(DIGIT|LETTER))) { + cp = rcp + 2; + return FOR; + } + goto id; + case 'g': + if (rcp[0] == 'o' + && rcp[1] == 't' + && rcp[2] == 'o' + && !(map[rcp[3]]&(DIGIT|LETTER))) { + cp = rcp + 3; + return GOTO; + } + goto id; + case 'l': + if (rcp[0] == 'o' + && rcp[1] == 'n' + && rcp[2] == 'g' + && !(map[rcp[3]]&(DIGIT|LETTER))) { + cp = rcp + 3; + return LONG; + } + goto id; + case 'r': + if (rcp[0] == 'e' + && rcp[1] == 'g' + && rcp[2] == 'i' + && rcp[3] == 's' + && rcp[4] == 't' + && rcp[5] == 'e' + && rcp[6] == 'r' + && !(map[rcp[7]]&(DIGIT|LETTER))) { + cp = rcp + 7; + return REGISTER; + } + if (rcp[0] == 'e' + && rcp[1] == 't' + && rcp[2] == 'u' + && rcp[3] == 'r' + && rcp[4] == 'n' + && !(map[rcp[5]]&(DIGIT|LETTER))) { + cp = rcp + 5; + return RETURN; + } + goto id; + case 's': + if (rcp[0] == 'h' + && rcp[1] == 'o' + && rcp[2] == 'r' + && rcp[3] == 't' + && !(map[rcp[4]]&(DIGIT|LETTER))) { + cp = rcp + 4; + return SHORT; + } + if (rcp[0] == 'i' + && rcp[1] == 'g' + && rcp[2] == 'n' + && rcp[3] == 'e' + && rcp[4] == 'd' + && !(map[rcp[5]]&(DIGIT|LETTER))) { + cp = rcp + 5; + return SIGNED; + } + if (rcp[0] == 'i' + && rcp[1] == 'z' + && rcp[2] == 'e' + && rcp[3] == 'o' + && rcp[4] == 'f' + && !(map[rcp[5]]&(DIGIT|LETTER))) { + cp = rcp + 5; + return SIZEOF; + } + if (rcp[0] == 't' + && rcp[1] == 'a' + && rcp[2] == 't' + && rcp[3] == 'i' + && rcp[4] == 'c' + && !(map[rcp[5]]&(DIGIT|LETTER))) { + cp = rcp + 5; + return STATIC; + } + if (rcp[0] == 't' + && rcp[1] == 'r' + && rcp[2] == 'u' + && rcp[3] == 'c' + && rcp[4] == 't' + && !(map[rcp[5]]&(DIGIT|LETTER))) { + cp = rcp + 5; + return STRUCT; + } + if (rcp[0] == 'w' + && rcp[1] == 'i' + && rcp[2] == 't' + && rcp[3] == 'c' + && rcp[4] == 'h' + && !(map[rcp[5]]&(DIGIT|LETTER))) { + cp = rcp + 5; + return SWITCH; + } + goto id; + case 't': + if (rcp[0] == 'y' + && rcp[1] == 'p' + && rcp[2] == 'e' + && rcp[3] == 'd' + && rcp[4] == 'e' + && rcp[5] == 'f' + && !(map[rcp[6]]&(DIGIT|LETTER))) { + cp = rcp + 6; + return TYPEDEF; + } + goto id; + case 'u': + if (rcp[0] == 'n' + && rcp[1] == 'i' + && rcp[2] == 'o' + && rcp[3] == 'n' + && !(map[rcp[4]]&(DIGIT|LETTER))) { + cp = rcp + 4; + return UNION; + } + if (rcp[0] == 'n' + && rcp[1] == 's' + && rcp[2] == 'i' + && rcp[3] == 'g' + && rcp[4] == 'n' + && rcp[5] == 'e' + && rcp[6] == 'd' + && !(map[rcp[7]]&(DIGIT|LETTER))) { + cp = rcp + 7; + return UNSIGNED; + } + goto id; + case 'v': + if (rcp[0] == 'o' + && rcp[1] == 'i' + && rcp[2] == 'd' + && !(map[rcp[3]]&(DIGIT|LETTER))) { + cp = rcp + 3; + tsym = voidtype->u.sym; + return VOID; + } + if (rcp[0] == 'o' + && rcp[1] == 'l' + && rcp[2] == 'a' + && rcp[3] == 't' + && rcp[4] == 'i' + && rcp[5] == 'l' + && rcp[6] == 'e' + && !(map[rcp[7]]&(DIGIT|LETTER))) { + cp = rcp + 7; + return VOLATILE; + } + goto id; + case 'w': + if (rcp[0] == 'h' + && rcp[1] == 'i' + && rcp[2] == 'l' + && rcp[3] == 'e' + && !(map[rcp[4]]&(DIGIT|LETTER))) { + cp = rcp + 4; + return WHILE; + } + goto id; + case '_': + if (rcp[0] == '_' + && rcp[1] == 't' + && rcp[2] == 'y' + && rcp[3] == 'p' + && rcp[4] == 'e' + && rcp[5] == 'c' + && rcp[6] == 'o' + && rcp[7] == 'd' + && rcp[8] == 'e' + && !(map[rcp[9]]&(DIGIT|LETTER))) { + cp = rcp + 9; + return TYPECODE; + } + if (rcp[0] == '_' + && rcp[1] == 'f' + && rcp[2] == 'i' + && rcp[3] == 'r' + && rcp[4] == 's' + && rcp[5] == 't' + && rcp[6] == 'a' + && rcp[7] == 'r' + && rcp[8] == 'g' + && !(map[rcp[9]]&(DIGIT|LETTER))) { + cp = rcp + 9; + return FIRSTARG; + } + goto id; + default: + if ((map[cp[-1]]&BLANK) == 0) { + if (cp[-1] < ' ' || cp[-1] >= 0177) + error("illegal character `\\0%o'\n", cp[-1]); + else + error("illegal character `%c'\n", cp[-1]); + } + } + } +} +static Symbol icon(unsigned long n, int overflow, int base) { + if (((*cp=='u'||*cp=='U') && (cp[1]=='l'||cp[1]=='L')) + || ((*cp=='l'||*cp=='L') && (cp[1]=='u'||cp[1]=='U'))) { + tval.type = unsignedlong; + cp += 2; + } else if (*cp == 'u' || *cp == 'U') { + if (overflow || n > unsignedtype->u.sym->u.limits.max.i) + tval.type = unsignedlong; + else + tval.type = unsignedtype; + cp += 1; + } else if (*cp == 'l' || *cp == 'L') { + if (overflow || n > longtype->u.sym->u.limits.max.i) + tval.type = unsignedlong; + else + tval.type = longtype; + cp += 1; + } else if (overflow || n > longtype->u.sym->u.limits.max.i) + tval.type = unsignedlong; + else if (n > inttype->u.sym->u.limits.max.i) + tval.type = longtype; + else if (base != 10 && n > inttype->u.sym->u.limits.max.i) + tval.type = unsignedtype; + else + tval.type = inttype; + switch (tval.type->op) { + case INT: + if (overflow || n > tval.type->u.sym->u.limits.max.i) { + warning("overflow in constant `%S'\n", token, + (char*)cp - token); + tval.u.c.v.i = tval.type->u.sym->u.limits.max.i; + } else + tval.u.c.v.i = n; + break; + case UNSIGNED: + if (overflow || n > tval.type->u.sym->u.limits.max.u) { + warning("overflow in constant `%S'\n", token, + (char*)cp - token); + tval.u.c.v.u = tval.type->u.sym->u.limits.max.u; + } else + tval.u.c.v.u = n; + break; + default: assert(0); + } + ppnumber("integer"); + return &tval; +} +static void ppnumber(char *which) { + unsigned char *rcp = cp--; + + for ( ; (map[*cp]&(DIGIT|LETTER)) || *cp == '.'; cp++) + if ((cp[0] == 'E' || cp[0] == 'e') + && (cp[1] == '-' || cp[1] == '+')) + cp++; + if (cp > rcp) + error("`%S' is a preprocessing number but an invalid %s constant\n", token, + + (char*)cp-token, which); +} +static Symbol fcon(void) { + if (*cp == '.') + do + cp++; + while (map[*cp]&DIGIT); + if (*cp == 'e' || *cp == 'E') { + if (*++cp == '-' || *cp == '+') + cp++; + if (map[*cp]&DIGIT) + do + cp++; + while (map[*cp]&DIGIT); + else + error("invalid floating constant `%S'\n", token, + (char*)cp - token); + } + + errno = 0; + tval.u.c.v.d = strtod(token, NULL); + if (errno == ERANGE) + warning("overflow in floating constant `%S'\n", token, + (char*)cp - token); + if (*cp == 'f' || *cp == 'F') { + ++cp; + if (tval.u.c.v.d > floattype->u.sym->u.limits.max.d) + warning("overflow in floating constant `%S'\n", token, + (char*)cp - token); + tval.type = floattype; + } else if (*cp == 'l' || *cp == 'L') { + cp++; + tval.type = longdouble; + } else { + if (tval.u.c.v.d > doubletype->u.sym->u.limits.max.d) + warning("overflow in floating constant `%S'\n", token, + (char*)cp - token); + tval.type = doubletype; + } + ppnumber("floating"); + return &tval; +} + +static void *cput(int c, void *cl) { + char *s = cl; + + if (c < 0 || c > 255) + warning("overflow in escape sequence with resulting value `%d'\n", c); + *s++ = c; + return s; +} + +static void *wcput(int c, void *cl) { + unsigned int *s = cl; + + *s++ = c; + return s; +} + +static void *scon(int q, void *put(int c, void *cl), void *cl) { + int n = 0, nbad = 0; + + do { + cp++; + while (*cp != q) { + int c; + if (map[*cp]&NEWLINE) { + if (cp < limit) + break; + cp++; + nextline(); + if (cp == limit) + break; + continue; + } + c = *cp++; + if (c == '\\') { + if (map[*cp]&NEWLINE) { + if (cp < limit) + break; + cp++; + nextline(); + } + if (limit - cp < MAXTOKEN) + fillbuf(); + c = backslash(q); + } else if (c < 0 || c > 255 || map[c] == 0) + nbad++; + if (n++ < BUFSIZE) + cl = put(c, cl); + } + if (*cp == q) + cp++; + else + error("missing %c\n", q); + } while (q == '"' && getchr() == '"'); + cl = put(0, cl); + if (n >= BUFSIZE) + error("%s literal too long\n", q == '"' ? "string" : "character"); + if (Aflag >= 2 && q == '"' && n > 509) + warning("more than 509 characters in a string literal\n"); + if (Aflag >= 2 && nbad > 0) + warning("%s literal contains non-portable characters\n", + q == '"' ? "string" : "character"); + return cl; +} +int getchr(void) { + for (;;) { + while (map[*cp]&BLANK) + cp++; + if (!(map[*cp]&NEWLINE)) + return *cp; + cp++; + nextline(); + if (cp == limit) + return EOI; + } +} +static int backslash(int q) { + unsigned int c; + + switch (*cp++) { + case 'a': return 7; + case 'b': return '\b'; + case 'f': return '\f'; + case 'n': return '\n'; + case 'r': return '\r'; + case 't': return '\t'; + case 'v': return '\v'; + case '\'': case '"': case '\\': case '\?': break; + case 'x': { + int overflow = 0; + if ((map[*cp]&(DIGIT|HEX)) == 0) { + if (*cp < ' ' || *cp == 0177) + error("ill-formed hexadecimal escape sequence\n"); + else + error("ill-formed hexadecimal escape sequence `\\x%c'\n", *cp); + if (*cp != q) + cp++; + return 0; + } + for (c = 0; map[*cp]&(DIGIT|HEX); cp++) { + if (c >> (8*widechar->size - 4)) + overflow = 1; + if (map[*cp]&DIGIT) + c = (c<<4) + *cp - '0'; + else + c = (c<<4) + (*cp&~040) - 'A' + 10; + } + if (overflow) + warning("overflow in hexadecimal escape sequence\n"); + return c&ones(8*widechar->size); + } + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c = *(cp-1) - '0'; + if (*cp >= '0' && *cp <= '7') { + c = (c<<3) + *cp++ - '0'; + if (*cp >= '0' && *cp <= '7') + c = (c<<3) + *cp++ - '0'; + } + return c; + default: + if (cp[-1] < ' ' || cp[-1] >= 0177) + warning("unrecognized character escape sequence\n"); + else + warning("unrecognized character escape sequence `\\%c'\n", cp[-1]); + } + return cp[-1]; +} diff --git a/src/tools/lcc/src/list.c b/src/tools/lcc/src/list.c new file mode 100644 index 0000000..29e660a --- /dev/null +++ b/src/tools/lcc/src/list.c @@ -0,0 +1,56 @@ +#include "c.h" + + +static List freenodes; /* free list nodes */ + +/* append - append x to list, return new list */ +List append(void *x, List list) { + List new; + + if ((new = freenodes) != NULL) + freenodes = freenodes->link; + else + NEW(new, PERM); + if (list) { + new->link = list->link; + list->link = new; + } else + new->link = new; + new->x = x; + return new; +} + +/* length - # elements in list */ +int length(List list) { + int n = 0; + + if (list) { + List lp = list; + do + n++; + while ((lp = lp->link) != list); + } + return n; +} + +/* ltov - convert list to an NULL-terminated vector allocated in arena */ +void *ltov(List *list, unsigned arena) { + int i = 0; + void **array = newarray(length(*list) + 1, sizeof array[0], arena); + + if (*list) { + List lp = *list; + do { + lp = lp->link; + array[i++] = lp->x; + } while (lp != *list); +#ifndef PURIFY + lp = (*list)->link; + (*list)->link = freenodes; + freenodes = lp; +#endif + } + *list = NULL; + array[i] = NULL; + return array; +} diff --git a/src/tools/lcc/src/main.c b/src/tools/lcc/src/main.c new file mode 100644 index 0000000..63b85f2 --- /dev/null +++ b/src/tools/lcc/src/main.c @@ -0,0 +1,225 @@ +#include "c.h" + +static char rcsid[] = "main.c - faked rcsid"; + +static void typestab(Symbol, void *); + +static void stabline(Coordinate *); +static void stabend(Coordinate *, Symbol, Coordinate **, Symbol *, Symbol *); +Interface *IR = NULL; + +int Aflag; /* >= 0 if -A specified */ +int Pflag; /* != 0 if -P specified */ +int glevel; /* == [0-9] if -g[0-9] specified */ +int xref; /* != 0 for cross-reference data */ +Symbol YYnull; /* _YYnull symbol if -n or -nvalidate specified */ +Symbol YYcheck; /* _YYcheck symbol if -nvalidate,check specified */ + +static char *comment; +static Interface stabIR; +static char *currentfile; /* current file name */ +static int currentline; /* current line number */ +static FILE *srcfp; /* stream for current file, if non-NULL */ +static int srcpos; /* position of srcfp, if srcfp is non-NULL */ +int main(int argc, char *argv[]) { + int i, j; + for (i = argc - 1; i > 0; i--) + if (strncmp(argv[i], "-target=", 8) == 0) + break; + if (i > 0) { + char *s = strchr(argv[i], '\\'); + if (s != NULL) + *s = '/'; + for (j = 0; bindings[j].name && bindings[j].ir; j++) + if (strcmp(&argv[i][8], bindings[j].name) == 0) { + IR = bindings[j].ir; + break; + } + if (s != NULL) + *s = '\\'; + } + if (!IR) { + fprint(stderr, "%s: unknown target", argv[0]); + if (i > 0) + fprint(stderr, " `%s'", &argv[i][8]); + fprint(stderr, "; must specify one of\n"); + for (i = 0; bindings[i].name; i++) + fprint(stderr, "\t-target=%s\n", bindings[i].name); + exit(EXIT_FAILURE); + } + init(argc, argv); + t = gettok(); + (*IR->progbeg)(argc, argv); + if (glevel && IR->stabinit) + (*IR->stabinit)(firstfile, argc, argv); + program(); + if (events.end) + apply(events.end, NULL, NULL); + memset(&events, 0, sizeof events); + if (glevel || xref) { + Symbol symroot = NULL; + Coordinate src; + foreach(types, GLOBAL, typestab, &symroot); + foreach(identifiers, GLOBAL, typestab, &symroot); + src.file = firstfile; + src.x = 0; + src.y = lineno; + if ((glevel > 2 || xref) && IR->stabend) + (*IR->stabend)(&src, symroot, + ltov(&loci, PERM), + ltov(&symbols, PERM), NULL); + else if (IR->stabend) + (*IR->stabend)(&src, NULL, NULL, NULL, NULL); + } + finalize(); + (*IR->progend)(); + deallocate(PERM); + return errcnt > 0; +} +/* main_init - process program arguments */ +void main_init(int argc, char *argv[]) { + char *infile = NULL, *outfile = NULL; + int i; + static int inited; + + if (inited) + return; + inited = 1; + type_init(argc, argv); + for (i = 1; i < argc; i++) + if (strcmp(argv[i], "-g") == 0 || strcmp(argv[i], "-g2") == 0) + glevel = 2; + else if (strncmp(argv[i], "-g", 2) == 0) { /* -gn[,x] */ + char *p = strchr(argv[i], ','); + glevel = atoi(argv[i]+2); + if (p) { + comment = p + 1; + if (glevel == 0) + glevel = 1; + if (stabIR.stabline == NULL) { + stabIR.stabline = IR->stabline; + stabIR.stabend = IR->stabend; + IR->stabline = stabline; + IR->stabend = stabend; + } + } + } else if (strcmp(argv[i], "-x") == 0) + xref++; + else if (strcmp(argv[i], "-A") == 0) { + ++Aflag; + } else if (strcmp(argv[i], "-P") == 0) + Pflag++; + else if (strcmp(argv[i], "-w") == 0) + wflag++; + else if (strcmp(argv[i], "-n") == 0) { + if (!YYnull) { + YYnull = install(string("_YYnull"), &globals, GLOBAL, PERM); + YYnull->type = func(voidptype, NULL, 1); + YYnull->sclass = EXTERN; + (*IR->defsymbol)(YYnull); + } + } else if (strncmp(argv[i], "-n", 2) == 0) { /* -nvalid[,check] */ + char *p = strchr(argv[i], ','); + if (p) { + YYcheck = install(string(p+1), &globals, GLOBAL, PERM); + YYcheck->type = func(voidptype, NULL, 1); + YYcheck->sclass = EXTERN; + (*IR->defsymbol)(YYcheck); + p = stringn(argv[i]+2, p - (argv[i]+2)); + } else + p = string(argv[i]+2); + YYnull = install(p, &globals, GLOBAL, PERM); + YYnull->type = func(voidptype, NULL, 1); + YYnull->sclass = EXTERN; + (*IR->defsymbol)(YYnull); + } else if (strcmp(argv[i], "-v") == 0) + fprint(stderr, "%s %s\n", argv[0], rcsid); + else if (strncmp(argv[i], "-s", 2) == 0) + density = strtod(&argv[i][2], NULL); + else if (strncmp(argv[i], "-errout=", 8) == 0) { + FILE *f = fopen(argv[i]+8, "w"); + if (f == NULL) { + fprint(stderr, "%s: can't write errors to `%s'\n", argv[0], argv[i]+8); + exit(EXIT_FAILURE); + } + fclose(f); + f = freopen(argv[i]+8, "w", stderr); + assert(f); + } else if (strncmp(argv[i], "-e", 2) == 0) { + int x; + if ((x = strtol(&argv[i][2], NULL, 0)) > 0) + errlimit = x; + } else if (strncmp(argv[i], "-little_endian=", 15) == 0) + IR->little_endian = argv[i][15] - '0'; + else if (strncmp(argv[i], "-mulops_calls=", 18) == 0) + IR->mulops_calls = argv[i][18] - '0'; + else if (strncmp(argv[i], "-wants_callb=", 13) == 0) + IR->wants_callb = argv[i][13] - '0'; + else if (strncmp(argv[i], "-wants_argb=", 12) == 0) + IR->wants_argb = argv[i][12] - '0'; + else if (strncmp(argv[i], "-left_to_right=", 15) == 0) + IR->left_to_right = argv[i][15] - '0'; + else if (strncmp(argv[i], "-wants_dag=", 11) == 0) + IR->wants_dag = argv[i][11] - '0'; + else if (*argv[i] != '-' || strcmp(argv[i], "-") == 0) { + if (infile == NULL) + infile = argv[i]; + else if (outfile == NULL) + outfile = argv[i]; + } + + if (infile != NULL && strcmp(infile, "-") != 0 + && freopen(infile, "r", stdin) == NULL) { + fprint(stderr, "%s: can't read `%s'\n", argv[0], infile); + exit(EXIT_FAILURE); + } + if (outfile != NULL && strcmp(outfile, "-") != 0 + && freopen(outfile, "w", stdout) == NULL) { + fprint(stderr, "%s: can't write `%s'\n", argv[0], outfile); + exit(EXIT_FAILURE); + } +} +/* typestab - emit stab entries for p */ +static void typestab(Symbol p, void *cl) { + if (*(Symbol *)cl == 0 && p->sclass && p->sclass != TYPEDEF) + *(Symbol *)cl = p; + if ((p->sclass == TYPEDEF || p->sclass == 0) && IR->stabtype) + (*IR->stabtype)(p); +} + +/* stabline - emit source code for source coordinate *cp */ +static void stabline(Coordinate *cp) { + if (cp->file && cp->file != currentfile) { + if (srcfp) + fclose(srcfp); + currentfile = cp->file; + srcfp = fopen(currentfile, "r"); + srcpos = 0; + currentline = 0; + } + if (currentline != cp->y && srcfp) { + char buf[512]; + if (srcpos > cp->y) { + rewind(srcfp); + srcpos = 0; + } + for ( ; srcpos < cp->y; srcpos++) + if (fgets(buf, sizeof buf, srcfp) == NULL) { + fclose(srcfp); + srcfp = NULL; + break; + } + if (srcfp && srcpos == cp->y) + print("%s%s", comment, buf); + } + currentline = cp->y; + if (stabIR.stabline) + (*stabIR.stabline)(cp); +} + +static void stabend(Coordinate *cp, Symbol p, Coordinate **cpp, Symbol *sp, Symbol *stab) { + if (stabIR.stabend) + (*stabIR.stabend)(cp, p, cpp, sp, stab); + if (srcfp) + fclose(srcfp); +} diff --git a/src/tools/lcc/src/null.c b/src/tools/lcc/src/null.c new file mode 100644 index 0000000..b9f551c --- /dev/null +++ b/src/tools/lcc/src/null.c @@ -0,0 +1,74 @@ +#include "c.h" +#define I(f) null_##f + +static Node I(gen)(Node p) { return p; } +static void I(address)(Symbol q, Symbol p, long n) {} +static void I(blockbeg)(Env *e) {} +static void I(blockend)(Env *e) {} +static void I(defaddress)(Symbol p) {} +static void I(defconst)(int suffix, int size, Value v) {} +static void I(defstring)(int len, char *s) {} +static void I(defsymbol)(Symbol p) {} +static void I(emit)(Node p) {} +static void I(export)(Symbol p) {} +static void I(function)(Symbol f, Symbol caller[], Symbol callee[], int ncalls) {} +static void I(global)(Symbol p) {} +static void I(import)(Symbol p) {} +static void I(local)(Symbol p) {} +static void I(progbeg)(int argc, char *argv[]) {} +static void I(progend)(void) {} +static void I(segment)(int s) {} +static void I(space)(int n) {} +static void I(stabblock)(int brace, int lev, Symbol *p) {} +static void I(stabend)(Coordinate *cp, Symbol p, Coordinate **cpp, Symbol *sp, Symbol *stab) {} +static void I(stabfend)(Symbol p, int lineno) {} +static void I(stabinit)(char *file, int argc, char *argv[]) {} +static void I(stabline)(Coordinate *cp) {} +static void I(stabsym)(Symbol p) {} +static void I(stabtype)(Symbol p) {} + + +Interface nullIR = { + {1, 1, 0}, /* char */ + {2, 2, 0}, /* short */ + {4, 4, 0}, /* int */ + {8, 8, 1}, /* long */ + {8 ,8, 1}, /* long long */ + {4, 4, 1}, /* float */ + {8, 8, 1}, /* double */ + {16,16,1}, /* long double */ + {4, 4, 0}, /* T* */ + {0, 4, 0}, /* struct */ + 1, /* little_endian */ + 0, /* mulops_calls */ + 0, /* wants_callb */ + 0, /* wants_argb */ + 1, /* left_to_right */ + 0, /* wants_dag */ + 0, /* unsigned_char */ + I(address), + I(blockbeg), + I(blockend), + I(defaddress), + I(defconst), + I(defstring), + I(defsymbol), + I(emit), + I(export), + I(function), + I(gen), + I(global), + I(import), + I(local), + I(progbeg), + I(progend), + I(segment), + I(space), + I(stabblock), + I(stabend), + I(stabfend), + I(stabinit), + I(stabline), + I(stabsym), + I(stabtype) +}; diff --git a/src/tools/lcc/src/output.c b/src/tools/lcc/src/output.c new file mode 100644 index 0000000..a9c93e7 --- /dev/null +++ b/src/tools/lcc/src/output.c @@ -0,0 +1,135 @@ +#include "c.h" + + +static char *outs(const char *str, FILE *f, char *bp) { + if (f) + fputs(str, f); + else + while ((*bp = *str++)) + bp++; + return bp; +} + +static char *outd(long n, FILE *f, char *bp) { + unsigned long m; + char buf[25], *s = buf + sizeof buf; + + *--s = '\0'; + if (n < 0) + m = -n; + else + m = n; + do + *--s = m%10 + '0'; + while ((m /= 10) != 0); + if (n < 0) + *--s = '-'; + return outs(s, f, bp); +} + +static char *outu(unsigned long n, int base, FILE *f, char *bp) { + char buf[25], *s = buf + sizeof buf; + + *--s = '\0'; + do + *--s = "0123456789abcdef"[n%base]; + while ((n /= base) != 0); + return outs(s, f, bp); +} +void print(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprint(stdout, NULL, fmt, ap); + va_end(ap); +} +/* fprint - formatted output to f */ +void fprint(FILE *f, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprint(f, NULL, fmt, ap); + va_end(ap); +} + +/* stringf - formatted output to a saved string */ +char *stringf(const char *fmt, ...) { + char buf[1024]; + va_list ap; + + va_start(ap, fmt); + vfprint(NULL, buf, fmt, ap); + va_end(ap); + return string(buf); +} + +/* vfprint - formatted output to f or string bp */ +void vfprint(FILE *f, char *bp, const char *fmt, va_list ap) { + for (; *fmt; fmt++) + if (*fmt == '%') + switch (*++fmt) { + case 'd': bp = outd(va_arg(ap, int), f, bp); break; + case 'D': bp = outd(va_arg(ap, long), f, bp); break; + case 'U': bp = outu(va_arg(ap, unsigned long), 10, f, bp); break; + case 'u': bp = outu(va_arg(ap, unsigned), 10, f, bp); break; + case 'o': bp = outu(va_arg(ap, unsigned), 8, f, bp); break; + case 'X': bp = outu(va_arg(ap, unsigned long), 16, f, bp); break; + case 'x': bp = outu(va_arg(ap, unsigned), 16, f, bp); break; + case 'f': case 'e': + case 'g': { + static char format[] = "%f"; + char buf[128]; + format[1] = *fmt; + sprintf(buf, format, va_arg(ap, double)); + bp = outs(buf, f, bp); + } +; break; + case 's': bp = outs(va_arg(ap, char *), f, bp); break; + case 'p': { + void *p = va_arg(ap, void *); + if (p) + bp = outs("0x", f, bp); + bp = outu((unsigned long)p, 16, f, bp); + break; + } + case 'c': if (f) fputc(va_arg(ap, int), f); else *bp++ = va_arg(ap, int); break; + case 'S': { char *s = va_arg(ap, char *); + int n = va_arg(ap, int); + if (s) { + for ( ; n-- > 0; s++) + if (f) (void)putc(*s, f); else *bp++ = *s; + } + } break; + case 'k': { int t = va_arg(ap, int); + static char *tokens[] = { +#define xx(a,b,c,d,e,f,g) g, +#define yy(a,b,c,d,e,f,g) g, +#include "token.h" + }; + assert(tokens[t&0177]); + bp = outs(tokens[t&0177], f, bp); + } break; + case 't': { Type ty = va_arg(ap, Type); + assert(f); + outtype(ty ? ty : voidtype, f); + } break; + case 'w': { Coordinate *p = va_arg(ap, Coordinate *); + if (p->file && *p->file) { + bp = outs(p->file, f, bp); + bp = outs(":", f, bp); + } + bp = outd(p->y, f, bp); + } break; + case 'I': { int n = va_arg(ap, int); + while (--n >= 0) + if (f) (void)putc(' ', f); else *bp++ = ' '; + } break; + default: if (f) (void)putc(*fmt, f); else *bp++ = *fmt; break; + } + else if (f) + (void)putc(*fmt, f); + else + *bp++ = *fmt; + if (!f) + *bp = '\0'; +} diff --git a/src/tools/lcc/src/prof.c b/src/tools/lcc/src/prof.c new file mode 100644 index 0000000..02709ed --- /dev/null +++ b/src/tools/lcc/src/prof.c @@ -0,0 +1,228 @@ +#include "c.h" + + +struct callsite { + char *file, *name; + union coordinate { + unsigned int coord; + struct { unsigned int y:16,x:10,index:6; } le; + struct { unsigned int index:6,x:10,y:16; } be; + } u; +}; +struct func { + struct func *link; + struct caller *callers; + char *name; + union coordinate src; +}; +struct map { /* source code map; 200 coordinates/map */ + int size; + union coordinate u[200]; +}; + +int npoints; /* # of execution points if -b specified */ +int ncalled = -1; /* #times prof.out says current function was called */ +static Symbol YYlink; /* symbol for file's struct _bbdata */ +static Symbol YYcounts; /* symbol for _YYcounts if -b specified */ +static List maplist; /* list of struct map *'s */ +static List filelist; /* list of file names */ +static Symbol funclist; /* list of struct func *'s */ +static Symbol afunc; /* current function's struct func */ + +/* bbcall - build tree to set _callsite at call site *cp, emit call site data */ +static void bbcall(Symbol yycounts, Coordinate *cp, Tree *e) { + static Symbol caller; + Value v; + union coordinate u; + Symbol p = genident(STATIC, array(voidptype, 0, 0), GLOBAL); + Tree t = *e; + + defglobal(p, LIT); + defpointer(cp->file ? mkstr(cp->file)->u.c.loc : (Symbol)0); + defpointer(mkstr(cfunc->name)->u.c.loc); + if (IR->little_endian) { + u.le.x = cp->x; + u.le.y = cp->y; + } else { + u.be.x = cp->x; + u.be.y = cp->y; + } + (*IR->defconst)(U, unsignedtype->size, (v.u = u.coord, v)); + if (caller == 0) { + caller = mksymbol(EXTERN, "_caller", ptr(voidptype)); + caller->defined = 0; + } + if (generic((*e)->op) != CALL) + t = (*e)->kids[0]; + assert(generic(t->op) == CALL); + t = tree(t->op, t->type, + tree(RIGHT, t->kids[0]->type, + t->kids[0], + tree(RIGHT, t->kids[0]->type, asgn(caller, idtree(p)), t->kids[0])), + t->kids[1]); + if (generic((*e)->op) != CALL) + t = tree((*e)->op, (*e)->type, t, (*e)->kids[1]); + *e = t; +} + +/* bbentry - return tree for _prologue(&afunc, &YYlink)' */ +static void bbentry(Symbol yylink, Symbol f) { + static Symbol prologue; + + afunc = genident(STATIC, array(voidptype, 4, 0), GLOBAL); + if (prologue == 0) { + prologue = mksymbol(EXTERN, "_prologue", ftype(inttype, voidptype)); + prologue->defined = 0; + } + walk(vcall(prologue, voidtype, pointer(idtree(afunc)), pointer(idtree(yylink)), NULL), 0, 0); + +} + +/* bbexit - return tree for _epilogue(&afunc)' */ +static void bbexit(Symbol yylink, Symbol f, Tree e) { + static Symbol epilogue; + + if (epilogue == 0) { + epilogue = mksymbol(EXTERN, "_epilogue", ftype(inttype, voidptype)); + epilogue->defined = 0; + } + walk(vcall(epilogue, voidtype, pointer(idtree(afunc)), NULL), 0, 0); +} + +/* bbfile - add file to list of file names, return its index */ +static int bbfile(char *file) { + if (file) { + List lp; + int i = 1; + if ((lp = filelist) != NULL) + do { + lp = lp->link; + if (((Symbol)lp->x)->u.c.v.p == file) + return i; + i++; + } while (lp != filelist); + filelist = append(mkstr(file), filelist); + return i; + } + return 0; +} + +/* bbfunc - emit function name and src coordinates */ +static void bbfunc(Symbol yylink, Symbol f) { + Value v; + union coordinate u; + + defglobal(afunc, DATA); + defpointer(funclist); + defpointer(NULL); + defpointer(mkstr(f->name)->u.c.loc); + if (IR->little_endian) { + u.le.x = f->u.f.pt.x; + u.le.y = f->u.f.pt.y; + u.le.index = bbfile(f->u.f.pt.file); + } else { + u.be.x = f->u.f.pt.x; + u.be.y = f->u.f.pt.y; + u.be.index = bbfile(f->u.f.pt.file); + } + (*IR->defconst)(U, unsignedtype->size, (v.u = u.coord, v)); + funclist = afunc; +} + +/* bbincr - build tree to increment execution point at *cp */ +static void bbincr(Symbol yycounts, Coordinate *cp, Tree *e) { + struct map *mp = maplist->x; + Tree t; + + /* append *cp to source map */ + if (mp->size >= NELEMS(mp->u)) { + NEW(mp, PERM); + mp->size = 0; + maplist = append(mp, maplist); + } + if (IR->little_endian) { + mp->u[mp->size].le.x = cp->x; + mp->u[mp->size].le.y = cp->y; + mp->u[mp->size++].le.index = bbfile(cp->file); + } else { + mp->u[mp->size].be.x = cp->x; + mp->u[mp->size].be.y = cp->y; + mp->u[mp->size++].be.index = bbfile(cp->file); + } + t = incr('+', rvalue((*optree['+'])(ADD, pointer(idtree(yycounts)), + consttree(npoints++, inttype))), consttree(1, inttype)); + if (*e) + *e = tree(RIGHT, (*e)->type, t, *e); + else + *e = t; +} + +/* bbvars - emit definition for basic block counting data */ +static void bbvars(Symbol yylink) { + int i, j, n = npoints; + Value v; + struct map **mp; + Symbol coords, files, *p; + + if (!YYcounts && !yylink) + return; + if (YYcounts) { + if (n <= 0) + n = 1; + YYcounts->type = array(unsignedtype, n, 0); + defglobal(YYcounts, BSS); + } + files = genident(STATIC, array(charptype, 1, 0), GLOBAL); + defglobal(files, LIT); + for (p = ltov(&filelist, PERM); *p; p++) + defpointer((*p)->u.c.loc); + defpointer(NULL); + coords = genident(STATIC, array(unsignedtype, n, 0), GLOBAL); + defglobal(coords, LIT); + for (i = n, mp = ltov(&maplist, PERM); *mp; i -= (*mp)->size, mp++) + for (j = 0; j < (*mp)->size; j++) + (*IR->defconst)(U, unsignedtype->size, (v.u = (*mp)->u[j].coord, v)); + if (i > 0) + (*IR->space)(i*coords->type->type->size); + defpointer(NULL); + defglobal(yylink, DATA); + defpointer(NULL); + (*IR->defconst)(U, unsignedtype->size, (v.u = n, v)); + defpointer(YYcounts); + defpointer(coords); + defpointer(files); + defpointer(funclist); +} + +/* profInit - initialize basic block profiling options */ +void prof_init(int argc, char *argv[]) { + int i; + static int inited; + + if (inited) + return; + inited = 1; + type_init(argc, argv); + if (IR) { + for (i = 1; i < argc; i++) + if (strncmp(argv[i], "-a", 2) == 0) { + if (ncalled == -1 + && process(argv[i][2] ? &argv[i][2] : "prof.out") > 0) + ncalled = 0; + } else if ((strcmp(argv[i], "-b") == 0 + || strcmp(argv[i], "-C") == 0) && YYlink == 0) { + YYlink = genident(STATIC, array(unsignedtype, 0, 0), GLOBAL); + attach((Apply)bbentry, YYlink, &events.entry); + attach((Apply)bbexit, YYlink, &events.returns); + attach((Apply)bbfunc, YYlink, &events.exit); + attach((Apply)bbvars, YYlink, &events.end); + if (strcmp(argv[i], "-b") == 0) { + YYcounts = genident(STATIC, array(unsignedtype, 0, 0), GLOBAL); + maplist = append(allocate(sizeof (struct map), PERM), maplist); + ((struct map *)maplist->x)->size = 0; + attach((Apply)bbcall, YYcounts, &events.calls); + attach((Apply)bbincr, YYcounts, &events.points); + } + } + } +} diff --git a/src/tools/lcc/src/profio.c b/src/tools/lcc/src/profio.c new file mode 100644 index 0000000..37fc25b --- /dev/null +++ b/src/tools/lcc/src/profio.c @@ -0,0 +1,276 @@ +/* C compiler: prof.out input + +prof.out format: +#files + name + ... (#files-1 times) +#functions + name file# x y count caller file x y + ... (#functions-1 times) +#points + file# x y count + ... (#points-1 times) +*/ +#include "c.h" + + +struct count { /* count data: */ + int x, y; /* source coordinate */ + int count; /* associated execution count */ +}; + +#define MAXTOKEN 64 + +struct file { /* per-file prof.out data: */ + struct file *link; /* link to next file */ + char *name; /* file name */ + int size; /* size of counts[] */ + int count; /* counts[0..count-1] hold valid data */ + struct count *counts; /* count data */ + struct func { /* function data: */ + struct func *link; /* link to next function */ + char *name; /* function name */ + struct count count; /* total number of calls */ + struct caller { /* caller data: */ + struct caller *link; /* link to next caller */ + char *name; /* caller's name */ + char *file; /* call site: file, x, y */ + int x, y; + int count; /* number of calls from this site */ + } *callers; + } *funcs; /* list of functions */ +} *filelist; +FILE *fp; + +/* acaller - add caller and site (file,x,y) to callee's callers list */ +static void acaller(char *caller, char *file, int x, int y, int count, struct func *callee) { + struct caller *q; + + assert(callee); + for (q = callee->callers; q && (caller != q->name + || file != q->file || x != q->x || y != q->y); q = q->link) + ; + if (!q) { + struct caller **r; + NEW(q, PERM); + q->name = caller; + q->file = file; + q->x = x; + q->y = y; + q->count = 0; + for (r = &callee->callers; *r && (strcmp(q->name, (*r)->name) > 0 + || strcmp(q->file, (*r)->file) > 0 || q->y > (*r)->y || q->y > (*r)->y); r = &(*r)->link) + ; + q->link = *r; + *r = q; + } + q->count += count; +} + +/* compare - return <0, 0, >0 if ab, resp. */ +static int compare(struct count *a, struct count *b) { + if (a->y == b->y) + return a->x - b->x; + return a->y - b->y; +} + +/* findfile - return file name's file list entry, or 0 */ +static struct file *findfile(char *name) { + struct file *p; + + for (p = filelist; p; p = p->link) + if (p->name == name) + return p; + return 0; +} + +/* afunction - add function name and its data to file's function list */ +static struct func *afunction(char *name, char *file, int x, int y, int count) { + struct file *p = findfile(file); + struct func *q; + + assert(p); + for (q = p->funcs; q && name != q->name; q = q->link) + ; + if (!q) { + struct func **r; + NEW(q, PERM); + q->name = name; + q->count.x = x; + q->count.y = y; + q->count.count = 0; + q->callers = 0; + for (r = &p->funcs; *r && compare(&q->count, &(*r)->count) > 0; r = &(*r)->link) + ; + q->link = *r; + *r = q; + } + q->count.count += count; + return q; +} + +/* apoint - append execution point i to file's data */ +static void apoint(int i, char *file, int x, int y, int count) { + struct file *p = findfile(file); + + assert(p); + if (i >= p->size) { + int j; + if (p->size == 0) { + p->size = i >= 200 ? 2*i : 200; + p->counts = newarray(p->size, sizeof *p->counts, PERM); + } else { + struct count *new; + p->size = 2*i; + new = newarray(p->size, sizeof *new, PERM); + for (j = 0; j < p->count; j++) + new[j] = p->counts[j]; + p->counts = new; + } + for (j = p->count; j < p->size; j++) { + static struct count z; + p->counts[j] = z; + } + } + p->counts[i].x = x; + p->counts[i].y = y; + p->counts[i].count += count; + if (i >= p->count) + p->count = i + 1; +} + +/* findcount - return count associated with (file,x,y) or -1 */ +int findcount(char *file, int x, int y) { + static struct file *cursor; + + if (cursor == 0 || cursor->name != file) + cursor = findfile(file); + if (cursor) { + int l, u; + struct count *c = cursor->counts; + for (l = 0, u = cursor->count - 1; l <= u; ) { + int k = (l + u)/2; + if (c[k].y > y || (c[k].y == y && c[k].x > x)) + u = k - 1; + else if (c[k].y < y || (c[k].y == y && c[k].x < x)) + l = k + 1; + else + return c[k].count; + } + } + return -1; +} + +/* findfunc - return count associated with function name in file or -1 */ +int findfunc(char *name, char *file) { + static struct file *cursor; + + if (cursor == 0 || cursor->name != file) + cursor = findfile(file); + if (cursor) { + struct func *p; + for (p = cursor->funcs; p; p = p->link) + if (p->name == name) + return p->count.count; + } + return -1; +} + +/* getd - read a nonnegative number */ +static int getd(void) { + int c, n = 0; + + while ((c = getc(fp)) != EOF && (c == ' ' || c == '\n' || c == '\t')) + ; + if (c >= '0' && c <= '9') { + do + n = 10*n + (c - '0'); + while ((c = getc(fp)) >= '0' && c <= '9'); + return n; + } + return -1; +} + +/* getstr - read a string */ +static char *getstr(void) { + int c; + char buf[MAXTOKEN], *s = buf; + + while ((c = getc(fp)) != EOF && c != ' ' && c != '\n' && c != '\t') + if (s - buf < (int)sizeof buf - 2) + *s++ = c; + *s = 0; + return s == buf ? (char *)0 : string(buf); +} + +/* gather - read prof.out data from fd */ +static int gather(void) { + int i, nfiles, nfuncs, npoints; + char *files[64]; + + if ((nfiles = getd()) < 0) + return 0; + assert(nfiles < NELEMS(files)); + for (i = 0; i < nfiles; i++) { + if ((files[i] = getstr()) == 0) + return -1; + if (!findfile(files[i])) { + struct file *new; + NEW(new, PERM); + new->name = files[i]; + new->size = new->count = 0; + new->counts = 0; + new->funcs = 0; + new->link = filelist; + filelist = new; + } + } + if ((nfuncs = getd()) < 0) + return -1; + for (i = 0; i < nfuncs; i++) { + struct func *q; + char *name, *file; + int f, x, y, count; + if ((name = getstr()) == 0 || (f = getd()) <= 0 + || (x = getd()) < 0 || (y = getd()) < 0 || (count = getd()) < 0) + return -1; + q = afunction(name, files[f-1], x, y, count); + if ((name = getstr()) == 0 || (file = getstr()) == 0 + || (x = getd()) < 0 || (y = getd()) < 0) + return -1; + if (*name != '?') + acaller(name, file, x, y, count, q); + } + if ((npoints = getd()) < 0) + return -1; + for (i = 0; i < npoints; i++) { + int f, x, y, count; + if ((f = getd()) < 0 || (x = getd()) < 0 || (y = getd()) < 0 + || (count = getd()) < 0) + return -1; + if (f) + apoint(i, files[f-1], x, y, count); + } + return 1; +} + +/* process - read prof.out data from file */ +int process(char *file) { + int more; + + if ((fp = fopen(file, "r")) != NULL) { + struct file *p; + while ((more = gather()) > 0) + ; + fclose(fp); + if (more < 0) + return more; + for (p = filelist; p; p = p->link) + qsort(p->counts, p->count, sizeof *p->counts, + (int (*)(const void *, const void *)) + compare); + + return 1; + } + return 0; +} diff --git a/src/tools/lcc/src/simp.c b/src/tools/lcc/src/simp.c new file mode 100644 index 0000000..ea26ab6 --- /dev/null +++ b/src/tools/lcc/src/simp.c @@ -0,0 +1,587 @@ +#include "c.h" +#include + + +#define foldcnst(TYPE,VAR,OP) \ + if (l->op == CNST+TYPE && r->op == CNST+TYPE) \ + return cnsttree(ty, l->u.v.VAR OP r->u.v.VAR) +#define commute(L,R) \ + if (generic(R->op) == CNST && generic(L->op) != CNST) \ + do { Tree t = L; L = R; R = t; } while(0) +#define xfoldcnst(TYPE,VAR,OP,FUNC)\ + if (l->op == CNST+TYPE && r->op == CNST+TYPE\ + && FUNC(l->u.v.VAR,r->u.v.VAR,\ + ty->u.sym->u.limits.min.VAR,\ + ty->u.sym->u.limits.max.VAR, needconst)) \ + return cnsttree(ty, l->u.v.VAR OP r->u.v.VAR) +#define xcvtcnst(FTYPE,SRC,DST,VAR,EXPR) \ + if (l->op == CNST+FTYPE) do {\ + if (!explicitCast\ + && ((SRC) < DST->u.sym->u.limits.min.VAR || (SRC) > DST->u.sym->u.limits.max.VAR))\ + warning("overflow in converting constant expression from `%t' to `%t'\n", l->type, DST);\ + if (needconst\ + || !((SRC) < DST->u.sym->u.limits.min.VAR || (SRC) > DST->u.sym->u.limits.max.VAR))\ + return cnsttree(ty, (EXPR)); } while(0) +#define identity(X,Y,TYPE,VAR,VAL) \ + if (X->op == CNST+TYPE && X->u.v.VAR == VAL) return Y +#define zerofield(OP,TYPE,VAR) \ + if (l->op == FIELD \ + && r->op == CNST+TYPE && r->u.v.VAR == 0)\ + return eqtree(OP, bittree(BAND, l->kids[0],\ + cnsttree(unsignedtype, \ + (unsigned long)fieldmask(l->u.field)<u.field))), r) +#define cfoldcnst(TYPE,VAR,OP) \ + if (l->op == CNST+TYPE && r->op == CNST+TYPE) \ + return cnsttree(inttype, (long)(l->u.v.VAR OP r->u.v.VAR)) +#define foldaddp(L,R,RTYPE,VAR) \ + if (L->op == CNST+P && R->op == CNST+RTYPE) { \ + Tree e = tree(CNST+P, ty, NULL, NULL);\ + e->u.v.p = (char *)L->u.v.p + R->u.v.VAR;\ + return e; } +#define ufoldcnst(TYPE,EXP) if (l->op == CNST+TYPE) return EXP +#define sfoldcnst(OP) \ + if (l->op == CNST+U && r->op == CNST+I \ + && r->u.v.i >= 0 && r->u.v.i < 8*l->type->size) \ + return cnsttree(ty, (unsigned long)(l->u.v.u OP r->u.v.i)) +#define geu(L,R,V) \ + if (R->op == CNST+U && R->u.v.u == 0) do { \ + warning("result of unsigned comparison is constant\n"); \ + return tree(RIGHT, inttype, root(L), cnsttree(inttype, (long)(V))); } while(0) +#define idempotent(OP) if (l->op == OP) return l->kids[0] + +int needconst; +int explicitCast; +static int addi(long x, long y, long min, long max, int needconst) { + int cond = x == 0 || y == 0 + || (x < 0 && y < 0 && x >= min - y) + || (x < 0 && y > 0) + || (x > 0 && y < 0) + || (x > 0 && y > 0 && x <= max - y); + if (!cond && needconst) { + warning("overflow in constant expression\n"); + cond = 1; + } + return cond; + + +} + +static int addd(double x, double y, double min, double max, int needconst) { + int cond = x == 0 || y == 0 + || (x < 0 && y < 0 && x >= min - y) + || (x < 0 && y > 0) + || (x > 0 && y < 0) + || (x > 0 && y > 0 && x <= max - y); + if (!cond && needconst) { + warning("overflow in constant expression\n"); + cond = 1; + } + return cond; + + +} + +static Tree addrtree(Tree e, long n, Type ty) { + Symbol p = e->u.sym, q; + + if (p->scope == GLOBAL + || p->sclass == STATIC || p->sclass == EXTERN) + NEW0(q, PERM); + else + NEW0(q, FUNC); + q->name = stringd(genlabel(1)); + q->sclass = p->sclass; + q->scope = p->scope; + assert(isptr(ty) || isarray(ty)); + q->type = isptr(ty) ? ty->type : ty; + q->temporary = p->temporary; + q->generated = p->generated; + q->addressed = p->addressed; + q->computed = 1; + q->defined = 1; + q->ref = 1; + if (p->scope == GLOBAL + || p->sclass == STATIC || p->sclass == EXTERN) { + if (p->sclass == AUTO) + q->sclass = STATIC; + (*IR->address)(q, p, n); + } else { + Code cp; + addlocal(p); + cp = code(Address); + cp->u.addr.sym = q; + cp->u.addr.base = p; + cp->u.addr.offset = n; + } + e = tree(e->op, ty, NULL, NULL); + e->u.sym = q; + return e; +} + +/* div[id] - return 1 if min <= x/y <= max, 0 otherwise */ +static int divi(long x, long y, long min, long max, int needconst) { + int cond = y != 0 && !(x == min && y == -1); + if (!cond && needconst) { + warning("overflow in constant expression\n"); + cond = 1; + } + return cond; + + +} + +static int divd(double x, double y, double min, double max, int needconst) { + int cond; + + if (x < 0) x = -x; + if (y < 0) y = -y; + cond = y != 0 && !(y < 1 && x > max*y); + if (!cond && needconst) { + warning("overflow in constant expression\n"); + cond = 1; + } + return cond; + +} + +/* mul[id] - return 1 if min <= x*y <= max, 0 otherwise */ +static int muli(long x, long y, long min, long max, int needconst) { + int cond = (x > -1 && x <= 1) || (y > -1 && y <= 1) + || (x < 0 && y < 0 && -x <= max/-y) + || (x < 0 && y > 0 && x >= min/y) + || (x > 0 && y < 0 && y >= min/x) + || (x > 0 && y > 0 && x <= max/y); + if (!cond && needconst) { + warning("overflow in constant expression\n"); + cond = 1; + } + return cond; + + +} + +static int muld(double x, double y, double min, double max, int needconst) { + int cond = (x >= -1 && x <= 1) || (y >= -1 && y <= 1) + || (x < 0 && y < 0 && -x <= max/-y) + || (x < 0 && y > 0 && x >= min/y) + || (x > 0 && y < 0 && y >= min/x) + || (x > 0 && y > 0 && x <= max/y); + if (!cond && needconst) { + warning("overflow in constant expression\n"); + cond = 1; + } + return cond; + + +} +/* sub[id] - return 1 if min <= x-y <= max, 0 otherwise */ +static int subi(long x, long y, long min, long max, int needconst) { + return addi(x, -y, min, max, needconst); +} + +static int subd(double x, double y, double min, double max, int needconst) { + return addd(x, -y, min, max, needconst); +} +Tree constexpr(int tok) { + Tree p; + + needconst++; + p = expr1(tok); + needconst--; + return p; +} + +int intexpr(int tok, int n) { + Tree p = constexpr(tok); + + needconst++; + if (p->op == CNST+I || p->op == CNST+U) + n = cast(p, inttype)->u.v.i; + else + error("integer expression must be constant\n"); + needconst--; + return n; +} +Tree simplify(int op, Type ty, Tree l, Tree r) { + int n; + + if (optype(op) == 0) + op = mkop(op, ty); + switch (op) { + case ADD+U: + foldcnst(U,u,+); + commute(r,l); + identity(r,l,U,u,0); + break; + case ADD+I: + xfoldcnst(I,i,+,addi); + commute(r,l); + identity(r,l,I,i,0); + break; + case CVI+I: + xcvtcnst(I,l->u.v.i,ty,i,(long)extend(l->u.v.i,ty)); + break; + case CVU+I: + if (l->op == CNST+U) { + if (!explicitCast && l->u.v.u > ty->u.sym->u.limits.max.i) + warning("overflow in converting constant expression from `%t' to `%t'\n", l->type, ty); + if (needconst || !(l->u.v.u > ty->u.sym->u.limits.max.i)) + return cnsttree(ty, (long)extend(l->u.v.u,ty)); + } + break; + case CVP+U: + xcvtcnst(P,(unsigned long)l->u.v.p,ty,u,(unsigned long)l->u.v.p); + break; + case CVU+P: + xcvtcnst(U,(void*)l->u.v.u,ty,p,(void*)l->u.v.u); + break; + case CVP+P: + xcvtcnst(P,l->u.v.p,ty,p,l->u.v.p); + break; + case CVI+U: + xcvtcnst(I,l->u.v.i,ty,u,((unsigned long)l->u.v.i)&ones(8*ty->size)); + break; + case CVU+U: + xcvtcnst(U,l->u.v.u,ty,u,l->u.v.u&ones(8*ty->size)); + break; + + case CVI+F: + xcvtcnst(I,l->u.v.i,ty,d,(double)l->u.v.i); + case CVU+F: + xcvtcnst(U,l->u.v.u,ty,d,(double)l->u.v.u); + break; + case CVF+I: + xcvtcnst(F,l->u.v.d,ty,i,(long)l->u.v.d); + break; + case CVF+F: { + float d = 0.0f; + if (l->op == CNST+F) { + if (l->u.v.d < ty->u.sym->u.limits.min.d) + d = ty->u.sym->u.limits.min.d; + else if (l->u.v.d > ty->u.sym->u.limits.max.d) + d = ty->u.sym->u.limits.max.d; + else + d = l->u.v.d; + } + xcvtcnst(F,l->u.v.d,ty,d,(double)d); + break; + } + case BAND+U: + foldcnst(U,u,&); + commute(r,l); + identity(r,l,U,u,ones(8*ty->size)); + if (r->op == CNST+U && r->u.v.u == 0) + return tree(RIGHT, ty, root(l), cnsttree(ty, 0UL)); + break; + case BAND+I: + foldcnst(I,i,&); + commute(r,l); + identity(r,l,I,i,ones(8*ty->size)); + if (r->op == CNST+I && r->u.v.u == 0) + return tree(RIGHT, ty, root(l), cnsttree(ty, 0L)); + break; + + case MUL+U: + commute(l,r); + if (l->op == CNST+U && (n = ispow2(l->u.v.u)) != 0) + return simplify(LSH, ty, r, cnsttree(inttype, (long)n)); + foldcnst(U,u,*); + identity(r,l,U,u,1); + break; + case NE+I: + cfoldcnst(I,i,!=); + commute(r,l); + zerofield(NE,I,i); + break; + + case EQ+I: + cfoldcnst(I,i,==); + commute(r,l); + zerofield(EQ,I,i); + break; + case ADD+P: + foldaddp(l,r,I,i); + foldaddp(l,r,U,u); + foldaddp(r,l,I,i); + foldaddp(r,l,U,u); + commute(r,l); + identity(r,retype(l,ty),I,i,0); + identity(r,retype(l,ty),U,u,0); + if (isaddrop(l->op) + && ((r->op == CNST+I && r->u.v.i <= longtype->u.sym->u.limits.max.i + && r->u.v.i >= longtype->u.sym->u.limits.min.i) + || (r->op == CNST+U && r->u.v.u <= longtype->u.sym->u.limits.max.i))) + return addrtree(l, cast(r, longtype)->u.v.i, ty); + if (l->op == ADD+P && isaddrop(l->kids[1]->op) + && ((r->op == CNST+I && r->u.v.i <= longtype->u.sym->u.limits.max.i + && r->u.v.i >= longtype->u.sym->u.limits.min.i) + || (r->op == CNST+U && r->u.v.u <= longtype->u.sym->u.limits.max.i))) + return simplify(ADD+P, ty, l->kids[0], + addrtree(l->kids[1], cast(r, longtype)->u.v.i, ty)); + if ((l->op == ADD+I || l->op == SUB+I) + && l->kids[1]->op == CNST+I && isaddrop(r->op)) + return simplify(ADD+P, ty, l->kids[0], + simplify(generic(l->op)+P, ty, r, l->kids[1])); + if (l->op == ADD+P && generic(l->kids[1]->op) == CNST + && generic(r->op) == CNST) + return simplify(ADD+P, ty, l->kids[0], + simplify(ADD, l->kids[1]->type, l->kids[1], r)); + if (l->op == ADD+I && generic(l->kids[1]->op) == CNST + && r->op == ADD+P && generic(r->kids[1]->op) == CNST) + return simplify(ADD+P, ty, l->kids[0], + simplify(ADD+P, ty, r->kids[0], + simplify(ADD, r->kids[1]->type, l->kids[1], r->kids[1]))); + if (l->op == RIGHT && l->kids[1]) + return tree(RIGHT, ty, l->kids[0], + simplify(ADD+P, ty, l->kids[1], r)); + else if (l->op == RIGHT && l->kids[0]) + return tree(RIGHT, ty, + simplify(ADD+P, ty, l->kids[0], r), NULL); + break; + + case ADD+F: + xfoldcnst(F,d,+,addd); + commute(r,l); + break; + case AND+I: + op = AND; + ufoldcnst(I,l->u.v.i ? cond(r) : l); /* 0&&r => 0, 1&&r => r */ + break; + case OR+I: + op = OR; + /* 0||r => r, 1||r => 1 */ + ufoldcnst(I,l->u.v.i ? cnsttree(ty, 1L) : cond(r)); + break; + case BCOM+I: + ufoldcnst(I,cnsttree(ty, (long)extend((~l->u.v.i)&ones(8*ty->size), ty))); + idempotent(BCOM+U); + break; + case BCOM+U: + ufoldcnst(U,cnsttree(ty, (unsigned long)((~l->u.v.u)&ones(8*ty->size)))); + idempotent(BCOM+U); + break; + case BOR+U: + foldcnst(U,u,|); + commute(r,l); + identity(r,l,U,u,0); + break; + case BOR+I: + foldcnst(I,i,|); + commute(r,l); + identity(r,l,I,i,0); + break; + case BXOR+U: + foldcnst(U,u,^); + commute(r,l); + identity(r,l,U,u,0); + break; + case BXOR+I: + foldcnst(I,i,^); + commute(r,l); + identity(r,l,I,i,0); + break; + case DIV+F: + xfoldcnst(F,d,/,divd); + break; + case DIV+I: + identity(r,l,I,i,1); + if ((r->op == CNST+I && r->u.v.i == 0) + || (l->op == CNST+I && l->u.v.i == ty->u.sym->u.limits.min.i + && r->op == CNST+I && r->u.v.i == -1)) + break; + xfoldcnst(I,i,/,divi); + break; + case DIV+U: + identity(r,l,U,u,1); + if (r->op == CNST+U && r->u.v.u == 0) + break; + if (r->op == CNST+U && (n = ispow2(r->u.v.u)) != 0) + return simplify(RSH, ty, l, cnsttree(inttype, (long)n)); + foldcnst(U,u,/); + break; + case EQ+F: + cfoldcnst(F,d,==); + commute(r,l); + break; + case EQ+U: + cfoldcnst(U,u,==); + commute(r,l); + zerofield(EQ,U,u); + break; + case GE+F: cfoldcnst(F,d,>=); break; + case GE+I: cfoldcnst(I,i,>=); break; + case GE+U: + geu(l,r,1); /* l >= 0 => (l,1) */ + cfoldcnst(U,u,>=); + if (l->op == CNST+U && l->u.v.u == 0) /* 0 >= r => r == 0 */ + return eqtree(EQ, r, l); + break; + case GT+F: cfoldcnst(F,d, >); break; + case GT+I: cfoldcnst(I,i, >); break; + case GT+U: + geu(r,l,0); /* 0 > r => (r,0) */ + cfoldcnst(U,u, >); + if (r->op == CNST+U && r->u.v.u == 0) /* l > 0 => l != 0 */ + return eqtree(NE, l, r); + break; + case LE+F: cfoldcnst(F,d,<=); break; + case LE+I: cfoldcnst(I,i,<=); break; + case LE+U: + geu(r,l,1); /* 0 <= r => (r,1) */ + cfoldcnst(U,u,<=); + if (r->op == CNST+U && r->u.v.u == 0) /* l <= 0 => l == 0 */ + return eqtree(EQ, l, r); + break; + case LSH+I: + identity(r,l,I,i,0); + if (l->op == CNST+I && r->op == CNST+I + && r->u.v.i >= 0 && r->u.v.i < 8*l->type->size + && muli(l->u.v.i, 1<u.v.i, ty->u.sym->u.limits.min.i, ty->u.sym->u.limits.max.i, needconst)) + return cnsttree(ty, (long)(l->u.v.i<u.v.i)); + if (r->op == CNST+I && (r->u.v.i >= 8*ty->size || r->u.v.i < 0)) { + warning("shifting an `%t' by %d bits is undefined\n", ty, r->u.v.i); + break; + } + + break; + case LSH+U: + identity(r,l,I,i,0); + sfoldcnst(<<); + if (r->op == CNST+I && (r->u.v.i >= 8*ty->size || r->u.v.i < 0)) { + warning("shifting an `%t' by %d bits is undefined\n", ty, r->u.v.i); + break; + } + + break; + + case LT+F: cfoldcnst(F,d, <); break; + case LT+I: cfoldcnst(I,i, <); break; + case LT+U: + geu(l,r,0); /* l < 0 => (l,0) */ + cfoldcnst(U,u, <); + if (l->op == CNST+U && l->u.v.u == 0) /* 0 < r => r != 0 */ + return eqtree(NE, r, l); + break; + case MOD+I: + if (r->op == CNST+I && r->u.v.i == 1) /* l%1 => (l,0) */ + return tree(RIGHT, ty, root(l), cnsttree(ty, 0L)); + if ((r->op == CNST+I && r->u.v.i == 0) + || (l->op == CNST+I && l->u.v.i == ty->u.sym->u.limits.min.i + && r->op == CNST+I && r->u.v.i == -1)) + break; + xfoldcnst(I,i,%,divi); + break; + case MOD+U: + if (r->op == CNST+U && ispow2(r->u.v.u)) /* l%2^n => l&(2^n-1) */ + return bittree(BAND, l, cnsttree(ty, r->u.v.u - 1)); + if (r->op == CNST+U && r->u.v.u == 0) + break; + foldcnst(U,u,%); + break; + case MUL+F: + xfoldcnst(F,d,*,muld); + commute(l,r); + break; + case MUL+I: + commute(l,r); + xfoldcnst(I,i,*,muli); + if (l->op == CNST+I && r->op == ADD+I && r->kids[1]->op == CNST+I) + /* c1*(x + c2) => c1*x + c1*c2 */ + return simplify(ADD, ty, simplify(MUL, ty, l, r->kids[0]), + simplify(MUL, ty, l, r->kids[1])); + if (l->op == CNST+I && r->op == SUB+I && r->kids[1]->op == CNST+I) + /* c1*(x - c2) => c1*x - c1*c2 */ + return simplify(SUB, ty, simplify(MUL, ty, l, r->kids[0]), + simplify(MUL, ty, l, r->kids[1])); + if (l->op == CNST+I && l->u.v.i > 0 && (n = ispow2(l->u.v.i)) != 0) + /* 2^n * r => r<u.v.d)); + idempotent(NEG+F); + break; + case NEG+I: + if (l->op == CNST+I) { + if (needconst && l->u.v.i == ty->u.sym->u.limits.min.i) + warning("overflow in constant expression\n"); + if (needconst || l->u.v.i != ty->u.sym->u.limits.min.i) + return cnsttree(ty, -l->u.v.i); + } + idempotent(NEG+I); + break; + case NOT+I: + op = NOT; + ufoldcnst(I,cnsttree(ty, !l->u.v.i)); + break; + case RSH+I: + identity(r,l,I,i,0); + if (l->op == CNST+I && r->op == CNST+I + && r->u.v.i >= 0 && r->u.v.i < 8*l->type->size) { + long n = l->u.v.i>>r->u.v.i; + if (l->u.v.i < 0) + n |= ~0UL<<(8*l->type->size - r->u.v.i); + return cnsttree(ty, n); + } + if (r->op == CNST+I && (r->u.v.i >= 8*ty->size || r->u.v.i < 0)) { + warning("shifting an `%t' by %d bits is undefined\n", ty, r->u.v.i); + break; + } + + break; + case RSH+U: + identity(r,l,I,i,0); + sfoldcnst(>>); + if (r->op == CNST+I && (r->u.v.i >= 8*ty->size || r->u.v.i < 0)) { + warning("shifting an `%t' by %d bits is undefined\n", ty, r->u.v.i); + break; + } + + break; + case SUB+F: + xfoldcnst(F,d,-,subd); + break; + case SUB+I: + xfoldcnst(I,i,-,subi); + identity(r,l,I,i,0); + break; + case SUB+U: + foldcnst(U,u,-); + identity(r,l,U,u,0); + break; + case SUB+P: + if (l->op == CNST+P && r->op == CNST+P) + return cnsttree(ty, (long)((char *)l->u.v.p - (char *)r->u.v.p)); + if (r->op == CNST+I || r->op == CNST+U) + return simplify(ADD, ty, l, + cnsttree(inttype, r->op == CNST+I ? -r->u.v.i : -(long)r->u.v.u)); + if (isaddrop(l->op) && r->op == ADD+I && r->kids[1]->op == CNST+I) + /* l - (x + c) => l-c - x */ + return simplify(SUB, ty, + simplify(SUB, ty, l, r->kids[1]), r->kids[0]); + break; + default:assert(0); + } + return tree(op, ty, l, r); +} +/* ispow2 - if u > 1 && u == 2^n, return n, otherwise return 0 */ +int ispow2(unsigned long u) { + int n; + + if (u > 1 && (u&(u-1)) == 0) + for (n = 0; u; u >>= 1, n++) + if (u&1) + return n; + return 0; +} + diff --git a/src/tools/lcc/src/stmt.c b/src/tools/lcc/src/stmt.c new file mode 100644 index 0000000..9c3bdbe --- /dev/null +++ b/src/tools/lcc/src/stmt.c @@ -0,0 +1,696 @@ +#include "c.h" + + +#define SWSIZE 512 + +#define den(i,j) ((j-buckets[i]+1.0)/(v[j]-v[buckets[i]]+1)) + +struct code codehead = { Start }; +Code codelist = &codehead; +float density = 0.5; +Table stmtlabs; + +static int foldcond(Tree e1, Tree e2); +static void caselabel(Swtch, long, int); +static void cmp(int, Symbol, long, int); +static Tree conditional(int); +static void dostmt(int, Swtch, int); +static int equal(Symbol, Symbol); +static void forstmt(int, Swtch, int); +static void ifstmt(int, int, Swtch, int); +static Symbol localaddr(Tree); +static void stmtlabel(void); +static void swstmt(int, int, int); +static void whilestmt(int, Swtch, int); +Code code(int kind) { + Code cp; + + if (!reachable(kind)) + warning("unreachable code\n"); + + NEW(cp, FUNC); + cp->kind = kind; + cp->prev = codelist; + cp->next = NULL; + codelist->next = cp; + codelist = cp; + return cp; +} +int reachable(int kind) { + + if (kind > Start) { + Code cp; + for (cp = codelist; cp->kind < Label; ) + cp = cp->prev; + if (cp->kind == Jump || cp->kind == Switch) + return 0; + } + return 1; +} +void addlocal(Symbol p) { + if (!p->defined) { + code(Local)->u.var = p; + p->defined = 1; + p->scope = level; + } +} +void definept(Coordinate *p) { + Code cp = code(Defpoint); + + cp->u.point.src = p ? *p : src; + cp->u.point.point = npoints; + if (ncalled > 0) { + int n = findcount(cp->u.point.src.file, + cp->u.point.src.x, cp->u.point.src.y); + if (n > 0) + refinc = (float)n/ncalled; + } + if (glevel > 2) locus(identifiers, &cp->u.point.src); + if (events.points && reachable(Gen)) + { + Tree e = NULL; + apply(events.points, &cp->u.point.src, &e); + if (e) + listnodes(e, 0, 0); + } +} +void statement(int loop, Swtch swp, int lev) { + float ref = refinc; + + if (Aflag >= 2 && lev == 15) + warning("more than 15 levels of nested statements\n"); + switch (t) { + case IF: ifstmt(genlabel(2), loop, swp, lev + 1); + break; + case WHILE: whilestmt(genlabel(3), swp, lev + 1); break; + case DO: dostmt(genlabel(3), swp, lev + 1); expect(';'); + break; + + case FOR: forstmt(genlabel(4), swp, lev + 1); + break; + case BREAK: walk(NULL, 0, 0); + definept(NULL); + if (swp && swp->lab > loop) + branch(swp->lab + 1); + else if (loop) + branch(loop + 2); + else + error("illegal break statement\n"); + t = gettok(); expect(';'); + break; + + case CONTINUE: walk(NULL, 0, 0); + definept(NULL); + if (loop) + branch(loop + 1); + else + error("illegal continue statement\n"); + t = gettok(); expect(';'); + break; + + case SWITCH: swstmt(loop, genlabel(2), lev + 1); + break; + case CASE: { + int lab = genlabel(1); + if (swp == NULL) + error("illegal case label\n"); + definelab(lab); + while (t == CASE) { + static char stop[] = { IF, ID, 0 }; + Tree p; + t = gettok(); + p = constexpr(0); + if (generic(p->op) == CNST && isint(p->type)) { + if (swp) { + needconst++; + p = cast(p, swp->sym->type); + if (p->type->op == UNSIGNED) + p->u.v.i = extend(p->u.v.u, p->type); + needconst--; + caselabel(swp, p->u.v.i, lab); + } + } else + error("case label must be a constant integer expression\n"); + + test(':', stop); + } + statement(loop, swp, lev); + } break; + case DEFAULT: if (swp == NULL) + error("illegal default label\n"); + else if (swp->deflab) + error("extra default label\n"); + else { + swp->deflab = findlabel(swp->lab); + definelab(swp->deflab->u.l.label); + } + t = gettok(); + expect(':'); + statement(loop, swp, lev); break; + case RETURN: { + Type rty = freturn(cfunc->type); + t = gettok(); + definept(NULL); + if (t != ';') + if (rty == voidtype) { + error("extraneous return value\n"); + expr(0); + retcode(NULL); + } else + retcode(expr(0)); + else { + if (rty != voidtype) + warning("missing return value\n"); + retcode(NULL); + } + branch(cfunc->u.f.label); + } expect(';'); + break; + + case '{': compound(loop, swp, lev + 1); break; + case ';': definept(NULL); t = gettok(); break; + case GOTO: walk(NULL, 0, 0); + definept(NULL); + t = gettok(); + if (t == ID) { + Symbol p = lookup(token, stmtlabs); + if (p == NULL) { + p = install(token, &stmtlabs, 0, FUNC); + p->scope = LABELS; + p->u.l.label = genlabel(1); + p->src = src; + } + use(p, src); + branch(p->u.l.label); + t = gettok(); + } else + error("missing label in goto\n"); expect(';'); + break; + + case ID: if (getchr() == ':') { + stmtlabel(); + statement(loop, swp, lev); + break; + } + default: definept(NULL); + if (kind[t] != ID) { + error("unrecognized statement\n"); + t = gettok(); + } else { + Tree e = expr0(0); + listnodes(e, 0, 0); + if (nodecount == 0 || nodecount > 200) + walk(NULL, 0, 0); + else if (glevel) walk(NULL, 0, 0); + deallocate(STMT); + } expect(';'); + break; + + } + if (kind[t] != IF && kind[t] != ID + && t != '}' && t != EOI) { + static char stop[] = { IF, ID, '}', 0 }; + error("illegal statement termination\n"); + skipto(0, stop); + } + refinc = ref; +} + +static void ifstmt(int lab, int loop, Swtch swp, int lev) { + t = gettok(); + expect('('); + definept(NULL); + walk(conditional(')'), 0, lab); + refinc /= 2.0; + statement(loop, swp, lev); + if (t == ELSE) { + branch(lab + 1); + t = gettok(); + definelab(lab); + statement(loop, swp, lev); + if (findlabel(lab + 1)->ref) + definelab(lab + 1); + } else + definelab(lab); +} +static Tree conditional(int tok) { + Tree p = expr(tok); + + if (Aflag > 1 && isfunc(p->type)) + warning("%s used in a conditional expression\n", + funcname(p)); + return cond(p); +} +static void stmtlabel(void) { + Symbol p = lookup(token, stmtlabs); + + if (p == NULL) { + p = install(token, &stmtlabs, 0, FUNC); + p->scope = LABELS; + p->u.l.label = genlabel(1); + p->src = src; + } + if (p->defined) + error("redefinition of label `%s' previously defined at %w\n", p->name, &p->src); + + p->defined = 1; + definelab(p->u.l.label); + t = gettok(); + expect(':'); +} +static void forstmt(int lab, Swtch swp, int lev) { + int once = 0; + Tree e1 = NULL, e2 = NULL, e3 = NULL; + Coordinate pt2, pt3; + + t = gettok(); + expect('('); + definept(NULL); + if (kind[t] == ID) + e1 = texpr(expr0, ';', FUNC); + else + expect(';'); + walk(e1, 0, 0); + pt2 = src; + refinc *= 10.0; + if (kind[t] == ID) + e2 = texpr(conditional, ';', FUNC); + else + expect(';'); + pt3 = src; + if (kind[t] == ID) + e3 = texpr(expr0, ')', FUNC); + else { + static char stop[] = { IF, ID, '}', 0 }; + test(')', stop); + } + if (e2) { + once = foldcond(e1, e2); + if (!once) + branch(lab + 3); + } + definelab(lab); + statement(lab, swp, lev); + definelab(lab + 1); + definept(&pt3); + if (e3) + walk(e3, 0, 0); + if (e2) { + if (!once) + definelab(lab + 3); + definept(&pt2); + walk(e2, lab, 0); + } else { + definept(&pt2); + branch(lab); + } + if (findlabel(lab + 2)->ref) + definelab(lab + 2); +} +static void swstmt(int loop, int lab, int lev) { + Tree e; + struct swtch sw; + Code head, tail; + + t = gettok(); + expect('('); + definept(NULL); + e = expr(')'); + if (!isint(e->type)) { + error("illegal type `%t' in switch expression\n", + e->type); + e = retype(e, inttype); + } + e = cast(e, promote(e->type)); + if (generic(e->op) == INDIR && isaddrop(e->kids[0]->op) + && e->kids[0]->u.sym->type == e->type + && !isvolatile(e->kids[0]->u.sym->type)) { + sw.sym = e->kids[0]->u.sym; + walk(NULL, 0, 0); + } else { + sw.sym = genident(REGISTER, e->type, level); + addlocal(sw.sym); + walk(asgn(sw.sym, e), 0, 0); + } + head = code(Switch); + sw.lab = lab; + sw.deflab = NULL; + sw.ncases = 0; + sw.size = SWSIZE; + sw.values = newarray(SWSIZE, sizeof *sw.values, FUNC); + sw.labels = newarray(SWSIZE, sizeof *sw.labels, FUNC); + refinc /= 10.0; + statement(loop, &sw, lev); + if (sw.deflab == NULL) { + sw.deflab = findlabel(lab); + definelab(lab); + if (sw.ncases == 0) + warning("switch statement with no cases\n"); + } + if (findlabel(lab + 1)->ref) + definelab(lab + 1); + tail = codelist; + codelist = head->prev; + codelist->next = head->prev = NULL; + if (sw.ncases > 0) + swgen(&sw); + branch(lab); + head->next->prev = codelist; + codelist->next = head->next; + codelist = tail; +} +static void caselabel(Swtch swp, long val, int lab) { + int k; + + if (swp->ncases >= swp->size) + { + long *vals = swp->values; + Symbol *labs = swp->labels; + swp->size *= 2; + swp->values = newarray(swp->size, sizeof *swp->values, FUNC); + swp->labels = newarray(swp->size, sizeof *swp->labels, FUNC); + for (k = 0; k < swp->ncases; k++) { + swp->values[k] = vals[k]; + swp->labels[k] = labs[k]; + } + } + k = swp->ncases; + for ( ; k > 0 && swp->values[k-1] >= val; k--) { + swp->values[k] = swp->values[k-1]; + swp->labels[k] = swp->labels[k-1]; + } + if (k < swp->ncases && swp->values[k] == val) + error("duplicate case label `%d'\n", val); + swp->values[k] = val; + swp->labels[k] = findlabel(lab); + ++swp->ncases; + if (Aflag >= 2 && swp->ncases == 258) + warning("more than 257 cases in a switch\n"); +} +void swgen(Swtch swp) { + int *buckets, k, n; + long *v = swp->values; + + buckets = newarray(swp->ncases + 1, + sizeof *buckets, FUNC); + for (n = k = 0; k < swp->ncases; k++, n++) { + buckets[n] = k; + while (n > 0 && den(n-1, k) >= density) + n--; + } + buckets[n] = swp->ncases; + swcode(swp, buckets, 0, n - 1); +} +void swcode(Swtch swp, int b[], int lb, int ub) { + int hilab, lolab, l, u, k = (lb + ub)/2; + long *v = swp->values; + + if (k > lb && k < ub) { + lolab = genlabel(1); + hilab = genlabel(1); + } else if (k > lb) { + lolab = genlabel(1); + hilab = swp->deflab->u.l.label; + } else if (k < ub) { + lolab = swp->deflab->u.l.label; + hilab = genlabel(1); + } else + lolab = hilab = swp->deflab->u.l.label; + l = b[k]; + u = b[k+1] - 1; + if (u - l + 1 <= 3) + { + int i; + for (i = l; i <= u; i++) + cmp(EQ, swp->sym, v[i], swp->labels[i]->u.l.label); + if (k > lb && k < ub) + cmp(GT, swp->sym, v[u], hilab); + else if (k > lb) + cmp(GT, swp->sym, v[u], hilab); + else if (k < ub) + cmp(LT, swp->sym, v[l], lolab); + else + assert(lolab == hilab), + branch(lolab); + walk(NULL, 0, 0); + } + else { + Tree e; + Type ty = signedint(swp->sym->type); + Symbol table = genident(STATIC, + array(voidptype, u - l + 1, 0), GLOBAL); + (*IR->defsymbol)(table); + if (!isunsigned(swp->sym->type) || v[l] != 0) + cmp(LT, swp->sym, v[l], lolab); + cmp(GT, swp->sym, v[u], hilab); + e = (*optree['-'])(SUB, cast(idtree(swp->sym), ty), cnsttree(ty, v[l])); + if (e->type->size < unsignedptr->size) + e = cast(e, unsignedlong); + walk(tree(JUMP, voidtype, + rvalue((*optree['+'])(ADD, pointer(idtree(table)), e)), NULL), + 0, 0); + code(Switch); + codelist->u.swtch.table = table; + codelist->u.swtch.sym = swp->sym; + codelist->u.swtch.deflab = swp->deflab; + codelist->u.swtch.size = u - l + 1; + codelist->u.swtch.values = &v[l]; + codelist->u.swtch.labels = &swp->labels[l]; + if (v[u] - v[l] + 1 >= 10000) + warning("switch generates a huge table\n"); + } + if (k > lb) { + assert(lolab != swp->deflab->u.l.label); + definelab(lolab); + swcode(swp, b, lb, k - 1); + } + if (k < ub) { + assert(hilab != swp->deflab->u.l.label); + definelab(hilab); + swcode(swp, b, k + 1, ub); + } +} +static void cmp(int op, Symbol p, long n, int lab) { + Type ty = signedint(p->type); + + listnodes(eqtree(op, + cast(idtree(p), ty), + cnsttree(ty, n)), + lab, 0); +} +void retcode(Tree p) { + Type ty; + + if (p == NULL) { + if (events.returns) + apply(events.returns, cfunc, NULL); + return; + } + p = pointer(p); + ty = assign(freturn(cfunc->type), p); + if (ty == NULL) { + error("illegal return type; found `%t' expected `%t'\n", + p->type, freturn(cfunc->type)); + return; + } + p = cast(p, ty); + if (retv) + { + if (iscallb(p)) + p = tree(RIGHT, p->type, + tree(CALL+B, p->type, + p->kids[0]->kids[0], idtree(retv)), + rvalue(idtree(retv))); + else + p = asgntree(ASGN, rvalue(idtree(retv)), p); + walk(p, 0, 0); + if (events.returns) + apply(events.returns, cfunc, rvalue(idtree(retv))); + return; + } + if (events.returns) + { + Symbol t1 = genident(AUTO, p->type, level); + addlocal(t1); + walk(asgn(t1, p), 0, 0); + apply(events.returns, cfunc, idtree(t1)); + p = idtree(t1); + } + if (!isfloat(p->type)) + p = cast(p, promote(p->type)); + if (isptr(p->type)) + { + Symbol q = localaddr(p); + if (q && (q->computed || q->generated)) + warning("pointer to a %s is an illegal return value\n", + q->scope == PARAM ? "parameter" : "local"); + else if (q) + warning("pointer to %s `%s' is an illegal return value\n", + q->scope == PARAM ? "parameter" : "local", q->name); + } + walk(tree(mkop(RET,p->type), p->type, p, NULL), 0, 0); +} +void definelab(int lab) { + Code cp; + Symbol p = findlabel(lab); + + assert(lab); + walk(NULL, 0, 0); + code(Label)->u.forest = newnode(LABEL+V, NULL, NULL, p); + for (cp = codelist->prev; cp->kind <= Label; ) + cp = cp->prev; + while ( cp->kind == Jump + && cp->u.forest->kids[0] + && specific(cp->u.forest->kids[0]->op) == ADDRG+P + && cp->u.forest->kids[0]->syms[0] == p) { + assert(cp->u.forest->kids[0]->syms[0]->u.l.label == lab); + p->ref--; + assert(cp->next); + assert(cp->prev); + cp->prev->next = cp->next; + cp->next->prev = cp->prev; + cp = cp->prev; + while (cp->kind <= Label) + cp = cp->prev; + } +} +Node jump(int lab) { + Symbol p = findlabel(lab); + + p->ref++; + return newnode(JUMP+V, newnode(ADDRG+ttob(voidptype), NULL, NULL, p), + NULL, NULL); +} +void branch(int lab) { + Code cp; + Symbol p = findlabel(lab); + + assert(lab); + walk(NULL, 0, 0); + code(Label)->u.forest = jump(lab); + for (cp = codelist->prev; cp->kind < Label; ) + cp = cp->prev; + while ( cp->kind == Label + && cp->u.forest->op == LABEL+V + && !equal(cp->u.forest->syms[0], p)) { + equatelab(cp->u.forest->syms[0], p); + assert(cp->next); + assert(cp->prev); + cp->prev->next = cp->next; + cp->next->prev = cp->prev; + cp = cp->prev; + while (cp->kind < Label) + cp = cp->prev; + } + if (cp->kind == Jump || cp->kind == Switch) { + p->ref--; + codelist->prev->next = NULL; + codelist = codelist->prev; + } else { + codelist->kind = Jump; + if (cp->kind == Label + && cp->u.forest->op == LABEL+V + && equal(cp->u.forest->syms[0], p)) + warning("source code specifies an infinite loop"); + } +} +void equatelab(Symbol old, Symbol new) { + assert(old->u.l.equatedto == NULL); + old->u.l.equatedto = new; + new->ref++; +} +static int equal(Symbol lprime, Symbol dst) { + assert(dst && lprime); + for ( ; dst; dst = dst->u.l.equatedto) + if (lprime == dst) + return 1; + return 0; +} +/* dostmt - do statement while ( expression ) */ +static void dostmt(int lab, Swtch swp, int lev) { + refinc *= 10.0; + t = gettok(); + definelab(lab); + statement(lab, swp, lev); + definelab(lab + 1); + expect(WHILE); + expect('('); + definept(NULL); + walk(conditional(')'), lab, 0); + if (findlabel(lab + 2)->ref) + definelab(lab + 2); +} + +/* foldcond - check if initial test in for(e1;e2;e3) S is necessary */ +static int foldcond(Tree e1, Tree e2) { + int op = generic(e2->op); + Symbol v; + + if (e1 == 0 || e2 == 0) + return 0; + if (generic(e1->op) == ASGN && isaddrop(e1->kids[0]->op) + && generic(e1->kids[1]->op) == CNST) { + v = e1->kids[0]->u.sym; + e1 = e1->kids[1]; + } else + return 0; + if ((op==LE || op==LT || op==EQ || op==NE || op==GT || op==GE) + && generic(e2->kids[0]->op) == INDIR + && e2->kids[0]->kids[0]->u.sym == v + && e2->kids[1]->op == e1->op) { + e1 = simplify(op, e2->type, e1, e2->kids[1]); + if (e1->op == CNST+I) + return e1->u.v.i; + } + return 0; +} + +/* localaddr - returns q if p yields the address of local/parameter q; otherwise returns 0 */ +static Symbol localaddr(Tree p) { + if (p == NULL) + return NULL; + switch (generic(p->op)) { + case INDIR: case CALL: case ARG: + return NULL; + case ADDRL: case ADDRF: + return p->u.sym; + case RIGHT: case ASGN: + if (p->kids[1]) + return localaddr(p->kids[1]); + return localaddr(p->kids[0]); + case COND: { + Symbol q; + assert(p->kids[1] && p->kids[1]->op == RIGHT); + if ((q = localaddr(p->kids[1]->kids[0])) != NULL) + return q; + return localaddr(p->kids[1]->kids[1]); + } + default: { + Symbol q; + if (p->kids[0] && (q = localaddr(p->kids[0])) != NULL) + return q; + return localaddr(p->kids[1]); + } + } +} + +/* whilestmt - while ( expression ) statement */ +static void whilestmt(int lab, Swtch swp, int lev) { + Coordinate pt; + Tree e; + + refinc *= 10.0; + t = gettok(); + expect('('); + walk(NULL, 0, 0); + pt = src; + e = texpr(conditional, ')', FUNC); + branch(lab + 1); + definelab(lab); + statement(lab, swp, lev); + definelab(lab + 1); + definept(&pt); + walk(e, lab, 0); + if (findlabel(lab + 2)->ref) + definelab(lab + 2); +} diff --git a/src/tools/lcc/src/string.c b/src/tools/lcc/src/string.c new file mode 100644 index 0000000..73cfc85 --- /dev/null +++ b/src/tools/lcc/src/string.c @@ -0,0 +1,122 @@ +#include "c.h" + + +static struct string { + char *str; + int len; + struct string *link; +} *buckets[1024]; +static int scatter[] = { /* map characters to random values */ + 2078917053, 143302914, 1027100827, 1953210302, 755253631, + 2002600785, 1405390230, 45248011, 1099951567, 433832350, + 2018585307, 438263339, 813528929, 1703199216, 618906479, + 573714703, 766270699, 275680090, 1510320440, 1583583926, + 1723401032, 1965443329, 1098183682, 1636505764, 980071615, + 1011597961, 643279273, 1315461275, 157584038, 1069844923, + 471560540, 89017443, 1213147837, 1498661368, 2042227746, + 1968401469, 1353778505, 1300134328, 2013649480, 306246424, + 1733966678, 1884751139, 744509763, 400011959, 1440466707, + 1363416242, 973726663, 59253759, 1639096332, 336563455, + 1642837685, 1215013716, 154523136, 593537720, 704035832, + 1134594751, 1605135681, 1347315106, 302572379, 1762719719, + 269676381, 774132919, 1851737163, 1482824219, 125310639, + 1746481261, 1303742040, 1479089144, 899131941, 1169907872, + 1785335569, 485614972, 907175364, 382361684, 885626931, + 200158423, 1745777927, 1859353594, 259412182, 1237390611, + 48433401, 1902249868, 304920680, 202956538, 348303940, + 1008956512, 1337551289, 1953439621, 208787970, 1640123668, + 1568675693, 478464352, 266772940, 1272929208, 1961288571, + 392083579, 871926821, 1117546963, 1871172724, 1771058762, + 139971187, 1509024645, 109190086, 1047146551, 1891386329, + 994817018, 1247304975, 1489680608, 706686964, 1506717157, + 579587572, 755120366, 1261483377, 884508252, 958076904, + 1609787317, 1893464764, 148144545, 1415743291, 2102252735, + 1788268214, 836935336, 433233439, 2055041154, 2109864544, + 247038362, 299641085, 834307717, 1364585325, 23330161, + 457882831, 1504556512, 1532354806, 567072918, 404219416, + 1276257488, 1561889936, 1651524391, 618454448, 121093252, + 1010757900, 1198042020, 876213618, 124757630, 2082550272, + 1834290522, 1734544947, 1828531389, 1982435068, 1002804590, + 1783300476, 1623219634, 1839739926, 69050267, 1530777140, + 1802120822, 316088629, 1830418225, 488944891, 1680673954, + 1853748387, 946827723, 1037746818, 1238619545, 1513900641, + 1441966234, 367393385, 928306929, 946006977, 985847834, + 1049400181, 1956764878, 36406206, 1925613800, 2081522508, + 2118956479, 1612420674, 1668583807, 1800004220, 1447372094, + 523904750, 1435821048, 923108080, 216161028, 1504871315, + 306401572, 2018281851, 1820959944, 2136819798, 359743094, + 1354150250, 1843084537, 1306570817, 244413420, 934220434, + 672987810, 1686379655, 1301613820, 1601294739, 484902984, + 139978006, 503211273, 294184214, 176384212, 281341425, + 228223074, 147857043, 1893762099, 1896806882, 1947861263, + 1193650546, 273227984, 1236198663, 2116758626, 489389012, + 593586330, 275676551, 360187215, 267062626, 265012701, + 719930310, 1621212876, 2108097238, 2026501127, 1865626297, + 894834024, 552005290, 1404522304, 48964196, 5816381, + 1889425288, 188942202, 509027654, 36125855, 365326415, + 790369079, 264348929, 513183458, 536647531, 13672163, + 313561074, 1730298077, 286900147, 1549759737, 1699573055, + 776289160, 2143346068, 1975249606, 1136476375, 262925046, + 92778659, 1856406685, 1884137923, 53392249, 1735424165, + 1602280572 +}; +char *string(const char *str) { + const char *s; + + for (s = str; *s; s++) + ; + return stringn(str, s - str); +} +char *stringd(long n) { + char str[25], *s = str + sizeof (str); + unsigned long m; + + if (n == LONG_MIN) + m = (unsigned long)LONG_MAX + 1; + else if (n < 0) + m = -n; + else + m = n; + do + *--s = m%10 + '0'; + while ((m /= 10) != 0); + if (n < 0) + *--s = '-'; + return stringn(s, str + sizeof (str) - s); +} +char *stringn(const char *str, int len) { + int i; + unsigned int h; + const char *end; + struct string *p; + + assert(str); + for (h = 0, i = len, end = str; i > 0; i--) + h = (h<<1) + scatter[*(unsigned char *)end++]; + h &= NELEMS(buckets)-1; + for (p = buckets[h]; p; p = p->link) + if (len == p->len) { + const char *s1 = str; + char *s2 = p->str; + do { + if (s1 == end) + return p->str; + } while (*s1++ == *s2++); + } + { + static char *next, *strlimit; + if (len + 1 >= strlimit - next) { + int n = len + 4*1024; + next = allocate(n, PERM); + strlimit = next + n; + } + NEW(p, PERM); + p->len = len; + for (p->str = next; str < end; ) + *next++ = *str++; + *next++ = 0; + p->link = buckets[h]; + buckets[h] = p; + return p->str; + } +} diff --git a/src/tools/lcc/src/sym.c b/src/tools/lcc/src/sym.c new file mode 100644 index 0000000..2a1cfeb --- /dev/null +++ b/src/tools/lcc/src/sym.c @@ -0,0 +1,314 @@ +#include "c.h" +#include + + +#define equalp(x) v.x == p->sym.u.c.v.x + +struct table { + int level; + Table previous; + struct entry { + struct symbol sym; + struct entry *link; + } *buckets[256]; + Symbol all; +}; +#define HASHSIZE NELEMS(((Table)0)->buckets) +static struct table + cns = { CONSTANTS }, + ext = { GLOBAL }, + ids = { GLOBAL }, + tys = { GLOBAL }; +Table constants = &cns; +Table externals = &ext; +Table identifiers = &ids; +Table globals = &ids; +Table types = &tys; +Table labels; +int level = GLOBAL; +static int tempid; +List loci, symbols; + +Table table(Table tp, int level) { + Table new; + + NEW0(new, FUNC); + new->previous = tp; + new->level = level; + if (tp) + new->all = tp->all; + return new; +} +void foreach(Table tp, int lev, void (*apply)(Symbol, void *), void *cl) { + assert(tp); + while (tp && tp->level > lev) + tp = tp->previous; + if (tp && tp->level == lev) { + Symbol p; + Coordinate sav; + sav = src; + for (p = tp->all; p && p->scope == lev; p = p->up) { + src = p->src; + (*apply)(p, cl); + } + src = sav; + } +} +void enterscope(void) { + if (++level == LOCAL) + tempid = 0; +} +void exitscope(void) { + rmtypes(level); + if (types->level == level) + types = types->previous; + if (identifiers->level == level) { + if (Aflag >= 2) { + int n = 0; + Symbol p; + for (p = identifiers->all; p && p->scope == level; p = p->up) + if (++n > 127) { + warning("more than 127 identifiers declared in a block\n"); + break; + } + } + identifiers = identifiers->previous; + } + assert(level >= GLOBAL); + --level; +} +Symbol install(const char *name, Table *tpp, int level, int arena) { + Table tp = *tpp; + struct entry *p; + unsigned h = (unsigned long)name&(HASHSIZE-1); + + assert(level == 0 || level >= tp->level); + if (level > 0 && tp->level < level) + tp = *tpp = table(tp, level); + NEW0(p, arena); + p->sym.name = (char *)name; + p->sym.scope = level; + p->sym.up = tp->all; + tp->all = &p->sym; + p->link = tp->buckets[h]; + tp->buckets[h] = p; + return &p->sym; +} +Symbol relocate(const char *name, Table src, Table dst) { + struct entry *p, **q; + Symbol *r; + unsigned h = (unsigned long)name&(HASHSIZE-1); + + for (q = &src->buckets[h]; *q; q = &(*q)->link) + if (name == (*q)->sym.name) + break; + assert(*q); + /* + Remove the entry from src's hash chain + and from its list of all symbols. + */ + p = *q; + *q = (*q)->link; + for (r = &src->all; *r && *r != &p->sym; r = &(*r)->up) + ; + assert(*r == &p->sym); + *r = p->sym.up; + /* + Insert the entry into dst's hash chain + and into its list of all symbols. + Return the symbol-table entry. + */ + p->link = dst->buckets[h]; + dst->buckets[h] = p; + p->sym.up = dst->all; + dst->all = &p->sym; + return &p->sym; +} +Symbol lookup(const char *name, Table tp) { + struct entry *p; + unsigned h = (unsigned long)name&(HASHSIZE-1); + + assert(tp); + do + for (p = tp->buckets[h]; p; p = p->link) + if (name == p->sym.name) + return &p->sym; + while ((tp = tp->previous) != NULL); + return NULL; +} +int genlabel(int n) { + static int label = 1; + + label += n; + return label - n; +} +Symbol findlabel(int lab) { + struct entry *p; + unsigned h = lab&(HASHSIZE-1); + + for (p = labels->buckets[h]; p; p = p->link) + if (lab == p->sym.u.l.label) + return &p->sym; + NEW0(p, FUNC); + p->sym.name = stringd(lab); + p->sym.scope = LABELS; + p->sym.up = labels->all; + labels->all = &p->sym; + p->link = labels->buckets[h]; + labels->buckets[h] = p; + p->sym.generated = 1; + p->sym.u.l.label = lab; + (*IR->defsymbol)(&p->sym); + return &p->sym; +} +Symbol constant(Type ty, Value v) { + struct entry *p; + unsigned h = v.u&(HASHSIZE-1); + + ty = unqual(ty); + for (p = constants->buckets[h]; p; p = p->link) + if (eqtype(ty, p->sym.type, 1)) + switch (ty->op) { + case INT: if (equalp(i)) return &p->sym; break; + case UNSIGNED: if (equalp(u)) return &p->sym; break; + case FLOAT: if (equalp(d)) return &p->sym; break; + case FUNCTION: if (equalp(g)) return &p->sym; break; + case ARRAY: + case POINTER: if (equalp(p)) return &p->sym; break; + default: assert(0); + } + NEW0(p, PERM); + p->sym.name = vtoa(ty, v); + p->sym.scope = CONSTANTS; + p->sym.type = ty; + p->sym.sclass = STATIC; + p->sym.u.c.v = v; + p->link = constants->buckets[h]; + p->sym.up = constants->all; + constants->all = &p->sym; + constants->buckets[h] = p; + if (ty->u.sym && !ty->u.sym->addressed) + (*IR->defsymbol)(&p->sym); + p->sym.defined = 1; + return &p->sym; +} +Symbol intconst(int n) { + Value v; + + v.i = n; + return constant(inttype, v); +} +Symbol genident(int scls, Type ty, int lev) { + Symbol p; + + NEW0(p, lev >= LOCAL ? FUNC : PERM); + p->name = stringd(genlabel(1)); + p->scope = lev; + p->sclass = scls; + p->type = ty; + p->generated = 1; + if (lev == GLOBAL) + (*IR->defsymbol)(p); + return p; +} + +Symbol temporary(int scls, Type ty) { + Symbol p; + + NEW0(p, FUNC); + p->name = stringd(++tempid); + p->scope = level < LOCAL ? LOCAL : level; + p->sclass = scls; + p->type = ty; + p->temporary = 1; + p->generated = 1; + return p; +} +Symbol newtemp(int sclass, int tc, int size) { + Symbol p = temporary(sclass, btot(tc, size)); + + (*IR->local)(p); + p->defined = 1; + return p; +} + +Symbol allsymbols(Table tp) { + return tp->all; +} + +void locus(Table tp, Coordinate *cp) { + loci = append(cp, loci); + symbols = append(allsymbols(tp), symbols); +} + +void use(Symbol p, Coordinate src) { + Coordinate *cp; + + NEW(cp, PERM); + *cp = src; + p->uses = append(cp, p->uses); +} +/* findtype - find type ty in identifiers */ +Symbol findtype(Type ty) { + Table tp = identifiers; + int i; + struct entry *p; + + assert(tp); + do + for (i = 0; i < HASHSIZE; i++) + for (p = tp->buckets[i]; p; p = p->link) + if (p->sym.type == ty && p->sym.sclass == TYPEDEF) + return &p->sym; + while ((tp = tp->previous) != NULL); + return NULL; +} + +/* mkstr - make a string constant */ +Symbol mkstr(char *str) { + Value v; + Symbol p; + + v.p = str; + p = constant(array(chartype, strlen(v.p) + 1, 0), v); + if (p->u.c.loc == NULL) + p->u.c.loc = genident(STATIC, p->type, GLOBAL); + return p; +} + +/* mksymbol - make a symbol for name, install in &globals if sclass==EXTERN */ +Symbol mksymbol(int sclass, const char *name, Type ty) { + Symbol p; + + if (sclass == EXTERN) + p = install(string(name), &globals, GLOBAL, PERM); + else { + NEW0(p, PERM); + p->name = string(name); + p->scope = GLOBAL; + } + p->sclass = sclass; + p->type = ty; + (*IR->defsymbol)(p); + p->defined = 1; + return p; +} + +/* vtoa - return string for the constant v of type ty */ +char *vtoa(Type ty, Value v) { + + ty = unqual(ty); + switch (ty->op) { + case INT: return stringd(v.i); + case UNSIGNED: return stringf((v.u&~0x7FFF) ? "0x%X" : "%U", v.u); + case FLOAT: return stringf("%g", (double)v.d); + case ARRAY: + if (ty->type == chartype || ty->type == signedchar + || ty->type == unsignedchar) + return v.p; + return stringf("%p", v.p); + case POINTER: return stringf("%p", v.p); + case FUNCTION: return stringf("%p", v.g); + } + assert(0); return NULL; +} diff --git a/src/tools/lcc/src/symbolic.c b/src/tools/lcc/src/symbolic.c new file mode 100644 index 0000000..affa67a --- /dev/null +++ b/src/tools/lcc/src/symbolic.c @@ -0,0 +1,494 @@ +#include +#include +#include "c.h" + +#define I(f) s_##f + +static Node *tail; +static int off, maxoff, uid = 0, verbose = 0, html = 0; + +static const char *yyBEGIN(const char *tag) { + if (html) + print("<%s>", tag); + return tag; +} + +static void yyEND(const char *tag) { + if (html) + print("", tag); + if (isupper(*tag)) + print("\n"); +} + +#define BEGIN(tag) do { const char *yytag=yyBEGIN(#tag); +#define END yyEND(yytag); } while (0) +#define ITEM BEGIN(li) +#define START BEGIN(LI) +#define ANCHOR(attr,code) do { const char *yytag="a"; if (html) { printf(""); } +#define NEWLINE print(html ? "
    \n" : "\n") + +static void emitCoord(Coordinate src) { + if (src.file && *src.file) { + ANCHOR(href,print("%s", src.file)); print("%s", src.file); END; + print(":"); + } + print("%d.%d", src.y, src.x); +} + +static void emitString(int len, const char *s) { + for ( ; len-- > 0; s++) + if (*s == '&' && html) + print("&"); + else if (*s == '<' && html) + print("<"); + else if (*s == '>' && html) + print("<"); + else if (*s == '"' || *s == '\\') + print("\\%c", *s); + else if (*s >= ' ' && *s < 0177) + print("%c", *s); + else + print("\\%d%d%d", (*s>>6)&3, (*s>>3)&7, *s&7); +} + +static void emitSymRef(Symbol p) { + (*IR->defsymbol)(p); + ANCHOR(href,print("#%s", p->x.name)); BEGIN(code); print("%s", p->name); END; END; +} + +static void emitSymbol(Symbol p) { + (*IR->defsymbol)(p); + ANCHOR(name,print("%s", p->x.name)); BEGIN(code); print("%s", p->name); END; END; + BEGIN(ul); +#define xx(field,code) ITEM; if (!html) print(" "); print(#field "="); code; END + if (verbose && (src.y || src.x)) + xx(src,emitCoord(p->src)); + xx(type,print("%t", p->type)); + xx(sclass,print("%k", p->sclass)); + switch (p->scope) { + case CONSTANTS: xx(scope,print("CONSTANTS")); break; + case LABELS: xx(scope,print("LABELS")); break; + case GLOBAL: xx(scope,print("GLOBAL")); break; + case PARAM: xx(scope,print("PARAM")); break; + case LOCAL: xx(scope,print("LOCAL")); break; + default: + if (p->scope > LOCAL) + xx(scope,print("LOCAL+%d", p->scope-LOCAL)); + else + xx(scope,print("%d", p->scope)); + } + if (p->scope >= PARAM && p->sclass != STATIC) + xx(offset,print("%d", p->x.offset)); + xx(ref,print("%f", p->ref)); + if (p->temporary && p->u.t.cse) + xx(u.t.cse,print("%p", p->u.t.cse)); + END; +#undef xx +} + +/* address - initialize q for addressing expression p+n */ +static void I(address)(Symbol q, Symbol p, long n) { + q->name = stringf("%s%s%D", p->name, n > 0 ? "+" : "", n); + (*IR->defsymbol)(q); + START; print("address "); emitSymbol(q); END; +} + +/* blockbeg - start a block */ +static void I(blockbeg)(Env *e) { + e->offset = off; + START; print("blockbeg off=%d", off); END; +} + +/* blockend - start a block */ +static void I(blockend)(Env *e) { + if (off > maxoff) + maxoff = off; + START; print("blockend off=%d", off); END; + off = e->offset; +} + +/* defaddress - initialize an address */ +static void I(defaddress)(Symbol p){ + START; print("defaddress "); emitSymRef(p); END; +} + +/* defconst - define a constant */ +static void I(defconst)(int suffix, int size, Value v) { + START; + print("defconst "); + switch (suffix) { + case I: + print("int.%d ", size); + BEGIN(code); + if (size > sizeof (int)) + print("%D", v.i); + else + print("%d", (int)v.i); + END; + break; + case U: + print("unsigned.%d ", size); + BEGIN(code); + if (size > sizeof (unsigned)) + print("%U", v.u); + else + print("%u", (unsigned)v.u); + END; + break; + case P: print("void*.%d ", size); BEGIN(code); print("%p", v.p); END; break; + case F: print("float.%d ", size); BEGIN(code); print("%g", (double)v.d); END; break; + default: assert(0); + } + END; +} + +/* defstring - emit a string constant */ +static void I(defstring)(int len, char *s) { + START; print("defstring "); + BEGIN(code); print("\""); emitString(len, s); print("\""); END; + END; +} + +/* defsymbol - define a symbol: initialize p->x */ +static void I(defsymbol)(Symbol p) { + if (p->x.name == NULL) + p->x.name = stringd(++uid); +} + +/* emit - emit the dags on list p */ +static void I(emit)(Node p){ + ITEM; + if (!html) + print(" "); + for (; p; p = p->x.next) { + if (p->op == LABEL+V) { + assert(p->syms[0]); + ANCHOR(name,print("%s", p->syms[0]->x.name)); + BEGIN(code); print("%s", p->syms[0]->name); END; + END; + print(":"); + } else { + int i; + if (p->x.listed) { + BEGIN(strong); print("%d", p->x.inst); END; print("'"); + print(" %s", opname(p->op)); + } else + print("%d. %s", p->x.inst, opname(p->op)); + if (p->count > 1) + print(" count=%d", p->count); + for (i = 0; i < NELEMS(p->kids) && p->kids[i]; i++) + print(" #%d", p->kids[i]->x.inst); + if (generic(p->op) == CALL && p->syms[0] && p->syms[0]->type) + print(" {%t}", p->syms[0]->type); + else + for (i = 0; i < NELEMS(p->syms) && p->syms[i]; i++) { + print(" "); + if (p->syms[i]->scope == CONSTANTS) + print(p->syms[i]->name); + else + emitSymRef(p->syms[i]); + } + } + NEWLINE; + } + END; +} + +/* export - announce p as exported */ +static void I(export)(Symbol p) { + START; print("export "); emitSymRef(p); END; +} + +/* function - generate code for a function */ +static void I(function)(Symbol f, Symbol caller[], Symbol callee[], int ncalls) { + int i; + + (*IR->defsymbol)(f); + off = 0; + for (i = 0; caller[i] && callee[i]; i++) { + off = roundup(off, caller[i]->type->align); + caller[i]->x.offset = callee[i]->x.offset = off; + off += caller[i]->type->size; + } + if (!html) { + print("function "); + emitSymbol(f); + print(" ncalls=%d\n", ncalls); + for (i = 0; caller[i]; i++) + START; print("caller "); emitSymbol(caller[i]); END; + for (i = 0; callee[i]; i++) + START; print("callee "); emitSymbol(callee[i]); END; + } else { + START; + print("function"); + BEGIN(UL); +#define xx(field,code) ITEM; print(#field "="); code; END + xx(f,emitSymbol(f)); + xx(ncalls,print("%d", ncalls)); + if (caller[0]) { + ITEM; print("caller"); BEGIN(OL); + for (i = 0; caller[i]; i++) + ITEM; emitSymbol(caller[i]); END; + END; END; + ITEM; print("callee"); BEGIN(OL); + for (i = 0; callee[i]; i++) + ITEM; emitSymbol(callee[i]); END; + END; END; + } else { + xx(caller,BEGIN(em); print("empty"); END); + xx(callee,BEGIN(em); print("empty"); END); + } + END; + END; + } + maxoff = off = 0; + gencode(caller, callee); + if (html) + START; print("emitcode"); BEGIN(ul); emitcode(); END; END; + else + emitcode(); + START; print("maxoff=%d", maxoff); END; +#undef xx +} + +/* visit - generate code for *p */ +static int visit(Node p, int n) { + if (p && p->x.inst == 0) { + p->x.inst = ++n; + n = visit(p->kids[0], n); + n = visit(p->kids[1], n); + *tail = p; + tail = &p->x.next; + } + return n; +} + +/* gen0 - generate code for the dags on list p */ +static Node I(gen)(Node p) { + int n; + Node nodelist; + + tail = &nodelist; + for (n = 0; p; p = p->link) { + switch (generic(p->op)) { /* check for valid forest */ + case CALL: + assert(IR->wants_dag || p->count == 0); + break; + case ARG: + case ASGN: case JUMP: case LABEL: case RET: + case EQ: case GE: case GT: case LE: case LT: case NE: + assert(p->count == 0); + break; + case INDIR: + assert(IR->wants_dag && p->count > 0); + break; + default: + assert(0); + } + check(p); + p->x.listed = 1; + n = visit(p, n); + } + *tail = 0; + return nodelist; +} + +/* global - announce a global */ +static void I(global)(Symbol p) { + START; print("global "); emitSymbol(p); END; +} + +/* import - import a symbol */ +static void I(import)(Symbol p) { + START; print("import "); emitSymRef(p); END; +} + +/* local - local variable */ +static void I(local)(Symbol p) { + if (p->temporary) + p->name = stringf("t%s", p->name); + (*IR->defsymbol)(p); + off = roundup(off, p->type->align); + p->x.offset = off; + off += p->type->size; + START; print(p->temporary ? "temporary " : "local "); emitSymbol(p); END; +} + +/* progbeg - beginning of program */ +static void I(progbeg)(int argc, char *argv[]) { + int i; + + for (i = 1; i < argc; i++) + if (strcmp(argv[i], "-v") == 0) + verbose++; + else if (strcmp(argv[i], "-html") == 0) + html++; + if (html) { + print("\n"); + print(""); + BEGIN(head); + if (firstfile && *firstfile) + BEGIN(title); emitString(strlen(firstfile), firstfile); END; + print("\n"); + END; + print("\n"); + if (firstfile && *firstfile) + BEGIN(h1); emitString(strlen(firstfile), firstfile); END; + BEGIN(P); BEGIN(em); + print("Links lead from uses of identifiers and labels to their definitions."); + END; END; + print("
      \n"); + START; + print("progbeg"); + BEGIN(ol); + for (i = 1; i < argc; i++) { + ITEM; + BEGIN(code); print("\""); emitString(strlen(argv[i]), argv[i]); print("\""); END; + END; + } + END; + END; + } +} + +/* progend - end of program */ +static void I(progend)(void) { + START; print("progend"); END; + if (html) { + time_t t; + print("
    \n"); + time(&t); + print("
    %s
    \n", ctime(&t)); + print("\n"); + } +} + +/* segment - switch to segment s */ +static void I(segment)(int s) { + START; print("segment %s", &"text\0bss\0.data\0lit\0.sym\0."[5*s-5]); END; +} + +/* space - initialize n bytes of space */ +static void I(space)(int n) { + START; print("space %d", n); END; +} + +static void I(stabblock)(int brace, int lev, Symbol *p) {} + +/* stabend - finalize stab output */ +static void I(stabend)(Coordinate *cp, Symbol p, Coordinate **cpp, Symbol *sp, Symbol *stab) { + int i; + + if (p) + emitSymRef(p); + print("\n"); + if (cpp && sp) + for (i = 0; cpp[i] && sp[i]; i++) { + print("%w.%d: ", cpp[i], cpp[i]->x); + emitSymRef(sp[i]); + print("\n"); + } +} + +static void I(stabfend)(Symbol p, int lineno) {} +static void I(stabinit)(char *file, int argc, char *argv[]) {} + +/* stabline - emit line number information for source coordinate *cp */ +static void I(stabline)(Coordinate *cp) { + if (cp->file) + print("%s:", cp->file); + print("%d.%d:\n", cp->y, cp->x); +} + +static void I(stabsym)(Symbol p) {} +static void I(stabtype)(Symbol p) {} + +Interface symbolicIR = { + {1, 1, 0}, /* char */ + {2, 2, 0}, /* short */ + {4, 4, 0}, /* int */ + {4, 4, 0}, /* long */ + {4, 4, 0}, /* long long */ + {4, 4, 1}, /* float */ + {8, 8, 1}, /* double */ + {8, 8, 1}, /* long double */ + {4, 4, 0}, /* T* */ + {0, 4, 0}, /* struct */ + 0, /* little_endian */ + 0, /* mulops_calls */ + 0, /* wants_callb */ + 1, /* wants_argb */ + 1, /* left_to_right */ + 1, /* wants_dag */ + 0, /* unsigned_char */ + I(address), + I(blockbeg), + I(blockend), + I(defaddress), + I(defconst), + I(defstring), + I(defsymbol), + I(emit), + I(export), + I(function), + I(gen), + I(global), + I(import), + I(local), + I(progbeg), + I(progend), + I(segment), + I(space), + I(stabblock), + I(stabend), + I(stabfend), + I(stabinit), + I(stabline), + I(stabsym), + I(stabtype) +}; + +Interface symbolic64IR = { + {1, 1, 0}, /* char */ + {2, 2, 0}, /* short */ + {4, 4, 0}, /* int */ + {8, 8, 0}, /* long */ + {8, 8, 0}, /* long long */ + {4, 4, 1}, /* float */ + {8, 8, 1}, /* double */ + {8, 8, 1}, /* long double */ + {8, 8, 0}, /* T* */ + {0, 1, 0}, /* struct */ + 1, /* little_endian */ + 0, /* mulops_calls */ + 0, /* wants_callb */ + 1, /* wants_argb */ + 1, /* left_to_right */ + 1, /* wants_dag */ + 0, /* unsigned_char */ + I(address), + I(blockbeg), + I(blockend), + I(defaddress), + I(defconst), + I(defstring), + I(defsymbol), + I(emit), + I(export), + I(function), + I(gen), + I(global), + I(import), + I(local), + I(progbeg), + I(progend), + I(segment), + I(space), + I(stabblock), + I(stabend), + I(stabfend), + I(stabinit), + I(stabline), + I(stabsym), + I(stabtype) +}; diff --git a/src/tools/lcc/src/token.h b/src/tools/lcc/src/token.h new file mode 100644 index 0000000..d309f9b --- /dev/null +++ b/src/tools/lcc/src/token.h @@ -0,0 +1,133 @@ +/* +xx(symbol, value, prec, op, optree, kind, string) +*/ +yy(0, 0, 0, 0, 0, 0, 0) +xx(FLOAT, 1, 0, 0, 0, CHAR, "float") +xx(DOUBLE, 2, 0, 0, 0, CHAR, "double") +xx(CHAR, 3, 0, 0, 0, CHAR, "char") +xx(SHORT, 4, 0, 0, 0, CHAR, "short") +xx(INT, 5, 0, 0, 0, CHAR, "int") +xx(UNSIGNED, 6, 0, 0, 0, CHAR, "unsigned") +xx(POINTER, 7, 0, 0, 0, 0, "pointer") +xx(VOID, 8, 0, 0, 0, CHAR, "void") +xx(STRUCT, 9, 0, 0, 0, CHAR, "struct") +xx(UNION, 10, 0, 0, 0, CHAR, "union") +xx(FUNCTION, 11, 0, 0, 0, 0, "function") +xx(ARRAY, 12, 0, 0, 0, 0, "array") +xx(ENUM, 13, 0, 0, 0, CHAR, "enum") +xx(LONG, 14, 0, 0, 0, CHAR, "long") +xx(CONST, 15, 0, 0, 0, CHAR, "const") +xx(VOLATILE, 16, 0, 0, 0, CHAR, "volatile") +yy(0, 17, 0, 0, 0, 0, 0) +yy(0, 18, 0, 0, 0, 0, 0) +yy(0, 19, 0, 0, 0, 0, 0) +yy(0, 20, 0, 0, 0, 0, 0) +yy(0, 21, 0, 0, 0, 0, 0) +yy(0, 22, 0, 0, 0, 0, 0) +yy(0, 23, 0, 0, 0, 0, 0) +yy(0, 24, 0, 0, 0, 0, 0) +yy(0, 25, 0, 0, 0, 0, 0) +yy(0, 26, 0, 0, 0, 0, 0) +yy(0, 27, 0, 0, 0, 0, 0) +yy(0, 28, 0, 0, 0, 0, "long long") +yy(0, 29, 0, 0, 0, 0, 0) +yy(0, 30, 0, 0, 0, 0, 0) +yy(0, 31, 0, 0, 0, 0, "const volatile") +xx(ID, 32, 0, 0, 0, ID, "identifier") +yy(0, 33, 0, 0, 0, ID, "!") +xx(FCON, 34, 0, 0, 0, ID, "floating constant") +xx(ICON, 35, 0, 0, 0, ID, "integer constant") +xx(SCON, 36, 0, 0, 0, ID, "string constant") +yy(0, 37, 13, MOD, bittree,'%', "%") +yy(0, 38, 8, BAND, bittree,ID, "&") +xx(INCR, 39, 0, ADD, addtree,ID, "++") +yy(0, 40, 0, 0, 0, ID, "(") +yy(0, 41, 0, 0, 0, ')', ")") +yy(0, 42, 13, MUL, multree,ID, "*") +yy(0, 43, 12, ADD, addtree,ID, "+") +yy(0, 44, 1, 0, 0, ',', ",") +yy(0, 45, 12, SUB, subtree,ID, "-") +yy(0, 46, 0, 0, 0, '.', ".") +yy(0, 47, 13, DIV, multree,'/', "/") +xx(DECR, 48, 0, SUB, subtree,ID, "--") +xx(DEREF, 49, 0, 0, 0, DEREF, "->") +xx(ANDAND, 50, 5, AND, andtree,ANDAND, "&&") +xx(OROR, 51, 4, OR, andtree,OROR, "||") +xx(LEQ, 52, 10, LE, cmptree,LEQ, "<=") +xx(EQL, 53, 9, EQ, eqtree, EQL, "==") +xx(NEQ, 54, 9, NE, eqtree, NEQ, "!=") +xx(GEQ, 55, 10, GE, cmptree,GEQ, ">=") +xx(RSHIFT, 56, 11, RSH, shtree, RSHIFT, ">>") +xx(LSHIFT, 57, 11, LSH, shtree, LSHIFT, "<<") +yy(0, 58, 0, 0, 0, ':', ":") +yy(0, 59, 0, 0, 0, IF, ";") +yy(0, 60, 10, LT, cmptree,'<', "<") +yy(0, 61, 2, ASGN, asgntree,'=', "=") +yy(0, 62, 10, GT, cmptree,'>', ">") +yy(0, 63, 0, 0, 0, '?', "?") +xx(ELLIPSIS, 64, 0, 0, 0, ELLIPSIS,"...") +xx(SIZEOF, 65, 0, 0, 0, ID, "sizeof") +yy(0, 66, 0, 0, 0, 0, 0) +xx(AUTO, 67, 0, 0, 0, STATIC, "auto") +xx(BREAK, 68, 0, 0, 0, IF, "break") +xx(CASE, 69, 0, 0, 0, IF, "case") +xx(CONTINUE, 70, 0, 0, 0, IF, "continue") +xx(DEFAULT, 71, 0, 0, 0, IF, "default") +xx(DO, 72, 0, 0, 0, IF, "do") +xx(ELSE, 73, 0, 0, 0, IF, "else") +xx(EXTERN, 74, 0, 0, 0, STATIC, "extern") +xx(FOR, 75, 0, 0, 0, IF, "for") +xx(GOTO, 76, 0, 0, 0, IF, "goto") +xx(IF, 77, 0, 0, 0, IF, "if") +xx(REGISTER, 78, 0, 0, 0, STATIC, "register") +xx(RETURN, 79, 0, 0, 0, IF, "return") +xx(SIGNED, 80, 0, 0, 0, CHAR, "signed") +xx(STATIC, 81, 0, 0, 0, STATIC, "static") +xx(SWITCH, 82, 0, 0, 0, IF, "switch") +xx(TYPEDEF, 83, 0, 0, 0, STATIC, "typedef") +xx(WHILE, 84, 0, 0, 0, IF, "while") +xx(TYPECODE, 85, 0, 0, 0, ID, "__typecode") +xx(FIRSTARG, 86, 0, 0, 0, ID, "__firstarg") +yy(0, 87, 0, 0, 0, 0, 0) +yy(0, 88, 0, 0, 0, 0, 0) +yy(0, 89, 0, 0, 0, 0, 0) +yy(0, 90, 0, 0, 0, 0, 0) +yy(0, 91, 0, 0, 0, '[', "[") +yy(0, 92, 0, 0, 0, 0, 0) +yy(0, 93, 0, 0, 0, ']', "]") +yy(0, 94, 7, BXOR, bittree,'^', "^") +yy(0, 95, 0, 0, 0, 0, 0) +yy(0, 96, 0, 0, 0, 0, 0) +yy(0, 97, 0, 0, 0, 0, 0) +yy(0, 98, 0, 0, 0, 0, 0) +yy(0, 99, 0, 0, 0, 0, 0) +yy(0, 100, 0, 0, 0, 0, 0) +yy(0, 101, 0, 0, 0, 0, 0) +yy(0, 102, 0, 0, 0, 0, 0) +yy(0, 103, 0, 0, 0, 0, 0) +yy(0, 104, 0, 0, 0, 0, 0) +yy(0, 105, 0, 0, 0, 0, 0) +yy(0, 106, 0, 0, 0, 0, 0) +yy(0, 107, 0, 0, 0, 0, 0) +yy(0, 108, 0, 0, 0, 0, 0) +yy(0, 109, 0, 0, 0, 0, 0) +yy(0, 110, 0, 0, 0, 0, 0) +yy(0, 111, 0, 0, 0, 0, 0) +yy(0, 112, 0, 0, 0, 0, 0) +yy(0, 113, 0, 0, 0, 0, 0) +yy(0, 114, 0, 0, 0, 0, 0) +yy(0, 115, 0, 0, 0, 0, 0) +yy(0, 116, 0, 0, 0, 0, 0) +yy(0, 117, 0, 0, 0, 0, 0) +yy(0, 118, 0, 0, 0, 0, 0) +yy(0, 119, 0, 0, 0, 0, 0) +yy(0, 120, 0, 0, 0, 0, 0) +yy(0, 121, 0, 0, 0, 0, 0) +yy(0, 122, 0, 0, 0, 0, 0) +yy(0, 123, 0, 0, 0, IF, "{") +yy(0, 124, 6, BOR, bittree,'|', "|") +yy(0, 125, 0, 0, 0, '}', "}") +yy(0, 126, 0, BCOM, 0, ID, "~") +xx(EOI, 127, 0, 0, 0, EOI, "end of input") +#undef xx +#undef yy diff --git a/src/tools/lcc/src/trace.c b/src/tools/lcc/src/trace.c new file mode 100644 index 0000000..3b9ba78 --- /dev/null +++ b/src/tools/lcc/src/trace.c @@ -0,0 +1,181 @@ +#include "c.h" + + +static char *fmt, *fp, *fmtend; /* format string, current & limit pointer */ +static Tree args; /* printf arguments */ +static Symbol frameno; /* local holding frame number */ + +/* appendstr - append str to the evolving format string, expanding it if necessary */ +static void appendstr(char *str) { + do + if (fp == fmtend) { + if (fp) { + char *s = allocate(2*(fmtend - fmt), FUNC); + strncpy(s, fmt, fmtend - fmt); + fp = s + (fmtend - fmt); + fmtend = s + 2*(fmtend - fmt); + fmt = s; + } else { + fp = fmt = allocate(80, FUNC); + fmtend = fmt + 80; + } + } + while ((*fp++ = *str++) != 0); + fp--; +} + +/* tracevalue - append format and argument to print the value of e */ +static void tracevalue(Tree e, int lev) { + Type ty = unqual(e->type); + + switch (ty->op) { + case INT: + if (ty == chartype || ty == signedchar) + appendstr("'\\x%02x'"); + else if (ty == longtype) + appendstr("0x%ld"); + else + appendstr("0x%d"); + break; + case UNSIGNED: + if (ty == chartype || ty == unsignedchar) + appendstr("'\\x%02x'"); + else if (ty == unsignedlong) + appendstr("0x%lx"); + else + appendstr("0x%x"); + break; + case FLOAT: + if (ty == longdouble) + appendstr("%Lg"); + else + appendstr("%g"); + break; + case POINTER: + if (unqual(ty->type) == chartype + || unqual(ty->type) == signedchar + || unqual(ty->type) == unsignedchar) { + static Symbol null; + if (null == NULL) + null = mkstr("(null)"); + tracevalue(cast(e, unsignedtype), lev + 1); + appendstr(" \"%.30s\""); + e = condtree(e, e, pointer(idtree(null->u.c.loc))); + } else { + appendstr("("); appendstr(typestring(ty, "")); appendstr(")0x%x"); + } + break; + case STRUCT: { + Field q; + appendstr("("); appendstr(typestring(ty, "")); appendstr("){"); + for (q = ty->u.sym->u.s.flist; q; q = q->link) { + appendstr(q->name); appendstr("="); + tracevalue(field(addrof(e), q->name), lev + 1); + if (q->link) + appendstr(","); + } + appendstr("}"); + return; + } + case UNION: + appendstr("("); appendstr(typestring(ty, "")); appendstr("){...}"); + return; + case ARRAY: + if (lev && ty->type->size > 0) { + int i; + e = pointer(e); + appendstr("{"); + for (i = 0; i < ty->size/ty->type->size; i++) { + Tree p = (*optree['+'])(ADD, e, consttree(i, inttype)); + if (isptr(p->type) && isarray(p->type->type)) + p = retype(p, p->type->type); + else + p = rvalue(p); + if (i) + appendstr(","); + tracevalue(p, lev + 1); + } + appendstr("}"); + } else + appendstr(typestring(ty, "")); + return; + default: + assert(0); + } + e = cast(e, promote(ty)); + args = tree(mkop(ARG,e->type), e->type, e, args); +} + +/* tracefinis - complete & generate the trace call to print */ +static void tracefinis(Symbol printer) { + Tree *ap; + Symbol p; + + *fp = 0; + p = mkstr(string(fmt)); + for (ap = &args; *ap; ap = &(*ap)->kids[1]) + ; + *ap = tree(ARG+P, charptype, pointer(idtree(p->u.c.loc)), 0); + walk(calltree(pointer(idtree(printer)), freturn(printer->type), args, NULL), 0, 0); + args = 0; + fp = fmtend = 0; +} + +/* tracecall - generate code to trace entry to f */ +static void tracecall(Symbol printer, Symbol f) { + int i; + Symbol counter = genident(STATIC, inttype, GLOBAL); + + defglobal(counter, BSS); + (*IR->space)(counter->type->size); + frameno = genident(AUTO, inttype, level); + addlocal(frameno); + appendstr(f->name); appendstr("#"); + tracevalue(asgn(frameno, incr(INCR, idtree(counter), consttree(1, inttype))), 0); + appendstr("("); + for (i = 0; f->u.f.callee[i]; i++) { + if (i) + appendstr(","); + appendstr(f->u.f.callee[i]->name); appendstr("="); + tracevalue(idtree(f->u.f.callee[i]), 0); + } + if (variadic(f->type)) + appendstr(",..."); + appendstr(") called\n"); + tracefinis(printer); +} + +/* tracereturn - generate code to trace return e */ +static void tracereturn(Symbol printer, Symbol f, Tree e) { + appendstr(f->name); appendstr("#"); + tracevalue(idtree(frameno), 0); + appendstr(" returned"); + if (freturn(f->type) != voidtype && e) { + appendstr(" "); + tracevalue(e, 0); + } + appendstr("\n"); + tracefinis(printer); +} + +/* trace_init - initialize for tracing */ +void trace_init(int argc, char *argv[]) { + int i; + static int inited; + + if (inited) + return; + inited = 1; + type_init(argc, argv); + if (IR) + for (i = 1; i < argc; i++) + if (strncmp(argv[i], "-t", 2) == 0 && strchr(argv[i], '=') == NULL) { + Symbol printer = mksymbol(EXTERN, + argv[i][2] ? &argv[i][2] : "printf", + ftype(inttype, ptr(qual(CONST, chartype)))); + printer->defined = 0; + attach((Apply)tracecall, printer, &events.entry); + attach((Apply)tracereturn, printer, &events.returns); + break; + } +} diff --git a/src/tools/lcc/src/tree.c b/src/tools/lcc/src/tree.c new file mode 100644 index 0000000..d2b6a91 --- /dev/null +++ b/src/tools/lcc/src/tree.c @@ -0,0 +1,223 @@ +#include "c.h" + + +int where = STMT; +static int warn; +static int nid = 1; /* identifies trees & nodes in debugging output */ +static struct nodeid { + int printed; + Tree node; +} ids[500]; /* if ids[i].node == p, then p's id is i */ + +static void printtree1(Tree, int, int); + +Tree tree(int op, Type type, Tree left, Tree right) { + Tree p; + + NEW0(p, where); + p->op = op; + p->type = type; + p->kids[0] = left; + p->kids[1] = right; + return p; +} + +Tree texpr(Tree (*f)(int), int tok, int a) { + int save = where; + Tree p; + + where = a; + p = (*f)(tok); + where = save; + return p; +} +static Tree root1(Tree p) { + if (p == NULL) + return p; + if (p->type == voidtype) + warn++; + switch (generic(p->op)) { + case COND: { + Tree q = p->kids[1]; + assert(q && q->op == RIGHT); + if (p->u.sym && q->kids[0] && generic(q->kids[0]->op) == ASGN) + q->kids[0] = root1(q->kids[0]->kids[1]); + else + q->kids[0] = root1(q->kids[0]); + if (p->u.sym && q->kids[1] && generic(q->kids[1]->op) == ASGN) + q->kids[1] = root1(q->kids[1]->kids[1]); + else + q->kids[1] = root1(q->kids[1]); + p->u.sym = 0; + if (q->kids[0] == 0 && q->kids[1] == 0) + p = root1(p->kids[0]); + } + break; + case AND: case OR: + if ((p->kids[1] = root1(p->kids[1])) == 0) + p = root1(p->kids[0]); + break; + case NOT: + if (warn++ == 0) + warning("expression with no effect elided\n"); + return root1(p->kids[0]); + case RIGHT: + if (p->kids[1] == 0) + return root1(p->kids[0]); + if (p->kids[0] && p->kids[0]->op == CALL+B + && p->kids[1] && p->kids[1]->op == INDIR+B) + /* avoid premature release of the CALL+B temporary */ + return p->kids[0]; + if (p->kids[0] && p->kids[0]->op == RIGHT + && p->kids[1] == p->kids[0]->kids[0]) + /* de-construct e++ construction */ + return p->kids[0]->kids[1]; + p = tree(RIGHT, p->type, root1(p->kids[0]), root1(p->kids[1])); + return p->kids[0] || p->kids[1] ? p : (Tree)0; + case EQ: case NE: case GT: case GE: case LE: case LT: + case ADD: case SUB: case MUL: case DIV: case MOD: + case LSH: case RSH: case BAND: case BOR: case BXOR: + if (warn++ == 0) + warning("expression with no effect elided\n"); + p = tree(RIGHT, p->type, root1(p->kids[0]), root1(p->kids[1])); + return p->kids[0] || p->kids[1] ? p : (Tree)0; + case INDIR: + if (p->type->size == 0 && unqual(p->type) != voidtype) + warning("reference to `%t' elided\n", p->type); + if (isptr(p->kids[0]->type) && isvolatile(p->kids[0]->type->type)) + warning("reference to `volatile %t' elided\n", p->type); + /* fall thru */ + case CVI: case CVF: case CVU: case CVP: + case NEG: case BCOM: case FIELD: + if (warn++ == 0) + warning("expression with no effect elided\n"); + return root1(p->kids[0]); + case ADDRL: case ADDRG: case ADDRF: case CNST: + if (needconst) + return p; + if (warn++ == 0) + warning("expression with no effect elided\n"); + return NULL; + case ARG: case ASGN: case CALL: case JUMP: case LABEL: + break; + default: assert(0); + } + return p; +} + +Tree root(Tree p) { + warn = 0; + return root1(p); +} + +char *opname(int op) { + static char *opnames[] = { + "", + "CNST", + "ARG", + "ASGN", + "INDIR", + "CVC", + "CVD", + "CVF", + "CVI", + "CVP", + "CVS", + "CVU", + "NEG", + "CALL", + "*LOAD*", + "RET", + "ADDRG", + "ADDRF", + "ADDRL", + "ADD", + "SUB", + "LSH", + "MOD", + "RSH", + "BAND", + "BCOM", + "BOR", + "BXOR", + "DIV", + "MUL", + "EQ", + "GE", + "GT", + "LE", + "LT", + "NE", + "JUMP", + "LABEL", + "AND", + "NOT", + "OR", + "COND", + "RIGHT", + "FIELD" + }, *suffixes[] = { + "0", "F", "D", "C", "S", "I", "U", "P", "V", "B", + "10","11","12","13","14","15" + }; + + if (generic(op) >= AND && generic(op) <= FIELD && opsize(op) == 0) + return opnames[opindex(op)]; + return stringf("%s%s%s", + opindex(op) > 0 && opindex(op) < NELEMS(opnames) ? + opnames[opindex(op)] : stringd(opindex(op)), + suffixes[optype(op)], opsize(op) > 0 ? stringd(opsize(op)) : ""); +} + +int nodeid(Tree p) { + int i = 1; + + ids[nid].node = p; + while (ids[i].node != p) + i++; + if (i == nid) + ids[nid++].printed = 0; + return i; +} + +/* printed - return pointer to ids[id].printed */ +int *printed(int id) { + if (id) + return &ids[id].printed; + nid = 1; + return 0; +} + +/* printtree - print tree p on fd */ +void printtree(Tree p, int fd) { + (void)printed(0); + printtree1(p, fd, 1); +} + +/* printtree1 - recursively print tree p */ +static void printtree1(Tree p, int fd, int lev) { + FILE *f = fd == 1 ? stdout : stderr; + int i; + static char blanks[] = " "; + + if (p == 0 || *printed(i = nodeid(p))) + return; + fprint(f, "#%d%S%S", i, blanks, i < 10 ? 2 : i < 100 ? 1 : 0, blanks, lev); + fprint(f, "%s %t", opname(p->op), p->type); + *printed(i) = 1; + for (i = 0; i < NELEMS(p->kids); i++) + if (p->kids[i]) + fprint(f, " #%d", nodeid(p->kids[i])); + if (p->op == FIELD && p->u.field) + fprint(f, " %s %d..%d", p->u.field->name, + fieldsize(p->u.field) + fieldright(p->u.field), fieldright(p->u.field)); + else if (generic(p->op) == CNST) + fprint(f, " %s", vtoa(p->type, p->u.v)); + else if (p->u.sym) + fprint(f, " %s", p->u.sym->name); + if (p->node) + fprint(f, " node=%p", p->node); + fprint(f, "\n"); + for (i = 0; i < NELEMS(p->kids); i++) + printtree1(p->kids[i], fd, lev + 1); +} diff --git a/src/tools/lcc/src/types.c b/src/tools/lcc/src/types.c new file mode 100644 index 0000000..4aa3d18 --- /dev/null +++ b/src/tools/lcc/src/types.c @@ -0,0 +1,748 @@ +#include "c.h" +#include + + +static Field isfield(const char *, Field); +static Type type(int, Type, int, int, void *); + +static struct entry { + struct type type; + struct entry *link; +} *typetable[128]; +static int maxlevel; + +static Symbol pointersym; + +Type chartype; /* char */ +Type doubletype; /* double */ +Type floattype; /* float */ +Type inttype; /* signed int */ +Type longdouble; /* long double */ +Type longtype; /* long */ +Type longlong; /* long long */ +Type shorttype; /* signed short int */ +Type signedchar; /* signed char */ +Type unsignedchar; /* unsigned char */ +Type unsignedlong; /* unsigned long int */ +Type unsignedlonglong; /* unsigned long long int */ +Type unsignedshort; /* unsigned short int */ +Type unsignedtype; /* unsigned int */ +Type funcptype; /* void (*)() */ +Type charptype; /* char* */ +Type voidptype; /* void* */ +Type voidtype; /* basic types: void */ +Type unsignedptr; /* unsigned type to hold void* */ +Type signedptr; /* signed type to hold void* */ +Type widechar; /* unsigned type that represents wchar_t */ + +static Type xxinit(int op, char *name, Metrics m) { + Symbol p = install(string(name), &types, GLOBAL, PERM); + Type ty = type(op, 0, m.size, m.align, p); + + assert(ty->align == 0 || ty->size%ty->align == 0); + p->type = ty; + p->addressed = m.outofline; + switch (ty->op) { + case INT: + p->u.limits.max.i = ones(8*ty->size)>>1; + p->u.limits.min.i = -p->u.limits.max.i - 1; + break; + case UNSIGNED: + p->u.limits.max.u = ones(8*ty->size); + p->u.limits.min.u = 0; + break; + case FLOAT: + if (ty->size == sizeof (float)) + p->u.limits.max.d = FLT_MAX; + else if (ty->size == sizeof (double)) + p->u.limits.max.d = DBL_MAX; + else + p->u.limits.max.d = LDBL_MAX; + p->u.limits.min.d = -p->u.limits.max.d; + break; + default: assert(0); + } + return ty; +} +static Type type(int op, Type ty, int size, int align, void *sym) { + unsigned h = (op^((unsigned long)ty>>3)) +&(NELEMS(typetable)-1); + struct entry *tn; + + if (op != FUNCTION && (op != ARRAY || size > 0)) + for (tn = typetable[h]; tn; tn = tn->link) + if (tn->type.op == op && tn->type.type == ty + && tn->type.size == size && tn->type.align == align + && tn->type.u.sym == sym) + return &tn->type; + NEW0(tn, PERM); + tn->type.op = op; + tn->type.type = ty; + tn->type.size = size; + tn->type.align = align; + tn->type.u.sym = sym; + tn->link = typetable[h]; + typetable[h] = tn; + return &tn->type; +} +void type_init(int argc, char *argv[]) { + static int inited; + int i; + + if (inited) + return; + inited = 1; + if (!IR) + return; + for (i = 1; i < argc; i++) { + int size, align, outofline; + if (strncmp(argv[i], "-unsigned_char=", 15) == 0) + IR->unsigned_char = argv[i][15] - '0'; +#define xx(name) \ + else if (sscanf(argv[i], "-" #name "=%d,%d,%d", &size, &align, &outofline) == 3) { \ + IR->name.size = size; IR->name.align = align; \ + IR->name.outofline = outofline; } + xx(charmetric) + xx(shortmetric) + xx(intmetric) + xx(longmetric) + xx(longlongmetric) + xx(floatmetric) + xx(doublemetric) + xx(longdoublemetric) + xx(ptrmetric) + xx(structmetric) +#undef xx + } +#define xx(v,name,op,metrics) v=xxinit(op,name,IR->metrics) + xx(chartype, "char", IR->unsigned_char ? UNSIGNED : INT,charmetric); + xx(doubletype, "double", FLOAT, doublemetric); + xx(floattype, "float", FLOAT, floatmetric); + xx(inttype, "int", INT, intmetric); + xx(longdouble, "long double", FLOAT, longdoublemetric); + xx(longtype, "long int", INT, longmetric); + xx(longlong, "long long int", INT, longlongmetric); + xx(shorttype, "short", INT, shortmetric); + xx(signedchar, "signed char", INT, charmetric); + xx(unsignedchar, "unsigned char", UNSIGNED,charmetric); + xx(unsignedlong, "unsigned long", UNSIGNED,longmetric); + xx(unsignedshort, "unsigned short", UNSIGNED,shortmetric); + xx(unsignedtype, "unsigned int", UNSIGNED,intmetric); + xx(unsignedlonglong,"unsigned long long",UNSIGNED,longlongmetric); +#undef xx + { + Symbol p; + p = install(string("void"), &types, GLOBAL, PERM); + voidtype = type(VOID, NULL, 0, 0, p); + p->type = voidtype; + } + pointersym = install(string("T*"), &types, GLOBAL, PERM); + pointersym->addressed = IR->ptrmetric.outofline; + pointersym->u.limits.max.p = (void*)ones(8*IR->ptrmetric.size); + pointersym->u.limits.min.p = 0; + voidptype = ptr(voidtype); + funcptype = ptr(func(voidtype, NULL, 1)); + charptype = ptr(chartype); +#define xx(v,t) if (v==NULL && t->size==voidptype->size && t->align==voidptype->align) v=t + xx(unsignedptr,unsignedshort); + xx(unsignedptr,unsignedtype); + xx(unsignedptr,unsignedlong); + xx(unsignedptr,unsignedlonglong); + if (unsignedptr == NULL) + unsignedptr = type(UNSIGNED, NULL, voidptype->size, voidptype->align, voidptype->u.sym); + xx(signedptr,shorttype); + xx(signedptr,inttype); + xx(signedptr,longtype); + xx(signedptr,longlong); + if (signedptr == NULL) + signedptr = type(INT, NULL, voidptype->size, voidptype->align, voidptype->u.sym); +#undef xx + widechar = unsignedshort; + for (i = 0; i < argc; i++) { +#define xx(name,type) \ + if (strcmp(argv[i], "-wchar_t=" #name) == 0) \ + widechar = type; + xx(unsigned_char,unsignedchar) + xx(unsigned_int,unsignedtype) + xx(unsigned_short,unsignedshort) + } +#undef xx +} +void rmtypes(int lev) { + if (maxlevel >= lev) { + int i; + maxlevel = 0; + for (i = 0; i < NELEMS(typetable); i++) { + struct entry *tn, **tq = &typetable[i]; + while ((tn = *tq) != NULL) + if (tn->type.op == FUNCTION) + tq = &tn->link; + else if (tn->type.u.sym && tn->type.u.sym->scope >= lev) + *tq = tn->link; + else { + if (tn->type.u.sym && tn->type.u.sym->scope > maxlevel) + maxlevel = tn->type.u.sym->scope; + tq = &tn->link; + } + + } + } +} +Type ptr(Type ty) { + return type(POINTER, ty, IR->ptrmetric.size, + IR->ptrmetric.align, pointersym); +} +Type deref(Type ty) { + if (isptr(ty)) + ty = ty->type; + else + error("type error: %s\n", "pointer expected"); + return isenum(ty) ? unqual(ty)->type : ty; +} +Type array(Type ty, int n, int a) { + assert(ty); + if (isfunc(ty)) { + error("illegal type `array of %t'\n", ty); + return array(inttype, n, 0); + } + if (isarray(ty) && ty->size == 0) + error("missing array size\n"); + if (ty->size == 0) { + if (unqual(ty) == voidtype) + error("illegal type `array of %t'\n", ty); + else if (Aflag >= 2) + warning("declaring type array of %t' is undefined\n", ty); + + } else if (n > INT_MAX/ty->size) { + error("size of `array of %t' exceeds %d bytes\n", + ty, INT_MAX); + n = 1; + } + return type(ARRAY, ty, n*ty->size, + a ? a : ty->align, NULL); +} +Type atop(Type ty) { + if (isarray(ty)) + return ptr(ty->type); + error("type error: %s\n", "array expected"); + return ptr(ty); +} +Type qual(int op, Type ty) { + if (isarray(ty)) + ty = type(ARRAY, qual(op, ty->type), ty->size, + ty->align, NULL); + else if (isfunc(ty)) + warning("qualified function type ignored\n"); + else if ((isconst(ty) && op == CONST) + || (isvolatile(ty) && op == VOLATILE)) + error("illegal type `%k %t'\n", op, ty); + else { + if (isqual(ty)) { + op += ty->op; + ty = ty->type; + } + ty = type(op, ty, ty->size, ty->align, NULL); + } + return ty; +} +Type func(Type ty, Type *proto, int style) { + if (ty && (isarray(ty) || isfunc(ty))) + error("illegal return type `%t'\n", ty); + ty = type(FUNCTION, ty, 0, 0, NULL); + ty->u.f.proto = proto; + ty->u.f.oldstyle = style; + return ty; +} +Type freturn(Type ty) { + if (isfunc(ty)) + return ty->type; + error("type error: %s\n", "function expected"); + return inttype; +} +int variadic(Type ty) { + if (isfunc(ty) && ty->u.f.proto) { + int i; + for (i = 0; ty->u.f.proto[i]; i++) + ; + return i > 1 && ty->u.f.proto[i-1] == voidtype; + } + return 0; +} +Type newstruct(int op, char *tag) { + Symbol p; + + assert(tag); + if (*tag == 0) + tag = stringd(genlabel(1)); + else + if ((p = lookup(tag, types)) != NULL && (p->scope == level + || (p->scope == PARAM && level == PARAM+1))) { + if (p->type->op == op && !p->defined) + return p->type; + error("redefinition of `%s' previously defined at %w\n", + p->name, &p->src); + } + p = install(tag, &types, level, PERM); + p->type = type(op, NULL, 0, 0, p); + if (p->scope > maxlevel) + maxlevel = p->scope; + p->src = src; + return p->type; +} +Field newfield(char *name, Type ty, Type fty) { + Field p, *q = &ty->u.sym->u.s.flist; + + if (name == NULL) + name = stringd(genlabel(1)); + for (p = *q; p; q = &p->link, p = *q) + if (p->name == name) + error("duplicate field name `%s' in `%t'\n", + name, ty); + NEW0(p, PERM); + *q = p; + p->name = name; + p->type = fty; + if (xref) { /* omit */ + if (ty->u.sym->u.s.ftab == NULL) /* omit */ + ty->u.sym->u.s.ftab = table(NULL, level); /* omit */ + install(name, &ty->u.sym->u.s.ftab, 0, PERM)->src = src;/* omit */ + } /* omit */ + return p; +} +int eqtype(Type ty1, Type ty2, int ret) { + if (ty1 == ty2) + return 1; + if (ty1->op != ty2->op) + return 0; + switch (ty1->op) { + case ENUM: case UNION: case STRUCT: + case UNSIGNED: case INT: case FLOAT: + return 0; + case POINTER: return eqtype(ty1->type, ty2->type, 1); + case VOLATILE: case CONST+VOLATILE: + case CONST: return eqtype(ty1->type, ty2->type, 1); + case ARRAY: if (eqtype(ty1->type, ty2->type, 1)) { + if (ty1->size == ty2->size) + return 1; + if (ty1->size == 0 || ty2->size == 0) + return ret; + } + return 0; + case FUNCTION: if (eqtype(ty1->type, ty2->type, 1)) { + Type *p1 = ty1->u.f.proto, *p2 = ty2->u.f.proto; + if (p1 == p2) + return 1; + if (p1 && p2) { + for ( ; *p1 && *p2; p1++, p2++) + if (eqtype(unqual(*p1), unqual(*p2), 1) == 0) + return 0; + if (*p1 == NULL && *p2 == NULL) + return 1; + } else { + if (variadic(p1 ? ty1 : ty2)) + return 0; + if (p1 == NULL) + p1 = p2; + for ( ; *p1; p1++) { + Type ty = unqual(*p1); + if (promote(ty) != (isenum(ty) ? ty->type : ty)) + return 0; + } + return 1; + } + } + return 0; + } + assert(0); return 0; +} +Type promote(Type ty) { + ty = unqual(ty); + switch (ty->op) { + case ENUM: + return inttype; + case INT: + if (ty->size < inttype->size) + return inttype; + break; + case UNSIGNED: + if (ty->size < inttype->size) + return inttype; + if (ty->size < unsignedtype->size) + return unsignedtype; + break; + case FLOAT: + if (ty->size < doubletype->size) + return doubletype; + } + return ty; +} +Type signedint(Type ty) { + if (ty->op == INT) + return ty; + assert(ty->op == UNSIGNED); +#define xx(t) if (ty->size == t->size) return t + xx(inttype); + xx(longtype); + xx(longlong); +#undef xx + assert(0); return NULL; +} +Type compose(Type ty1, Type ty2) { + if (ty1 == ty2) + return ty1; + assert(ty1->op == ty2->op); + switch (ty1->op) { + case POINTER: + return ptr(compose(ty1->type, ty2->type)); + case CONST+VOLATILE: + return qual(CONST, qual(VOLATILE, + compose(ty1->type, ty2->type))); + case CONST: case VOLATILE: + return qual(ty1->op, compose(ty1->type, ty2->type)); + case ARRAY: { Type ty = compose(ty1->type, ty2->type); + if (ty1->size && ((ty1->type->size && ty2->size == 0) || ty1->size == ty2->size)) + return array(ty, ty1->size/ty1->type->size, ty1->align); + if (ty2->size && ty2->type->size && ty1->size == 0) + return array(ty, ty2->size/ty2->type->size, ty2->align); + return array(ty, 0, 0); } + case FUNCTION: { Type *p1 = ty1->u.f.proto, *p2 = ty2->u.f.proto; + Type ty = compose(ty1->type, ty2->type); + List tlist = NULL; + if (p1 == NULL && p2 == NULL) + return func(ty, NULL, 1); + if (p1 && p2 == NULL) + return func(ty, p1, ty1->u.f.oldstyle); + if (p2 && p1 == NULL) + return func(ty, p2, ty2->u.f.oldstyle); + for ( ; *p1 && *p2; p1++, p2++) { + Type ty = compose(unqual(*p1), unqual(*p2)); + if (isconst(*p1) || isconst(*p2)) + ty = qual(CONST, ty); + if (isvolatile(*p1) || isvolatile(*p2)) + ty = qual(VOLATILE, ty); + tlist = append(ty, tlist); + } + assert(*p1 == NULL && *p2 == NULL); + return func(ty, ltov(&tlist, PERM), 0); } + } + assert(0); return NULL; +} +int ttob(Type ty) { + switch (ty->op) { + case CONST: case VOLATILE: case CONST+VOLATILE: + return ttob(ty->type); + case VOID: case INT: case UNSIGNED: case FLOAT: + return ty->op + sizeop(ty->size); + case POINTER: + return POINTER + sizeop(voidptype->size); + case FUNCTION: + return POINTER + sizeop(funcptype->size); + case ARRAY: case STRUCT: case UNION: + return STRUCT; + case ENUM: + return INT + sizeop(inttype->size); + } + assert(0); return INT; +} +Type btot(int op, int size) { +#define xx(ty) if (size == (ty)->size) return ty; + switch (optype(op)) { + case F: + xx(floattype); + xx(doubletype); + xx(longdouble); + assert(0); return 0; + case I: + if (chartype->op == INT) + xx(chartype); + xx(signedchar); + xx(shorttype); + xx(inttype); + xx(longtype); + xx(longlong); + assert(0); return 0; + case U: + if (chartype->op == UNSIGNED) + xx(chartype); + xx(unsignedchar); + xx(unsignedshort); + xx(unsignedtype); + xx(unsignedlong); + xx(unsignedlonglong); + assert(0); return 0; + case P: + xx(voidptype); + xx(funcptype); + assert(0); return 0; + } +#undef xx + assert(0); return 0; +} +int hasproto(Type ty) { + if (ty == 0) + return 1; + switch (ty->op) { + case CONST: case VOLATILE: case CONST+VOLATILE: case POINTER: + case ARRAY: + return hasproto(ty->type); + case FUNCTION: + return hasproto(ty->type) && ty->u.f.proto; + case STRUCT: case UNION: + case VOID: case FLOAT: case ENUM: case INT: case UNSIGNED: + return 1; + } + assert(0); return 0; +} +/* fieldlist - construct a flat list of fields in type ty */ +Field fieldlist(Type ty) { + return ty->u.sym->u.s.flist; +} + +/* fieldref - find field name of type ty, return entry */ +Field fieldref(const char *name, Type ty) { + Field p = isfield(name, unqual(ty)->u.sym->u.s.flist); + + if (p && xref) { + Symbol q; + assert(unqual(ty)->u.sym->u.s.ftab); + q = lookup(name, unqual(ty)->u.sym->u.s.ftab); + assert(q); + use(q, src); + } + return p; +} + +/* ftype - return a function type for rty function (ty,...)' */ +Type ftype(Type rty, Type ty) { + List list = append(ty, NULL); + + list = append(voidtype, list); + return func(rty, ltov(&list, PERM), 0); +} + +/* isfield - if name is a field in flist, return pointer to the field structure */ +static Field isfield(const char *name, Field flist) { + for ( ; flist; flist = flist->link) + if (flist->name == name) + break; + return flist; +} + +/* outtype - output type ty */ +void outtype(Type ty, FILE *f) { + switch (ty->op) { + case CONST+VOLATILE: case CONST: case VOLATILE: + fprint(f, "%k %t", ty->op, ty->type); + break; + case STRUCT: case UNION: case ENUM: + assert(ty->u.sym); + if (ty->size == 0) + fprint(f, "incomplete "); + assert(ty->u.sym->name); + if (*ty->u.sym->name >= '1' && *ty->u.sym->name <= '9') { + Symbol p = findtype(ty); + if (p == 0) + fprint(f, "%k defined at %w", ty->op, &ty->u.sym->src); + else + fprint(f, p->name); + } else { + fprint(f, "%k %s", ty->op, ty->u.sym->name); + if (ty->size == 0) + fprint(f, " defined at %w", &ty->u.sym->src); + } + break; + case VOID: case FLOAT: case INT: case UNSIGNED: + fprint(f, ty->u.sym->name); + break; + case POINTER: + fprint(f, "pointer to %t", ty->type); + break; + case FUNCTION: + fprint(f, "%t function", ty->type); + if (ty->u.f.proto && ty->u.f.proto[0]) { + int i; + fprint(f, "(%t", ty->u.f.proto[0]); + for (i = 1; ty->u.f.proto[i]; i++) + if (ty->u.f.proto[i] == voidtype) + fprint(f, ",..."); + else + fprint(f, ",%t", ty->u.f.proto[i]); + fprint(f, ")"); + } else if (ty->u.f.proto && ty->u.f.proto[0] == 0) + fprint(f, "(void)"); + + break; + case ARRAY: + if (ty->size > 0 && ty->type && ty->type->size > 0) { + fprint(f, "array %d", ty->size/ty->type->size); + while (ty->type && isarray(ty->type) && ty->type->type->size > 0) { + ty = ty->type; + fprint(f, ",%d", ty->size/ty->type->size); + } + } else + fprint(f, "incomplete array"); + if (ty->type) + fprint(f, " of %t", ty->type); + break; + default: assert(0); + } +} + +/* printdecl - output a C declaration for symbol p of type ty */ +void printdecl(Symbol p, Type ty) { + switch (p->sclass) { + case AUTO: + fprint(stderr, "%s;\n", typestring(ty, p->name)); + break; + case STATIC: case EXTERN: + fprint(stderr, "%k %s;\n", p->sclass, typestring(ty, p->name)); + break; + case TYPEDEF: case ENUM: + break; + default: assert(0); + } +} + +/* printproto - output a prototype declaration for function p */ +void printproto(Symbol p, Symbol callee[]) { + if (p->type->u.f.proto) + printdecl(p, p->type); + else { + int i; + List list = 0; + if (callee[0] == 0) + list = append(voidtype, list); + else + for (i = 0; callee[i]; i++) + list = append(callee[i]->type, list); + printdecl(p, func(freturn(p->type), ltov(&list, PERM), 0)); + } +} + +/* prtype - print details of type ty on f with given indent */ +static void prtype(Type ty, FILE *f, int indent, unsigned mark) { + switch (ty->op) { + default: + fprint(f, "(%d %d %d [%p])", ty->op, ty->size, ty->align, ty->u.sym); + break; + case FLOAT: case INT: case UNSIGNED: case VOID: + fprint(f, "(%k %d %d [\"%s\"])", ty->op, ty->size, ty->align, ty->u.sym->name); + break; + case CONST+VOLATILE: case CONST: case VOLATILE: case POINTER: case ARRAY: + fprint(f, "(%k %d %d ", ty->op, ty->size, ty->align); + prtype(ty->type, f, indent+1, mark); + fprint(f, ")"); + break; + case STRUCT: case UNION: + fprint(f, "(%k %d %d [\"%s\"]", ty->op, ty->size, ty->align, ty->u.sym->name); + if (ty->x.marked != mark) { + Field p; + ty->x.marked = mark; + for (p = ty->u.sym->u.s.flist; p; p = p->link) { + fprint(f, "\n%I", indent+1); + prtype(p->type, f, indent+1, mark); + fprint(f, " %s@%d", p->name, p->offset); + if (p->lsb) + fprint(f, ":%d..%d", + fieldsize(p) + fieldright(p), fieldright(p)); + } + fprint(f, "\n%I", indent); + } + fprint(f, ")"); + break; + case ENUM: + fprint(f, "(%k %d %d [\"%s\"]", ty->op, ty->size, ty->align, ty->u.sym->name); + if (ty->x.marked != mark) { + int i; + Symbol *p = ty->u.sym->u.idlist; + ty->x.marked = mark; + for (i = 0; p[i] != NULL; i++) + fprint(f, "%I%s=%d\n", indent+1, p[i]->name, p[i]->u.value); + } + fprint(f, ")"); + break; + case FUNCTION: + fprint(f, "(%k %d %d ", ty->op, ty->size, ty->align); + prtype(ty->type, f, indent+1, mark); + if (ty->u.f.proto) { + int i; + fprint(f, "\n%I{", indent+1); + for (i = 0; ty->u.f.proto[i]; i++) { + if (i > 0) + fprint(f, "%I", indent+2); + prtype(ty->u.f.proto[i], f, indent+2, mark); + fprint(f, "\n"); + } + fprint(f, "%I}", indent+1); + } + fprint(f, ")"); + break; + } +} + +/* printtype - print details of type ty on fd */ +void printtype(Type ty, int fd) { + static unsigned mark; + prtype(ty, fd == 1 ? stdout : stderr, 0, ++mark); + fprint(fd == 1 ? stdout : stderr, "\n"); +} + +/* typestring - return ty as C declaration for str, which may be "" */ +char *typestring(Type ty, char *str) { + for ( ; ty; ty = ty->type) { + Symbol p; + switch (ty->op) { + case CONST+VOLATILE: case CONST: case VOLATILE: + if (isptr(ty->type)) + str = stringf("%k %s", ty->op, str); + else + return stringf("%k %s", ty->op, typestring(ty->type, str)); + break; + case STRUCT: case UNION: case ENUM: + assert(ty->u.sym); + if ((p = findtype(ty)) != NULL) + return *str ? stringf("%s %s", p->name, str) : p->name; + if (*ty->u.sym->name >= '1' && *ty->u.sym->name <= '9') + warning("unnamed %k in prototype\n", ty->op); + if (*str) + return stringf("%k %s %s", ty->op, ty->u.sym->name, str); + else + return stringf("%k %s", ty->op, ty->u.sym->name); + case VOID: case FLOAT: case INT: case UNSIGNED: + return *str ? stringf("%s %s", ty->u.sym->name, str) : ty->u.sym->name; + case POINTER: + if (!ischar(ty->type) && (p = findtype(ty)) != NULL) + return *str ? stringf("%s %s", p->name, str) : p->name; + str = stringf(isarray(ty->type) || isfunc(ty->type) ? "(*%s)" : "*%s", str); + break; + case FUNCTION: + if ((p = findtype(ty)) != NULL) + return *str ? stringf("%s %s", p->name, str) : p->name; + if (ty->u.f.proto == 0) + str = stringf("%s()", str); + else if (ty->u.f.proto[0]) { + int i; + str = stringf("%s(%s", str, typestring(ty->u.f.proto[0], "")); + for (i = 1; ty->u.f.proto[i]; i++) + if (ty->u.f.proto[i] == voidtype) + str = stringf("%s, ...", str); + else + str = stringf("%s, %s", str, typestring(ty->u.f.proto[i], "")); + str = stringf("%s)", str); + } else + str = stringf("%s(void)", str); + break; + case ARRAY: + if ((p = findtype(ty)) != NULL) + return *str ? stringf("%s %s", p->name, str) : p->name; + if (ty->type && ty->type->size > 0) + str = stringf("%s[%d]", str, ty->size/ty->type->size); + else + str = stringf("%s[]", str); + break; + default: assert(0); + } + } + assert(0); return 0; +} + diff --git a/src/ui/ui_atoms.c b/src/ui/ui_atoms.c new file mode 100644 index 0000000..af3a3ef --- /dev/null +++ b/src/ui/ui_atoms.c @@ -0,0 +1,295 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/********************************************************************** + UI_ATOMS.C + + User interface building blocks and support functions. +**********************************************************************/ +#include "ui_local.h" + +qboolean m_entersound; // after a frame, so caching won't disrupt the sound + +void QDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[1024]; + + va_start( argptr, error ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); + va_end( argptr ); + + trap_Error( text ); +} + +void QDECL Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + trap_Print( text ); +} + + +/* +================= +UI_ClampCvar +================= +*/ +float UI_ClampCvar( float min, float max, float value ) +{ + if( value < min ) return min; + + if( value > max ) return max; + + return value; +} + +/* +================= +UI_StartDemoLoop +================= +*/ +void UI_StartDemoLoop( void ) +{ + trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" ); +} + +char *UI_Argv( int arg ) +{ + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + +char *UI_ConcatArgs( int arg, char *buf, int len ) +{ + char *p; + int c; + + if( len <= 0 ) + return buf; + + p = buf; + c = trap_Argc(); + + for( ; arg < c; arg++ ) + { + char *argp = UI_Argv( arg ); + + while( *argp && p < &buf[ len - 1 ] ) + *p++ = *argp++; + + if( p < &buf[ len - 2 ] ) + *p++ = ' '; + else + break; + } + + *p = '\0'; + + return buf; +} + +char *UI_Cvar_VariableString( const char *var_name ) +{ + static char buffer[MAX_STRING_CHARS]; + + trap_Cvar_VariableStringBuffer( var_name, buffer, sizeof( buffer ) ); + + return buffer; +} + +static void UI_Cache_f( void ) +{ + Display_CacheAll(); +} + +static void UI_Menu_f( void ) +{ + if( Menu_Count( ) > 0 ) + { + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_ActivateByName( UI_Argv( 1 ) ); + } +} + +static void UI_CloseMenus_f( void ) +{ + if( Menu_Count( ) > 0 ) + { + trap_Key_SetCatcher( trap_Key_GetCatcher( ) & ~KEYCATCH_UI ); + trap_Key_ClearStates( ); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll( ); + } +} + +static void UI_MessageMode_f( void ) +{ + char *arg = UI_Argv( 0 ); + + trap_Cvar_Set( "ui_sayBuffer", "" ); + + switch( arg[ 11 ] ) + { + default: + case '\0': + // Global + uiInfo.chatTeam = qfalse; + break; + + case '2': + // Team + uiInfo.chatTeam = qtrue; + break; + } + + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseByName( "say" ); + Menus_CloseByName( "say_team" ); + + if( uiInfo.chatTeam ) + Menus_ActivateByName( "say_team" ); + else + Menus_ActivateByName( "say" ); +} + +static void UI_Me_f( void ) +{ + char buf[ MAX_SAY_TEXT - 4 ]; + + UI_ConcatArgs( 1, buf, sizeof( buf ) ); + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"/me %s\"", buf ) ); +} + +struct uicmd +{ + char *cmd; + void ( *function )( void ); +} commands[ ] = { + { "closemenus", UI_CloseMenus_f }, + { "me", UI_Me_f }, + { "menu", UI_Menu_f }, + { "messagemode", UI_MessageMode_f }, + { "messagemode2", UI_MessageMode_f }, + { "ui_cache", UI_Cache_f }, + { "ui_load", UI_Load }, + { "ui_report", UI_Report } +}; + +/* +================= +UI_ConsoleCommand +================= +*/ +qboolean UI_ConsoleCommand( int realTime ) +{ + struct uicmd *cmd = bsearch( UI_Argv( 0 ), commands, + sizeof( commands ) / sizeof( commands[ 0 ] ), sizeof( commands[ 0 ] ), + cmdcmp ); + + uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime; + uiInfo.uiDC.realTime = realTime; + + if( cmd ) + { + cmd->function( ); + return qtrue; + } + + return qfalse; +} + +void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) +{ + qhandle_t hShader; + + hShader = trap_R_RegisterShaderNoMip( picname ); + UI_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ) +{ + float s0; + float s1; + float t0; + float t1; + + if( w < 0 ) + { // flip about vertical + w = -w; + s0 = 1; + s1 = 0; + } + else + { + s0 = 0; + s1 = 1; + } + + if( h < 0 ) + { // flip about horizontal + h = -h; + t0 = 1; + t1 = 0; + } + else + { + t0 = 0; + t1 = 1; + } + + UI_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, w, h, s0, t0, s1, t1, hShader ); +} + +/* +================ +UI_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void UI_FillRect( float x, float y, float width, float height, const float *color ) +{ + trap_R_SetColor( color ); + + UI_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + + trap_R_SetColor( NULL ); +} + +void UI_SetColor( const float *rgba ) +{ + trap_R_SetColor( rgba ); +} diff --git a/src/ui/ui_gameinfo.c b/src/ui/ui_gameinfo.c new file mode 100644 index 0000000..18a604b --- /dev/null +++ b/src/ui/ui_gameinfo.c @@ -0,0 +1,377 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// +// gameinfo.c +// + +#include "ui_local.h" + + +// +// arena and bot info +// + + +int ui_numBots; +static char *ui_botInfos[MAX_BOTS]; + +static int ui_numArenas; +static char *ui_arenaInfos[MAX_ARENAS]; + +/* +=============== +UI_ParseInfos +=============== +*/ +int UI_ParseInfos( char *buf, int max, char *infos[] ) +{ + char * token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; + + count = 0; + + while( 1 ) + { + token = COM_Parse( &buf ); + + if( !token[0] ) + break; + + if( strcmp( token, "{" ) ) + { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if( count == max ) + { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + info[0] = '\0'; + + while( 1 ) + { + token = COM_ParseExt( &buf, qtrue ); + + if( !token[0] ) + { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + + if( !strcmp( token, "}" ) ) + break; + + Q_strncpyz( key, token, sizeof( key ) ); + + token = COM_ParseExt( &buf, qfalse ); + + if( !token[0] ) + strcpy( token, "" ); + + Info_SetValueForKey( info, key, token ); + } + + //NOTE: extra space for arena number + infos[count] = UI_Alloc( strlen( info ) + strlen( "\\num\\" ) + strlen( va( "%d", MAX_ARENAS ) ) + 1 ); + + if( infos[count] ) + { + strcpy( infos[count], info ); + count++; + } + } + + return count; +} + +/* +=============== +UI_LoadArenasFromFile +=============== +*/ +static void UI_LoadArenasFromFile( char *filename ) +{ + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + + if( !f ) + { + trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + + if( len >= MAX_ARENAS_TEXT ) + { + trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] ); +} + +/* +================= +UI_MapNameCompare +================= +*/ +static int UI_MapNameCompare( const void *a, const void *b ) +{ + mapInfo * A = ( mapInfo * )a; + mapInfo *B = ( mapInfo * )b; + + return Q_stricmp( A->mapName, B->mapName ); +} + +/* +=============== +UI_LoadArenas +=============== +*/ +void UI_LoadArenas( void ) +{ + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + char *type; + + ui_numArenas = 0; + uiInfo.mapCount = 0; + + // get all arenas from .arena files + numdirs = trap_FS_GetFileList( "scripts", ".arena", dirlist, 1024 ); + dirptr = dirlist; + + for( i = 0; i < numdirs; i++, dirptr += dirlen + 1 ) + { + dirlen = strlen( dirptr ); + strcpy( filename, "scripts/" ); + strcat( filename, dirptr ); + UI_LoadArenasFromFile( filename ); + } + + trap_Print( va( "[skipnotify]%i arenas parsed\n", ui_numArenas ) ); + + if( UI_OutOfMemory() ) + trap_Print( S_COLOR_YELLOW"WARNING: not anough memory in pool to load all arenas\n" ); + + for( n = 0; n < ui_numArenas; n++ ) + { + // determine type + type = Info_ValueForKey( ui_arenaInfos[ n ], "type" ); + // if no type specified, it will be treated as "ffa" + + uiInfo.mapList[uiInfo.mapCount].cinematic = -1; + uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc( Info_ValueForKey( ui_arenaInfos[n], "map" ) ); + uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc( Info_ValueForKey( ui_arenaInfos[n], "longname" ) ); + uiInfo.mapList[uiInfo.mapCount].levelShot = -1; + uiInfo.mapList[uiInfo.mapCount].imageName = String_Alloc( va( "levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName ) ); + + uiInfo.mapCount++; + + if( uiInfo.mapCount >= MAX_MAPS ) + break; + } + + qsort( uiInfo.mapList, uiInfo.mapCount, sizeof( mapInfo ), UI_MapNameCompare ); +} + + +/* +=============== +UI_LoadBotsFromFile +=============== +*/ +static void UI_LoadBotsFromFile( char *filename ) +{ + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + + if( !f ) + { + trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + + if( len >= MAX_BOTS_TEXT ) + { + trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress( buf ); + + ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] ); +} + +/* +=============== +UI_LoadBots +=============== +*/ +void UI_LoadBots( void ) +{ + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + ui_numBots = 0; + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT | CVAR_ROM ); + + if( *botsFile.string ) + UI_LoadBotsFromFile( botsFile.string ); + else + UI_LoadBotsFromFile( "scripts/bots.txt" ); + + // get all bots from .bot files + numdirs = trap_FS_GetFileList( "scripts", ".bot", dirlist, 1024 ); + + dirptr = dirlist; + + for( i = 0; i < numdirs; i++, dirptr += dirlen + 1 ) + { + dirlen = strlen( dirptr ); + strcpy( filename, "scripts/" ); + strcat( filename, dirptr ); + UI_LoadBotsFromFile( filename ); + } + + trap_Print( va( "%i bots parsed\n", ui_numBots ) ); +} + + +/* +=============== +UI_GetBotInfoByNumber +=============== +*/ +char *UI_GetBotInfoByNumber( int num ) +{ + if( num < 0 || num >= ui_numBots ) + { + trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + + return ui_botInfos[num]; +} + + +/* +=============== +UI_GetBotInfoByName +=============== +*/ +char *UI_GetBotInfoByName( const char *name ) +{ + int n; + char *value; + + for( n = 0; n < ui_numBots ; n++ ) + { + value = Info_ValueForKey( ui_botInfos[n], "name" ); + + if( !Q_stricmp( value, name ) ) + return ui_botInfos[n]; + } + + return NULL; +} + +int UI_GetNumBots( void ) +{ + return ui_numBots; +} + + +char *UI_GetBotNameByNumber( int num ) +{ + char * info = UI_GetBotInfoByNumber( num ); + + if( info ) + return Info_ValueForKey( info, "name" ); + + return ""; +} + +void UI_ServerInfo( void ) +{ + char info[ MAX_INFO_VALUE ]; + char hostname[MAX_HOSTNAME_LENGTH]; + + info[0] = '\0'; + + if( trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ) ) + { + trap_Cvar_Set( "ui_serverinfo_mapname", + Info_ValueForKey( info, "mapname" ) ); + trap_Cvar_Set( "ui_serverinfo_timelimit", + Info_ValueForKey( info, "timelimit" ) ); + trap_Cvar_Set( "ui_serverinfo_sd", + Info_ValueForKey( info, "g_suddenDeathTime" ) ); + UI_EscapeEmoticons( hostname, Info_ValueForKey( info, "sv_hostname" ), + sizeof( hostname ) ); + trap_Cvar_Set( "ui_serverinfo_hostname", hostname ); + trap_Cvar_Set( "ui_serverinfo_maxclients", + Info_ValueForKey( info, "sv_maxclients" ) ); + trap_Cvar_Set( "ui_serverinfo_version", + Info_ValueForKey( info, "version" ) ); + trap_Cvar_Set( "ui_serverinfo_unlagged", + Info_ValueForKey( info, "g_unlagged" ) ); + trap_Cvar_Set( "ui_serverinfo_friendlyFire", + Info_ValueForKey( info, "g_friendlyFire" ) ); + trap_Cvar_Set( "ui_serverinfo_friendlyBuildableFire", + Info_ValueForKey( info, "g_friendlyBuildableFire" ) ); + trap_Cvar_Set( "ui_serverinfo_allowdl", + Info_ValueForKey( info, "sv_allowdownload" ) ); + } +} diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h new file mode 100644 index 0000000..2c78e26 --- /dev/null +++ b/src/ui/ui_local.h @@ -0,0 +1,400 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef UI_LOCAL_H +#define UI_LOCAL_H + +#include "../qcommon/q_shared.h" +#include "../renderer/tr_types.h" +#include "ui_public.h" +#include "../client/keycodes.h" +#include "../game/bg_public.h" +#include "ui_shared.h" + +// +// ui_main.c +// +void UI_Report( void ); +void UI_Load( void ); +void UI_LoadMenus( const char *menuFile, qboolean reset ); +int UI_AdjustTimeByGame( int time ); +void UI_ClearScores( void ); +void UI_LoadArenas( void ); +void UI_ServerInfo( void ); +void UI_UpdateNews( qboolean ); + +void UI_RegisterCvars( void ); +void UI_UpdateCvars( void ); +void UI_DrawConnectScreen( qboolean overlay ); + +// new ui stuff +#define MAX_MAPS 128 +#define MAX_PINGREQUESTS 32 +#define MAX_ADDRESSLENGTH 64 +#define MAX_DISPLAY_SERVERS 2048 +#define MAX_SERVERSTATUS_LINES 128 +#define MAX_SERVERSTATUS_TEXT 1024 +#define MAX_NEWS_LINES 50 +#define MAX_NEWS_LINEWIDTH 85 +#define MAX_FOUNDPLAYER_SERVERS 16 +#define MAX_MODS 64 +#define MAX_DEMOS 256 +#define MAX_MOVIES 256 +#define MAX_HELP_INFOPANES 32 +#define MAX_RESOLUTIONS 32 + +typedef struct +{ + const char *mapName; + const char *mapLoadName; + const char *imageName; + int cinematic; + qhandle_t levelShot; +} +mapInfo; + +typedef struct serverFilter_s +{ + const char *description; + const char *basedir; +} +serverFilter_t; + +typedef struct +{ + char adrstr[MAX_ADDRESSLENGTH]; + int start; +} +pinglist_t; + + +typedef struct serverStatus_s +{ + pinglist_t pingList[MAX_PINGREQUESTS]; + int numqueriedservers; + int currentping; + int nextpingtime; + int maxservers; + int refreshtime; + int numServers; + int sortKey; + int sortDir; + qboolean sorted; + int lastCount; + qboolean refreshActive; + int currentServer; + int displayServers[MAX_DISPLAY_SERVERS]; + int numDisplayServers; + int numPlayersOnServers; + int nextDisplayRefresh; + int nextSortTime; + qhandle_t currentServerPreview; + int currentServerCinematic; + int motdLen; + int motdWidth; + int motdPaintX; + int motdPaintX2; + int motdOffset; + int motdTime; + char motd[MAX_STRING_CHARS]; +} +serverStatus_t; + + +typedef struct +{ + char adrstr[MAX_ADDRESSLENGTH]; + char name[MAX_ADDRESSLENGTH]; + int startTime; + int serverNum; + qboolean valid; +} +pendingServer_t; + +typedef struct +{ + int num; + pendingServer_t server[MAX_SERVERSTATUSREQUESTS]; +} +pendingServerStatus_t; + +typedef struct +{ + char address[MAX_ADDRESSLENGTH]; + char *lines[MAX_SERVERSTATUS_LINES][4]; + char text[MAX_SERVERSTATUS_TEXT]; + char pings[MAX_CLIENTS * 3]; + int numLines; +} +serverStatusInfo_t; + +typedef struct +{ + char text[MAX_NEWS_LINES][MAX_NEWS_LINEWIDTH]; + int numLines; + qboolean refreshActive; + int refreshtime; +} +newsInfo_t; + +typedef struct +{ + const char *modName; + const char *modDescr; +} +modInfo_t; + +typedef enum +{ + INFOTYPE_TEXT, + INFOTYPE_BUILDABLE, + INFOTYPE_CLASS, + INFOTYPE_WEAPON, + INFOTYPE_UPGRADE +} infoType_t; + +typedef struct +{ + const char *text; + const char *cmd; + infoType_t type; + union + { + const char *text; + buildable_t buildable; + class_t pclass; + weapon_t weapon; + upgrade_t upgrade; + } v; +} +menuItem_t; + +typedef struct +{ + int w; + int h; +} +resolution_t; + +typedef struct +{ + displayContextDef_t uiDC; + + int playerCount; + int myTeamCount; + int teamPlayerIndex; + int playerRefresh; + int playerIndex; + int playerNumber; + int myPlayerIndex; + int ignoreIndex; + char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char rawPlayerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char rawTeamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + int clientNums[MAX_CLIENTS]; + int teamClientNums[MAX_CLIENTS]; + clientList_t ignoreList[MAX_CLIENTS]; + + int mapCount; + mapInfo mapList[MAX_MAPS]; + + modInfo_t modList[MAX_MODS]; + int modCount; + int modIndex; + + const char *demoList[MAX_DEMOS]; + int demoCount; + int demoIndex; + + const char *movieList[MAX_MOVIES]; + int movieCount; + int movieIndex; + int previewMovie; + + menuItem_t teamList[ 4 ]; + int teamCount; + int teamIndex; + + menuItem_t alienClassList[ 3 ]; + int alienClassCount; + int alienClassIndex; + + menuItem_t humanItemList[ 3 ]; + int humanItemCount; + int humanItemIndex; + + menuItem_t humanArmouryBuyList[ 32 ]; + int humanArmouryBuyCount; + int humanArmouryBuyIndex; + + menuItem_t humanArmourySellList[ 32 ]; + int humanArmourySellCount; + int humanArmourySellIndex; + + menuItem_t alienUpgradeList[ 16 ]; + int alienUpgradeCount; + int alienUpgradeIndex; + + menuItem_t alienBuildList[ 32 ]; + int alienBuildCount; + int alienBuildIndex; + + menuItem_t humanBuildList[ 32 ]; + int humanBuildCount; + int humanBuildIndex; + + menuItem_t helpList[ MAX_HELP_INFOPANES ]; + int helpCount; + int helpIndex; + + int weapons; + int upgrades; + + serverStatus_t serverStatus; + + // for showing the game news window + newsInfo_t newsInfo; + + // for the showing the status of a server + char serverStatusAddress[MAX_ADDRESSLENGTH]; + serverStatusInfo_t serverStatusInfo; + int nextServerStatusRefresh; + + // to retrieve the status of server to find a player + pendingServerStatus_t pendingServerStatus; + char findPlayerName[MAX_STRING_CHARS]; + char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; + char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; + int currentFoundPlayerServer; + int numFoundPlayerServers; + int nextFindPlayerRefresh; + + resolution_t resolutions[ MAX_RESOLUTIONS ]; + int numResolutions; + int resolutionIndex; + + qboolean inGameLoad; + + qboolean chatTeam; +} +uiInfo_t; + +extern uiInfo_t uiInfo; + + +qboolean UI_ConsoleCommand( int realTime ); +char *UI_Cvar_VariableString( const char *var_name ); +void UI_SetColor( const float *rgba ); +void UI_AdjustFrom640( float *x, float *y, float *w, float *h ); +void UI_Refresh( int time ); +void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ); +void UI_FillRect( float x, float y, float width, float height, const float *color ); + +// +// ui_syscalls.c +// +void trap_Print( const char *string ); +void trap_Error( const char *string ); +int trap_Milliseconds( void ); +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +float trap_Cvar_VariableValue( const char *var_name ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +void trap_Cvar_SetValue( const char *var_name, float value ); +void trap_Cvar_Reset( const char *name ); +void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ); +void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ); +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Cmd_ExecuteText( int exec_when, const char *text ); // don't use EXEC_NOW! +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t +qhandle_t trap_R_RegisterModel( const char *name ); +qhandle_t trap_R_RegisterSkin( const char *name ); +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); +void trap_R_SetClipRegion( const float *region ); +void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +void trap_UpdateScreen( void ); +int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ); +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ); +void trap_Key_SetBinding( int keynum, const char *binding ); +qboolean trap_Key_IsDown( int keynum ); +qboolean trap_Key_GetOverstrikeMode( void ); +void trap_Key_SetOverstrikeMode( qboolean state ); +void trap_Key_ClearStates( void ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +void trap_GetClipboardData( char *buf, int bufsize ); +void trap_GetClientState( uiClientState_t *state ); +void trap_GetGlconfig( glconfig_t *glconfig ); +int trap_GetConfigString( int index, char* buff, int buffsize ); +int trap_LAN_GetServerCount( int source ); +void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ); +void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ); +int trap_LAN_GetServerPing( int source, int n ); +int trap_LAN_GetPingQueueCount( void ); +void trap_LAN_ClearPing( int n ); +void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ); +void trap_LAN_GetPingInfo( int n, char *buf, int buflen ); +void trap_LAN_LoadCachedServers( void ); +void trap_LAN_SaveCachedServers( void ); +void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ); +int trap_LAN_ServerIsVisible( int source, int n ); +qboolean trap_LAN_UpdateVisiblePings( int source ); +int trap_LAN_AddServer( int source, const char *name, const char *addr ); +void trap_LAN_RemoveServer( int source, const char *addr ); +void trap_LAN_ResetPings( int n ); +int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ); +qboolean trap_GetNews( qboolean force ); +int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ); +int trap_MemoryRemaining( void ); +void trap_R_RegisterFont( const char *pFontname, int pointSize, fontInfo_t *font ); +void trap_S_StopBackgroundTrack( void ); +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ); +e_status trap_CIN_StopCinematic( int handle ); +e_status trap_CIN_RunCinematic ( int handle ); +void trap_CIN_DrawCinematic ( int handle ); +void trap_CIN_SetExtents ( int handle, int x, int y, int w, int h ); +int trap_RealTime( qtime_t *qtime ); +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +void trap_SetPbClStatus( int status ); + +#endif diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c new file mode 100644 index 0000000..6566d5e --- /dev/null +++ b/src/ui/ui_main.c @@ -0,0 +1,4653 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/* +======================================================================= + +USER INTERFACE MAIN + +======================================================================= +*/ + +#include "ui_local.h" + +uiInfo_t uiInfo; + +static const char *MonthAbbrev[ ] = +{ + "Jan", "Feb", "Mar", + "Apr", "May", "Jun", + "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec" +}; + +static const char *netSources[ ] = +{ + "Internet", + "Mplayer", + "LAN", + "Favorites" +}; + +static const int numNetSources = sizeof( netSources ) / sizeof( const char* ); + +static const char *netnames[ ] = +{ + "???", + "UDP", + "IPX", + NULL +}; + +/* +================ +cvars +================ +*/ + +typedef struct +{ + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} + +cvarTable_t; + +vmCvar_t ui_browserShowFull; +vmCvar_t ui_browserShowEmpty; + +vmCvar_t ui_dedicated; +vmCvar_t ui_netSource; +vmCvar_t ui_selectedMap; +vmCvar_t ui_lastServerRefresh_0; +vmCvar_t ui_lastServerRefresh_1; +vmCvar_t ui_lastServerRefresh_2; +vmCvar_t ui_lastServerRefresh_3; +vmCvar_t ui_lastServerRefresh_0_time; +vmCvar_t ui_lastServerRefresh_1_time; +vmCvar_t ui_lastServerRefresh_2_time; +vmCvar_t ui_lastServerRefresh_3_time; +vmCvar_t ui_smallFont; +vmCvar_t ui_bigFont; +vmCvar_t ui_findPlayer; +vmCvar_t ui_serverStatusTimeOut; +vmCvar_t ui_textWrapCache; +vmCvar_t ui_developer; + +vmCvar_t ui_emoticons; +vmCvar_t ui_winner; +vmCvar_t ui_chatCommands; + +static cvarTable_t cvarTable[ ] = +{ + { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE }, + { &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE }, + + { &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE }, + { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE }, + { &ui_selectedMap, "ui_selectedMap", "0", CVAR_ARCHIVE }, + { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0", "", CVAR_ARCHIVE}, + { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1", "", CVAR_ARCHIVE}, + { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2", "", CVAR_ARCHIVE}, + { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3", "", CVAR_ARCHIVE}, + { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0_time", "", CVAR_ARCHIVE}, + { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1_time", "", CVAR_ARCHIVE}, + { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2_time", "", CVAR_ARCHIVE}, + { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3_time", "", CVAR_ARCHIVE}, + { &ui_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE | CVAR_LATCH }, + { &ui_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE | CVAR_LATCH }, + { &ui_findPlayer, "ui_findPlayer", "", CVAR_ARCHIVE}, + { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE}, + { &ui_textWrapCache, "ui_textWrapCache", "1", CVAR_ARCHIVE }, + { &ui_developer, "ui_developer", "0", CVAR_ARCHIVE | CVAR_CHEAT }, + { &ui_emoticons, "cg_emoticons", "1", CVAR_LATCH | CVAR_ARCHIVE }, + { &ui_winner, "ui_winner", "", CVAR_ROM }, + { &ui_chatCommands, "ui_chatCommands", "1", CVAR_ARCHIVE } +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .qvm file +================ +*/ +void UI_Init( qboolean ); +void UI_Shutdown( void ); +void UI_KeyEvent( int key, qboolean down ); +void UI_MouseEvent( int dx, int dy ); +int UI_MousePosition( void ); +void UI_SetMousePosition( int x, int y ); +void UI_Refresh( int realtime ); +qboolean UI_IsFullscreen( void ); +void UI_SetActiveMenu( uiMenuCommand_t menu ); +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, + int arg4, int arg5, int arg6, int arg7, + int arg8, int arg9, int arg10, int arg11 ) +{ + switch( command ) + { + case UI_GETAPIVERSION: + return UI_API_VERSION; + + case UI_INIT: + UI_Init( arg0 ); + return 0; + + case UI_SHUTDOWN: + UI_Shutdown(); + return 0; + + case UI_KEY_EVENT: + UI_KeyEvent( arg0, arg1 ); + return 0; + + case UI_MOUSE_EVENT: + UI_MouseEvent( arg0, arg1 ); + return 0; + + case UI_MOUSE_POSITION: + return UI_MousePosition( ); + + case UI_SET_MOUSE_POSITION: + UI_SetMousePosition( arg0, arg1 ); + return 0; + + case UI_REFRESH: + UI_Refresh( arg0 ); + return 0; + + case UI_IS_FULLSCREEN: + return UI_IsFullscreen( ); + + case UI_SET_ACTIVE_MENU: + UI_SetActiveMenu( arg0 ); + return 0; + + case UI_CONSOLE_COMMAND: + return UI_ConsoleCommand( arg0 ); + + case UI_DRAW_CONNECT_SCREEN: + UI_DrawConnectScreen( arg0 ); + return 0; + } + + return -1; +} + + + +void AssetCache( void ) +{ + int i; + + uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + uiInfo.uiDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + uiInfo.uiDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + uiInfo.uiDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + uiInfo.uiDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + uiInfo.uiDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + uiInfo.uiDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + uiInfo.uiDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); + + if( ui_emoticons.integer ) + { + uiInfo.uiDC.Assets.emoticonCount = BG_LoadEmoticons( + uiInfo.uiDC.Assets.emoticons, MAX_EMOTICONS ); + } + else + uiInfo.uiDC.Assets.emoticonCount = 0; + + for( i = 0; i < uiInfo.uiDC.Assets.emoticonCount; i++ ) + { + uiInfo.uiDC.Assets.emoticons[ i ].shader = trap_R_RegisterShaderNoMip( + va( "emoticons/%s_%dx1.tga", uiInfo.uiDC.Assets.emoticons[ i ].name, + uiInfo.uiDC.Assets.emoticons[ i ].width ) ); + } +} + +void UI_DrawSides( float x, float y, float w, float h, float size ) +{ + float sizeY; + + UI_AdjustFrom640( &x, &y, &w, &h ); + sizeY = size * uiInfo.uiDC.yscale; + size *= uiInfo.uiDC.xscale; + + trap_R_DrawStretchPic( x, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y + sizeY, size, h - ( sizeY * 2.0f ), 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} + +void UI_DrawTopBottom( float x, float y, float w, float h, float size ) +{ + UI_AdjustFrom640( &x, &y, &w, &h ); + size *= uiInfo.uiDC.yscale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} + +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void UI_DrawRect( float x, float y, float width, float height, float size, const float *color ) +{ + trap_R_SetColor( color ); + + UI_DrawTopBottom( x, y, width, height, size ); + UI_DrawSides( x, y, width, height, size ); + + trap_R_SetColor( NULL ); +} + +/* +================== +UI_ServerInfoIsValid + +Return false if the infostring contains nonprinting characters, + or if the hostname is blank/undefined +================== +*/ +static qboolean UI_ServerInfoIsValid( char *info ) +{ + char *c; + int len = 0; + + for( c = info; *c; c++ ) + { + if( !isprint( *c ) ) + return qfalse; + } + + for( c = Info_ValueForKey( info, "hostname" ); *c; c++ ) + { + if( isgraph( *c ) ) + len++; + } + + if( len ) + return qtrue; + else + return qfalse; +} + +/* +================== +UI_InsertServerIntoDisplayList +================== +*/ +static void UI_InsertServerIntoDisplayList( int num, int position ) +{ + int i; + static char info[MAX_STRING_CHARS]; + + if( position < 0 || position > uiInfo.serverStatus.numDisplayServers ) + return; + + trap_LAN_GetServerInfo( ui_netSource.integer, num, info, MAX_STRING_CHARS ); + + if( !UI_ServerInfoIsValid( info ) ) // don't list servers with invalid info + return; + + uiInfo.serverStatus.numDisplayServers++; + + for( i = uiInfo.serverStatus.numDisplayServers; i > position; i-- ) + uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i-1]; + + uiInfo.serverStatus.displayServers[position] = num; +} + +/* +================== +UI_RemoveServerFromDisplayList +================== +*/ +static void UI_RemoveServerFromDisplayList( int num ) +{ + int i, j; + static char info[MAX_STRING_CHARS]; + + for( i = 0; i < uiInfo.serverStatus.numDisplayServers; i++ ) + { + if( uiInfo.serverStatus.displayServers[i] == num ) + { + uiInfo.serverStatus.numDisplayServers--; + + trap_LAN_GetServerInfo( ui_netSource.integer, num, info, MAX_STRING_CHARS ); + + for( j = i; j < uiInfo.serverStatus.numDisplayServers; j++ ) + uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j+1]; + + return; + } + } +} + +/* +================== +UI_BinaryServerInsertion +================== +*/ +static void UI_BinaryServerInsertion( int num ) +{ + int mid, offset, res, len; + + // use binary search to insert server + len = uiInfo.serverStatus.numDisplayServers; + mid = len; + offset = 0; + res = 0; + + while( mid > 0 ) + { + mid = len >> 1; + // + res = trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, + uiInfo.serverStatus.sortDir, num, uiInfo.serverStatus.displayServers[offset+mid] ); + // if equal + + if( res == 0 ) + { + UI_InsertServerIntoDisplayList( num, offset + mid ); + return; + } + + // if larger + else if( res == 1 ) + { + offset += mid; + len -= mid; + } + + // if smaller + else + len -= mid; + } + + if( res == 1 ) + offset++; + + UI_InsertServerIntoDisplayList( num, offset ); +} + +typedef struct +{ + char *name, *altName; +} + +serverStatusCvar_t; + +serverStatusCvar_t serverStatusCvars[] = { + {"sv_hostname", "Name"}, + {"Address", ""}, + {"gamename", "Game name"}, + {"mapname", "Map"}, + {"version", ""}, + {"protocol", ""}, + {"timelimit", ""}, + {NULL, NULL} + }; + +/* +================== +UI_SortServerStatusInfo +================== +*/ + +static int UI_SortServerStatusCompare( const void *a, const void *b ) +{ + const char **la = (const char **)a; + const char **lb = (const char **)b; + + return strcmp( la[0], lb[0] ); +} + +static void UI_SortServerStatusInfo( serverStatusInfo_t *info ) +{ + int i, j, index; + char *tmp1, *tmp2; + + index = 0; + + for( i = 0; serverStatusCvars[i].name; i++ ) + { + for( j = 0; j < info->numLines; j++ ) + { + if( !info->lines[j][1] || info->lines[j][1][0] ) + continue; + + if( !Q_stricmp( serverStatusCvars[i].name, info->lines[j][0] ) ) + { + // swap lines + tmp1 = info->lines[index][0]; + tmp2 = info->lines[index][3]; + info->lines[index][0] = info->lines[j][0]; + info->lines[index][3] = info->lines[j][3]; + info->lines[j][0] = tmp1; + info->lines[j][3] = tmp2; + // + + if( strlen( serverStatusCvars[i].altName ) ) + info->lines[index][0] = serverStatusCvars[i].altName; + + index++; + } + } + } + + // sort remaining cvars + qsort( info->lines + index, + info->numLines - index, sizeof( info->lines[ 0 ] ), + UI_SortServerStatusCompare ); +} + +/* +================== +UI_GetServerStatusInfo +================== +*/ +static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info ) +{ + char *p, *score, *ping, *name; + int i, len; + + if( !info ) + { + trap_LAN_ServerStatus( serverAddress, NULL, 0 ); + return qfalse; + } + + memset( info, 0, sizeof( *info ) ); + + if( trap_LAN_ServerStatus( serverAddress, info->text, sizeof( info->text ) ) ) + { + Q_strncpyz( info->address, serverAddress, sizeof( info->address ) ); + p = info->text; + info->numLines = 0; + info->lines[info->numLines][0] = "Address"; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + info->lines[info->numLines][3] = info->address; + info->numLines++; + // get the cvars + + while( p && *p ) + { + p = strchr( p, '\\' ); + + if( !p ) break; + + *p++ = '\0'; + + if( *p == '\\' ) + break; + + info->lines[info->numLines][0] = p; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + + p = strchr( p, '\\' ); + + if( !p ) break; + + *p++ = '\0'; + + info->lines[info->numLines][3] = p; + info->numLines++; + + if( info->numLines >= MAX_SERVERSTATUS_LINES ) + break; + } + + UI_SortServerStatusInfo( info ); + + // get the player list + if( info->numLines < MAX_SERVERSTATUS_LINES - 3 ) + { + // empty line + info->lines[info->numLines][0] = ""; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + info->lines[info->numLines][3] = ""; + info->numLines++; + // header + info->lines[info->numLines][0] = "num"; + info->lines[info->numLines][1] = "score"; + info->lines[info->numLines][2] = "ping"; + info->lines[info->numLines][3] = "name"; + info->numLines++; + // parse players + i = 0; + len = 0; + + while( p && *p ) + { + if( *p == '\\' ) + *p++ = '\0'; + + if( !p ) + break; + + score = p; + + p = strchr( p, ' ' ); + + if( !p ) + break; + + *p++ = '\0'; + + ping = p; + + p = strchr( p, ' ' ); + + if( !p ) + break; + + *p++ = '\0'; + + name = p; + + Com_sprintf( &info->pings[len], sizeof( info->pings ) - len, "%d", i ); + + info->lines[info->numLines][0] = &info->pings[len]; + + len += strlen( &info->pings[len] ) + 1; + + info->lines[info->numLines][1] = score; + info->lines[info->numLines][2] = ping; + info->lines[info->numLines][3] = name; + info->numLines++; + + if( info->numLines >= MAX_SERVERSTATUS_LINES ) + break; + + p = strchr( p, '\\' ); + + if( !p ) + break; + + *p++ = '\0'; + + // + i++; + } + } + + return qtrue; + } + + return qfalse; +} + +/* +================== +stristr +================== +*/ +static char *stristr( char *str, char *charset ) +{ + int i; + + while( *str ) + { + for( i = 0; charset[i] && str[i]; i++ ) + if( toupper( charset[i] ) != toupper( str[i] ) ) break; + + if( !charset[i] ) return str; + + str++; + } + + return NULL; +} + +/* +================== +UI_BuildFindPlayerList +================== +*/ +static void UI_FeederSelection( int feederID, int index ); + +static void UI_BuildFindPlayerList( qboolean force ) +{ + static int numFound, numTimeOuts; + int i, j, k, resend; + serverStatusInfo_t info; + char name[MAX_NAME_LENGTH+2]; + char infoString[MAX_STRING_CHARS]; + qboolean duplicate; + + if( !force ) + { + if( !uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime ) + return; + } + else + { + memset( &uiInfo.pendingServerStatus, 0, sizeof( uiInfo.pendingServerStatus ) ); + uiInfo.numFoundPlayerServers = 0; + uiInfo.currentFoundPlayerServer = 0; + trap_Cvar_VariableStringBuffer( "ui_findPlayer", uiInfo.findPlayerName, sizeof( uiInfo.findPlayerName ) ); + Q_CleanStr( uiInfo.findPlayerName ); + // should have a string of some length + + if( !strlen( uiInfo.findPlayerName ) ) + { + uiInfo.nextFindPlayerRefresh = 0; + return; + } + + // set resend time + resend = ui_serverStatusTimeOut.integer / 2 - 10; + + if( resend < 50 ) + resend = 50; + + trap_Cvar_Set( "cl_serverStatusResendTime", va( "%d", resend ) ); + // reset all server status requests + trap_LAN_ServerStatus( NULL, NULL, 0 ); + // + uiInfo.numFoundPlayerServers = 1; + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + sizeof( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1] ), + "searching %d...", uiInfo.pendingServerStatus.num ); + numFound = 0; + numTimeOuts++; + } + + for( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) + { + // if this pending server is valid + + if( uiInfo.pendingServerStatus.server[i].valid ) + { + // try to get the server status for this server + + if( UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, &info ) ) + { + // + numFound++; + // parse through the server status lines + + for( j = 0; j < info.numLines; j++ ) + { + // should have ping info + + if( !info.lines[j][2] || !info.lines[j][2][0] ) + continue; + + // clean string first + Q_strncpyz( name, info.lines[j][3], sizeof( name ) ); + + Q_CleanStr( name ); + + duplicate = qfalse; + + for( k = 0; k < uiInfo.numFoundPlayerServers - 1; k++ ) + { + if( Q_strncmp( uiInfo.foundPlayerServerAddresses[ k ], + uiInfo.pendingServerStatus.server[ i ].adrstr, + MAX_ADDRESSLENGTH ) == 0 ) + duplicate = qtrue; + } + + // if the player name is a substring + if( stristr( name, uiInfo.findPlayerName ) && !duplicate ) + { + // add to found server list if we have space (always leave space for a line with the number found) + + if( uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS - 1 ) + { + // + Q_strncpyz( uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers-1], + uiInfo.pendingServerStatus.server[i].adrstr, + sizeof( uiInfo.foundPlayerServerAddresses[0] ) ); + Q_strncpyz( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + uiInfo.pendingServerStatus.server[i].name, + sizeof( uiInfo.foundPlayerServerNames[0] ) ); + uiInfo.numFoundPlayerServers++; + } + else + { + // can't add any more so we're done + uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers; + } + } + } + + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + sizeof( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1] ), + "searching %d/%d...", numFound, uiInfo.pendingServerStatus.num ); + // retrieved the server status so reuse this spot + uiInfo.pendingServerStatus.server[i].valid = qfalse; + } + } + + // if empty pending slot or timed out + if( !uiInfo.pendingServerStatus.server[i].valid || + uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer ) + { + if( uiInfo.pendingServerStatus.server[i].valid ) + numTimeOuts++; + + // reset server status request for this address + UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, NULL ); + + // reuse pending slot + uiInfo.pendingServerStatus.server[i].valid = qfalse; + + // if we didn't try to get the status of all servers in the main browser yet + if( uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers ) + { + uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime; + trap_LAN_GetServerAddressString( ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], + uiInfo.pendingServerStatus.server[i].adrstr, + sizeof( uiInfo.pendingServerStatus.server[i].adrstr ) ); + + trap_LAN_GetServerInfo( ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], + infoString, sizeof( infoString ) ); + + Q_strncpyz( uiInfo.pendingServerStatus.server[i].name, + Info_ValueForKey( infoString, "hostname" ), + sizeof( uiInfo.pendingServerStatus.server[0].name ) ); + + uiInfo.pendingServerStatus.server[i].valid = qtrue; + uiInfo.pendingServerStatus.num++; + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + sizeof( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1] ), + "searching %d/%d...", numFound, uiInfo.pendingServerStatus.num ); + } + } + } + + for( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) + { + if( uiInfo.pendingServerStatus.server[i].valid ) + break; + } + + // if still trying to retrieve server status info + if( i < MAX_SERVERSTATUSREQUESTS ) + uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25; + else + { + // add a line that shows the number of servers found + + if( !uiInfo.numFoundPlayerServers ) + { + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + sizeof( uiInfo.foundPlayerServerAddresses[0] ), "no servers found" ); + } + else + { + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + sizeof( uiInfo.foundPlayerServerAddresses[0] ), + "%d server%s found with player %s", uiInfo.numFoundPlayerServers - 1, + uiInfo.numFoundPlayerServers == 2 ? "" : "s", uiInfo.findPlayerName ); + } + + uiInfo.nextFindPlayerRefresh = 0; + // show the server status info for the selected server + UI_FeederSelection( FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer ); + } +} + +/* +================== +UI_BuildServerStatus +================== +*/ +static void UI_BuildServerStatus( qboolean force ) +{ + if( uiInfo.nextFindPlayerRefresh ) + return; + + if( !force ) + { + if( !uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime ) + return; + } + else + { + Menu_SetFeederSelection( NULL, FEEDER_SERVERSTATUS, 0, NULL ); + uiInfo.serverStatusInfo.numLines = 0; + // reset all server status requests + trap_LAN_ServerStatus( NULL, NULL, 0 ); + } + + if( uiInfo.serverStatus.currentServer < 0 || + uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || + uiInfo.serverStatus.numDisplayServers == 0 ) + return; + + if( UI_GetServerStatusInfo( uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo ) ) + { + uiInfo.nextServerStatusRefresh = 0; + UI_GetServerStatusInfo( uiInfo.serverStatusAddress, NULL ); + } + else + uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500; +} + +/* +================== +UI_BuildServerDisplayList +================== +*/ +static void UI_BuildServerDisplayList( qboolean force ) +{ + int i, count, clients, maxClients, ping, len, visible; + char info[MAX_STRING_CHARS]; + static int numinvisible; + + if( !( force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh ) ) + return; + + // if we shouldn't reset + if( force == 2 ) + force = 0; + + // do motd updates here too + trap_Cvar_VariableStringBuffer( "cl_motdString", uiInfo.serverStatus.motd, sizeof( uiInfo.serverStatus.motd ) ); + + len = strlen( uiInfo.serverStatus.motd ); + + if( len != uiInfo.serverStatus.motdLen ) + { + uiInfo.serverStatus.motdLen = len; + uiInfo.serverStatus.motdWidth = -1; + } + + if( force ) + { + numinvisible = 0; + // clear number of displayed servers + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + // set list box index to zero + Menu_SetFeederSelection( NULL, FEEDER_SERVERS, 0, NULL ); + // mark all servers as visible so we store ping updates for them + trap_LAN_MarkServerVisible( ui_netSource.integer, -1, qtrue ); + } + + // get the server count (comes from the master) + count = trap_LAN_GetServerCount( ui_netSource.integer ); + + if( count == -1 || ( ui_netSource.integer == AS_LOCAL && count == 0 ) ) + { + // still waiting on a response from the master + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500; + return; + } + + visible = qfalse; + + for( i = 0; i < count; i++ ) + { + // if we already got info for this server + + if( !trap_LAN_ServerIsVisible( ui_netSource.integer, i ) ) + continue; + + visible = qtrue; + // get the ping for this server + ping = trap_LAN_GetServerPing( ui_netSource.integer, i ); + + if( ping > 0 || ui_netSource.integer == AS_FAVORITES ) + { + trap_LAN_GetServerInfo( ui_netSource.integer, i, info, MAX_STRING_CHARS ); + + clients = atoi( Info_ValueForKey( info, "clients" ) ); + uiInfo.serverStatus.numPlayersOnServers += clients; + + if( ui_browserShowEmpty.integer == 0 ) + { + if( clients == 0 ) + { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + if( ui_browserShowFull.integer == 0 ) + { + maxClients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + + if( clients == maxClients ) + { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + // make sure we never add a favorite server twice + if( ui_netSource.integer == AS_FAVORITES ) + UI_RemoveServerFromDisplayList( i ); + + // insert the server into the list + UI_BinaryServerInsertion( i ); + + // done with this server + if( ping > 0 ) + { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + numinvisible++; + } + } + } + + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime; + + // if there were no servers visible for ping updates + + if( !visible ) + { +// UI_StopServerRefresh(); +// uiInfo.serverStatus.nextDisplayRefresh = 0; + } +} + + +/* +================= +UI_StopServerRefresh +================= +*/ +static void UI_StopServerRefresh( void ) +{ + int count; + + if( !uiInfo.serverStatus.refreshActive ) + { + // not currently refreshing + return; + } + + uiInfo.serverStatus.refreshActive = qfalse; + Com_Printf( "%d servers listed in browser with %d players.\n", + uiInfo.serverStatus.numDisplayServers, + uiInfo.serverStatus.numPlayersOnServers ); + count = trap_LAN_GetServerCount( ui_netSource.integer ); + + if( count - uiInfo.serverStatus.numDisplayServers > 0 ) + { + Com_Printf( "%d servers not listed due to packet loss, invalid info," + " or pings higher than %d\n", + count - uiInfo.serverStatus.numDisplayServers, + ( int ) trap_Cvar_VariableValue( "cl_maxPing" ) ); + } + +} + +/* +================= +UI_DoServerRefresh +================= +*/ +static void UI_DoServerRefresh( void ) +{ + qboolean wait = qfalse; + + if( !uiInfo.serverStatus.refreshActive ) + return; + + if( ui_netSource.integer != AS_FAVORITES ) + { + if( ui_netSource.integer == AS_LOCAL ) + { + if( !trap_LAN_GetServerCount( ui_netSource.integer ) ) + wait = qtrue; + } + else + { + if( trap_LAN_GetServerCount( ui_netSource.integer ) < 0 ) + wait = qtrue; + } + } + + if( uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime ) + { + if( wait ) + return; + } + + // if still trying to retrieve pings + if( trap_LAN_UpdateVisiblePings( ui_netSource.integer ) ) + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + else if( !wait ) + { + // get the last servers in the list + UI_BuildServerDisplayList( 2 ); + // stop the refresh + UI_StopServerRefresh(); + } + + // + UI_BuildServerDisplayList( qfalse ); +} + +/* +================= +UI_UpdatePendingPings +================= +*/ +static void UI_UpdatePendingPings( void ) +{ + trap_LAN_ResetPings( ui_netSource.integer ); + uiInfo.serverStatus.refreshActive = qtrue; + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + +} + +/* +================= +UI_StartServerRefresh +================= +*/ +static void UI_StartServerRefresh( qboolean full ) +{ + int time; + qtime_t q; + + time = trap_RealTime( &q ); + trap_Cvar_Set( va( "ui_lastServerRefresh_%i_time", ui_netSource.integer ), + va( "%i", time ) ); + trap_Cvar_Set( va( "ui_lastServerRefresh_%i", ui_netSource.integer ), + va( "%s-%i, %i at %i:%02i", MonthAbbrev[q.tm_mon], + q.tm_mday, 1900 + q.tm_year, q.tm_hour, q.tm_min ) ); + + if( !full ) + { + UI_UpdatePendingPings(); + return; + } + + uiInfo.serverStatus.refreshActive = qtrue; + uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000; + // clear number of displayed servers + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + // mark all servers as visible so we store ping updates for them + trap_LAN_MarkServerVisible( ui_netSource.integer, -1, qtrue ); + // reset all the pings + trap_LAN_ResetPings( ui_netSource.integer ); + // + + if( ui_netSource.integer == AS_LOCAL ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, "localservers\n" ); + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + return; + } + + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; + + if( ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER ) + { + qboolean global = ui_netSource.integer == AS_GLOBAL; + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "globalservers %d 70 full empty\n", + global ? 0 : 1 ) ); + } +} + +int frameCount = 0; +int startTime; + +#define UI_FPS_FRAMES 4 +void UI_Refresh( int realtime ) +{ + static int index; + static int previousTimes[UI_FPS_FRAMES]; + + //if( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) { + // return; + //} + + uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime; + uiInfo.uiDC.realTime = realtime; + + previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime; + index++; + + if( index > UI_FPS_FRAMES ) + { + int i, total; + // average multiple frames together to smooth changes out a bit + total = 0; + + for( i = 0 ; i < UI_FPS_FRAMES ; i++ ) + total += previousTimes[i]; + + if( !total ) + total = 1; + + uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total; + } + + UI_UpdateCvars(); + + if( Menu_Count() > 0 ) + { + Menu_UpdateAll( ); + Menu_PaintAll( ); + UI_DoServerRefresh( ); + UI_BuildServerStatus( qfalse ); + UI_BuildFindPlayerList( qfalse ); + UI_UpdateNews( qfalse ); + } + + // draw cursor + UI_SetColor( NULL ); + + if( trap_Key_GetCatcher( ) == KEYCATCH_UI && !trap_Cvar_VariableValue( "ui_hideCursor" ) ) + { + UI_DrawHandlePic( uiInfo.uiDC.cursorx - ( 16.0f * uiInfo.uiDC.aspectScale ), uiInfo.uiDC.cursory - 16.0f, + 32.0f * uiInfo.uiDC.aspectScale, 32.0f, uiInfo.uiDC.Assets.cursor ); + } +} + +/* +================= +UI_Shutdown +================= +*/ +void UI_Shutdown( void ) +{ + trap_LAN_SaveCachedServers(); +} + +qboolean Asset_Parse( int handle ) +{ + pc_token_t token; + const char *tempStr; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( Q_stricmp( token.string, "{" ) != 0 ) + return qfalse; + + while( 1 ) + { + memset( &token, 0, sizeof( pc_token_t ) ); + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( Q_stricmp( token.string, "}" ) == 0 ) + return qtrue; + + // font + if( Q_stricmp( token.string, "font" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.textFont ); + uiInfo.uiDC.Assets.fontRegistered = qtrue; + continue; + } + + if( Q_stricmp( token.string, "smallFont" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.smallFont ); + continue; + } + + if( Q_stricmp( token.string, "bigFont" ) == 0 ) + { + int pointSize; + + if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) + return qfalse; + + trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.bigFont ); + continue; + } + + + // gradientbar + if( Q_stricmp( token.string, "gradientbar" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr ); + continue; + } + + // enterMenuSound + if( Q_stricmp( token.string, "menuEnterSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // exitMenuSound + if( Q_stricmp( token.string, "menuExitSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // itemFocusSound + if( Q_stricmp( token.string, "itemFocusSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // menuBuzzSound + if( Q_stricmp( token.string, "menuBuzzSound" ) == 0 ) + { + if( !PC_String_Parse( handle, &tempStr ) ) + return qfalse; + + uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if( Q_stricmp( token.string, "cursor" ) == 0 ) + { + if( !PC_String_Parse( handle, &uiInfo.uiDC.Assets.cursorStr ) ) + return qfalse; + + uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr ); + continue; + } + + if( Q_stricmp( token.string, "fadeClamp" ) == 0 ) + { + if( !PC_Float_Parse( handle, &uiInfo.uiDC.Assets.fadeClamp ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "fadeCycle" ) == 0 ) + { + if( !PC_Int_Parse( handle, &uiInfo.uiDC.Assets.fadeCycle ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "fadeAmount" ) == 0 ) + { + if( !PC_Float_Parse( handle, &uiInfo.uiDC.Assets.fadeAmount ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowX" ) == 0 ) + { + if( !PC_Float_Parse( handle, &uiInfo.uiDC.Assets.shadowX ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowY" ) == 0 ) + { + if( !PC_Float_Parse( handle, &uiInfo.uiDC.Assets.shadowY ) ) + return qfalse; + + continue; + } + + if( Q_stricmp( token.string, "shadowColor" ) == 0 ) + { + if( !PC_Color_Parse( handle, &uiInfo.uiDC.Assets.shadowColor ) ) + return qfalse; + + uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3]; + continue; + } + + } + + return qfalse; +} + +void UI_Report( void ) +{ + String_Report(); +} + +void UI_ParseMenu( const char *menuFile ) +{ + int handle; + pc_token_t token; + + handle = trap_Parse_LoadSource( menuFile ); + + if( !handle ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: Menu file %s not found\n", + menuFile ); + return; + } + + while( 1 ) + { + memset( &token, 0, sizeof( pc_token_t ) ); + + if( !trap_Parse_ReadToken( handle, &token ) ) + break; + + //if( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if( token.string[0] == '}' ) + break; + + if( Q_stricmp( token.string, "assetGlobalDef" ) == 0 ) + { + if( Asset_Parse( handle ) ) + continue; + else + break; + } + + if( Q_stricmp( token.string, "menudef" ) == 0 ) + { + // start a new menu + Menu_New( handle ); + } + } + + trap_Parse_FreeSource( handle ); +} + +qboolean Load_Menu( int handle ) +{ + pc_token_t token; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( token.string[0] != '{' ) + return qfalse; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( token.string[0] == 0 ) + return qfalse; + + if( token.string[0] == '}' ) + return qtrue; + + UI_ParseMenu( token.string ); + } + + return qfalse; +} + +void UI_LoadMenus( const char *menuFile, qboolean reset ) +{ + pc_token_t token; + int handle; + int start; + + start = trap_Milliseconds(); + + handle = trap_Parse_LoadSource( menuFile ); + + if( !handle ) + trap_Error( va( S_COLOR_RED "menu list '%s' not found, unable to continue!\n", menuFile ) ); + + if( reset ) + Menu_Reset(); + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + break; + + if( token.string[0] == 0 || token.string[0] == '}' ) + break; + + if( token.string[0] == '}' ) + break; + + if( Q_stricmp( token.string, "loadmenu" ) == 0 ) + { + if( Load_Menu( handle ) ) + continue; + else + break; + } + } + + Com_Printf( "UI menu file '%s' loaded in %d msec\n", menuFile, trap_Milliseconds() - start ); + + trap_Parse_FreeSource( handle ); +} + +void UI_LoadHelp( const char *helpFile ) +{ + pc_token_t token; + int handle, start; + char title[ 32 ], buffer[ 1024 ]; + + start = trap_Milliseconds(); + + handle = trap_Parse_LoadSource( helpFile ); + if( !handle ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: help file '%s' not found!\n", + helpFile ); + return; + } + + if( !trap_Parse_ReadToken( handle, &token ) || + token.string[0] == 0 || token.string[0] != '{' ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: help file '%s' does not start with " + "'{'\n", helpFile ); + return; + } + + uiInfo.helpCount = 0; + title[ 0 ] = 0; + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) || + token.string[0] == 0 || token.string[0] == '}' ) + break; + + if( token.string[0] == '{' ) + { + buffer[ 0 ] = 0; + Q_strcat( buffer, sizeof( buffer ), title ); + Q_strcat( buffer, sizeof( buffer ), "\n\n" ); + while( trap_Parse_ReadToken( handle, &token ) && + token.string[0] != 0 && token.string[0] != '}' ) + { + Q_strcat( buffer, sizeof( buffer ), token.string ); + } + + uiInfo.helpList[ uiInfo.helpCount ].text = String_Alloc( title ); + uiInfo.helpList[ uiInfo.helpCount ].v.text = String_Alloc( buffer ); + uiInfo.helpList[ uiInfo.helpCount ].type = INFOTYPE_TEXT; + uiInfo.helpCount++; + title[ 0 ] = 0; + } + else + Q_strcat( title, sizeof( title ), token.string ); + } + + trap_Parse_FreeSource( handle ); + + Com_Printf( "UI help file '%s' loaded in %d msec (%d infopanes)\n", + helpFile, trap_Milliseconds() - start, uiInfo.helpCount ); +} + +void UI_Load( void ) +{ + char lastName[1024]; + menuDef_t *menu = Menu_GetFocused(); + + if( menu && menu->window.name ) + strcpy( lastName, menu->window.name ); + + String_Init(); + + UI_LoadMenus( "ui/menus.txt", qtrue ); + UI_LoadMenus( "ui/ingame.txt", qfalse ); + UI_LoadMenus( "ui/tremulous.txt", qfalse ); + UI_LoadHelp( "ui/help.txt" ); + Menus_CloseAll( ); + Menus_ActivateByName( lastName ); + +} + +/* +=============== +UI_GetCurrentAlienStage +=============== +*/ +static stage_t UI_GetCurrentAlienStage( void ) +{ + char buffer[ MAX_TOKEN_CHARS ]; + stage_t stage, dummy; + + trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) ); + sscanf( buffer, "%d %d", ( int * ) & stage , ( int * ) & dummy ); + + return stage; +} + +/* +=============== +UI_GetCurrentHumanStage +=============== +*/ +static stage_t UI_GetCurrentHumanStage( void ) +{ + char buffer[ MAX_TOKEN_CHARS ]; + stage_t stage, dummy; + + trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) ); + sscanf( buffer, "%d %d", ( int * ) & dummy, ( int * ) & stage ); + + return stage; +} + +/* +=============== +UI_DrawInfoPane +=============== +*/ +static void UI_DrawInfoPane( menuItem_t *item, rectDef_t *rect, float text_x, float text_y, + float scale, int textalign, int textvalign, vec4_t color, int textStyle ) +{ + int value = 0; + const char *s = ""; + char *string = ""; + + int class, credits; + char ui_currentClass[ MAX_STRING_CHARS ]; + + trap_Cvar_VariableStringBuffer( "ui_currentClass", ui_currentClass, MAX_STRING_CHARS ); + + sscanf( ui_currentClass, "%d %d", &class, &credits ); + + switch( item->type ) + { + case INFOTYPE_TEXT: + s = item->v.text; + break; + + case INFOTYPE_CLASS: + value = ( BG_ClassCanEvolveFromTo( class, item->v.pclass, credits, + UI_GetCurrentAlienStage(), 0 ) + + ALIEN_CREDITS_PER_KILL - 1 ) / ALIEN_CREDITS_PER_KILL; + + if( value < 1 ) + { + s = va( "%s\n\n%s", + BG_ClassConfig( item->v.pclass )->humanName, + BG_Class( item->v.pclass )->info ); + } + else + { + s = va( "%s\n\n%s\n\nFrags: %d", + BG_ClassConfig( item->v.pclass )->humanName, + BG_Class( item->v.pclass )->info, + value ); + } + + break; + + case INFOTYPE_WEAPON: + value = BG_Weapon( item->v.weapon )->price; + + if( value == 0 ) + { + s = va( "%s\n\n%s\n\nCredits: Free", + BG_Weapon( item->v.weapon )->humanName, + BG_Weapon( item->v.weapon )->info ); + } + else + { + s = va( "%s\n\n%s\n\nCredits: %d", + BG_Weapon( item->v.weapon )->humanName, + BG_Weapon( item->v.weapon )->info, + value ); + } + + break; + + case INFOTYPE_UPGRADE: + value = BG_Upgrade( item->v.upgrade )->price; + + if( value == 0 ) + { + s = va( "%s\n\n%s\n\nCredits: Free", + BG_Upgrade( item->v.upgrade )->humanName, + BG_Upgrade( item->v.upgrade )->info ); + } + else + { + s = va( "%s\n\n%s\n\nCredits: %d", + BG_Upgrade( item->v.upgrade )->humanName, + BG_Upgrade( item->v.upgrade )->info, + value ); + } + + break; + + case INFOTYPE_BUILDABLE: + value = BG_Buildable( item->v.buildable, NULL )->buildPoints; + + switch( BG_Buildable( item->v.buildable, NULL )->team ) + { + case TEAM_ALIENS: + string = "Sentience"; + break; + + case TEAM_HUMANS: + string = "Power"; + break; + + default: + break; + } + + if( value == 0 ) + { + s = va( "%s\n\n%s", + BG_Buildable( item->v.buildable, NULL )->humanName, + BG_Buildable( item->v.buildable, NULL )->info ); + } + else + { + s = va( "%s\n\n%s\n\n%s: %d", + BG_Buildable( item->v.buildable, NULL )->humanName, + BG_Buildable( item->v.buildable, NULL )->info, + string, value ); + } + + break; + } + + UI_DrawTextBlock( rect, text_x, text_y, color, scale, + textalign, textvalign, textStyle, s ); +} + + +static void UI_DrawServerMapPreview( rectDef_t *rect, float scale, vec4_t color ) +{ + if( uiInfo.serverStatus.currentServerCinematic >= 0 ) + { + trap_CIN_RunCinematic( uiInfo.serverStatus.currentServerCinematic ); + trap_CIN_SetExtents( uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h ); + trap_CIN_DrawCinematic( uiInfo.serverStatus.currentServerCinematic ); + } + else if( uiInfo.serverStatus.currentServerPreview > 0 ) + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview ); + else + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip( "gfx/2d/load_screen" ) ); +} + +static void UI_DrawSelectedMapPreview( rectDef_t *rect, float scale, vec4_t color ) +{ + int map = ui_selectedMap.integer; + + if( map < 0 || map > uiInfo.mapCount ) + { + ui_selectedMap.integer = 0; + trap_Cvar_Set( "ui_selectedMap", "0" ); + map = 0; + } + + if( uiInfo.mapList[map].cinematic >= -1 ) + { + if( uiInfo.mapList[map].cinematic == -1 ) + uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic( va( "%s.roq", uiInfo.mapList[map].mapLoadName ), + 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + + if( uiInfo.mapList[map].cinematic >= 0 ) + { + trap_CIN_RunCinematic( uiInfo.mapList[map].cinematic ); + trap_CIN_SetExtents( uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h ); + trap_CIN_DrawCinematic( uiInfo.mapList[map].cinematic ); + } + else + uiInfo.mapList[map].cinematic = -2; + } + else + { + if( uiInfo.mapList[map].levelShot == -1 ) + uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip( uiInfo.mapList[map].imageName ); + + if( uiInfo.mapList[map].levelShot > 0 ) + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot ); + else + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip( "gfx/2d/load_screen" ) ); + } +} + +static void UI_DrawSelectedMapName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) +{ + int map = ui_selectedMap.integer; + + if( map >= 0 && map < uiInfo.mapCount ) + UI_Text_Paint( rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle ); +} + +static const char *UI_OwnerDrawText( int ownerDraw ) +{ + const char *s = NULL; + + switch( ownerDraw ) + { + case UI_NETSOURCE: + if( ui_netSource.integer < 0 || ui_netSource.integer >= numNetSources ) + ui_netSource.integer = 0; + + s = netSources[ui_netSource.integer]; + break; + + case UI_KEYBINDSTATUS: + if( Display_KeyBindPending() ) + s = "Waiting for new key... Press ESCAPE to cancel"; + else + s = "Press ENTER or CLICK to change, Press BACKSPACE to clear"; + + break; + + case UI_SERVERREFRESHDATE: + if( uiInfo.serverStatus.refreshActive ) + { +#define MAX_DOTS 5 + int numServers = trap_LAN_GetServerCount( ui_netSource.integer ); + int numDots = ( uiInfo.uiDC.realTime / 500 ) % ( MAX_DOTS + 1 ); + char dots[ MAX_DOTS + 1 ]; + int i; + + for( i = 0; i < numDots; i++ ) + dots[ i ] = '.'; + + dots[ i ] = '\0'; + + s = numServers < 0 ? va( "Waiting for response%s", dots ) : + va( "Getting info for %d servers (ESC to cancel)%s", numServers, dots ); + } + else + s = va( "Refresh Time: %s", UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i", ui_netSource.integer ) ) ); + + break; + + case UI_SERVERMOTD: + s = uiInfo.serverStatus.motd; + break; + + default: + break; + } + + return s; +} + +static int UI_OwnerDrawWidth( int ownerDraw, float scale ) +{ + const char *s = NULL; + + switch( ownerDraw ) + { + case UI_NETSOURCE: + case UI_KEYBINDSTATUS: + case UI_SERVERREFRESHDATE: + case UI_SERVERMOTD: + s = UI_OwnerDrawText( ownerDraw ); + break; + + default: + break; + } + + if( s ) + return UI_Text_Width( s, scale ); + + return 0; +} + +/* +=============== +UI_BuildPlayerList +=============== +*/ +static void UI_BuildPlayerList( void ) +{ + uiClientState_t cs; + int n, count, team, team2, playerTeamNumber; + char info[MAX_INFO_STRING]; + + trap_GetClientState( &cs ); + trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING ); + uiInfo.playerNumber = cs.clientNum; + team = atoi( Info_ValueForKey( info, "t" ) ); + trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ); + count = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + uiInfo.playerCount = 0; + uiInfo.myTeamCount = 0; + uiInfo.myPlayerIndex = 0; + playerTeamNumber = 0; + + for( n = 0; n < count; n++ ) + { + trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING ); + + if( info[0] ) + { + Com_ClientListParse( &uiInfo.ignoreList[ uiInfo.playerCount ], + Info_ValueForKey( info, "ig" ) ); + Q_strncpyz( uiInfo.rawPlayerNames[uiInfo.playerCount], + Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); + Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount], + Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); + Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] ); + uiInfo.clientNums[uiInfo.playerCount] = n; + + if( n == uiInfo.playerNumber ) + uiInfo.myPlayerIndex = uiInfo.playerCount; + + uiInfo.playerCount++; + + team2 = atoi( Info_ValueForKey( info, "t" ) ); + + if( team2 == team ) + { + Q_strncpyz( uiInfo.rawTeamNames[uiInfo.myTeamCount], + Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); + Q_strncpyz( uiInfo.teamNames[uiInfo.myTeamCount], + Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); + Q_CleanStr( uiInfo.teamNames[uiInfo.myTeamCount] ); + uiInfo.teamClientNums[uiInfo.myTeamCount] = n; + + if( uiInfo.playerNumber == n ) + playerTeamNumber = uiInfo.myTeamCount; + + uiInfo.myTeamCount++; + } + } + } +} + +static void UI_DrawGLInfo( rectDef_t *rect, float scale, int textalign, int textvalign, + vec4_t color, int textStyle, float text_x, float text_y ) +{ + char buffer[ 4096 ]; + + Com_sprintf( buffer, sizeof( buffer ), "VENDOR: %s\nVERSION: %s\n" + "PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)\n%s", + uiInfo.uiDC.glconfig.vendor_string, uiInfo.uiDC.glconfig.renderer_string, + uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, + uiInfo.uiDC.glconfig.stencilBits, uiInfo.uiDC.glconfig.extensions_string ); + + UI_DrawTextBlock( rect, text_x, text_y, color, scale, + textalign, textvalign, textStyle, buffer ); +} + +// FIXME: table drive +// +static void UI_OwnerDraw( float x, float y, float w, float h, + float text_x, float text_y, int ownerDraw, + int ownerDrawFlags, int align, + int textalign, int textvalign, float borderSize, + float scale, vec4_t foreColor, vec4_t backColor, + qhandle_t shader, + int textStyle ) +{ + rectDef_t rect; + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + switch( ownerDraw ) + { + case UI_TEAMINFOPANE: + UI_DrawInfoPane( &uiInfo.teamList[ uiInfo.teamIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_ACLASSINFOPANE: + UI_DrawInfoPane( &uiInfo.alienClassList[ uiInfo.alienClassIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_AUPGRADEINFOPANE: + UI_DrawInfoPane( &uiInfo.alienUpgradeList[ uiInfo.alienUpgradeIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_HITEMINFOPANE: + UI_DrawInfoPane( &uiInfo.humanItemList[ uiInfo.humanItemIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_HBUYINFOPANE: + UI_DrawInfoPane( &uiInfo.humanArmouryBuyList[ uiInfo.humanArmouryBuyIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_HSELLINFOPANE: + UI_DrawInfoPane( &uiInfo.humanArmourySellList[ uiInfo.humanArmourySellIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_ABUILDINFOPANE: + UI_DrawInfoPane( &uiInfo.alienBuildList[ uiInfo.alienBuildIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_HBUILDINFOPANE: + UI_DrawInfoPane( &uiInfo.humanBuildList[ uiInfo.humanBuildIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_HELPINFOPANE: + UI_DrawInfoPane( &uiInfo.helpList[ uiInfo.helpIndex ], + &rect, text_x, text_y, scale, textalign, textvalign, foreColor, textStyle ); + break; + + case UI_NETMAPPREVIEW: + UI_DrawServerMapPreview( &rect, scale, foreColor ); + break; + + case UI_SELECTEDMAPPREVIEW: + UI_DrawSelectedMapPreview( &rect, scale, foreColor ); + break; + + case UI_SELECTEDMAPNAME: + UI_DrawSelectedMapName( &rect, scale, foreColor, textStyle ); + break; + + case UI_GLINFO: + UI_DrawGLInfo( &rect, scale, textalign, textvalign, foreColor, textStyle, text_x, text_y ); + break; + + default: + break; + } + +} + +static qboolean UI_OwnerDrawVisible( int flags ) +{ + qboolean vis = qtrue; + uiClientState_t cs; + team_t team; + char info[ MAX_INFO_STRING ]; + + trap_GetClientState( &cs ); + trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING ); + team = atoi( Info_ValueForKey( info, "t" ) ); + + + while( flags ) + { + if( flags & UI_SHOW_NOTSPECTATING ) + { + if( team == TEAM_NONE ) + vis = qfalse; + + flags &= ~UI_SHOW_NOTSPECTATING; + } + + if( flags & UI_SHOW_VOTEACTIVE ) + { + if( !trap_Cvar_VariableValue( "ui_voteActive" ) ) + vis = qfalse; + + flags &= ~UI_SHOW_VOTEACTIVE; + } + + if( flags & UI_SHOW_CANVOTE ) + { + if( trap_Cvar_VariableValue( "ui_voteActive" ) ) + vis = qfalse; + + flags &= ~UI_SHOW_CANVOTE; + } + + if( flags & UI_SHOW_TEAMVOTEACTIVE ) + { + if( team == TEAM_ALIENS ) + { + if( !trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) ) + vis = qfalse; + } + else if( team == TEAM_HUMANS ) + { + if( !trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) ) + vis = qfalse; + } + + flags &= ~UI_SHOW_TEAMVOTEACTIVE; + } + + if( flags & UI_SHOW_CANTEAMVOTE ) + { + if( team == TEAM_ALIENS ) + { + if( trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) ) + vis = qfalse; + } + else if( team == TEAM_HUMANS ) + { + if( trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) ) + vis = qfalse; + } + + flags &= ~UI_SHOW_CANTEAMVOTE; + } + + if( flags & UI_SHOW_FAVORITESERVERS ) + { + // this assumes you only put this type of display flag on something showing in the proper context + + if( ui_netSource.integer != AS_FAVORITES ) + vis = qfalse; + + flags &= ~UI_SHOW_FAVORITESERVERS; + } + + if( flags & UI_SHOW_NOTFAVORITESERVERS ) + { + // this assumes you only put this type of display flag on something showing in the proper context + + if( ui_netSource.integer == AS_FAVORITES ) + vis = qfalse; + + flags &= ~UI_SHOW_NOTFAVORITESERVERS; + } + else + flags = 0; + } + + return vis; +} + +static qboolean UI_NetSource_HandleKey( int key ) +{ + if( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) + { + if( key == K_MOUSE2 ) + { + ui_netSource.integer--; + + if( ui_netSource.integer == AS_MPLAYER ) + ui_netSource.integer--; + } + else + { + ui_netSource.integer++; + + if( ui_netSource.integer == AS_MPLAYER ) + ui_netSource.integer++; + } + + if( ui_netSource.integer >= numNetSources ) + ui_netSource.integer = 0; + else if( ui_netSource.integer < 0 ) + ui_netSource.integer = numNetSources - 1; + + UI_BuildServerDisplayList( qtrue ); + + if( ui_netSource.integer != AS_GLOBAL ) + UI_StartServerRefresh( qtrue ); + + trap_Cvar_Set( "ui_netSource", va( "%d", ui_netSource.integer ) ); + return qtrue; + } + + return qfalse; +} + +static qboolean UI_OwnerDrawHandleKey( int ownerDraw, int key ) +{ + switch( ownerDraw ) + { + case UI_NETSOURCE: + UI_NetSource_HandleKey( key ); + break; + + default: + break; + } + + return qfalse; +} + + +/* +================= +UI_ServersQsortCompare +================= +*/ +static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ) +{ + return trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, + uiInfo.serverStatus.sortDir, *( int* )arg1, *( int* )arg2 ); +} + + +/* +================= +UI_ServersSort +================= +*/ +void UI_ServersSort( int column, qboolean force ) +{ + if( !force ) + { + if( uiInfo.serverStatus.sortKey == column ) + return; + } + + uiInfo.serverStatus.sortKey = column; + qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, + sizeof( int ), UI_ServersQsortCompare ); +} + +/* +=============== +UI_LoadTeams +=============== +*/ +static void UI_LoadTeams( void ) +{ + uiInfo.teamCount = 4; + + uiInfo.teamList[ 0 ].text = "Aliens"; + uiInfo.teamList[ 0 ].cmd = "cmd team aliens\n"; + uiInfo.teamList[ 0 ].type = INFOTYPE_TEXT; + uiInfo.teamList[ 0 ].v.text = + "The Alien Team\n\n" + "The Aliens' strengths are in movement and the ability to " + "quickly construct new bases quickly. They possess a range " + "of abilities including basic melee attacks, movement-" + "crippling poisons and more."; + + uiInfo.teamList[ 1 ].text = "Humans"; + uiInfo.teamList[ 1 ].cmd = "cmd team humans\n"; + uiInfo.teamList[ 1 ].type = INFOTYPE_TEXT; + uiInfo.teamList[ 1 ].v.text = + "The Human Team\n\n" + "The humans are the masters of technology. Although their " + "bases take long to construct, their automated defense " + "ensures they stay built. A wide range of upgrades and " + "weapons are available to the humans, each contributing " + "to eradicate the alien threat."; + + uiInfo.teamList[ 2 ].text = "Spectate"; + uiInfo.teamList[ 2 ].cmd = "cmd team spectate\n"; + uiInfo.teamList[ 2 ].type = INFOTYPE_TEXT; + uiInfo.teamList[ 2 ].v.text = "Watch the game without playing."; + + uiInfo.teamList[ 3 ].text = "Auto select"; + uiInfo.teamList[ 3 ].cmd = "cmd team auto\n"; + uiInfo.teamList[ 3 ].type = INFOTYPE_TEXT; + uiInfo.teamList[ 3 ].v.text = "Join the team with the least players."; +} + +/* +=============== +UI_AddClass +=============== +*/ + +static void UI_AddClass( class_t class ) +{ + uiInfo.alienClassList[ uiInfo.alienClassCount ].text = + BG_ClassConfig( class )->humanName; + uiInfo.alienClassList[ uiInfo.alienClassCount ].cmd = + String_Alloc( va( "cmd class %s\n", BG_Class( class )->name ) ); + uiInfo.alienClassList[ uiInfo.alienClassCount ].type = INFOTYPE_CLASS; + + uiInfo.alienClassList[ uiInfo.alienClassCount ].v.pclass = class; + + uiInfo.alienClassCount++; +} + +/* +=============== +UI_LoadAlienClasses +=============== +*/ +static void UI_LoadAlienClasses( void ) +{ + uiInfo.alienClassCount = 0; + + if( BG_ClassIsAllowed( PCL_ALIEN_LEVEL0 ) ) + UI_AddClass( PCL_ALIEN_LEVEL0 ); + + if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) && + BG_ClassAllowedInStage( PCL_ALIEN_BUILDER0_UPG, UI_GetCurrentAlienStage( ) ) ) + UI_AddClass( PCL_ALIEN_BUILDER0_UPG ); + else if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) ) + UI_AddClass( PCL_ALIEN_BUILDER0 ); +} + +/* +=============== +UI_AddItem +=============== +*/ +static void UI_AddItem( weapon_t weapon ) +{ + uiInfo.humanItemList[ uiInfo.humanItemCount ].text = + BG_Weapon( weapon )->humanName; + uiInfo.humanItemList[ uiInfo.humanItemCount ].cmd = + String_Alloc( va( "cmd class %s\n", BG_Weapon( weapon )->name ) ); + uiInfo.humanItemList[ uiInfo.humanItemCount ].type = INFOTYPE_WEAPON; + uiInfo.humanItemList[ uiInfo.humanItemCount ].v.weapon = weapon; + + uiInfo.humanItemCount++; +} + +/* +=============== +UI_LoadHumanItems +=============== +*/ +static void UI_LoadHumanItems( void ) +{ + uiInfo.humanItemCount = 0; + + if( BG_WeaponIsAllowed( WP_MACHINEGUN ) ) + UI_AddItem( WP_MACHINEGUN ); + + if( BG_WeaponIsAllowed( WP_HBUILD ) ) + UI_AddItem( WP_HBUILD ); +} + +/* +=============== +UI_ParseCarriageList +=============== +*/ +static void UI_ParseCarriageList( void ) +{ + int i; + char carriageCvar[ MAX_TOKEN_CHARS ]; + char *iterator; + char buffer[ MAX_TOKEN_CHARS ]; + char *bufPointer; + + trap_Cvar_VariableStringBuffer( "ui_carriage", carriageCvar, sizeof( carriageCvar ) ); + iterator = carriageCvar; + + uiInfo.weapons = 0; + uiInfo.upgrades = 0; + + //simple parser to give rise to weapon/upgrade list + + while( iterator && iterator[ 0 ] != '$' ) + { + bufPointer = buffer; + + if( iterator[ 0 ] == 'W' ) + { + iterator++; + + while( iterator[ 0 ] != ' ' ) + *bufPointer++ = *iterator++; + + *bufPointer++ = '\n'; + + i = atoi( buffer ); + + uiInfo.weapons |= ( 1 << i ); + } + else if( iterator[ 0 ] == 'U' ) + { + iterator++; + + while( iterator[ 0 ] != ' ' ) + *bufPointer++ = *iterator++; + + *bufPointer++ = '\n'; + + i = atoi( buffer ); + + uiInfo.upgrades |= ( 1 << i ); + } + + iterator++; + } +} + +/* +=============== +UI_LoadHumanArmouryBuys +=============== +*/ +static void UI_LoadHumanArmouryBuys( void ) +{ + int i, j = 0; + stage_t stage = UI_GetCurrentHumanStage( ); + int slots = 0; + + UI_ParseCarriageList( ); + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( uiInfo.weapons & ( 1 << i ) ) + slots |= BG_Weapon( i )->slots; + } + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( uiInfo.upgrades & ( 1 << i ) ) + slots |= BG_Upgrade( i )->slots; + } + + uiInfo.humanArmouryBuyCount = 0; + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( BG_Weapon( i )->team == TEAM_HUMANS && + BG_Weapon( i )->purchasable && + BG_WeaponAllowedInStage( i, stage ) && + BG_WeaponIsAllowed( i ) && + !( BG_Weapon( i )->slots & slots ) && + !( uiInfo.weapons & ( 1 << i ) ) ) + { + uiInfo.humanArmouryBuyList[ j ].text = BG_Weapon( i )->humanName; + uiInfo.humanArmouryBuyList[ j ].cmd = + String_Alloc( va( "cmd buy %s\n", BG_Weapon( i )->name ) ); + uiInfo.humanArmouryBuyList[ j ].type = INFOTYPE_WEAPON; + uiInfo.humanArmouryBuyList[ j ].v.weapon = i; + + j++; + + uiInfo.humanArmouryBuyCount++; + } + } + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_Upgrade( i )->team == TEAM_HUMANS && + BG_Upgrade( i )->purchasable && + BG_UpgradeAllowedInStage( i, stage ) && + BG_UpgradeIsAllowed( i ) && + !( BG_Upgrade( i )->slots & slots ) && + !( uiInfo.upgrades & ( 1 << i ) ) ) + { + uiInfo.humanArmouryBuyList[ j ].text = BG_Upgrade( i )->humanName; + uiInfo.humanArmouryBuyList[ j ].cmd = + String_Alloc( va( "cmd buy %s\n", BG_Upgrade( i )->name ) ); + uiInfo.humanArmouryBuyList[ j ].type = INFOTYPE_UPGRADE; + uiInfo.humanArmouryBuyList[ j ].v.upgrade = i; + + j++; + + uiInfo.humanArmouryBuyCount++; + } + } +} + +/* +=============== +UI_LoadHumanArmourySells +=============== +*/ +static void UI_LoadHumanArmourySells( void ) +{ + int i, j = 0; + + uiInfo.humanArmourySellCount = 0; + UI_ParseCarriageList( ); + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( uiInfo.weapons & ( 1 << i ) ) + { + uiInfo.humanArmourySellList[ j ].text = BG_Weapon( i )->humanName; + uiInfo.humanArmourySellList[ j ].cmd = + String_Alloc( va( "cmd sell %s\n", BG_Weapon( i )->name ) ); + uiInfo.humanArmourySellList[ j ].type = INFOTYPE_WEAPON; + uiInfo.humanArmourySellList[ j ].v.weapon = i; + + j++; + + uiInfo.humanArmourySellCount++; + } + } + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( uiInfo.upgrades & ( 1 << i ) ) + { + uiInfo.humanArmourySellList[ j ].text = BG_Upgrade( i )->humanName; + uiInfo.humanArmourySellList[ j ].cmd = + String_Alloc( va( "cmd sell %s\n", BG_Upgrade( i )->name ) ); + uiInfo.humanArmourySellList[ j ].type = INFOTYPE_UPGRADE; + uiInfo.humanArmourySellList[ j ].v.upgrade = i; + + j++; + + uiInfo.humanArmourySellCount++; + } + } +} + +/* +=============== +UI_ArmouryRefreshCb +=============== +*/ +static void UI_ArmouryRefreshCb( void *data ) +{ + int oldWeapons = uiInfo.weapons; + int oldUpgrades = uiInfo.upgrades; + + UI_ParseCarriageList( ); + + if( uiInfo.weapons != oldWeapons || uiInfo.upgrades != oldUpgrades ) + { + UI_LoadHumanArmouryBuys( ); + UI_LoadHumanArmourySells( ); + UI_RemoveCaptureFunc( ); + } +} + +/* +=============== +UI_LoadAlienUpgrades +=============== +*/ +static void UI_LoadAlienUpgrades( void ) +{ + int i, j = 0; + + int class, credits; + char ui_currentClass[ MAX_STRING_CHARS ]; + stage_t stage = UI_GetCurrentAlienStage( ); + + trap_Cvar_VariableStringBuffer( "ui_currentClass", ui_currentClass, MAX_STRING_CHARS ); + + sscanf( ui_currentClass, "%d %d", &class, &credits ); + + uiInfo.alienUpgradeCount = 0; + + for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) + { + if( BG_ClassCanEvolveFromTo( class, i, credits, stage, 0 ) >= 0 ) + { + uiInfo.alienUpgradeList[ j ].text = BG_ClassConfig( i )->humanName; + uiInfo.alienUpgradeList[ j ].cmd = + String_Alloc( va( "cmd class %s\n", BG_Class( i )->name ) ); + uiInfo.alienUpgradeList[ j ].type = INFOTYPE_CLASS; + uiInfo.alienUpgradeList[ j ].v.pclass = i; + + j++; + + uiInfo.alienUpgradeCount++; + } + } +} + +/* +=============== +UI_LoadAlienBuilds +=============== +*/ +static void UI_LoadAlienBuilds( void ) +{ + int i, j = 0; + stage_t stage; + + UI_ParseCarriageList( ); + stage = UI_GetCurrentAlienStage( ); + + uiInfo.alienBuildCount = 0; + + for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) + { + if( BG_Buildable( i, NULL )->team == TEAM_ALIENS && + BG_Buildable( i, NULL )->buildWeapon & uiInfo.weapons && + BG_BuildableAllowedInStage( i, stage ) && + BG_BuildableIsAllowed( i ) ) + { + uiInfo.alienBuildList[ j ].text = BG_Buildable( i, NULL )->humanName; + uiInfo.alienBuildList[ j ].cmd = + String_Alloc( va( "cmd build %s\n", BG_Buildable( i, NULL )->name ) ); + uiInfo.alienBuildList[ j ].type = INFOTYPE_BUILDABLE; + uiInfo.alienBuildList[ j ].v.buildable = i; + + j++; + + uiInfo.alienBuildCount++; + } + } +} + +/* +=============== +UI_LoadHumanBuilds +=============== +*/ +static void UI_LoadHumanBuilds( void ) +{ + int i, j = 0; + stage_t stage; + + UI_ParseCarriageList( ); + stage = UI_GetCurrentHumanStage( ); + + uiInfo.humanBuildCount = 0; + + for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) + { + if( BG_Buildable( i, NULL )->team == TEAM_HUMANS && + BG_Buildable( i, NULL )->buildWeapon & uiInfo.weapons && + BG_BuildableAllowedInStage( i, stage ) && + BG_BuildableIsAllowed( i ) ) + { + uiInfo.humanBuildList[ j ].text = BG_Buildable( i, NULL )->humanName; + uiInfo.humanBuildList[ j ].cmd = + String_Alloc( va( "cmd build %s\n", BG_Buildable( i, NULL )->name ) ); + uiInfo.humanBuildList[ j ].type = INFOTYPE_BUILDABLE; + uiInfo.humanBuildList[ j ].v.buildable = i; + + j++; + + uiInfo.humanBuildCount++; + } + } +} + +/* +=============== +UI_LoadMods +=============== +*/ +static void UI_LoadMods( void ) +{ + int numdirs; + char dirlist[2048]; + char *dirptr; + char *descptr; + int i; + int dirlen; + + uiInfo.modCount = 0; + numdirs = trap_FS_GetFileList( "$modlist", "", dirlist, sizeof( dirlist ) ); + dirptr = dirlist; + + for( i = 0; i < numdirs; i++ ) + { + dirlen = strlen( dirptr ) + 1; + descptr = dirptr + dirlen; + uiInfo.modList[uiInfo.modCount].modName = String_Alloc( dirptr ); + uiInfo.modList[uiInfo.modCount].modDescr = String_Alloc( descptr ); + dirptr += dirlen + strlen( descptr ) + 1; + uiInfo.modCount++; + + if( uiInfo.modCount >= MAX_MODS ) + break; + } + +} + + +/* +=============== +UI_LoadMovies +=============== +*/ +static void UI_LoadMovies( void ) +{ + char movielist[4096]; + char *moviename; + int i, len; + + uiInfo.movieCount = trap_FS_GetFileList( "video", "roq", movielist, 4096 ); + + if( uiInfo.movieCount ) + { + if( uiInfo.movieCount > MAX_MOVIES ) + uiInfo.movieCount = MAX_MOVIES; + + moviename = movielist; + + for( i = 0; i < uiInfo.movieCount; i++ ) + { + len = strlen( moviename ); + + if( !Q_stricmp( moviename + len - 4, ".roq" ) ) + moviename[len-4] = '\0'; + + Q_strupr( moviename ); + uiInfo.movieList[i] = String_Alloc( moviename ); + moviename += len + 1; + } + } + +} + + + +/* +=============== +UI_LoadDemos +=============== +*/ +static void UI_LoadDemos( void ) +{ + char demolist[4096]; + char demoExt[32]; + char *demoname; + int i, len; + + Com_sprintf( demoExt, sizeof( demoExt ), "dm_%d", ( int )trap_Cvar_VariableValue( "protocol" ) ); + + uiInfo.demoCount = trap_FS_GetFileList( "demos", demoExt, demolist, 4096 ); + + Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", ( int )trap_Cvar_VariableValue( "protocol" ) ); + + if( uiInfo.demoCount ) + { + if( uiInfo.demoCount > MAX_DEMOS ) + uiInfo.demoCount = MAX_DEMOS; + + demoname = demolist; + + for( i = 0; i < uiInfo.demoCount; i++ ) + { + len = strlen( demoname ); + + if( !Q_stricmp( demoname + len - strlen( demoExt ), demoExt ) ) + demoname[len-strlen( demoExt )] = '\0'; + + uiInfo.demoList[i] = String_Alloc( demoname ); + demoname += len + 1; + } + } + +} + +static void UI_Update( const char *name ) +{ + int val = trap_Cvar_VariableValue( name ); + + if( Q_stricmp( name, "ui_SetName" ) == 0 ) + trap_Cvar_Set( "name", UI_Cvar_VariableString( "ui_Name" ) ); + else if( Q_stricmp( name, "ui_setRate" ) == 0 ) + { + float rate = trap_Cvar_VariableValue( "rate" ); + + if( rate >= 5000 ) + { + trap_Cvar_Set( "cl_maxpackets", "30" ); + trap_Cvar_Set( "cl_packetdup", "1" ); + } + else if( rate >= 4000 ) + { + trap_Cvar_Set( "cl_maxpackets", "15" ); + trap_Cvar_Set( "cl_packetdup", "2" ); // favor less prediction errors when there's packet loss + } + else + { + trap_Cvar_Set( "cl_maxpackets", "15" ); + trap_Cvar_Set( "cl_packetdup", "1" ); // favor lower bandwidth + } + } + else if( Q_stricmp( name, "ui_GetName" ) == 0 ) + trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString( "name" ) ); + else if( Q_stricmp( name, "r_colorbits" ) == 0 ) + { + switch( val ) + { + case 0: + trap_Cvar_SetValue( "r_depthbits", 0 ); + trap_Cvar_SetValue( "r_stencilbits", 0 ); + break; + + case 16: + trap_Cvar_SetValue( "r_depthbits", 16 ); + trap_Cvar_SetValue( "r_stencilbits", 0 ); + break; + + case 32: + trap_Cvar_SetValue( "r_depthbits", 24 ); + break; + } + } + else if( Q_stricmp( name, "r_lodbias" ) == 0 ) + { + switch( val ) + { + case 0: + trap_Cvar_SetValue( "r_subdivisions", 4 ); + break; + + case 1: + trap_Cvar_SetValue( "r_subdivisions", 12 ); + break; + + case 2: + trap_Cvar_SetValue( "r_subdivisions", 20 ); + break; + } + } + else if( Q_stricmp( name, "ui_glCustom" ) == 0 ) + { + switch( val ) + { + case 0: // high quality + trap_Cvar_SetValue( "r_subdivisions", 4 ); + trap_Cvar_SetValue( "r_vertexlight", 0 ); + trap_Cvar_SetValue( "r_lodbias", 0 ); + trap_Cvar_SetValue( "r_colorbits", 32 ); + trap_Cvar_SetValue( "r_depthbits", 24 ); + trap_Cvar_SetValue( "r_picmip", 0 ); + trap_Cvar_SetValue( "r_texturebits", 32 ); + trap_Cvar_SetValue( "r_fastSky", 0 ); + trap_Cvar_SetValue( "r_inGameVideo", 1 ); + trap_Cvar_SetValue( "cg_shadows", 1 ); + trap_Cvar_SetValue( "cg_bounceParticles", 1 ); + trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + break; + + case 1: // normal + trap_Cvar_SetValue( "r_subdivisions", 12 ); + trap_Cvar_SetValue( "r_vertexlight", 0 ); + trap_Cvar_SetValue( "r_lodbias", 0 ); + trap_Cvar_SetValue( "r_colorbits", 0 ); + trap_Cvar_SetValue( "r_depthbits", 24 ); + trap_Cvar_SetValue( "r_picmip", 1 ); + trap_Cvar_SetValue( "r_texturebits", 0 ); + trap_Cvar_SetValue( "r_fastSky", 0 ); + trap_Cvar_SetValue( "r_inGameVideo", 1 ); + trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + trap_Cvar_SetValue( "cg_shadows", 0 ); + trap_Cvar_SetValue( "cg_bounceParticles", 0 ); + break; + + case 2: // fast + trap_Cvar_SetValue( "r_subdivisions", 8 ); + trap_Cvar_SetValue( "r_vertexlight", 0 ); + trap_Cvar_SetValue( "r_lodbias", 1 ); + trap_Cvar_SetValue( "r_colorbits", 0 ); + trap_Cvar_SetValue( "r_depthbits", 0 ); + trap_Cvar_SetValue( "r_picmip", 1 ); + trap_Cvar_SetValue( "r_texturebits", 0 ); + trap_Cvar_SetValue( "cg_shadows", 0 ); + trap_Cvar_SetValue( "r_fastSky", 1 ); + trap_Cvar_SetValue( "r_inGameVideo", 0 ); + trap_Cvar_SetValue( "cg_bounceParticles", 0 ); + trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); + break; + + case 3: // fastest + trap_Cvar_SetValue( "r_subdivisions", 20 ); + trap_Cvar_SetValue( "r_vertexlight", 1 ); + trap_Cvar_SetValue( "r_lodbias", 2 ); + trap_Cvar_SetValue( "r_colorbits", 16 ); + trap_Cvar_SetValue( "r_depthbits", 16 ); + trap_Cvar_SetValue( "r_picmip", 2 ); + trap_Cvar_SetValue( "r_texturebits", 16 ); + trap_Cvar_SetValue( "cg_shadows", 0 ); + trap_Cvar_SetValue( "r_fastSky", 1 ); + trap_Cvar_SetValue( "r_inGameVideo", 0 ); + trap_Cvar_SetValue( "cg_bounceParticles", 0 ); + trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); + break; + } + } + else if( Q_stricmp( name, "ui_mousePitch" ) == 0 ) + { + if( val == 0 ) + trap_Cvar_SetValue( "m_pitch", 0.022f ); + else + trap_Cvar_SetValue( "m_pitch", -0.022f ); + } +} + +//FIXME: lookup table +static void UI_RunMenuScript( char **args ) +{ + const char *name, *name2; + char buff[1024]; + const char *cmd; + + if( String_Parse( args, &name ) ) + { + if( Q_stricmp( name, "StartServer" ) == 0 ) + { + trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", + uiInfo.mapList[ui_selectedMap.integer].mapLoadName ) ); + } + else if( Q_stricmp( name, "resetDefaults" ) == 0 ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, "exec default.cfg\n" ); + trap_Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n" ); + Controls_SetDefaults(); + trap_Cvar_Set( "com_introPlayed", "1" ); + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" ); + } + else if( Q_stricmp( name, "loadArenas" ) == 0 ) + { + UI_LoadArenas(); + Menu_SetFeederSelection( NULL, FEEDER_MAPS, 0, "createserver" ); + } + else if( Q_stricmp( name, "loadServerInfo" ) == 0 ) + UI_ServerInfo(); + else if( Q_stricmp( name, "getNews" ) == 0 ) + UI_UpdateNews( qtrue ); + else if( Q_stricmp( name, "saveControls" ) == 0 ) + Controls_SetConfig( qtrue ); + else if( Q_stricmp( name, "loadControls" ) == 0 ) + Controls_GetConfig(); + else if (Q_stricmp(name, "clearError") == 0) { + trap_Cvar_Set("com_errorMessage", ""); + } else if (Q_stricmp(name, "downloadIgnore") == 0) { + trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_IGNORE)); + } else if (Q_stricmp(name, "downloadCURL") == 0) { + trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_CURL)); + } else if (Q_stricmp(name, "downloadUDP") == 0) { + trap_Cvar_Set("com_downloadPrompt", va("%d", DLP_UDP)); + } else if (Q_stricmp(name, "RefreshServers") == 0) { + UI_StartServerRefresh(qtrue); + UI_BuildServerDisplayList(qtrue); + } else if (Q_stricmp(name, "InitServerList") == 0) { + int time = trap_RealTime( NULL ); + int last; + int sortColumn; + + // set up default sorting + + if( !uiInfo.serverStatus.sorted && Int_Parse( args, &sortColumn ) ) + { + uiInfo.serverStatus.sortKey = sortColumn; + uiInfo.serverStatus.sortDir = 0; + } + + // refresh if older than 3 days or if list is empty + last = atoi( UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i_time", + ui_netSource.integer ) ) ); + + if( trap_LAN_GetServerCount( ui_netSource.integer ) < 1 || + ( time - last ) > 3600 ) + { + UI_StartServerRefresh( qtrue ); + UI_BuildServerDisplayList( qtrue ); + } + } + else if( Q_stricmp( name, "RefreshFilter" ) == 0 ) + { + UI_StartServerRefresh( qfalse ); + UI_BuildServerDisplayList( qtrue ); + } + else if( Q_stricmp( name, "LoadDemos" ) == 0 ) + UI_LoadDemos(); + else if( Q_stricmp( name, "LoadMovies" ) == 0 ) + UI_LoadMovies(); + else if( Q_stricmp( name, "LoadMods" ) == 0 ) + UI_LoadMods(); + else if( Q_stricmp( name, "LoadTeams" ) == 0 ) + UI_LoadTeams( ); + else if( Q_stricmp( name, "JoinTeam" ) == 0 ) + { + if( ( cmd = uiInfo.teamList[ uiInfo.teamIndex ].cmd ) ) + trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + } + else if( Q_stricmp( name, "LoadHumanItems" ) == 0 ) + UI_LoadHumanItems( ); + else if( Q_stricmp( name, "SpawnWithHumanItem" ) == 0 ) + { + if( ( cmd = uiInfo.humanItemList[ uiInfo.humanItemIndex ].cmd ) ) + trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + } + else if( Q_stricmp( name, "LoadAlienClasses" ) == 0 ) + UI_LoadAlienClasses( ); + else if( Q_stricmp( name, "SpawnAsAlienClass" ) == 0 ) + { + if( ( cmd = uiInfo.alienClassList[ uiInfo.alienClassIndex ].cmd ) ) + trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + } + else if( Q_stricmp( name, "LoadHumanArmouryBuys" ) == 0 ) + UI_LoadHumanArmouryBuys( ); + else if( Q_stricmp( name, "BuyFromArmoury" ) == 0 ) + { + if( ( cmd = uiInfo.humanArmouryBuyList[ uiInfo.humanArmouryBuyIndex ].cmd ) ) + trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + + UI_InstallCaptureFunc( UI_ArmouryRefreshCb, NULL, 1000 ); + } + else if( Q_stricmp( name, "LoadHumanArmourySells" ) == 0 ) + UI_LoadHumanArmourySells( ); + else if( Q_stricmp( name, "SellToArmoury" ) == 0 ) + { + if( ( cmd = uiInfo.humanArmourySellList[ uiInfo.humanArmourySellIndex ].cmd ) ) + trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + + UI_InstallCaptureFunc( UI_ArmouryRefreshCb, NULL, 1000 ); + } + else if( Q_stricmp( name, "LoadAlienUpgrades" ) == 0 ) + { + UI_LoadAlienUpgrades( ); + + //disallow the menu if it would be empty + + if( uiInfo.alienUpgradeCount <= 0 ) + Menus_CloseAll( ); + } + else if( Q_stricmp( name, "UpgradeToNewClass" ) == 0 ) + { + if( ( cmd = uiInfo.alienUpgradeList[ uiInfo.alienUpgradeIndex ].cmd ) ) + trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + } + else if( Q_stricmp( name, "LoadAlienBuilds" ) == 0 ) + UI_LoadAlienBuilds( ); + else if( Q_stricmp( name, "BuildAlienBuildable" ) == 0 ) + { + if( ( cmd = uiInfo.alienBuildList[ uiInfo.alienBuildIndex ].cmd ) ) + trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + } + else if( Q_stricmp( name, "LoadHumanBuilds" ) == 0 ) + UI_LoadHumanBuilds( ); + else if( Q_stricmp( name, "BuildHumanBuildable" ) == 0 ) + { + if( ( cmd = uiInfo.humanBuildList[ uiInfo.humanBuildIndex ].cmd ) ) + trap_Cmd_ExecuteText( EXEC_APPEND, cmd ); + } + else if( Q_stricmp( name, "Say" ) == 0 ) + { + char buffer[ MAX_CVAR_VALUE_STRING ]; + trap_Cvar_VariableStringBuffer( "ui_sayBuffer", buffer, sizeof( buffer ) ); + + if( !buffer[ 0 ] ) + ; + else if( ui_chatCommands.integer && ( buffer[ 0 ] == '/' || + buffer[ 0 ] == '\\' ) ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "%s\n", buffer + 1 ) ); + } + else if( uiInfo.chatTeam ) + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say_team \"%s\"\n", buffer ) ); + else + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say \"%s\"\n", buffer ) ); + } + else if( Q_stricmp( name, "SayKeydown" ) == 0 ) + { + if( ui_chatCommands.integer ) + { + char buffer[ MAX_CVAR_VALUE_STRING ]; + trap_Cvar_VariableStringBuffer( "ui_sayBuffer", buffer, sizeof( buffer ) ); + + if( buffer[ 0 ] == '/' || buffer[ 0 ] == '\\' ) + Menus_ReplaceActiveByName( "say_command" ); + else if( uiInfo.chatTeam ) + Menus_ReplaceActiveByName( "say_team" ); + else + Menus_ReplaceActiveByName( "say" ); + } + } + else if( Q_stricmp( name, "playMovie" ) == 0 ) + { + if( uiInfo.previewMovie >= 0 ) + trap_CIN_StopCinematic( uiInfo.previewMovie ); + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "cinematic %s.roq 2\n", uiInfo.movieList[uiInfo.movieIndex] ) ); + } + else if( Q_stricmp( name, "RunMod" ) == 0 ) + { + trap_Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName ); + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); + } + else if( Q_stricmp( name, "RunDemo" ) == 0 ) + trap_Cmd_ExecuteText( EXEC_APPEND, va( "demo %s\n", uiInfo.demoList[uiInfo.demoIndex] ) ); + else if( Q_stricmp( name, "Tremulous" ) == 0 ) + { + trap_Cvar_Set( "fs_game", "" ); + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); + } + else if( Q_stricmp( name, "closeJoin" ) == 0 ) + { + if( uiInfo.serverStatus.refreshActive ) + { + UI_StopServerRefresh(); + uiInfo.serverStatus.nextDisplayRefresh = 0; + uiInfo.nextServerStatusRefresh = 0; + uiInfo.nextFindPlayerRefresh = 0; + UI_BuildServerDisplayList( qtrue ); + } + else + { + Menus_CloseByName( "joinserver" ); + Menus_ActivateByName( "main" ); + } + } + else if( Q_stricmp( name, "StopRefresh" ) == 0 ) + { + UI_StopServerRefresh(); + uiInfo.serverStatus.nextDisplayRefresh = 0; + uiInfo.nextServerStatusRefresh = 0; + uiInfo.nextFindPlayerRefresh = 0; + } + else if( Q_stricmp( name, "UpdateFilter" ) == 0 ) + { + if( ui_netSource.integer == AS_LOCAL ) + UI_StartServerRefresh( qtrue ); + + UI_BuildServerDisplayList( qtrue ); + UI_FeederSelection( FEEDER_SERVERS, 0 ); + } + else if( Q_stricmp( name, "ServerStatus" ) == 0 ) + { + trap_LAN_GetServerAddressString( ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], + uiInfo.serverStatusAddress, sizeof( uiInfo.serverStatusAddress ) ); + UI_BuildServerStatus( qtrue ); + } + else if( Q_stricmp( name, "FoundPlayerServerStatus" ) == 0 ) + { + Q_strncpyz( uiInfo.serverStatusAddress, + uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], + sizeof( uiInfo.serverStatusAddress ) ); + UI_BuildServerStatus( qtrue ); + Menu_SetFeederSelection( NULL, FEEDER_FINDPLAYER, 0, NULL ); + } + else if( Q_stricmp( name, "FindPlayer" ) == 0 ) + { + UI_BuildFindPlayerList( qtrue ); + // clear the displayed server status info + uiInfo.serverStatusInfo.numLines = 0; + Menu_SetFeederSelection( NULL, FEEDER_FINDPLAYER, 0, NULL ); + } + else if( Q_stricmp( name, "JoinServer" ) == 0 ) + { + if( uiInfo.serverStatus.currentServer >= 0 && + uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers ) + { + trap_LAN_GetServerAddressString( ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], + buff, 1024 ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", buff ) ); + } + } + else if( Q_stricmp( name, "FoundPlayerJoinServer" ) == 0 ) + { + if( uiInfo.currentFoundPlayerServer >= 0 && + uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", + uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer] ) ); + } + } + else if( Q_stricmp( name, "Quit" ) == 0 ) + trap_Cmd_ExecuteText( EXEC_APPEND, "quit" ); + else if( Q_stricmp( name, "Leave" ) == 0 ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll( ); + Menus_ActivateByName( "main" ); + } + else if( Q_stricmp( name, "ServerSort" ) == 0 ) + { + int sortColumn; + + if( Int_Parse( args, &sortColumn ) ) + { + // if same column we're already sorting on then flip the direction + + if( sortColumn == uiInfo.serverStatus.sortKey ) + uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir; + + // make sure we sort again + UI_ServersSort( sortColumn, qtrue ); + + uiInfo.serverStatus.sorted = qtrue; + } + } + else if( Q_stricmp( name, "closeingame" ) == 0 ) + { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll( ); + } + else if( Q_stricmp( name, "voteMap" ) == 0 ) + { + if( ui_selectedMap.integer >= 0 && ui_selectedMap.integer < uiInfo.mapCount ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote map %s\n", + uiInfo.mapList[ui_selectedMap.integer].mapLoadName ) ); + } + } + else if( Q_stricmp( name, "voteNextMap" ) == 0 ) + { + if( ui_selectedMap.integer >= 0 && ui_selectedMap.integer < uiInfo.mapCount ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote nextmap %s\n", + uiInfo.mapList[ui_selectedMap.integer].mapLoadName ) ); + } + } + else if( Q_stricmp( name, "voteKick" ) == 0 ) + { + if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) + { + char buffer[ MAX_CVAR_VALUE_STRING ]; + trap_Cvar_VariableStringBuffer( "ui_reason", buffer, sizeof( buffer ) ); + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote kick %d %s\n", + uiInfo.clientNums[ uiInfo.playerIndex ], + buffer ) ); + trap_Cvar_Set( "ui_reason", "" ); + } + } + else if( Q_stricmp( name, "voteMute" ) == 0 ) + { + if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) + { + char buffer[ MAX_CVAR_VALUE_STRING ]; + trap_Cvar_VariableStringBuffer( "ui_reason", buffer, sizeof( buffer ) ); + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote mute %d %s\n", + uiInfo.clientNums[ uiInfo.playerIndex ], + buffer ) ); + trap_Cvar_Set( "ui_reason", "" ); + } + } + else if( Q_stricmp( name, "voteUnMute" ) == 0 ) + { + if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote unmute %d\n", + uiInfo.clientNums[ uiInfo.playerIndex ] ) ); + } + } + else if( Q_stricmp( name, "voteTeamKick" ) == 0 ) + { + if( uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount ) + { + char buffer[ MAX_CVAR_VALUE_STRING ]; + trap_Cvar_VariableStringBuffer( "ui_reason", buffer, sizeof( buffer ) ); + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote kick %d %s\n", + uiInfo.teamClientNums[ uiInfo.teamPlayerIndex ], + buffer ) ); + trap_Cvar_Set( "ui_reason", "" ); + } + } + else if( Q_stricmp( name, "voteTeamDenyBuild" ) == 0 ) + { + if( uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount ) + { + char buffer[ MAX_CVAR_VALUE_STRING ]; + trap_Cvar_VariableStringBuffer( "ui_reason", buffer, sizeof( buffer ) ); + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote denybuild %d %s\n", + uiInfo.teamClientNums[ uiInfo.teamPlayerIndex ], + buffer ) ); + trap_Cvar_Set( "ui_reason", "" ); + } + } + else if( Q_stricmp( name, "voteTeamAllowBuild" ) == 0 ) + { + if( uiInfo.teamPlayerIndex >= 0 && uiInfo.teamPlayerIndex < uiInfo.myTeamCount ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote allowbuild %d\n", + uiInfo.teamClientNums[ uiInfo.teamPlayerIndex ] ) ); + } + } + else if( Q_stricmp( name, "addFavorite" ) == 0 ) + { + if( ui_netSource.integer != AS_FAVORITES ) + { + char name[MAX_NAME_LENGTH]; + char addr[MAX_NAME_LENGTH]; + int res; + + trap_LAN_GetServerInfo( ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], + buff, MAX_STRING_CHARS ); + name[0] = addr[0] = '\0'; + Q_strncpyz( name, Info_ValueForKey( buff, "hostname" ), MAX_NAME_LENGTH ); + Q_strncpyz( addr, Info_ValueForKey( buff, "addr" ), MAX_NAME_LENGTH ); + + if( strlen( name ) > 0 && strlen( addr ) > 0 ) + { + res = trap_LAN_AddServer( AS_FAVORITES, name, addr ); + + if( res == 0 ) + { + // server already in the list + Com_Printf( "Favorite already in list\n" ); + } + else if( res == -1 ) + { + // list full + Com_Printf( "Favorite list full\n" ); + } + else + { + // successfully added + Com_Printf( "Added favorite server %s\n", addr ); + } + } + } + } + else if( Q_stricmp( name, "deleteFavorite" ) == 0 ) + { + if( ui_netSource.integer == AS_FAVORITES ) + { + char addr[MAX_NAME_LENGTH]; + trap_LAN_GetServerInfo( ui_netSource.integer, + uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], + buff, MAX_STRING_CHARS ); + addr[0] = '\0'; + Q_strncpyz( addr, Info_ValueForKey( buff, "addr" ), MAX_NAME_LENGTH ); + + if( strlen( addr ) > 0 ) + trap_LAN_RemoveServer( AS_FAVORITES, addr ); + } + } + else if( Q_stricmp( name, "createFavorite" ) == 0 ) + { + if( ui_netSource.integer == AS_FAVORITES ) + { + char name[MAX_NAME_LENGTH]; + char addr[MAX_NAME_LENGTH]; + int res; + + name[0] = addr[0] = '\0'; + Q_strncpyz( name, UI_Cvar_VariableString( "ui_favoriteName" ), MAX_NAME_LENGTH ); + Q_strncpyz( addr, UI_Cvar_VariableString( "ui_favoriteAddress" ), MAX_NAME_LENGTH ); + + if( strlen( name ) > 0 && strlen( addr ) > 0 ) + { + res = trap_LAN_AddServer( AS_FAVORITES, name, addr ); + + if( res == 0 ) + { + // server already in the list + Com_Printf( "Favorite already in list\n" ); + } + else if( res == -1 ) + { + // list full + Com_Printf( "Favorite list full\n" ); + } + else + { + // successfully added + Com_Printf( "Added favorite server %s\n", addr ); + } + } + } + } + else if( Q_stricmp( name, "glCustom" ) == 0 ) + trap_Cvar_Set( "ui_glCustom", "4" ); + else if( Q_stricmp( name, "update" ) == 0 ) + { + if( String_Parse( args, &name2 ) ) + UI_Update( name2 ); + } + else if( Q_stricmp( name, "InitIgnoreList" ) == 0 ) + UI_BuildPlayerList(); + else if( Q_stricmp( name, "ToggleIgnore" ) == 0 ) + { + if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) + { + if( Com_ClientListContains( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], + uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) + { + Com_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], + uiInfo.clientNums[ uiInfo.ignoreIndex ] ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "unignore %i\n", + uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); + } + else + { + Com_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], + uiInfo.clientNums[ uiInfo.ignoreIndex ] ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "ignore %i\n", + uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); + } + } + } + else if( Q_stricmp( name, "IgnorePlayer" ) == 0 ) + { + if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) + { + if( !Com_ClientListContains( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], + uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) + { + Com_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], + uiInfo.clientNums[ uiInfo.ignoreIndex ] ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "ignore %i\n", + uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); + } + } + } + else if( Q_stricmp( name, "UnIgnorePlayer" ) == 0 ) + { + if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount ) + { + if( Com_ClientListContains( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], + uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ) + { + Com_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], + uiInfo.clientNums[ uiInfo.ignoreIndex ] ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "unignore %i\n", + uiInfo.clientNums[ uiInfo.ignoreIndex ] ) ); + } + } + } + else + Com_Printf( "unknown UI script %s\n", name ); + } +} + +static int UI_FeederInitialise( int feederID ); + +/* +================== +UI_FeederCount +================== +*/ +static int UI_FeederCount( int feederID ) +{ + if( feederID == FEEDER_CINEMATICS ) + return uiInfo.movieCount; + else if( feederID == FEEDER_MAPS ) + return uiInfo.mapCount; + else if( feederID == FEEDER_SERVERS ) + return uiInfo.serverStatus.numDisplayServers; + else if( feederID == FEEDER_SERVERSTATUS ) + return uiInfo.serverStatusInfo.numLines; + else if( feederID == FEEDER_NEWS ) + return uiInfo.newsInfo.numLines; + else if( feederID == FEEDER_FINDPLAYER ) + return uiInfo.numFoundPlayerServers; + else if( feederID == FEEDER_PLAYER_LIST ) + { + if( uiInfo.uiDC.realTime > uiInfo.playerRefresh ) + { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } + + return uiInfo.playerCount; + } + else if( feederID == FEEDER_TEAM_LIST ) + { + if( uiInfo.uiDC.realTime > uiInfo.playerRefresh ) + { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } + + return uiInfo.myTeamCount; + } + else if( feederID == FEEDER_IGNORE_LIST ) + return uiInfo.playerCount; + else if( feederID == FEEDER_HELP_LIST ) + return uiInfo.helpCount; + else if( feederID == FEEDER_MODS ) + return uiInfo.modCount; + else if( feederID == FEEDER_DEMOS ) + return uiInfo.demoCount; + else if( feederID == FEEDER_TREMTEAMS ) + return uiInfo.teamCount; + else if( feederID == FEEDER_TREMHUMANITEMS ) + return uiInfo.humanItemCount; + else if( feederID == FEEDER_TREMALIENCLASSES ) + return uiInfo.alienClassCount; + else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) + return uiInfo.humanArmouryBuyCount; + else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) + return uiInfo.humanArmourySellCount; + else if( feederID == FEEDER_TREMALIENUPGRADE ) + return uiInfo.alienUpgradeCount; + else if( feederID == FEEDER_TREMALIENBUILD ) + return uiInfo.alienBuildCount; + else if( feederID == FEEDER_TREMHUMANBUILD ) + return uiInfo.humanBuildCount; + else if( feederID == FEEDER_RESOLUTIONS ) + { + if( UI_FeederInitialise( feederID ) == uiInfo.numResolutions ) + return uiInfo.numResolutions + 1; + else + return uiInfo.numResolutions; + } + + return 0; +} + +static const char *UI_SelectedMap( int index, int *actual ) +{ + int i, c; + c = 0; + *actual = 0; + + for( i = 0; i < uiInfo.mapCount; i++ ) + { + if( c == index ) + { + *actual = i; + return uiInfo.mapList[i].mapName; + } + else + c++; + } + + return ""; +} + +static int GCD( int a, int b ) +{ + int c; + + while( b != 0 ) + { + c = a % b; + a = b; + b = c; + } + + return a; +} + +static const char *UI_DisplayAspectString( int w, int h ) +{ + int gcd = GCD( w, h ); + + w /= gcd; + h /= gcd; + + // For some reason 8:5 is usually referred to as 16:10 + if( w == 8 && h == 5 ) + { + w = 16; + h = 10; + } + + return va( "%d:%d", w, h ); +} + +static const char *UI_FeederItemText( int feederID, int index, int column, qhandle_t *handle ) +{ + if( handle ) + *handle = -1; + + if( feederID == FEEDER_MAPS ) + { + int actual; + return UI_SelectedMap( index, &actual ); + } + else if( feederID == FEEDER_SERVERS ) + { + if( index >= 0 && index < UI_FeederCount( feederID ) ) + { + static char info[MAX_STRING_CHARS]; + static char clientBuff[ 32 ]; + static char cleaned[ MAX_STRING_CHARS ]; + static int lastColumn = -1; + static int lastTime = 0; + int ping; + + if( lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000 ) + { + trap_LAN_GetServerInfo( ui_netSource.integer, uiInfo.serverStatus.displayServers[index], + info, MAX_STRING_CHARS ); + lastColumn = column; + lastTime = uiInfo.uiDC.realTime; + } + + ping = atoi( Info_ValueForKey( info, "ping" ) ); + + UI_EscapeEmoticons( cleaned, Info_ValueForKey( info, "hostname" ), sizeof( cleaned ) ); + + switch( column ) + { + case SORT_HOST: + if( ping <= 0 ) + return Info_ValueForKey( info, "addr" ); + else + { + static char hostname[1024]; + + if( ui_netSource.integer == AS_LOCAL ) + { + Com_sprintf( hostname, sizeof( hostname ), "%s [%s]", cleaned, + netnames[atoi( Info_ValueForKey( info, "nettype" ) )] ); + return hostname; + } + else + { + char *text; + char *label; + + label = Info_ValueForKey( info, "label" ); + if( label[0] ) + { + // First char of the label response is a sorting tag. Skip it. + label+= 1; + + Com_sprintf( hostname, sizeof( hostname ), "%s %s", + label, cleaned ); + } + else + { + Com_sprintf( hostname, sizeof( hostname ), "%s", + cleaned ); + } + + // Strip leading whitespace + text = hostname; + + while( *text != '\0' && *text == ' ' ) + text++; + + return text; + } + } + + case SORT_GAME: + return Info_ValueForKey( info, "game" ); + + case SORT_MAP: + return Info_ValueForKey( info, "mapname" ); + + case SORT_CLIENTS: + Com_sprintf( clientBuff, sizeof( clientBuff ), "%s (%s)", + Info_ValueForKey( info, "clients" ), Info_ValueForKey( info, "sv_maxclients" ) ); + return clientBuff; + + case SORT_PING: + if( ping <= 0 ) + return "..."; + else + return Info_ValueForKey( info, "ping" ); + } + } + } + else if( feederID == FEEDER_SERVERSTATUS ) + { + if( index >= 0 && index < uiInfo.serverStatusInfo.numLines ) + { + if( column >= 0 && column < 4 ) + return uiInfo.serverStatusInfo.lines[index][column]; + } + } + else if( feederID == FEEDER_NEWS ) + { + if( index >= 0 && index < uiInfo.newsInfo.numLines ) + return uiInfo.newsInfo.text[index]; + } + else if( feederID == FEEDER_FINDPLAYER ) + { + if( index >= 0 && index < uiInfo.numFoundPlayerServers ) + return uiInfo.foundPlayerServerNames[index]; + } + else if( feederID == FEEDER_PLAYER_LIST ) + { + if( index >= 0 && index < uiInfo.playerCount ) + return uiInfo.playerNames[index]; + } + else if( feederID == FEEDER_TEAM_LIST ) + { + if( index >= 0 && index < uiInfo.myTeamCount ) + return uiInfo.teamNames[index]; + } + else if( feederID == FEEDER_IGNORE_LIST ) + { + if( index >= 0 && index < uiInfo.playerCount ) + { + switch( column ) + { + case 1: + // am I ignoring him + return Com_ClientListContains( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ], + uiInfo.clientNums[ index ] ) ? "X" : ""; + + case 2: + // is he ignoring me + return Com_ClientListContains( &uiInfo.ignoreList[ index ], + uiInfo.playerNumber ) ? "X" : ""; + + default: + return uiInfo.playerNames[index]; + } + } + } + else if( feederID == FEEDER_HELP_LIST ) + { + if( index >= 0 && index < uiInfo.helpCount ) + return uiInfo.helpList[ index ].text; + } + else if( feederID == FEEDER_MODS ) + { + if( index >= 0 && index < uiInfo.modCount ) + { + if( uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr ) + return uiInfo.modList[index].modDescr; + else + return uiInfo.modList[index].modName; + } + } + else if( feederID == FEEDER_CINEMATICS ) + { + if( index >= 0 && index < uiInfo.movieCount ) + return uiInfo.movieList[index]; + } + else if( feederID == FEEDER_DEMOS ) + { + if( index >= 0 && index < uiInfo.demoCount ) + return uiInfo.demoList[index]; + } + else if( feederID == FEEDER_TREMTEAMS ) + { + if( index >= 0 && index < uiInfo.teamCount ) + return uiInfo.teamList[ index ].text; + } + else if( feederID == FEEDER_TREMHUMANITEMS ) + { + if( index >= 0 && index < uiInfo.humanItemCount ) + return uiInfo.humanItemList[ index ].text; + } + else if( feederID == FEEDER_TREMALIENCLASSES ) + { + if( index >= 0 && index < uiInfo.alienClassCount ) + return uiInfo.alienClassList[ index ].text; + } + else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) + { + if( index >= 0 && index < uiInfo.humanArmouryBuyCount ) + return uiInfo.humanArmouryBuyList[ index ].text; + } + else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) + { + if( index >= 0 && index < uiInfo.humanArmourySellCount ) + return uiInfo.humanArmourySellList[ index ].text; + } + else if( feederID == FEEDER_TREMALIENUPGRADE ) + { + if( index >= 0 && index < uiInfo.alienUpgradeCount ) + return uiInfo.alienUpgradeList[ index ].text; + } + else if( feederID == FEEDER_TREMALIENBUILD ) + { + if( index >= 0 && index < uiInfo.alienBuildCount ) + return uiInfo.alienBuildList[ index ].text; + } + else if( feederID == FEEDER_TREMHUMANBUILD ) + { + if( index >= 0 && index < uiInfo.humanBuildCount ) + return uiInfo.humanBuildList[ index ].text; + } + else if( feederID == FEEDER_RESOLUTIONS ) + { + static char resolution[MAX_STRING_CHARS]; + int w, h; + + if( index >= 0 && index < uiInfo.numResolutions ) + { + w = uiInfo.resolutions[ index ].w; + h = uiInfo.resolutions[ index ].h; + + Com_sprintf( resolution, sizeof( resolution ), "%dx%d (%s)", w, h, + UI_DisplayAspectString( w, h ) ); + + return resolution; + } + + w = (int)trap_Cvar_VariableValue( "r_width" ); + h = (int)trap_Cvar_VariableValue( "r_height" ); + Com_sprintf( resolution, sizeof( resolution ), "Custom (%dx%d)", w, h ); + + return resolution; + } + + return ""; +} + + +static qhandle_t UI_FeederItemImage( int feederID, int index ) +{ + if( feederID == FEEDER_MAPS ) + { + int actual; + UI_SelectedMap( index, &actual ); + index = actual; + + if( index >= 0 && index < uiInfo.mapCount ) + { + if( uiInfo.mapList[index].levelShot == -1 ) + uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip( uiInfo.mapList[index].imageName ); + + return uiInfo.mapList[index].levelShot; + } + } + + return 0; +} + +static void UI_FeederSelection( int feederID, int index ) +{ + static char info[MAX_STRING_CHARS]; + + if( feederID == FEEDER_MAPS ) + { + int actual, map; + map = ui_selectedMap.integer; + + if( uiInfo.mapList[map].cinematic >= 0 ) + { + trap_CIN_StopCinematic( uiInfo.mapList[map].cinematic ); + uiInfo.mapList[map].cinematic = -1; + } + + UI_SelectedMap( index, &actual ); + + ui_selectedMap.integer = actual; + trap_Cvar_Set( "ui_selectedMap", va( "%d", actual ) ); + uiInfo.mapList[ui_selectedMap.integer].cinematic = + trap_CIN_PlayCinematic( va( "%s.roq", uiInfo.mapList[ui_selectedMap.integer].mapLoadName ), + 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + } + else if( feederID == FEEDER_SERVERS ) + { + const char *mapName = NULL; + + uiInfo.serverStatus.currentServer = index; + trap_LAN_GetServerInfo( ui_netSource.integer, uiInfo.serverStatus.displayServers[index], + info, MAX_STRING_CHARS ); + uiInfo.serverStatus.currentServerPreview = + trap_R_RegisterShaderNoMip( va( "levelshots/%s", Info_ValueForKey( info, "mapname" ) ) ); + + if( uiInfo.serverStatus.currentServerCinematic >= 0 ) + { + trap_CIN_StopCinematic( uiInfo.serverStatus.currentServerCinematic ); + uiInfo.serverStatus.currentServerCinematic = -1; + } + + mapName = Info_ValueForKey( info, "mapname" ); + + if( mapName && *mapName ) + { + uiInfo.serverStatus.currentServerCinematic = + trap_CIN_PlayCinematic( va( "%s.roq", mapName ), 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + } + } + else if( feederID == FEEDER_SERVERSTATUS ) + { + } + else if( feederID == FEEDER_FINDPLAYER ) + { + uiInfo.currentFoundPlayerServer = index; + // + + if( index < uiInfo.numFoundPlayerServers - 1 ) + { + // build a new server status for this server + Q_strncpyz( uiInfo.serverStatusAddress, + uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], + sizeof( uiInfo.serverStatusAddress ) ); + Menu_SetFeederSelection( NULL, FEEDER_SERVERSTATUS, 0, NULL ); + UI_BuildServerStatus( qtrue ); + } + } + else if( feederID == FEEDER_PLAYER_LIST ) + uiInfo.playerIndex = index; + else if( feederID == FEEDER_TEAM_LIST ) + uiInfo.teamPlayerIndex = index; + else if( feederID == FEEDER_IGNORE_LIST ) + uiInfo.ignoreIndex = index; + else if( feederID == FEEDER_HELP_LIST ) + uiInfo.helpIndex = index; + else if( feederID == FEEDER_MODS ) + uiInfo.modIndex = index; + else if( feederID == FEEDER_CINEMATICS ) + { + uiInfo.movieIndex = index; + + if( uiInfo.previewMovie >= 0 ) + trap_CIN_StopCinematic( uiInfo.previewMovie ); + + uiInfo.previewMovie = -1; + } + else if( feederID == FEEDER_DEMOS ) + uiInfo.demoIndex = index; + else if( feederID == FEEDER_TREMTEAMS ) + uiInfo.teamIndex = index; + else if( feederID == FEEDER_TREMHUMANITEMS ) + uiInfo.humanItemIndex = index; + else if( feederID == FEEDER_TREMALIENCLASSES ) + uiInfo.alienClassIndex = index; + else if( feederID == FEEDER_TREMHUMANARMOURYBUY ) + uiInfo.humanArmouryBuyIndex = index; + else if( feederID == FEEDER_TREMHUMANARMOURYSELL ) + uiInfo.humanArmourySellIndex = index; + else if( feederID == FEEDER_TREMALIENUPGRADE ) + uiInfo.alienUpgradeIndex = index; + else if( feederID == FEEDER_TREMALIENBUILD ) + uiInfo.alienBuildIndex = index; + else if( feederID == FEEDER_TREMHUMANBUILD ) + uiInfo.humanBuildIndex = index; + else if( feederID == FEEDER_RESOLUTIONS ) + { + if( index >= 0 && index < uiInfo.numResolutions ) + { + trap_Cvar_Set( "r_width", va( "%d", uiInfo.resolutions[ index ].w ) ); + trap_Cvar_Set( "r_height", va( "%d", uiInfo.resolutions[ index ].h ) ); + } + + uiInfo.resolutionIndex = index; + } +} + +static int UI_FeederInitialise( int feederID ) +{ + if( feederID == FEEDER_RESOLUTIONS ) + { + int i; + int w = trap_Cvar_VariableValue( "r_width" ); + int h = trap_Cvar_VariableValue( "r_height" ); + + for( i = 0; i < uiInfo.numResolutions; i++ ) + { + if( w == uiInfo.resolutions[ i ].w && h == uiInfo.resolutions[ i ].h ) + return i; + } + + return uiInfo.numResolutions; + } + + return 0; +} + +static void UI_Pause( qboolean b ) +{ + if( b ) + { + // pause the game and set the ui keycatcher + trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + } + else + { + // unpause the game and clear the ui keycatcher + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + } +} + +static int UI_PlayCinematic( const char *name, float x, float y, float w, float h ) +{ + return trap_CIN_PlayCinematic( name, x, y, w, h, ( CIN_loop | CIN_silent ) ); +} + +static void UI_StopCinematic( int handle ) +{ + if( handle >= 0 ) + trap_CIN_StopCinematic( handle ); + else + { + handle = abs( handle ); + + if( handle == UI_NETMAPCINEMATIC ) + { + if( uiInfo.serverStatus.currentServerCinematic >= 0 ) + { + trap_CIN_StopCinematic( uiInfo.serverStatus.currentServerCinematic ); + uiInfo.serverStatus.currentServerCinematic = -1; + } + } + } +} + +static void UI_DrawCinematic( int handle, float x, float y, float w, float h ) +{ + trap_CIN_SetExtents( handle, x, y, w, h ); + trap_CIN_DrawCinematic( handle ); +} + +static void UI_RunCinematicFrame( int handle ) +{ + trap_CIN_RunCinematic( handle ); +} + +static float UI_GetValue( int ownerDraw ) +{ + return 0.0f; +} + +/* +================= +UI_ParseResolutions +================= +*/ +void UI_ParseResolutions( void ) +{ + char buf[ MAX_STRING_CHARS ]; + char w[ 16 ], h[ 16 ]; + char *p; + const char *out; + char *s = NULL; + + trap_Cvar_VariableStringBuffer( "r_availableModes", buf, sizeof( buf ) ); + p = buf; + uiInfo.numResolutions = 0; + + while( String_Parse( &p, &out ) ) + { + Q_strncpyz( w, out, sizeof( w ) ); + s = strchr( w, 'x' ); + if( !s ) + return; + + *s++ = '\0'; + Q_strncpyz( h, s, sizeof( h ) ); + + uiInfo.resolutions[ uiInfo.numResolutions ].w = atoi( w ); + uiInfo.resolutions[ uiInfo.numResolutions ].h = atoi( h ); + uiInfo.numResolutions++; + } +} + +/* +================= +UI_Init +================= +*/ +void UI_Init( qboolean inGameLoad ) +{ + int start; + + BG_InitClassConfigs( ); + BG_InitAllowedGameElements( ); + + uiInfo.inGameLoad = inGameLoad; + + UI_RegisterCvars(); + UI_InitMemory(); + + // cache redundant calulations + trap_GetGlconfig( &uiInfo.uiDC.glconfig ); + + // for 640x480 virtualized screen + uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * ( 1.0f / 480.0f ); + uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * ( 1.0f / 640.0f ); + + // wide screen + uiInfo.uiDC.aspectScale = ( ( 640.0f * uiInfo.uiDC.glconfig.vidHeight ) / + ( 480.0f * uiInfo.uiDC.glconfig.vidWidth ) ); + + uiInfo.uiDC.smallFontScale = trap_Cvar_VariableValue( "ui_smallFont" ); + uiInfo.uiDC.bigFontScale = trap_Cvar_VariableValue( "ui_bigFont" ); + + uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + uiInfo.uiDC.setColor = &UI_SetColor; + uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic; + uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic; + uiInfo.uiDC.registerModel = &trap_R_RegisterModel; + uiInfo.uiDC.modelBounds = &trap_R_ModelBounds; + uiInfo.uiDC.fillRect = &UI_FillRect; + uiInfo.uiDC.drawRect = &UI_DrawRect; + uiInfo.uiDC.drawSides = &UI_DrawSides; + uiInfo.uiDC.drawTopBottom = &UI_DrawTopBottom; + uiInfo.uiDC.clearScene = &trap_R_ClearScene; + uiInfo.uiDC.drawSides = &UI_DrawSides; + uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + uiInfo.uiDC.renderScene = &trap_R_RenderScene; + uiInfo.uiDC.registerFont = &trap_R_RegisterFont; + uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw; + uiInfo.uiDC.getValue = &UI_GetValue; + uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible; + uiInfo.uiDC.runScript = &UI_RunMenuScript; + uiInfo.uiDC.setCVar = trap_Cvar_Set; + uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer; + uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue; + uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound; + uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey; + uiInfo.uiDC.feederCount = &UI_FeederCount; + uiInfo.uiDC.feederItemImage = &UI_FeederItemImage; + uiInfo.uiDC.feederItemText = &UI_FeederItemText; + uiInfo.uiDC.feederSelection = &UI_FeederSelection; + uiInfo.uiDC.feederInitialise = &UI_FeederInitialise; + uiInfo.uiDC.setBinding = &trap_Key_SetBinding; + uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf; + uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText; + uiInfo.uiDC.Error = &Com_Error; + uiInfo.uiDC.Print = &Com_Printf; + uiInfo.uiDC.Pause = &UI_Pause; + uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth; + uiInfo.uiDC.ownerDrawText = &UI_OwnerDrawText; + uiInfo.uiDC.registerSound = &trap_S_RegisterSound; + uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + uiInfo.uiDC.playCinematic = &UI_PlayCinematic; + uiInfo.uiDC.stopCinematic = &UI_StopCinematic; + uiInfo.uiDC.drawCinematic = &UI_DrawCinematic; + uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame; + + Init_Display( &uiInfo.uiDC ); + + String_Init(); + + uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip( "white" ); + + AssetCache(); + + start = trap_Milliseconds(); + + UI_LoadMenus( "ui/menus.txt", qtrue ); + UI_LoadMenus( "ui/ingame.txt", qfalse ); + UI_LoadMenus( "ui/tremulous.txt", qfalse ); + UI_LoadHelp( "ui/help.txt" ); + + Menus_CloseAll( ); + + trap_LAN_LoadCachedServers(); + + // sets defaults for ui temp cvars + trap_Cvar_Set( "ui_mousePitch", ( trap_Cvar_VariableValue( "m_pitch" ) >= 0 ) ? "0" : "1" ); + + uiInfo.serverStatus.currentServerCinematic = -1; + uiInfo.previewMovie = -1; + + UI_ParseResolutions( ); +} + + +/* +================= +UI_KeyEvent +================= +*/ +void UI_KeyEvent( int key, qboolean down ) +{ + if( Menu_Count() > 0 ) + { + menuDef_t *menu = Menu_GetFocused(); + + if( menu ) + { + if( key == K_ESCAPE && down && !Menus_AnyFullScreenVisible() ) + Menus_CloseAll( ); + else + Menu_HandleKey( menu, key, down ); + } + else + { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + } + } +} + +/* +================= +UI_MouseEvent +================= +*/ +void UI_MouseEvent( int dx, int dy ) +{ + // update mouse screen position + uiInfo.uiDC.cursorx += ( dx * uiInfo.uiDC.aspectScale ); + + if( uiInfo.uiDC.cursorx < 0 ) + uiInfo.uiDC.cursorx = 0; + else if( uiInfo.uiDC.cursorx > SCREEN_WIDTH ) + uiInfo.uiDC.cursorx = SCREEN_WIDTH; + + uiInfo.uiDC.cursory += dy; + + if( uiInfo.uiDC.cursory < 0 ) + uiInfo.uiDC.cursory = 0; + else if( uiInfo.uiDC.cursory > SCREEN_HEIGHT ) + uiInfo.uiDC.cursory = SCREEN_HEIGHT; + + if( Menu_Count( ) > 0 ) + Display_MouseMove( NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory ); +} + +/* +================= +UI_MousePosition +================= +*/ +int UI_MousePosition( void ) +{ + return (int)rint( uiInfo.uiDC.cursorx ) | + (int)rint( uiInfo.uiDC.cursory ) << 16; +} + +/* +================= +UI_SetMousePosition +================= +*/ +void UI_SetMousePosition( int x, int y ) +{ + uiInfo.uiDC.cursorx = x; + uiInfo.uiDC.cursory = y; + + if( Menu_Count( ) > 0 ) + Display_MouseMove( NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory ); +} + +void UI_SetActiveMenu( uiMenuCommand_t menu ) +{ + char buf[256]; + + // this should be the ONLY way the menu system is brought up + // enusure minumum menu data is cached + + if( Menu_Count() > 0 ) + { + vec3_t v; + v[0] = v[1] = v[2] = 0; + + switch( menu ) + { + case UIMENU_NONE: + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll( ); + + return; + + case UIMENU_MAIN: + trap_Cvar_Set( "sv_killserver", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll( ); + Menus_ActivateByName( "main" ); + trap_Cvar_VariableStringBuffer( "com_errorMessage", buf, sizeof( buf ) ); + + if( strlen( buf ) ) + { + if( trap_Cvar_VariableValue( "com_errorCode" ) == ERR_SERVERDISCONNECT ) + Menus_ActivateByName( "drop_popmenu" ); + else + Menus_ActivateByName( "error_popmenu" ); + } + + return; + + case UIMENU_INGAME: + trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + UI_BuildPlayerList(); + Menus_CloseAll( ); + Menus_ActivateByName( "ingame" ); + return; + } + } +} + +qboolean UI_IsFullscreen( void ) +{ + return Menus_AnyFullScreenVisible( ); +} + + + +static connstate_t lastConnState; +static char lastLoadingText[MAX_INFO_VALUE]; + +static void UI_ReadableSize ( char *buf, int bufsize, int value ) +{ + if( value > 1024 * 1024 * 1024 ) + { // gigs + Com_sprintf( buf, bufsize, "%d", value / ( 1024 * 1024 * 1024 ) ); + Com_sprintf( buf + strlen( buf ), bufsize - strlen( buf ), ".%02d GB", + ( value % ( 1024 * 1024 * 1024 ) ) * 100 / ( 1024 * 1024 * 1024 ) ); + } + else if( value > 1024 * 1024 ) + { // megs + Com_sprintf( buf, bufsize, "%d", value / ( 1024 * 1024 ) ); + Com_sprintf( buf + strlen( buf ), bufsize - strlen( buf ), ".%02d MB", + ( value % ( 1024 * 1024 ) ) * 100 / ( 1024 * 1024 ) ); + } + else if( value > 1024 ) + { // kilos + Com_sprintf( buf, bufsize, "%d KB", value / 1024 ); + } + else + { // bytes + Com_sprintf( buf, bufsize, "%d bytes", value ); + } +} + +// Assumes time is in msec +static void UI_PrintTime ( char *buf, int bufsize, int time ) +{ + time /= 1000; // change to seconds + + if( time > 3600 ) + { // in the hours range + Com_sprintf( buf, bufsize, "%d hr %d min", time / 3600, ( time % 3600 ) / 60 ); + } + else if( time > 60 ) + { // mins + Com_sprintf( buf, bufsize, "%d min %d sec", time / 60, time % 60 ); + } + else + { // secs + Com_sprintf( buf, bufsize, "%d sec", time ); + } +} + +// FIXME: move to ui_shared.c? +void Text_PaintCenter( float x, float y, float scale, vec4_t color, const char *text, float adjust ) +{ + int len = UI_Text_Width( text, scale ); + UI_Text_Paint( x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); +} + +void Text_PaintCenter_AutoWrapped( float x, float y, float xmax, float ystep, float scale, vec4_t color, const char *str, float adjust ) +{ + int width; + char *s1, *s2, *s3; + char c_bcp; + char buf[1024]; + + if( !str || str[0] == '\0' ) + return; + + Q_strncpyz( buf, str, sizeof( buf ) ); + + s1 = s2 = s3 = buf; + + while( 1 ) + { + do + s3++; + while( *s3 != ' ' && *s3 != '\0' ); + + c_bcp = *s3; + + *s3 = '\0'; + + width = UI_Text_Width( s1, scale ); + + *s3 = c_bcp; + + if( width > xmax ) + { + if( s1 == s2 ) + { + // fuck, don't have a clean cut, we'll overflow + s2 = s3; + } + + *s2 = '\0'; + Text_PaintCenter( x, y, scale, color, s1, adjust ); + y += ystep; + + if( c_bcp == '\0' ) + { + // that was the last word + // we could start a new loop, but that wouldn't be much use + // even if the word is too long, we would overflow it (see above) + // so just print it now if needed + s2++; + + if( *s2 != '\0' ) // if we are printing an overflowing line we have s2 == s3 + Text_PaintCenter( x, y, scale, color, s2, adjust ); + + break; + } + + s2++; + s1 = s2; + s3 = s2; + } + else + { + s2 = s3; + + if( c_bcp == '\0' ) // we reached the end + { + Text_PaintCenter( x, y, scale, color, s1, adjust ); + break; + } + } + } +} + + +static void UI_DisplayDownloadInfo( const char *downloadName, float centerPoint, float yStart, float scale ) +{ + static char dlText[] = "Downloading:"; + static char etaText[] = "Estimated time left:"; + static char xferText[] = "Transfer rate:"; + + int downloadSize, downloadCount, downloadTime; + char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64]; + int xferRate; + int leftWidth; + const char *s; + + downloadSize = trap_Cvar_VariableValue( "cl_downloadSize" ); + downloadCount = trap_Cvar_VariableValue( "cl_downloadCount" ); + downloadTime = trap_Cvar_VariableValue( "cl_downloadTime" ); + + leftWidth = 320; + + UI_SetColor( colorWhite ); + Text_PaintCenter( centerPoint, yStart + 112, scale, colorWhite, dlText, 0 ); + Text_PaintCenter( centerPoint, yStart + 192, scale, colorWhite, etaText, 0 ); + Text_PaintCenter( centerPoint, yStart + 248, scale, colorWhite, xferText, 0 ); + + if( downloadSize > 0 ) + s = va( "%s (%d%%)", downloadName, ( int )( ( float )downloadCount * 100.0f / downloadSize ) ); + else + s = downloadName; + + Text_PaintCenter( centerPoint, yStart + 136, scale, colorWhite, s, 0 ); + + UI_ReadableSize( dlSizeBuf, sizeof dlSizeBuf, downloadCount ); + UI_ReadableSize( totalSizeBuf, sizeof totalSizeBuf, downloadSize ); + + if( downloadCount < 4096 || !downloadTime ) + { + Text_PaintCenter( leftWidth, yStart + 216, scale, colorWhite, "estimating", 0 ); + Text_PaintCenter( leftWidth, yStart + 160, scale, colorWhite, va( "(%s of %s copied)", dlSizeBuf, totalSizeBuf ), 0 ); + } + else + { + if( ( uiInfo.uiDC.realTime - downloadTime ) / 1000 ) + xferRate = downloadCount / ( ( uiInfo.uiDC.realTime - downloadTime ) / 1000 ); + else + xferRate = 0; + + UI_ReadableSize( xferRateBuf, sizeof xferRateBuf, xferRate ); + + // Extrapolate estimated completion time + + if( downloadSize && xferRate ) + { + int n = downloadSize / xferRate; // estimated time for entire d/l in secs + + // We do it in K (/1024) because we'd overflow around 4MB + UI_PrintTime ( dlTimeBuf, sizeof dlTimeBuf, + ( n - ( ( ( downloadCount / 1024 ) * n ) / ( downloadSize / 1024 ) ) ) * 1000 ); + + Text_PaintCenter( leftWidth, yStart + 216, scale, colorWhite, dlTimeBuf, 0 ); + Text_PaintCenter( leftWidth, yStart + 160, scale, colorWhite, va( "(%s of %s copied)", dlSizeBuf, totalSizeBuf ), 0 ); + } + else + { + Text_PaintCenter( leftWidth, yStart + 216, scale, colorWhite, "estimating", 0 ); + + if( downloadSize ) + Text_PaintCenter( leftWidth, yStart + 160, scale, colorWhite, va( "(%s of %s copied)", dlSizeBuf, totalSizeBuf ), 0 ); + else + Text_PaintCenter( leftWidth, yStart + 160, scale, colorWhite, va( "(%s copied)", dlSizeBuf ), 0 ); + } + + if( xferRate ) + Text_PaintCenter( leftWidth, yStart + 272, scale, colorWhite, va( "%s/Sec", xferRateBuf ), 0 ); + } +} + +/* +======================== +UI_DrawConnectScreen +======================== +*/ +void UI_DrawConnectScreen( qboolean overlay ) +{ + char *s; + uiClientState_t cstate; + char info[MAX_INFO_VALUE]; + char text[256]; + float centerPoint, yStart, scale; + + menuDef_t *menu = Menus_FindByName( "Connect" ); + + + if( !overlay && menu ) + Menu_Paint( menu, qtrue ); + + if( !overlay ) + { + centerPoint = 320; + yStart = 130; + scale = 0.5f; + } + else + { + centerPoint = 320; + yStart = 32; + scale = 0.6f; + return; + } + + // see what information we should display + trap_GetClientState( &cstate ); + + info[0] = '\0'; + + if( trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ) ) + Text_PaintCenter( centerPoint, yStart, scale, colorWhite, va( "Loading %s", Info_ValueForKey( info, "mapname" ) ), 0 ); + + if( !Q_stricmp( cstate.servername, "localhost" ) ) + Text_PaintCenter( centerPoint, yStart + 48, scale, colorWhite, + "Starting up...", ITEM_TEXTSTYLE_SHADOWEDMORE ); + else + { + Com_sprintf( text, sizeof( text ), "Connecting to %s", cstate.servername ); + Text_PaintCenter( centerPoint, yStart + 48, scale, colorWhite, text , ITEM_TEXTSTYLE_SHADOWEDMORE ); + } + + + // display global MOTD at bottom + Text_PaintCenter( centerPoint, 600, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0 ); + + // print any server info (server full, bad version, etc) + if( cstate.connState < CA_CONNECTED ) + Text_PaintCenter( centerPoint, yStart + 176, scale, colorWhite, cstate.messageString, 0 ); + + if( lastConnState > cstate.connState ) + lastLoadingText[0] = '\0'; + + lastConnState = cstate.connState; + + switch( cstate.connState ) + { + case CA_CONNECTING: + s = va( "Awaiting connection...%i", cstate.connectPacketCount ); + break; + + case CA_CHALLENGING: + s = va( "Awaiting challenge...%i", cstate.connectPacketCount ); + break; + + case CA_CONNECTED: + { + char downloadName[MAX_INFO_VALUE]; + int prompt = trap_Cvar_VariableValue( "com_downloadPrompt" ); + + if( prompt & DLP_SHOW ) { + Com_Printf("Opening download prompt...\n"); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_ActivateByName("download_popmenu"); + trap_Cvar_Set( "com_downloadPrompt", "0" ); + } + + trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof( downloadName ) ); + + if( *downloadName ) + { + UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, scale ); + return; + } + } + + s = "Awaiting gamestate..."; + break; + + case CA_LOADING: + return; + + case CA_PRIMED: + return; + + default: + return; + } + + + if( Q_stricmp( cstate.servername, "localhost" ) ) + Text_PaintCenter( centerPoint, yStart + 80, scale, colorWhite, s, 0 ); + + // password required / connection rejected information goes here +} + +/* +================= +UI_RegisterCvars +================= +*/ +void UI_RegisterCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) + trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags ); +} + +/* +================= +UI_UpdateCvars +================= +*/ +void UI_UpdateCvars( void ) +{ + int i; + cvarTable_t *cv; + + for( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) + trap_Cvar_Update( cv->vmCvar ); +} + +/* +================= +UI_UpdateNews +================= +*/ +void UI_UpdateNews( qboolean begin ) +{ + char newsString[ MAX_NEWS_STRING ]; + const char *c; + const char *wrapped; + int line = 0; + int linePos = 0; + qboolean finished; + + if( begin && !uiInfo.newsInfo.refreshActive ) { + uiInfo.newsInfo.refreshtime = uiInfo.uiDC.realTime + 10000; + uiInfo.newsInfo.refreshActive = qtrue; + } + else if( !uiInfo.newsInfo.refreshActive ) // do nothing + return; + else if( uiInfo.uiDC.realTime > uiInfo.newsInfo.refreshtime ) { + strcpy( uiInfo.newsInfo.text[ 0 ], + "^1Error: Timed out while contacting the server."); + uiInfo.newsInfo.numLines = 1; + return; + } + + // start the news fetching + finished = trap_GetNews( begin ); + + // parse what comes back. Parse newlines and otherwise chop when necessary + trap_Cvar_VariableStringBuffer( "cl_newsString", newsString, + sizeof( newsString ) ); + + // FIXME remove magic width constant + wrapped = Item_Text_Wrap( newsString, 0.25f, 325 * uiInfo.uiDC.aspectScale ); + + for( c = wrapped; *c != '\0'; ++c ) { + if( linePos == (MAX_NEWS_LINEWIDTH - 1) || *c == '\n' ) { + uiInfo.newsInfo.text[ line ][ linePos ] = '\0'; + + if( line == ( MAX_NEWS_LINES - 1 ) ) + break; + + linePos = 0; + line++; + + if( *c != '\n' ) { + uiInfo.newsInfo.text[ line ][ linePos ] = *c; + linePos++; + } + } else if( isprint( *c ) ) { + uiInfo.newsInfo.text[ line ][ linePos ] = *c; + linePos++; + } + } + + uiInfo.newsInfo.text[ line ] [linePos ] = '\0'; + uiInfo.newsInfo.numLines = line + 1; + + if( finished ) + uiInfo.newsInfo.refreshActive = qfalse; +} + diff --git a/src/ui/ui_public.h b/src/ui/ui_public.h new file mode 100644 index 0000000..0fb2292 --- /dev/null +++ b/src/ui/ui_public.h @@ -0,0 +1,205 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef UI_PUBLIC_H +#define UI_PUBLIC_H + +#define UI_API_VERSION 6 + +typedef struct +{ + connstate_t connState; + int connectPacketCount; + int clientNum; + char servername[MAX_STRING_CHARS]; + char updateInfoString[MAX_STRING_CHARS]; + char messageString[MAX_STRING_CHARS]; +} +uiClientState_t; + +typedef enum +{ + UI_ERROR, + UI_PRINT, + UI_MILLISECONDS, + UI_CVAR_SET, + UI_CVAR_VARIABLEVALUE, + UI_CVAR_VARIABLESTRINGBUFFER, + UI_CVAR_SETVALUE, + UI_CVAR_RESET, + UI_CVAR_CREATE, + UI_CVAR_INFOSTRINGBUFFER, + UI_ARGC, + UI_ARGV, + UI_CMD_EXECUTETEXT, + UI_FS_FOPENFILE, + UI_FS_READ, + UI_FS_WRITE, + UI_FS_FCLOSEFILE, + UI_FS_GETFILELIST, + UI_R_REGISTERMODEL, + UI_R_REGISTERSKIN, + UI_R_REGISTERSHADERNOMIP, + UI_R_CLEARSCENE, + UI_R_ADDREFENTITYTOSCENE, + UI_R_ADDPOLYTOSCENE, + UI_R_ADDLIGHTTOSCENE, + UI_R_RENDERSCENE, + UI_R_SETCOLOR, + UI_R_SETCLIPREGION, + UI_R_DRAWSTRETCHPIC, + UI_UPDATESCREEN, + UI_CM_LERPTAG, + UI_CM_LOADMODEL, + UI_S_REGISTERSOUND, + UI_S_STARTLOCALSOUND, + UI_KEY_KEYNUMTOSTRINGBUF, + UI_KEY_GETBINDINGBUF, + UI_KEY_SETBINDING, + UI_KEY_ISDOWN, + UI_KEY_GETOVERSTRIKEMODE, + UI_KEY_SETOVERSTRIKEMODE, + UI_KEY_CLEARSTATES, + UI_KEY_GETCATCHER, + UI_KEY_SETCATCHER, + UI_GETCLIPBOARDDATA, + UI_GETGLCONFIG, + UI_GETCLIENTSTATE, + UI_GETCONFIGSTRING, + UI_LAN_GETPINGQUEUECOUNT, + UI_LAN_CLEARPING, + UI_LAN_GETPING, + UI_LAN_GETPINGINFO, + UI_CVAR_REGISTER, + UI_CVAR_UPDATE, + UI_MEMORY_REMAINING, + UI_R_REGISTERFONT, + UI_R_MODELBOUNDS, + UI_S_STOPBACKGROUNDTRACK, + UI_S_STARTBACKGROUNDTRACK, + UI_REAL_TIME, + UI_LAN_GETSERVERCOUNT, + UI_LAN_GETSERVERADDRESSSTRING, + UI_LAN_GETSERVERINFO, + UI_LAN_MARKSERVERVISIBLE, + UI_LAN_UPDATEVISIBLEPINGS, + UI_LAN_RESETPINGS, + UI_LAN_LOADCACHEDSERVERS, + UI_LAN_SAVECACHEDSERVERS, + UI_LAN_ADDSERVER, + UI_LAN_REMOVESERVER, + UI_CIN_PLAYCINEMATIC, + UI_CIN_STOPCINEMATIC, + UI_CIN_RUNCINEMATIC, + UI_CIN_DRAWCINEMATIC, + UI_CIN_SETEXTENTS, + UI_R_REMAP_SHADER, + UI_LAN_SERVERSTATUS, + UI_LAN_GETSERVERPING, + UI_LAN_SERVERISVISIBLE, + UI_LAN_COMPARESERVERS, + // 1.32 + UI_FS_SEEK, + UI_SET_PBCLSTATUS, + + UI_PARSE_ADD_GLOBAL_DEFINE, + UI_PARSE_LOAD_SOURCE, + UI_PARSE_FREE_SOURCE, + UI_PARSE_READ_TOKEN, + UI_PARSE_SOURCE_FILE_AND_LINE, + UI_GETNEWS, + + UI_MEMSET = 100, + UI_MEMCPY, + UI_STRNCPY, + UI_SIN, + UI_COS, + UI_ATAN2, + UI_SQRT, + UI_FLOOR, + UI_CEIL +} +uiImport_t; + +typedef enum +{ + UIMENU_NONE, + UIMENU_MAIN, + UIMENU_INGAME +} +uiMenuCommand_t; + +typedef enum +{ + SORT_HOST, + SORT_MAP, + SORT_CLIENTS, + SORT_PING, + SORT_GAME +} +serverSortField_t; + +typedef enum +{ + UI_GETAPIVERSION = 0, // system reserved + + UI_INIT, + // void UI_Init( void ); + + UI_SHUTDOWN, + // void UI_Shutdown( void ); + + UI_KEY_EVENT, + // void UI_KeyEvent( int key ); + + UI_MOUSE_EVENT, + // void UI_MouseEvent( int dx, int dy ); + + UI_MOUSE_POSITION, + // int UI_MousePosition( void ); + + UI_SET_MOUSE_POSITION, + // void UI_SetMousePosition( int x, int y ); + + UI_REFRESH, + // void UI_Refresh( int time ); + + UI_IS_FULLSCREEN, + // qboolean UI_IsFullscreen( void ); + + UI_SET_ACTIVE_MENU, + // void UI_SetActiveMenu( uiMenuCommand_t menu ); + + UI_CONSOLE_COMMAND, + // qboolean UI_ConsoleCommand( int realTime ); + + UI_DRAW_CONNECT_SCREEN + // void UI_DrawConnectScreen( qboolean overlay ); + + // if !overlay, the background will be drawn, otherwise it will be + // overlayed over whatever the cgame has drawn. + // a GetClientState syscall will be made to get the current strings +} +uiExport_t; + +#endif diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c new file mode 100644 index 0000000..7822f93 --- /dev/null +++ b/src/ui/ui_shared.c @@ -0,0 +1,8083 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "ui_shared.h" + +#define SCROLL_TIME_START 500 +#define SCROLL_TIME_ADJUST 150 +#define SCROLL_TIME_ADJUSTOFFSET 40 +#define SCROLL_TIME_FLOOR 20 + +typedef struct scrollInfo_s +{ + int nextScrollTime; + int nextAdjustTime; + int adjustValue; + int scrollKey; + float xStart; + float yStart; + itemDef_t *item; + qboolean scrollDir; +} +scrollInfo_t; + +static scrollInfo_t scrollInfo; + +// prevent compiler warnings +void voidFunction( void *var ) +{ + return; +} + +qboolean voidFunction2( itemDef_t *var1, int var2 ) +{ + return qfalse; +} + +static CaptureFunc *captureFunc = voidFunction; +static int captureFuncExpiry = 0; +static void *captureData = NULL; +static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any ) + +displayContextDef_t *DC = NULL; + +static qboolean g_waitingForKey = qfalse; +static qboolean g_editingField = qfalse; + +static itemDef_t *g_bindItem = NULL; +static itemDef_t *g_editItem = NULL; +static itemDef_t *g_comboBoxItem = NULL; + +menuDef_t Menus[MAX_MENUS]; // defined menus +int menuCount = 0; // how many + +menuDef_t *menuStack[MAX_OPEN_MENUS]; +int openMenuCount = 0; + +#define DOUBLE_CLICK_DELAY 300 +static int lastListBoxClickTime = 0; + +itemDataType_t Item_DataType( itemDef_t *item ); +void Item_RunScript( itemDef_t *item, const char *s ); +void Item_SetupKeywordHash( void ); +static ID_INLINE qboolean Item_IsEditField( itemDef_t *item ); +static ID_INLINE qboolean Item_IsListBox( itemDef_t *item ); +static void Item_ListBox_SetStartPos( itemDef_t *item, int startPos ); +void Menu_SetupKeywordHash( void ); +int BindingIDFromName( const char *name ); +qboolean Item_Bind_HandleKey( itemDef_t *item, int key, qboolean down ); +itemDef_t *Menu_SetPrevCursorItem( menuDef_t *menu ); +itemDef_t *Menu_SetNextCursorItem( menuDef_t *menu ); +static qboolean Menu_OverActiveItem( menuDef_t *menu, float x, float y ); + +/* +=============== +UI_InstallCaptureFunc +=============== +*/ +void UI_InstallCaptureFunc( CaptureFunc *f, void *data, int timeout ) +{ + captureFunc = f; + captureData = data; + + if( timeout > 0 ) + captureFuncExpiry = DC->realTime + timeout; + else + captureFuncExpiry = 0; +} + +/* +=============== +UI_RemoveCaptureFunc +=============== +*/ +void UI_RemoveCaptureFunc( void ) +{ + captureFunc = voidFunction; + captureData = NULL; + captureFuncExpiry = 0; +} + +#ifdef CGAME +#define MEM_POOL_SIZE 128 * 1024 +#else +#define MEM_POOL_SIZE 1024 * 1024 +#endif + +static char UI_memoryPool[MEM_POOL_SIZE]; +static int allocPoint, outOfMemory; + +/* +=============== +UI_Alloc +=============== +*/ +void *UI_Alloc( int size ) +{ + char *p; + + if( allocPoint + size > MEM_POOL_SIZE ) + { + outOfMemory = qtrue; + + if( DC->Print ) + DC->Print( "UI_Alloc: Failure. Out of memory!\n" ); + + //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n"); + return NULL; + } + + p = &UI_memoryPool[ allocPoint ]; + + allocPoint += ( size + 15 ) & ~15; + + return p; +} + +/* +=============== +UI_InitMemory +=============== +*/ +void UI_InitMemory( void ) +{ + allocPoint = 0; + outOfMemory = qfalse; +} + +qboolean UI_OutOfMemory( ) +{ + return outOfMemory; +} + + + + + +#define HASH_TABLE_SIZE 2048 +/* +================ +return a hash value for the string +================ +*/ +static long hashForString( const char *str ) +{ + int i; + long hash; + char letter; + + hash = 0; + i = 0; + + while( str[i] != '\0' ) + { + letter = tolower( str[i] ); + hash += ( long )( letter ) * ( i + 119 ); + i++; + } + + hash &= ( HASH_TABLE_SIZE - 1 ); + return hash; +} + +typedef struct stringDef_s +{ + struct stringDef_s *next; + const char *str; +} + +stringDef_t; + +static int strPoolIndex = 0; +static char strPool[STRING_POOL_SIZE]; + +static int strHandleCount = 0; +static stringDef_t *strHandle[HASH_TABLE_SIZE]; + +// Make a copy of a string for later use. Can safely be called on the +// same string repeatedly. Redundant on string literals or global +// constants. +const char *String_Alloc( const char *p ) +{ + int len; + long hash; + stringDef_t *str, *last; + + if( p == NULL ) + return NULL; + + if( *p == 0 ) + return ""; + + hash = hashForString( p ); + + str = strHandle[hash]; + + while( str ) + { + if( strcmp( p, str->str ) == 0 ) + return str->str; + + str = str->next; + } + + len = strlen( p ); + + if( len + strPoolIndex + 1 < STRING_POOL_SIZE ) + { + int ph = strPoolIndex; + strcpy( &strPool[strPoolIndex], p ); + strPoolIndex += len + 1; + + str = strHandle[hash]; + last = str; + + while( str && str->next ) + { + last = str; + str = str->next; + } + + str = UI_Alloc( sizeof( stringDef_t ) ); + str->next = NULL; + str->str = &strPool[ph]; + + if( last ) + last->next = str; + else + strHandle[hash] = str; + + return &strPool[ph]; + } + else + { + Com_Error( ERR_DROP, "String_Alloc( %s ): string pool full!", p ); + return NULL; // not that we return at all + } +} + +void String_Report( void ) +{ + float f; + Com_Printf( "Memory/String Pool Info\n" ); + Com_Printf( "----------------\n" ); + f = strPoolIndex; + f /= STRING_POOL_SIZE; + f *= 100; + Com_Printf( "String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE ); + f = allocPoint; + f /= MEM_POOL_SIZE; + f *= 100; + Com_Printf( "Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE ); +} + +/* +================= +String_Init +================= +*/ +void String_Init( void ) +{ + int i; + + for( i = 0; i < HASH_TABLE_SIZE; i++ ) + strHandle[ i ] = 0; + + strHandleCount = 0; + + strPoolIndex = 0; + + menuCount = 0; + + openMenuCount = 0; + + UI_InitMemory( ); + + Item_SetupKeywordHash( ); + + Menu_SetupKeywordHash( ); + + if( DC && DC->getBindingBuf ) + Controls_GetConfig( ); +} + +/* +================= +PC_SourceWarning +================= +*/ +void PC_SourceWarning( int handle, char *format, ... ) +{ + int line; + char filename[128]; + va_list argptr; + static char string[4096]; + + va_start( argptr, format ); + Q_vsnprintf( string, sizeof( string ), format, argptr ); + va_end( argptr ); + + filename[0] = '\0'; + line = 0; + trap_Parse_SourceFileAndLine( handle, filename, &line ); + + Com_Printf( S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string ); +} + +/* +================= +PC_SourceError +================= +*/ +void PC_SourceError( int handle, char *format, ... ) +{ + int line; + char filename[128]; + va_list argptr; + static char string[4096]; + + va_start( argptr, format ); + Q_vsnprintf( string, sizeof( string ), format, argptr ); + va_end( argptr ); + + filename[0] = '\0'; + line = 0; + trap_Parse_SourceFileAndLine( handle, filename, &line ); + + Com_Printf( S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string ); +} + +/* +================= +LerpColor +================= +*/ +void LerpColor( vec4_t a, vec4_t b, vec4_t c, float t ) +{ + int i; + + // lerp and clamp each component + + for( i = 0; i < 4; i++ ) + { + c[i] = a[i] + t * ( b[i] - a[i] ); + + if( c[i] < 0 ) + c[i] = 0; + else if( c[i] > 1.0 ) + c[i] = 1.0; + } +} + +/* +================= +Float_Parse +================= +*/ +qboolean Float_Parse( char **p, float *f ) +{ + char *token; + token = COM_ParseExt( p, qfalse ); + + if( token && token[0] != 0 ) + { + *f = atof( token ); + return qtrue; + } + else + return qfalse; +} + +#define MAX_EXPR_ELEMENTS 32 + +typedef enum +{ + EXPR_OPERATOR, + EXPR_VALUE +} +exprType_t; + +typedef struct exprToken_s +{ + exprType_t type; + union + { + char op; + float val; + } u; +} +exprToken_t; + +typedef struct exprList_s +{ + exprToken_t l[ MAX_EXPR_ELEMENTS ]; + int f, b; +} +exprList_t; + +/* +================= +OpPrec + +Return a value reflecting operator precedence +================= +*/ +static ID_INLINE int OpPrec( char op ) +{ + switch( op ) + { + case '*': + return 4; + + case '/': + return 3; + + case '+': + return 2; + + case '-': + return 1; + + case '(': + return 0; + + default: + return -1; + } +} + +/* +================= +PC_Expression_Parse +================= +*/ +static qboolean PC_Expression_Parse( int handle, float *f ) +{ + pc_token_t token; + int unmatchedParentheses = 0; + exprList_t stack, fifo; + exprToken_t value; + qboolean expectingNumber = qtrue; + +#define FULL( a ) ( a.b >= ( MAX_EXPR_ELEMENTS - 1 ) ) +#define EMPTY( a ) ( a.f > a.b ) + +#define PUSH_VAL( a, v ) \ + { \ + if( FULL( a ) ) \ + return qfalse; \ + a.b++; \ + a.l[ a.b ].type = EXPR_VALUE; \ + a.l[ a.b ].u.val = v; \ + } + +#define PUSH_OP( a, o ) \ + { \ + if( FULL( a ) ) \ + return qfalse; \ + a.b++; \ + a.l[ a.b ].type = EXPR_OPERATOR; \ + a.l[ a.b ].u.op = o; \ + } + +#define POP_STACK( a ) \ + { \ + if( EMPTY( a ) ) \ + return qfalse; \ + value = a.l[ a.b ]; \ + a.b--; \ + } + +#define PEEK_STACK_OP( a ) ( a.l[ a.b ].u.op ) +#define PEEK_STACK_VAL( a ) ( a.l[ a.b ].u.val ) + +#define POP_FIFO( a ) \ + { \ + if( EMPTY( a ) ) \ + return qfalse; \ + value = a.l[ a.f ]; \ + a.f++; \ + } + + stack.f = fifo.f = 0; + stack.b = fifo.b = -1; + + while( trap_Parse_ReadToken( handle, &token ) ) + { + if( !unmatchedParentheses && token.string[ 0 ] == ')' ) + break; + + // Special case to catch negative numbers + if( expectingNumber && token.string[ 0 ] == '-' ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + token.floatvalue = -token.floatvalue; + } + + if( token.type == TT_NUMBER ) + { + if( !expectingNumber ) + return qfalse; + + expectingNumber = !expectingNumber; + + PUSH_VAL( fifo, token.floatvalue ); + } + else + { + switch( token.string[ 0 ] ) + { + case '(': + unmatchedParentheses++; + PUSH_OP( stack, '(' ); + break; + + case ')': + unmatchedParentheses--; + + if( unmatchedParentheses < 0 ) + return qfalse; + + while( !EMPTY( stack ) && PEEK_STACK_OP( stack ) != '(' ) + { + POP_STACK( stack ); + PUSH_OP( fifo, value.u.op ); + } + + // Pop the '(' + POP_STACK( stack ); + + break; + + case '*': + case '/': + case '+': + case '-': + if( expectingNumber ) + return qfalse; + + expectingNumber = !expectingNumber; + + if( EMPTY( stack ) ) + { + PUSH_OP( stack, token.string[ 0 ] ); + } + else + { + while( !EMPTY( stack ) && OpPrec( token.string[ 0 ] ) < OpPrec( PEEK_STACK_OP( stack ) ) ) + { + POP_STACK( stack ); + PUSH_OP( fifo, value.u.op ); + } + + PUSH_OP( stack, token.string[ 0 ] ); + } + + break; + + default: + // Unknown token + return qfalse; + } + } + } + + while( !EMPTY( stack ) ) + { + POP_STACK( stack ); + PUSH_OP( fifo, value.u.op ); + } + + while( !EMPTY( fifo ) ) + { + POP_FIFO( fifo ); + + if( value.type == EXPR_VALUE ) + { + PUSH_VAL( stack, value.u.val ); + } + else if( value.type == EXPR_OPERATOR ) + { + char op = value.u.op; + float operand1, operand2, result; + + POP_STACK( stack ); + operand2 = value.u.val; + POP_STACK( stack ); + operand1 = value.u.val; + + switch( op ) + { + case '*': + result = operand1 * operand2; + break; + + case '/': + result = operand1 / operand2; + break; + + case '+': + result = operand1 + operand2; + break; + + case '-': + result = operand1 - operand2; + break; + + default: + Com_Error( ERR_FATAL, "Unknown operator '%c' in postfix string", op ); + return qfalse; + } + + PUSH_VAL( stack, result ); + } + } + + POP_STACK( stack ); + + *f = value.u.val; + + return qtrue; + +#undef FULL +#undef EMPTY +#undef PUSH_VAL +#undef PUSH_OP +#undef POP_STACK +#undef PEEK_STACK_OP +#undef PEEK_STACK_VAL +#undef POP_FIFO +} + +/* +================= +PC_Float_Parse +================= +*/ +qboolean PC_Float_Parse( int handle, float *f ) +{ + pc_token_t token; + int negative = qfalse; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( token.string[ 0 ] == '(' ) + return PC_Expression_Parse( handle, f ); + + if( token.string[0] == '-' ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + negative = qtrue; + } + + if( token.type != TT_NUMBER ) + { + PC_SourceError( handle, "expected float but found %s\n", token.string ); + return qfalse; + } + + if( negative ) + * f = -token.floatvalue; + else + *f = token.floatvalue; + + return qtrue; +} + +/* +================= +Color_Parse +================= +*/ +qboolean Color_Parse( char **p, vec4_t *c ) +{ + int i; + float f; + + for( i = 0; i < 4; i++ ) + { + if( !Float_Parse( p, &f ) ) + return qfalse; + + ( *c )[i] = f; + } + + return qtrue; +} + +/* +================= +PC_Color_Parse +================= +*/ +qboolean PC_Color_Parse( int handle, vec4_t *c ) +{ + int i; + float f; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + ( *c )[i] = f; + } + + return qtrue; +} + +/* +================= +Int_Parse +================= +*/ +qboolean Int_Parse( char **p, int *i ) +{ + char *token; + token = COM_ParseExt( p, qfalse ); + + if( token && token[0] != 0 ) + { + *i = atoi( token ); + return qtrue; + } + else + return qfalse; +} + +/* +================= +PC_Int_Parse +================= +*/ +qboolean PC_Int_Parse( int handle, int *i ) +{ + pc_token_t token; + int negative = qfalse; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( token.string[ 0 ] == '(' ) + { + float f; + + if( PC_Expression_Parse( handle, &f ) ) + { + *i = ( int )f; + return qtrue; + } + else + return qfalse; + } + + if( token.string[0] == '-' ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + negative = qtrue; + } + + if( token.type != TT_NUMBER ) + { + PC_SourceError( handle, "expected integer but found %s\n", token.string ); + return qfalse; + } + + *i = token.intvalue; + + if( negative ) + * i = - *i; + + return qtrue; +} + +/* +================= +Rect_Parse +================= +*/ +qboolean Rect_Parse( char **p, rectDef_t *r ) +{ + if( Float_Parse( p, &r->x ) ) + { + if( Float_Parse( p, &r->y ) ) + { + if( Float_Parse( p, &r->w ) ) + { + if( Float_Parse( p, &r->h ) ) + return qtrue; + } + } + } + + return qfalse; +} + +/* +================= +PC_Rect_Parse +================= +*/ +qboolean PC_Rect_Parse( int handle, rectDef_t *r ) +{ + if( PC_Float_Parse( handle, &r->x ) ) + { + if( PC_Float_Parse( handle, &r->y ) ) + { + if( PC_Float_Parse( handle, &r->w ) ) + { + if( PC_Float_Parse( handle, &r->h ) ) + return qtrue; + } + } + } + + return qfalse; +} + +/* +================= +String_Parse +================= +*/ +qboolean String_Parse( char **p, const char **out ) +{ + char *token; + + token = COM_ParseExt( p, qfalse ); + + if( token && token[0] != 0 ) + { + *( out ) = String_Alloc( token ); + return qtrue; + } + + return qfalse; +} + +/* +================= +PC_String_Parse +================= +*/ +qboolean PC_String_Parse( int handle, const char **out ) +{ + pc_token_t token; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + *( out ) = String_Alloc( token.string ); + + return qtrue; +} + +/* +================= +PC_Script_Parse +================= +*/ +qboolean PC_Script_Parse( int handle, const char **out ) +{ + char script[1024]; + pc_token_t token; + + memset( script, 0, sizeof( script ) ); + // scripts start with { and have ; separated command lists.. commands are command, arg.. + // basically we want everything between the { } as it will be interpreted at run time + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( Q_stricmp( token.string, "{" ) != 0 ) + return qfalse; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( Q_stricmp( token.string, "}" ) == 0 ) + { + *out = String_Alloc( script ); + return qtrue; + } + + if( token.string[1] != '\0' ) + Q_strcat( script, 1024, va( "\"%s\"", token.string ) ); + else + Q_strcat( script, 1024, token.string ); + + Q_strcat( script, 1024, " " ); + } + + return qfalse; +} + +// display, window, menu, item code +// + +/* +================== +Init_Display + +Initializes the display with a structure to all the drawing routines +================== +*/ +void Init_Display( displayContextDef_t *dc ) +{ + DC = dc; +} + + + +// type and style painting + +void GradientBar_Paint( rectDef_t *rect, vec4_t color ) +{ + // gradient bar takes two paints + DC->setColor( color ); + DC->drawHandlePic( rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar ); + DC->setColor( NULL ); +} + + +/* +================== +Window_Init + +Initializes a window structure ( windowDef_t ) with defaults +================== +*/ +void Window_Init( Window *w ) +{ + memset( w, 0, sizeof( windowDef_t ) ); + w->borderSize = 1; + w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0; + w->cinematic = -1; +} + +void Fade( int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount ) +{ + if( *flags & ( WINDOW_FADINGOUT | WINDOW_FADINGIN ) ) + { + if( DC->realTime > *nextTime ) + { + *nextTime = DC->realTime + offsetTime; + + if( *flags & WINDOW_FADINGOUT ) + { + *f -= fadeAmount; + + if( bFlags && *f <= 0.0 ) + *flags &= ~( WINDOW_FADINGOUT | WINDOW_VISIBLE ); + } + else + { + *f += fadeAmount; + + if( *f >= clamp ) + { + *f = clamp; + + if( bFlags ) + *flags &= ~WINDOW_FADINGIN; + } + } + } + } +} + + + +static void Window_Paint( Window *w, float fadeAmount, float fadeClamp, float fadeCycle ) +{ + vec4_t color; + rectDef_t fillRect = w->rect; + + + if( DC->getCVarValue( "ui_developer" ) ) + { + color[0] = color[1] = color[2] = color[3] = 1; + DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color ); + } + + if( w == NULL || ( w->style == 0 && w->border == 0 ) ) + return; + + if( w->border != 0 ) + { + fillRect.x += w->borderSize; + fillRect.y += w->borderSize; + fillRect.w -= w->borderSize + 1; + fillRect.h -= w->borderSize + 1; + } + + if( w->style == WINDOW_STYLE_FILLED ) + { + // box, but possible a shader that needs filled + + if( w->background ) + { + Fade( &w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount ); + DC->setColor( w->backColor ); + DC->drawHandlePic( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background ); + DC->setColor( NULL ); + } + else + DC->fillRect( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor ); + } + else if( w->style == WINDOW_STYLE_GRADIENT ) + { + GradientBar_Paint( &fillRect, w->backColor ); + // gradient bar + } + else if( w->style == WINDOW_STYLE_SHADER ) + { + if( w->flags & WINDOW_FORECOLORSET ) + DC->setColor( w->foreColor ); + + DC->drawHandlePic( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background ); + DC->setColor( NULL ); + } + else if( w->style == WINDOW_STYLE_CINEMATIC ) + { + if( w->cinematic == -1 ) + { + w->cinematic = DC->playCinematic( w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h ); + + if( w->cinematic == -1 ) + w->cinematic = -2; + } + + if( w->cinematic >= 0 ) + { + DC->runCinematicFrame( w->cinematic ); + DC->drawCinematic( w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h ); + } + } +} + +static void Border_Paint( Window *w ) +{ + if( w == NULL || ( w->style == 0 && w->border == 0 ) ) + return; + + if( w->border == WINDOW_BORDER_FULL ) + { + // full + DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor ); + } + else if( w->border == WINDOW_BORDER_HORZ ) + { + // top/bottom + DC->setColor( w->borderColor ); + DC->drawTopBottom( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize ); + DC->setColor( NULL ); + } + else if( w->border == WINDOW_BORDER_VERT ) + { + // left right + DC->setColor( w->borderColor ); + DC->drawSides( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize ); + DC->setColor( NULL ); + } + else if( w->border == WINDOW_BORDER_KCGRADIENT ) + { + // this is just two gradient bars along each horz edge + rectDef_t r = w->rect; + r.h = w->borderSize; + GradientBar_Paint( &r, w->borderColor ); + r.y = w->rect.y + w->rect.h - 1; + GradientBar_Paint( &r, w->borderColor ); + } +} + + +void Item_SetScreenCoords( itemDef_t *item, float x, float y ) +{ + if( item == NULL ) + return; + + if( item->window.border != 0 ) + { + x += item->window.borderSize; + y += item->window.borderSize; + } + + item->window.rect.x = x + item->window.rectClient.x; + item->window.rect.y = y + item->window.rectClient.y; + item->window.rect.w = item->window.rectClient.w; + item->window.rect.h = item->window.rectClient.h; + + // force the text rects to recompute + item->textRect.w = 0; + item->textRect.h = 0; +} + +// FIXME: consolidate this with nearby stuff +void Item_UpdatePosition( itemDef_t *item ) +{ + float x, y; + menuDef_t *menu; + + if( item == NULL || item->parent == NULL ) + return; + + menu = item->parent; + + x = menu->window.rect.x; + y = menu->window.rect.y; + + if( menu->window.border != 0 ) + { + x += menu->window.borderSize; + y += menu->window.borderSize; + } + + Item_SetScreenCoords( item, x, y ); + +} + +// menus +void Menu_UpdatePosition( menuDef_t *menu ) +{ + int i; + float x, y; + + if( menu == NULL ) + return; + + x = menu->window.rect.x; + y = menu->window.rect.y; + + if( menu->window.border != 0 ) + { + x += menu->window.borderSize; + y += menu->window.borderSize; + } + + for( i = 0; i < menu->itemCount; i++ ) + Item_SetScreenCoords( menu->items[i], x, y ); +} + +static void Menu_AspectiseRect( int bias, Rectangle *rect ) +{ + switch( bias ) + { + case ALIGN_LEFT: + rect->x *= DC->aspectScale; + rect->w *= DC->aspectScale; + break; + + case ALIGN_CENTER: + rect->x = ( rect->x * DC->aspectScale ) + + ( 320.0f - ( 320.0f * DC->aspectScale ) ); + rect->w *= DC->aspectScale; + break; + + case ALIGN_RIGHT: + rect->x = 640.0f - ( ( 640.0f - rect->x ) * DC->aspectScale ); + rect->w *= DC->aspectScale; + break; + + default: + + case ASPECT_NONE: + break; + } +} + +void Menu_AspectCompensate( menuDef_t *menu ) +{ + int i; + + if( menu->window.aspectBias != ASPECT_NONE ) + { + Menu_AspectiseRect( menu->window.aspectBias, &menu->window.rect ); + + for( i = 0; i < menu->itemCount; i++ ) + { + menu->items[ i ]->window.rectClient.x *= DC->aspectScale; + menu->items[ i ]->window.rectClient.w *= DC->aspectScale; + menu->items[ i ]->textalignx *= DC->aspectScale; + } + } + else + { + for( i = 0; i < menu->itemCount; i++ ) + { + Menu_AspectiseRect( menu->items[ i ]->window.aspectBias, + &menu->items[ i ]->window.rectClient ); + + if( menu->items[ i ]->window.aspectBias != ASPECT_NONE ) + menu->items[ i ]->textalignx *= DC->aspectScale; + } + } +} + +void Menu_PostParse( menuDef_t *menu ) +{ + int i, j; + + if( menu == NULL ) + return; + + if( menu->fullScreen ) + { + menu->window.rect.x = 0; + menu->window.rect.y = 0; + menu->window.rect.w = 640; + menu->window.rect.h = 480; + } + + Menu_AspectCompensate( menu ); + Menu_UpdatePosition( menu ); + + // Push lists to the end of the array as they can potentially be drawn on top + // of other elements + for( i = 0; i < menu->itemCount; i++ ) + { + itemDef_t *item = menu->items[ i ]; + + if( Item_IsListBox( item ) ) + { + for( j = i; j < menu->itemCount - 1; j++ ) + menu->items[ j ] = menu->items[ j + 1 ]; + + menu->items[ j ] = item; + } + } +} + +itemDef_t *Menu_ClearFocus( menuDef_t *menu ) +{ + int i; + itemDef_t *ret = NULL; + + if( menu == NULL ) + return NULL; + + for( i = 0; i < menu->itemCount; i++ ) + { + if( menu->items[i]->window.flags & WINDOW_HASFOCUS ) + ret = menu->items[i]; + + menu->items[i]->window.flags &= ~WINDOW_HASFOCUS; + + if( menu->items[i]->leaveFocus ) + Item_RunScript( menu->items[i], menu->items[i]->leaveFocus ); + } + + return ret; +} + +qboolean IsVisible( int flags ) +{ + return ( flags & WINDOW_VISIBLE && !( flags & WINDOW_FADINGOUT ) ); +} + +qboolean Rect_ContainsPoint( rectDef_t *rect, float x, float y ) +{ + if( rect ) + { + if( x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h ) + return qtrue; + } + + return qfalse; +} + +int Menu_ItemsMatchingGroup( menuDef_t *menu, const char *name ) +{ + int i; + int count = 0; + + for( i = 0; i < menu->itemCount; i++ ) + { + if( Q_stricmp( menu->items[i]->window.name, name ) == 0 || + ( menu->items[i]->window.group && Q_stricmp( menu->items[i]->window.group, name ) == 0 ) ) + { + count++; + } + } + + return count; +} + +itemDef_t *Menu_GetMatchingItemByNumber( menuDef_t *menu, int index, const char *name ) +{ + int i; + int count = 0; + + for( i = 0; i < menu->itemCount; i++ ) + { + if( Q_stricmp( menu->items[i]->window.name, name ) == 0 || + ( menu->items[i]->window.group && Q_stricmp( menu->items[i]->window.group, name ) == 0 ) ) + { + if( count == index ) + return menu->items[i]; + + count++; + } + } + + return NULL; +} + + + +void Script_SetColor( itemDef_t *item, char **args ) +{ + const char *name; + int i; + float f; + vec4_t *out; + // expecting type of color to set and 4 args for the color + + if( String_Parse( args, &name ) ) + { + out = NULL; + + if( Q_stricmp( name, "backcolor" ) == 0 ) + { + out = &item->window.backColor; + item->window.flags |= WINDOW_BACKCOLORSET; + } + else if( Q_stricmp( name, "forecolor" ) == 0 ) + { + out = &item->window.foreColor; + item->window.flags |= WINDOW_FORECOLORSET; + } + else if( Q_stricmp( name, "bordercolor" ) == 0 ) + out = &item->window.borderColor; + + if( out ) + { + for( i = 0; i < 4; i++ ) + { + if( !Float_Parse( args, &f ) ) + return; + + ( *out )[i] = f; + } + } + } +} + +void Script_SetAsset( itemDef_t *item, char **args ) +{ + const char *name; + // expecting name to set asset to + + if( String_Parse( args, &name ) ) + { + // check for a model + + if( item->type == ITEM_TYPE_MODEL ) + {} + + } + +} + +void Script_SetBackground( itemDef_t *item, char **args ) +{ + const char *name; + // expecting name to set asset to + + if( String_Parse( args, &name ) ) + item->window.background = DC->registerShaderNoMip( name ); +} + + + + +itemDef_t *Menu_FindItemByName( menuDef_t *menu, const char *p ) +{ + int i; + + if( menu == NULL || p == NULL ) + return NULL; + + for( i = 0; i < menu->itemCount; i++ ) + { + if( Q_stricmp( p, menu->items[i]->window.name ) == 0 ) + return menu->items[i]; + } + + return NULL; +} + +void Script_SetItemColor( itemDef_t *item, char **args ) +{ + const char *itemname; + const char *name; + vec4_t color; + int i; + vec4_t *out; + // expecting type of color to set and 4 args for the color + + if( String_Parse( args, &itemname ) && String_Parse( args, &name ) ) + { + itemDef_t * item2; + int j; + int count = Menu_ItemsMatchingGroup( item->parent, itemname ); + + if( !Color_Parse( args, &color ) ) + return; + + for( j = 0; j < count; j++ ) + { + item2 = Menu_GetMatchingItemByNumber( item->parent, j, itemname ); + + if( item2 != NULL ) + { + out = NULL; + + if( Q_stricmp( name, "backcolor" ) == 0 ) + out = &item2->window.backColor; + else if( Q_stricmp( name, "forecolor" ) == 0 ) + { + out = &item2->window.foreColor; + item2->window.flags |= WINDOW_FORECOLORSET; + } + else if( Q_stricmp( name, "bordercolor" ) == 0 ) + out = &item2->window.borderColor; + + if( out ) + { + for( i = 0; i < 4; i++ ) + ( *out )[i] = color[i]; + } + } + } + } +} + + +void Menu_ShowItemByName( menuDef_t *menu, const char *p, qboolean bShow ) +{ + itemDef_t * item; + int i; + int count = Menu_ItemsMatchingGroup( menu, p ); + + for( i = 0; i < count; i++ ) + { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + + if( item != NULL ) + { + if( bShow ) + item->window.flags |= WINDOW_VISIBLE; + else + { + item->window.flags &= ~WINDOW_VISIBLE; + // stop cinematics playing in the window + + if( item->window.cinematic >= 0 ) + { + DC->stopCinematic( item->window.cinematic ); + item->window.cinematic = -1; + } + } + } + } +} + +void Menu_FadeItemByName( menuDef_t *menu, const char *p, qboolean fadeOut ) +{ + itemDef_t * item; + int i; + int count = Menu_ItemsMatchingGroup( menu, p ); + + for( i = 0; i < count; i++ ) + { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + + if( item != NULL ) + { + if( fadeOut ) + { + item->window.flags |= ( WINDOW_FADINGOUT | WINDOW_VISIBLE ); + item->window.flags &= ~WINDOW_FADINGIN; + } + else + { + item->window.flags |= ( WINDOW_VISIBLE | WINDOW_FADINGIN ); + item->window.flags &= ~WINDOW_FADINGOUT; + } + } + } +} + +menuDef_t *Menus_FindByName( const char *p ) +{ + int i; + + for( i = 0; i < menuCount; i++ ) + { + if( Q_stricmp( Menus[i].window.name, p ) == 0 ) + return & Menus[i]; + } + + return NULL; +} + +static void Menu_RunCloseScript( menuDef_t *menu ) +{ + if( menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose ) + { + itemDef_t item; + item.parent = menu; + Item_RunScript( &item, menu->onClose ); + } +} + +static void Menus_Close( menuDef_t *menu ) +{ + if( menu != NULL ) + { + Menu_RunCloseScript( menu ); + menu->window.flags &= ~( WINDOW_VISIBLE | WINDOW_HASFOCUS ); + + if( openMenuCount > 0 ) + openMenuCount--; + + if( openMenuCount > 0 ) + Menus_Activate( menuStack[ openMenuCount - 1 ] ); + } +} + +void Menus_CloseByName( const char *p ) +{ + Menus_Close( Menus_FindByName( p ) ); +} + +void Menus_CloseAll( void ) +{ + int i; + + // Close any menus on the stack first + if( openMenuCount > 0 ) + { + for( i = openMenuCount; i > 0; i-- ) + Menus_Close( menuStack[ i ] ); + + openMenuCount = 0; + } + + // Close all other menus + for( i = 0; i < menuCount; i++ ) + Menus_Close( &Menus[ i ] ); + + g_editingField = qfalse; + g_waitingForKey = qfalse; + g_comboBoxItem = NULL; +} + + +void Script_Show( itemDef_t *item, char **args ) +{ + const char *name; + + if( String_Parse( args, &name ) ) + Menu_ShowItemByName( item->parent, name, qtrue ); +} + +void Script_Hide( itemDef_t *item, char **args ) +{ + const char *name; + + if( String_Parse( args, &name ) ) + Menu_ShowItemByName( item->parent, name, qfalse ); +} + +void Script_FadeIn( itemDef_t *item, char **args ) +{ + const char *name; + + if( String_Parse( args, &name ) ) + Menu_FadeItemByName( item->parent, name, qfalse ); +} + +void Script_FadeOut( itemDef_t *item, char **args ) +{ + const char *name; + + if( String_Parse( args, &name ) ) + Menu_FadeItemByName( item->parent, name, qtrue ); +} + + + +void Script_Open( itemDef_t *item, char **args ) +{ + const char *name; + + if( String_Parse( args, &name ) ) + Menus_ActivateByName( name ); +} + +void Script_ConditionalOpen( itemDef_t *item, char **args ) +{ + const char *cvar; + const char *name1; + const char *name2; + float val; + + if( String_Parse( args, &cvar ) && String_Parse( args, &name1 ) && String_Parse( args, &name2 ) ) + { + val = DC->getCVarValue( cvar ); + + if( val == 0.0f ) + Menus_ActivateByName( name2 ); + else + Menus_ActivateByName( name1 ); + } +} + +void Script_Close( itemDef_t *item, char **args ) +{ + const char *name; + + if( String_Parse( args, &name ) ) + Menus_CloseByName( name ); +} + +void Menu_TransitionItemByName( menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, + int time, float amt ) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup( menu, p ); + + for( i = 0; i < count; i++ ) + { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + + if( item != NULL ) + { + item->window.flags |= ( WINDOW_INTRANSITION | WINDOW_VISIBLE ); + item->window.offsetTime = time; + memcpy( &item->window.rectClient, &rectFrom, sizeof( rectDef_t ) ); + memcpy( &item->window.rectEffects, &rectTo, sizeof( rectDef_t ) ); + item->window.rectEffects2.x = abs( rectTo.x - rectFrom.x ) / amt; + item->window.rectEffects2.y = abs( rectTo.y - rectFrom.y ) / amt; + item->window.rectEffects2.w = abs( rectTo.w - rectFrom.w ) / amt; + item->window.rectEffects2.h = abs( rectTo.h - rectFrom.h ) / amt; + Item_UpdatePosition( item ); + } + } +} + + +void Script_Transition( itemDef_t *item, char **args ) +{ + const char *name; + rectDef_t rectFrom, rectTo; + int time; + float amt; + + if( String_Parse( args, &name ) ) + { + if( Rect_Parse( args, &rectFrom ) && Rect_Parse( args, &rectTo ) && + Int_Parse( args, &time ) && Float_Parse( args, &amt ) ) + { + Menu_TransitionItemByName( item->parent, name, rectFrom, rectTo, time, amt ); + } + } +} + + +void Menu_OrbitItemByName( menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time ) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup( menu, p ); + + for( i = 0; i < count; i++ ) + { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + + if( item != NULL ) + { + item->window.flags |= ( WINDOW_ORBITING | WINDOW_VISIBLE ); + item->window.offsetTime = time; + item->window.rectEffects.x = cx; + item->window.rectEffects.y = cy; + item->window.rectClient.x = x; + item->window.rectClient.y = y; + Item_UpdatePosition( item ); + } + } +} + + +void Script_Orbit( itemDef_t *item, char **args ) +{ + const char *name; + float cx, cy, x, y; + int time; + + if( String_Parse( args, &name ) ) + { + if( Float_Parse( args, &x ) && Float_Parse( args, &y ) && + Float_Parse( args, &cx ) && Float_Parse( args, &cy ) && Int_Parse( args, &time ) ) + { + Menu_OrbitItemByName( item->parent, name, x, y, cx, cy, time ); + } + } +} + + + +void Script_SetFocus( itemDef_t *item, char **args ) +{ + const char *name; + itemDef_t *focusItem; + + if( String_Parse( args, &name ) ) + { + focusItem = Menu_FindItemByName( item->parent, name ); + + if( focusItem && !( focusItem->window.flags & WINDOW_DECORATION ) ) + { + Menu_ClearFocus( item->parent ); + focusItem->window.flags |= WINDOW_HASFOCUS; + + if( focusItem->onFocus ) + Item_RunScript( focusItem, focusItem->onFocus ); + + // Edit fields get activated too + if( Item_IsEditField( focusItem ) ) + { + g_editingField = qtrue; + g_editItem = focusItem; + } + + if( DC->Assets.itemFocusSound ) + DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND ); + } + } +} + +void Script_Reset( itemDef_t *item, char **args ) +{ + const char *name; + itemDef_t *resetItem; + + if( String_Parse( args, &name ) ) + { + resetItem = Menu_FindItemByName( item->parent, name ); + + if( resetItem ) + { + if( Item_IsListBox( resetItem ) ) + { + resetItem->cursorPos = DC->feederInitialise( resetItem->feederID ); + Item_ListBox_SetStartPos( resetItem, 0 ); + DC->feederSelection( resetItem->feederID, resetItem->cursorPos ); + } + } + } +} + +void Script_SetPlayerModel( itemDef_t *item, char **args ) +{ + const char *name; + + if( String_Parse( args, &name ) ) + DC->setCVar( "team_model", name ); +} + +void Script_SetPlayerHead( itemDef_t *item, char **args ) +{ + const char *name; + + if( String_Parse( args, &name ) ) + DC->setCVar( "team_headmodel", name ); +} + +void Script_SetCvar( itemDef_t *item, char **args ) +{ + const char *cvar, *val; + + if( String_Parse( args, &cvar ) && String_Parse( args, &val ) ) + DC->setCVar( cvar, val ); + +} + +void Script_Exec( itemDef_t *item, char **args ) +{ + const char *val; + + if( String_Parse( args, &val ) ) + DC->executeText( EXEC_APPEND, va( "%s ; ", val ) ); +} + +void Script_Play( itemDef_t *item, char **args ) +{ + const char *val; + + if( String_Parse( args, &val ) ) + DC->startLocalSound( DC->registerSound( val, qfalse ), CHAN_LOCAL_SOUND ); +} + +void Script_playLooped( itemDef_t *item, char **args ) +{ + const char *val; + + if( String_Parse( args, &val ) ) + { + DC->stopBackgroundTrack(); + DC->startBackgroundTrack( val, val ); + } +} + +static ID_INLINE float UI_EmoticonHeight( fontInfo_t *font, float scale ) +{ + return font->glyphs[ (int)'[' ].height * scale * font->glyphScale; +} + +static ID_INLINE float UI_EmoticonWidth( fontInfo_t *font, float scale ) +{ + return UI_EmoticonHeight( font, scale ) * DC->aspectScale; +} + +void UI_EscapeEmoticons( char *dest, const char *src, int destsize ) +{ + int len; + qboolean escaped; + + for( ; *src && destsize > 1; src++, destsize-- ) + { + if( UI_Text_IsEmoticon( src, &escaped, &len, NULL, NULL ) && !escaped ) + { + *dest++ = '['; + destsize--; + } + + *dest++ = *src; + } + + *dest++ = '\0'; +} + +qboolean UI_Text_IsEmoticon( const char *s, qboolean *escaped, + int *length, qhandle_t *h, int *width ) +{ + const char *p = s; + char emoticon[ MAX_EMOTICON_NAME_LEN ]; + int i; + + if( *p != '[' ) + return qfalse; + p++; + + if( *p == '[' ) + { + *escaped = qtrue; + p++; + } + else + *escaped = qfalse; + + for( *length = 0; p[ *length ] != ']'; ( *length )++ ) + { + if( !p[ *length ] || *length == MAX_EMOTICON_NAME_LEN - 1 ) + return qfalse; + + emoticon[ *length ] = p[ *length ]; + } + emoticon[ *length ] = '\0'; + + for( i = 0; i < DC->Assets.emoticonCount; i++ ) + if( !Q_stricmp( DC->Assets.emoticons[ i ].name, emoticon ) ) + break; + + if( i == DC->Assets.emoticonCount ) + return qfalse; + + if( h ) + *h = DC->Assets.emoticons[ i ].shader; + if( width ) + *width = DC->Assets.emoticons[ i ].width; + + ( *length ) += 2; + + if( *escaped ) + ( *length )++; + + return qtrue; +} + +static float UI_Parse_Indent( const char **text ) +{ + char indentWidth[ 32 ]; + char *indentWidthPtr; + const char *p = *text; + int numDigits; + float pixels; + + while( isdigit( *p ) || *p == '.' ) + p++; + + if( *p != INDENT_MARKER ) + return 0.0f; + + numDigits = ( p - *text ); + + if( numDigits > sizeof( indentWidth ) - 1 ) + return 0.0f; + + strncpy( indentWidth, *text, numDigits ); + + indentWidth[ numDigits ] = '\0'; + indentWidthPtr = indentWidth; + + if( !Float_Parse( &indentWidthPtr, &pixels ) ) + return 0.0f; + + (*text) += ( numDigits + 1 ); + + return pixels; +} + +static ID_INLINE fontInfo_t *UI_FontForScale( float scale ) +{ + if( scale <= DC->smallFontScale ) + return &DC->Assets.smallFont; + else if( scale >= DC->bigFontScale ) + return &DC->Assets.bigFont; + else + return &DC->Assets.textFont; +} + +float UI_Char_Width( const char **text, float scale ) +{ + glyphInfo_t *glyph; + fontInfo_t *font; + int emoticonLen; + qboolean emoticonEscaped; + int emoticonWidth; + + if( text && *text ) + { + if( Q_IsColorString( *text ) ) + { + *text += 2; + return 0.0f; + } + + if( **text == INDENT_MARKER ) + { + (*text)++; + return 0.0f; + } + + font = UI_FontForScale( scale ); + + if( UI_Text_IsEmoticon( *text, &emoticonEscaped, &emoticonLen, + NULL, &emoticonWidth ) ) + { + if( emoticonEscaped ) + (*text)++; + else + { + *text += emoticonLen; + return emoticonWidth * UI_EmoticonWidth( font, scale ); + } + } + + (*text)++; + + glyph = &font->glyphs[ (int)**text ]; + return glyph->xSkip * DC->aspectScale * scale * font->glyphScale; + } + + return 0.0f; +} + +float UI_Text_Width( const char *text, float scale ) +{ + float out; + const char *s = text; + float indentWidth = 0.0f; + + out = 0.0f; + + if( text ) + { + indentWidth = UI_Parse_Indent( &s ); + + while( *s ) + out += UI_Char_Width( &s, scale ); + } + + return out + indentWidth; +} + +float UI_Text_Height( const char *text, float scale ) +{ + float max; + glyphInfo_t *glyph; + float useScale; + const char *s = text; + fontInfo_t *font = UI_FontForScale( scale ); + + useScale = scale * font->glyphScale; + max = 0; + + if( text ) + { + while( s && *s ) + { + if( Q_IsColorString( s ) ) + { + s += 2; + continue; + } + else + { + glyph = &font->glyphs[( int )*s]; + + if( max < glyph->height ) + max = glyph->height; + + s++; + } + } + } + + return max * useScale; +} + +float UI_Text_EmWidth( float scale ) +{ + return UI_Text_Width( "M", scale ); +} + +float UI_Text_EmHeight( float scale ) +{ + return UI_Text_Height( "M", scale ); +} + + +/* +================ +UI_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void UI_AdjustFrom640( float *x, float *y, float *w, float *h ) +{ + *x *= DC->xscale; + *y *= DC->yscale; + *w *= DC->xscale; + *h *= DC->yscale; +} + +/* +================ +UI_SetClipRegion +================= +*/ +void UI_SetClipRegion( float x, float y, float w, float h ) +{ + vec4_t clip; + + UI_AdjustFrom640( &x, &y, &w, &h ); + + clip[ 0 ] = x; + clip[ 1 ] = y; + clip[ 2 ] = x + w; + clip[ 3 ] = y + h; + + trap_R_SetClipRegion( clip ); +} + +/* +================ +UI_ClearClipRegion +================= +*/ +void UI_ClearClipRegion( void ) +{ + trap_R_SetClipRegion( NULL ); +} + +static void UI_Text_PaintChar( float x, float y, float scale, + glyphInfo_t *glyph, float size ) +{ + float w, h; + + w = glyph->imageWidth; + h = glyph->imageHeight; + + if( size > 0.0f ) + { + float half = size * 0.5f * scale; + x -= half; + y -= half; + w += size; + h += size; + } + + w *= ( DC->aspectScale * scale ); + h *= scale; + y -= ( glyph->top * scale ); + UI_AdjustFrom640( &x, &y, &w, &h ); + + DC->drawStretchPic( x, y, w, h, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); +} + +static void UI_Text_Paint_Generic( float x, float y, float scale, float gapAdjust, + const char *text, vec4_t color, int style, + int limit, float *maxX, + int cursorPos, char cursor ) +{ + const char *s = text; + int len; + int count = 0; + vec4_t newColor; + fontInfo_t *font = UI_FontForScale( scale ); + glyphInfo_t *glyph; + float useScale; + qhandle_t emoticonHandle = 0; + float emoticonH, emoticonW; + qboolean emoticonEscaped; + int emoticonLen = 0; + int emoticonWidth; + int cursorX = -1; + + if( !text ) + return; + + useScale = scale * font->glyphScale; + + emoticonH = UI_EmoticonHeight( font, scale ); + emoticonW = UI_EmoticonWidth( font, scale ); + + len = strlen( text ); + if( limit > 0 && len > limit ) + len = limit; + + DC->setColor( color ); + memcpy( &newColor[0], &color[0], sizeof( vec4_t ) ); + + x += UI_Parse_Indent( &s ); + + while( s && *s && count < len ) + { + const char *t = s; + float charWidth = UI_Char_Width( &t, scale ); + glyph = &font->glyphs[ (int)*s ]; + + if( maxX && charWidth + x > *maxX ) + { + *maxX = 0; + break; + } + + if( cursorPos < 0 ) + { + if( Q_IsColorString( s ) ) + { + memcpy( newColor, g_color_table[ColorIndex( *( s+1 ) )], + sizeof( newColor ) ); + newColor[3] = color[3]; + DC->setColor( newColor ); + s += 2; + continue; + } + + if( *s == INDENT_MARKER ) + { + s++; + continue; + } + + if( UI_Text_IsEmoticon( s, &emoticonEscaped, &emoticonLen, + &emoticonHandle, &emoticonWidth ) ) + { + if( emoticonEscaped ) + s++; + else + { + float yadj = useScale * glyph->top; + + DC->setColor( NULL ); + DC->drawHandlePic( x, y - yadj, + ( emoticonW * emoticonWidth ), + emoticonH, + emoticonHandle ); + DC->setColor( newColor ); + x += ( emoticonW * emoticonWidth ) + gapAdjust; + s += emoticonLen; + count += emoticonWidth; + continue; + } + } + } + + if( style == ITEM_TEXTSTYLE_SHADOWED || + style == ITEM_TEXTSTYLE_SHADOWEDMORE ) + { + int ofs; + + if( style == ITEM_TEXTSTYLE_SHADOWED ) + ofs = 1; + else + ofs = 2; + colorBlack[3] = newColor[3]; + DC->setColor( colorBlack ); + UI_Text_PaintChar( x + ofs, y + ofs, useScale, glyph, 0.0f ); + DC->setColor( newColor ); + colorBlack[3] = 1.0f; + } + else if( style == ITEM_TEXTSTYLE_NEON ) + { + vec4_t glow; + + memcpy( &glow[0], &newColor[0], sizeof( vec4_t ) ); + glow[ 3 ] *= 0.2f; + + DC->setColor( glow ); + UI_Text_PaintChar( x, y, useScale, glyph, 6.0f ); + UI_Text_PaintChar( x, y, useScale, glyph, 4.0f ); + DC->setColor( newColor ); + UI_Text_PaintChar( x, y, useScale, glyph, 2.0f ); + + DC->setColor( colorWhite ); + } + + UI_Text_PaintChar( x, y, useScale, glyph, 0.0f ); + + if( count == cursorPos ) + cursorX = x; + + x += ( glyph->xSkip * DC->aspectScale * useScale ) + gapAdjust; + s++; + count++; + + } + + if( maxX ) + *maxX = x; + + // paint cursor + if( cursorPos >= 0 ) + { + if( cursorPos == len ) + cursorX = x; + + if( cursorX >= 0 && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) ) + { + glyph = &font->glyphs[ (int)cursor ]; + UI_Text_PaintChar( cursorX, y, useScale, glyph, 0.0f ); + } + } + + DC->setColor( NULL ); +} + +void UI_Text_Paint_Limit( float *maxX, float x, float y, float scale, + vec4_t color, const char *text, float adjust, int limit ) +{ + UI_Text_Paint_Generic( x, y, scale, adjust, + text, color, ITEM_TEXTSTYLE_NORMAL, + limit, maxX, -1, 0 ); +} + +void UI_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, + float adjust, int limit, int style ) +{ + UI_Text_Paint_Generic( x, y, scale, adjust, + text, color, style, + limit, NULL, -1, 0 ); +} + +void UI_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text, + int cursorPos, char cursor, int limit, int style ) +{ + UI_Text_Paint_Generic( x, y, scale, 0.0, + text, color, style, + limit, NULL, cursorPos, cursor ); +} + +commandDef_t commandList[] = + { + {"close", &Script_Close}, // menu + {"conditionalopen", &Script_ConditionalOpen}, // menu + {"exec", &Script_Exec}, // group/name + {"fadein", &Script_FadeIn}, // group/name + {"fadeout", &Script_FadeOut}, // group/name + {"hide", &Script_Hide}, // group/name + {"open", &Script_Open}, // menu + {"orbit", &Script_Orbit}, // group/name + {"play", &Script_Play}, // group/name + {"playlooped", &Script_playLooped}, // group/name + {"reset", &Script_Reset}, // resets the state of the item argument + {"setasset", &Script_SetAsset}, // works on this + {"setbackground", &Script_SetBackground}, // works on this + {"setcolor", &Script_SetColor}, // works on this + {"setcvar", &Script_SetCvar}, // group/name + {"setfocus", &Script_SetFocus}, // sets this background color to team color + {"setitemcolor", &Script_SetItemColor}, // group/name + {"setplayerhead", &Script_SetPlayerHead}, // sets this background color to team color + {"setplayermodel", &Script_SetPlayerModel}, // sets this background color to team color + {"show", &Script_Show}, // group/name + {"transition", &Script_Transition}, // group/name + }; + +static size_t scriptCommandCount = sizeof( commandList ) / sizeof( commandDef_t ); + +// despite what lcc thinks, we do not get cmdcmp here +static int commandComp( const void *a, const void *b ) +{ + return Q_stricmp( (const char *)a, ((commandDef_t *)b)->name ); +} + +void Item_RunScript( itemDef_t *item, const char *s ) +{ + char script[1024], *p; + commandDef_t *cmd; + memset( script, 0, sizeof( script ) ); + + if( item && s && s[0] ) + { + Q_strcat( script, 1024, s ); + p = script; + + while( 1 ) + { + const char *command; + // expect command then arguments, ; ends command, NULL ends script + + if( !String_Parse( &p, &command ) ) + return; + + if( command[0] == ';' && command[1] == '\0' ) + continue; + + cmd = bsearch( command, commandList, scriptCommandCount, + sizeof( commandDef_t ), commandComp ); + if( cmd ) + cmd->handler( item, &p ); + else + // not in our auto list, pass to handler + DC->runScript( &p ); + } + } +} + + +qboolean Item_EnableShowViaCvar( itemDef_t *item, int flag ) +{ + char script[1024], *p; + memset( script, 0, sizeof( script ) ); + + if( item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest ) + { + char buff[1024]; + DC->getCVarString( item->cvarTest, buff, sizeof( buff ) ); + + Q_strcat( script, 1024, item->enableCvar ); + p = script; + + while( 1 ) + { + const char *val; + // expect value then ; or NULL, NULL ends list + + if( !String_Parse( &p, &val ) ) + return ( item->cvarFlags & flag ) ? qfalse : qtrue; + + if( val[0] == ';' && val[1] == '\0' ) + continue; + + // enable it if any of the values are true + if( item->cvarFlags & flag ) + { + if( Q_stricmp( buff, val ) == 0 ) + return qtrue; + } + else + { + // disable it if any of the values are true + + if( Q_stricmp( buff, val ) == 0 ) + return qfalse; + } + + } + + return ( item->cvarFlags & flag ) ? qfalse : qtrue; + } + + return qtrue; +} + + +// will optionaly set focus to this item +qboolean Item_SetFocus( itemDef_t *item, float x, float y ) +{ + int i; + itemDef_t *oldFocus; + sfxHandle_t *sfx = &DC->Assets.itemFocusSound; + qboolean playSound = qfalse; + menuDef_t *parent; + // sanity check, non-null, not a decoration and does not already have the focus + + if( item == NULL || item->window.flags & WINDOW_DECORATION || + item->window.flags & WINDOW_HASFOCUS || !( item->window.flags & WINDOW_VISIBLE ) ) + { + return qfalse; + } + + parent = ( menuDef_t* )item->parent; + + // items can be enabled and disabled based on cvars + + if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) + return qfalse; + + if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( item, CVAR_SHOW ) ) + return qfalse; + + oldFocus = Menu_ClearFocus( item->parent ); + + if( item->type == ITEM_TYPE_TEXT ) + { + rectDef_t r; + r = item->textRect; + r.y -= r.h; + + if( Rect_ContainsPoint( &r, x, y ) ) + { + item->window.flags |= WINDOW_HASFOCUS; + + if( item->focusSound ) + sfx = &item->focusSound; + + playSound = qtrue; + } + else + { + if( oldFocus ) + { + oldFocus->window.flags |= WINDOW_HASFOCUS; + + if( oldFocus->onFocus ) + Item_RunScript( oldFocus, oldFocus->onFocus ); + } + } + } + else + { + item->window.flags |= WINDOW_HASFOCUS; + + if( item->onFocus ) + Item_RunScript( item, item->onFocus ); + + if( item->focusSound ) + sfx = &item->focusSound; + + playSound = qtrue; + } + + if( playSound && sfx ) + DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND ); + + for( i = 0; i < parent->itemCount; i++ ) + { + if( parent->items[i] == item ) + { + parent->cursorItem = i; + break; + } + } + + return qtrue; +} + +static float Item_ListBox_HeightForNumItems( itemDef_t *item, int numItems ) +{ + listBoxDef_t *listPtr = item->typeData.list; + + return ( listPtr->elementHeight * numItems ) + 2.0f; +} + +static int Item_ListBox_NumItemsForItemHeight( itemDef_t *item ) +{ + listBoxDef_t *listPtr = item->typeData.list; + + if( item->type == ITEM_TYPE_COMBOBOX ) + return listPtr->dropItems; + else + return ( ( item->window.rect.h - 2.0f ) / listPtr->elementHeight ); +} + +int Item_ListBox_MaxScroll( itemDef_t *item ) +{ + int total = DC->feederCount( item->feederID ); + int max = total - Item_ListBox_NumItemsForItemHeight( item ); + + if( max < 0 ) + return 0; + + return max; +} + +static float oldComboBoxY; +static float oldComboBoxH; + +static qboolean Item_ComboBox_MaybeCastToListBox( itemDef_t *item ) +{ + listBoxDef_t *listPtr = item->typeData.list; + qboolean cast = g_comboBoxItem != NULL && + ( item->type == ITEM_TYPE_COMBOBOX ); + + if( cast ) + { + oldComboBoxY = item->window.rect.y; + oldComboBoxH = item->window.rect.h; + + item->window.rect.y += item->window.rect.h; + item->window.rect.h = Item_ListBox_HeightForNumItems( item, listPtr->dropItems ); + item->type = ITEM_TYPE_LISTBOX; + } + + return cast; +} + +static void Item_ComboBox_MaybeUnCastFromListBox( itemDef_t *item, qboolean unCast ) +{ + if( unCast ) + { + item->window.rect.y = oldComboBoxY; + item->window.rect.h = oldComboBoxH; + item->type = ITEM_TYPE_COMBOBOX; + } +} + +static void Item_ListBox_SetStartPos( itemDef_t *item, int startPos ) +{ + listBoxDef_t *listPtr = item->typeData.list; + int total = DC->feederCount( item->feederID ); + int max = Item_ListBox_MaxScroll( item ); + + if( startPos < 0 ) + listPtr->startPos = 0; + else if( startPos > max ) + listPtr->startPos = max; + else + listPtr->startPos = startPos; + + listPtr->endPos = listPtr->startPos + MIN( ( total - listPtr->startPos ), + Item_ListBox_NumItemsForItemHeight( item ) ); +} + +float Item_ListBox_ThumbPosition( itemDef_t *item ) +{ + float max, pos, size; + float startPos = (float)item->typeData.list->startPos; + + max = Item_ListBox_MaxScroll( item ); + size = SCROLLBAR_SLIDER_HEIGHT( item ); + + if( max > 0.0f ) + pos = ( size - SCROLLBAR_ARROW_HEIGHT ) / max; + else + pos = 0.0f; + + pos *= startPos; + + return SCROLLBAR_SLIDER_Y( item ) + pos; +} + +float Item_ListBox_ThumbDrawPosition( itemDef_t *item ) +{ + if( itemCapture == item ) + { + float min = SCROLLBAR_SLIDER_Y( item ); + float max = min + SCROLLBAR_SLIDER_HEIGHT( item ) - SCROLLBAR_ARROW_HEIGHT; + float halfThumbSize = SCROLLBAR_ARROW_HEIGHT / 2.0f; + + if( DC->cursory >= min + halfThumbSize && DC->cursory <= max + halfThumbSize ) + return DC->cursory - halfThumbSize; + } + + return Item_ListBox_ThumbPosition( item ); +} + +float Item_Slider_ThumbPosition( itemDef_t *item ) +{ + float value, range, x; + editFieldDef_t *editDef = item->typeData.edit; + + if( item->text ) + x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET; + else + x = item->window.rect.x; + + if( editDef == NULL && item->cvar ) + return x; + + value = DC->getCVarValue( item->cvar ); + + if( value < editDef->minVal ) + value = editDef->minVal; + else if( value > editDef->maxVal ) + value = editDef->maxVal; + + range = editDef->maxVal - editDef->minVal; + value -= editDef->minVal; + value /= range; + value *= SLIDER_WIDTH; + x += value; + + return x; +} + +static float Item_Slider_VScale( itemDef_t *item ) +{ + if( SLIDER_THUMB_HEIGHT > item->window.rect.h ) + return item->window.rect.h / SLIDER_THUMB_HEIGHT; + else + return 1.0f; +} + +int Item_Slider_OverSlider( itemDef_t *item, float x, float y ) +{ + rectDef_t r; + float vScale = Item_Slider_VScale( item ); + + r.x = Item_Slider_ThumbPosition( item ) - ( SLIDER_THUMB_WIDTH / 2 ); + r.y = item->textRect.y - item->textRect.h + + ( ( item->textRect.h - ( SLIDER_THUMB_HEIGHT * vScale ) ) / 2.0f ); + r.w = SLIDER_THUMB_WIDTH; + r.h = SLIDER_THUMB_HEIGHT * vScale; + + if( Rect_ContainsPoint( &r, x, y ) ) + return WINDOW_LB_THUMB; + + return 0; +} + +int Item_ListBox_OverLB( itemDef_t *item, float x, float y ) +{ + rectDef_t r; + int thumbstart; + int count; + + count = DC->feederCount( item->feederID ); + + r.x = SCROLLBAR_SLIDER_X( item ); + r.y = SCROLLBAR_Y( item ); + r.w = SCROLLBAR_ARROW_WIDTH; + r.h = SCROLLBAR_ARROW_HEIGHT; + + if( Rect_ContainsPoint( &r, x, y ) ) + return WINDOW_LB_UPARROW; + + r.y = SCROLLBAR_SLIDER_Y( item ) + SCROLLBAR_SLIDER_HEIGHT( item ); + + if( Rect_ContainsPoint( &r, x, y ) ) + return WINDOW_LB_DOWNARROW; + + thumbstart = Item_ListBox_ThumbPosition( item ); + r.y = thumbstart; + + if( Rect_ContainsPoint( &r, x, y ) ) + return WINDOW_LB_THUMB; + + r.y = SCROLLBAR_SLIDER_Y( item ); + r.h = thumbstart - r.y; + + if( Rect_ContainsPoint( &r, x, y ) ) + return WINDOW_LB_PGUP; + + r.y = thumbstart + SCROLLBAR_ARROW_HEIGHT; + r.h = ( SCROLLBAR_SLIDER_Y( item ) + SCROLLBAR_SLIDER_HEIGHT( item ) ) - r.y; + + if( Rect_ContainsPoint( &r, x, y ) ) + return WINDOW_LB_PGDN; + + return 0; +} + + +void Item_ListBox_MouseEnter( itemDef_t *item, float x, float y ) +{ + rectDef_t r; + listBoxDef_t *listPtr = item->typeData.list; + int listBoxFlags = ( WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW | WINDOW_LB_THUMB | + WINDOW_LB_PGUP | WINDOW_LB_PGDN ); + int total = DC->feederCount( item->feederID ); + + item->window.flags &= ~listBoxFlags; + item->window.flags |= Item_ListBox_OverLB( item, x, y ); + + if( !( item->window.flags & listBoxFlags ) ) + { + r.x = SCROLLBAR_X( item ); + r.y = SCROLLBAR_Y( item ); + r.w = SCROLLBAR_W( item ); + r.h = listPtr->elementHeight * + MIN( Item_ListBox_NumItemsForItemHeight( item ), total ); + + if( Rect_ContainsPoint( &r, x, y ) ) + { + listPtr->cursorPos = (int)( ( y - r.y ) / listPtr->elementHeight ) + listPtr->startPos; + + if( listPtr->cursorPos >= listPtr->endPos ) + listPtr->cursorPos = listPtr->endPos - 1; + } + else + listPtr->cursorPos = -1; + } +} + +void Item_MouseEnter( itemDef_t *item, float x, float y ) +{ + rectDef_t r; + + if( item ) + { + r = item->textRect; + r.y -= r.h; + // in the text rect? + + // items can be enabled and disabled based on cvars + + if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) + return; + + if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( item, CVAR_SHOW ) ) + return; + + if( Rect_ContainsPoint( &r, x, y ) ) + { + if( !( item->window.flags & WINDOW_MOUSEOVERTEXT ) ) + { + Item_RunScript( item, item->mouseEnterText ); + item->window.flags |= WINDOW_MOUSEOVERTEXT; + } + + if( !( item->window.flags & WINDOW_MOUSEOVER ) ) + { + Item_RunScript( item, item->mouseEnter ); + item->window.flags |= WINDOW_MOUSEOVER; + } + + } + else + { + // not in the text rect + + if( item->window.flags & WINDOW_MOUSEOVERTEXT ) + { + // if we were + Item_RunScript( item, item->mouseExitText ); + item->window.flags &= ~WINDOW_MOUSEOVERTEXT; + } + + if( !( item->window.flags & WINDOW_MOUSEOVER ) ) + { + Item_RunScript( item, item->mouseEnter ); + item->window.flags |= WINDOW_MOUSEOVER; + } + + if( item->type == ITEM_TYPE_LISTBOX ) + Item_ListBox_MouseEnter( item, x, y ); + } + } +} + +void Item_MouseLeave( itemDef_t *item ) +{ + if( item ) + { + if( item->window.flags & WINDOW_MOUSEOVERTEXT ) + { + Item_RunScript( item, item->mouseExitText ); + item->window.flags &= ~WINDOW_MOUSEOVERTEXT; + } + + Item_RunScript( item, item->mouseExit ); + item->window.flags &= ~( WINDOW_LB_DOWNARROW | WINDOW_LB_UPARROW ); + } +} + +itemDef_t *Menu_HitTest( menuDef_t *menu, float x, float y ) +{ + int i; + + for( i = 0; i < menu->itemCount; i++ ) + { + if( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) ) + return menu->items[i]; + } + + return NULL; +} + +void Item_SetMouseOver( itemDef_t *item, qboolean focus ) +{ + if( item ) + { + if( focus ) + item->window.flags |= WINDOW_MOUSEOVER; + else + item->window.flags &= ~WINDOW_MOUSEOVER; + } +} + + +qboolean Item_OwnerDraw_HandleKey( itemDef_t *item, int key ) +{ + if( item && DC->ownerDrawHandleKey ) + return DC->ownerDrawHandleKey( item->window.ownerDraw, key ); + + return qfalse; +} + +qboolean Item_ListBox_HandleKey( itemDef_t *item, int key, qboolean down, qboolean force ) +{ + listBoxDef_t *listPtr = item->typeData.list; + int count = DC->feederCount( item->feederID ); + int viewmax; + + if( force || ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && + item->window.flags & WINDOW_HASFOCUS ) ) + { + viewmax = Item_ListBox_NumItemsForItemHeight( item ); + + switch( key ) + { + case K_MOUSE1: + case K_MOUSE2: + if( item->window.flags & WINDOW_LB_UPARROW ) + Item_ListBox_SetStartPos( item, listPtr->startPos - 1 ); + else if( item->window.flags & WINDOW_LB_DOWNARROW ) + Item_ListBox_SetStartPos( item, listPtr->startPos + 1 ); + else if( item->window.flags & WINDOW_LB_PGUP ) + Item_ListBox_SetStartPos( item, listPtr->startPos - viewmax ); + else if( item->window.flags & WINDOW_LB_PGDN ) + Item_ListBox_SetStartPos( item, listPtr->startPos + viewmax ); + else if( item->window.flags & WINDOW_LB_THUMB ) + break; // Handled by capture function + else + { + // Select an item + qboolean runDoubleClick = qfalse; + + // Mouse isn't over an item + if( listPtr->cursorPos < 0 ) + break; + + if( item->cursorPos != listPtr->cursorPos ) + { + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->feederID, item->cursorPos ); + } + + runDoubleClick = DC->realTime < lastListBoxClickTime && listPtr->doubleClick; + lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY; + + // Made a selection, so close combobox + if( g_comboBoxItem != NULL ) + { + if( listPtr->doubleClick ) + runDoubleClick = qtrue; + + g_comboBoxItem = NULL; + } + + if( runDoubleClick ) + Item_RunScript( item, listPtr->doubleClick ); + } + + break; + + case K_MWHEELUP: + Item_ListBox_SetStartPos( item, listPtr->startPos - 1 ); + break; + + case K_MWHEELDOWN: + Item_ListBox_SetStartPos( item, listPtr->startPos + 1 ); + break; + + case K_ENTER: + // Invoke the doubleClick handler when enter is pressed + if( listPtr->doubleClick ) + Item_RunScript( item, listPtr->doubleClick ); + + break; + + case K_PGUP: + case K_KP_PGUP: + if( !listPtr->notselectable ) + { + listPtr->cursorPos -= viewmax; + + if( listPtr->cursorPos < 0 ) + listPtr->cursorPos = 0; + + if( listPtr->cursorPos < listPtr->startPos ) + Item_ListBox_SetStartPos( item, listPtr->cursorPos ); + + if( listPtr->cursorPos >= listPtr->startPos + viewmax ) + Item_ListBox_SetStartPos( item, listPtr->cursorPos - viewmax + 1 ); + + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->feederID, item->cursorPos ); + } + else + Item_ListBox_SetStartPos( item, listPtr->startPos - viewmax ); + + break; + + case K_PGDN: + case K_KP_PGDN: + if( !listPtr->notselectable ) + { + listPtr->cursorPos += viewmax; + + if( listPtr->cursorPos < listPtr->startPos ) + Item_ListBox_SetStartPos( item, listPtr->cursorPos ); + + if( listPtr->cursorPos >= count ) + listPtr->cursorPos = count - 1; + + if( listPtr->cursorPos >= listPtr->startPos + viewmax ) + Item_ListBox_SetStartPos( item, listPtr->cursorPos - viewmax + 1 ); + + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->feederID, item->cursorPos ); + } + else + Item_ListBox_SetStartPos( item, listPtr->startPos + viewmax ); + + break; + + default: + // Not handled + return qfalse; + } + + return qtrue; + } + + return qfalse; +} + +qboolean Item_ComboBox_HandleKey( itemDef_t *item, int key, qboolean down, qboolean force ) +{ + if( g_comboBoxItem != NULL ) + { + qboolean result; + + qboolean cast = Item_ComboBox_MaybeCastToListBox( item ); + result = Item_ListBox_HandleKey( item, key, down, force ); + Item_ComboBox_MaybeUnCastFromListBox( item, cast ); + + if( !result ) + g_comboBoxItem = NULL; + + return result; + } + else + { + if( force || ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && + item->window.flags & WINDOW_HASFOCUS ) ) + { + if( key == K_MOUSE1 || key == K_MOUSE2 ) + { + g_comboBoxItem = item; + + return qtrue; + } + } + } + + return qfalse; +} + +qboolean Item_YesNo_HandleKey( itemDef_t *item, int key ) +{ + if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && + item->window.flags & WINDOW_HASFOCUS && item->cvar ) + { + if( key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3 ) + { + DC->setCVar( item->cvar, va( "%i", !DC->getCVarValue( item->cvar ) ) ); + return qtrue; + } + } + + return qfalse; + +} + +int Item_Multi_CountSettings( itemDef_t *item ) +{ + if( item->typeData.multi == NULL ) + return 0; + + return item->typeData.multi->count; +} + +int Item_Multi_FindCvarByValue( itemDef_t *item ) +{ + char buff[1024]; + float value = 0; + int i; + multiDef_t *multiPtr = item->typeData.multi; + + if( multiPtr ) + { + if( multiPtr->strDef ) + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + else + value = DC->getCVarValue( item->cvar ); + + for( i = 0; i < multiPtr->count; i++ ) + { + if( multiPtr->strDef ) + { + if( Q_stricmp( buff, multiPtr->cvarStr[i] ) == 0 ) + return i; + } + else + { + if( multiPtr->cvarValue[i] == value ) + return i; + } + } + } + + return 0; +} + +const char *Item_Multi_Setting( itemDef_t *item ) +{ + char buff[1024]; + float value = 0; + int i; + multiDef_t *multiPtr = item->typeData.multi; + + if( multiPtr ) + { + if( multiPtr->strDef ) + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + else + value = DC->getCVarValue( item->cvar ); + + for( i = 0; i < multiPtr->count; i++ ) + { + if( multiPtr->strDef ) + { + if( Q_stricmp( buff, multiPtr->cvarStr[i] ) == 0 ) + return multiPtr->cvarList[i]; + } + else + { + if( multiPtr->cvarValue[i] == value ) + return multiPtr->cvarList[i]; + } + } + } + + return ""; +} + +qboolean Item_Cycle_HandleKey( itemDef_t *item, int key ) +{ + cycleDef_t *cyclePtr = item->typeData.cycle; + qboolean mouseOver = Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ); + int count = DC->feederCount( item->feederID ); + + if( cyclePtr ) + { + if( item->window.flags & WINDOW_HASFOCUS ) + { + if( ( mouseOver && key == K_MOUSE1 ) || + key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW ) + { + if( count > 0 ) + cyclePtr->cursorPos = ( cyclePtr->cursorPos + 1 ) % count; + + DC->feederSelection( item->feederID, cyclePtr->cursorPos ); + + return qtrue; + } + else if( ( mouseOver && key == K_MOUSE2 ) || + key == K_LEFTARROW || key == K_UPARROW ) + { + if( count > 0 ) + cyclePtr->cursorPos = ( count + cyclePtr->cursorPos - 1 ) % count; + + DC->feederSelection( item->feederID, cyclePtr->cursorPos ); + + return qtrue; + } + } + } + + return qfalse; +} + +qboolean Item_Multi_HandleKey( itemDef_t *item, int key ) +{ + qboolean mouseOver = Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ); + int max = Item_Multi_CountSettings( item ); + qboolean changed = qfalse; + + if( item->typeData.multi ) + { + if( item->window.flags & WINDOW_HASFOCUS && item->cvar && max > 0 ) + { + int current; + + if( ( mouseOver && key == K_MOUSE1 ) || + key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW ) + { + current = ( Item_Multi_FindCvarByValue( item ) + 1 ) % max; + changed = qtrue; + } + else if( ( mouseOver && key == K_MOUSE2 ) || + key == K_LEFTARROW || key == K_UPARROW ) + { + current = ( Item_Multi_FindCvarByValue( item ) + max - 1 ) % max; + changed = qtrue; + } + + if( changed ) + { + if( item->typeData.multi->strDef ) + DC->setCVar( item->cvar, item->typeData.multi->cvarStr[current] ); + else + { + float value = item->typeData.multi->cvarValue[current]; + + if( ( ( float )( ( int ) value ) ) == value ) + DC->setCVar( item->cvar, va( "%i", ( int ) value ) ); + else + DC->setCVar( item->cvar, va( "%f", value ) ); + } + + return qtrue; + } + } + } + + return qfalse; +} + +#define MIN_FIELD_WIDTH 10 +#define EDIT_CURSOR_WIDTH 10 + +static void Item_TextField_CalcPaintOffset( itemDef_t *item, char *buff ) +{ + editFieldDef_t *editPtr = item->typeData.edit; + + if( item->cursorPos < editPtr->paintOffset ) + editPtr->paintOffset = item->cursorPos; + else + { + // If there is a maximum field width + + if( editPtr->maxFieldWidth > 0 ) + { + // If the cursor is at the end of the string, maximise the amount of the + // string that's visible + + if( buff[ item->cursorPos + 1 ] == '\0' ) + { + while( UI_Text_Width( &buff[ editPtr->paintOffset ], item->textscale ) <= + ( editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH ) && editPtr->paintOffset > 0 ) + editPtr->paintOffset--; + } + + buff[ item->cursorPos + 1 ] = '\0'; + + // Shift paintOffset so that the cursor is visible + + while( UI_Text_Width( &buff[ editPtr->paintOffset ], item->textscale ) > + ( editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH ) ) + editPtr->paintOffset++; + } + } +} + +qboolean Item_TextField_HandleKey( itemDef_t *item, int key ) +{ + char buff[1024]; + int len; + itemDef_t *newItem = NULL; + editFieldDef_t *editPtr = item->typeData.edit; + qboolean releaseFocus = qtrue; + + if( item->cvar ) + { + Com_Memset( buff, 0, sizeof( buff ) ); + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + len = strlen( buff ); + + if( len < item->cursorPos ) + item->cursorPos = len; + + if( editPtr->maxChars && len > editPtr->maxChars ) + len = editPtr->maxChars; + + if( key & K_CHAR_FLAG ) + { + key &= ~K_CHAR_FLAG; + + if( key == 'h' - 'a' + 1 ) + { + // ctrl-h is backspace + + if( item->cursorPos > 0 ) + { + memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos ); + item->cursorPos--; + } + + DC->setCVar( item->cvar, buff ); + } + else if( key < 32 || !item->cvar ) + { + // Ignore any non printable chars + releaseFocus = qfalse; + goto exit; + } + else if( item->type == ITEM_TYPE_NUMERICFIELD && ( key < '0' || key > '9' ) ) + { + // Ignore non-numeric characters + releaseFocus = qfalse; + goto exit; + } + else + { + if( !DC->getOverstrikeMode() ) + { + if( ( len == MAX_EDITFIELD - 1 ) || ( editPtr->maxChars && len >= editPtr->maxChars ) ) + { + // Reached maximum field length + releaseFocus = qfalse; + goto exit; + } + + memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos ); + } + else + { + // Reached maximum field length + if( editPtr->maxChars && item->cursorPos >= editPtr->maxChars ) + { + releaseFocus = qfalse; + goto exit; + } + } + + buff[ item->cursorPos ] = key; + + DC->setCVar( item->cvar, buff ); + + if( item->cursorPos < len + 1 ) + item->cursorPos++; + } + } + else + { + switch( key ) + { + case K_DEL: + case K_KP_DEL: + if( item->cursorPos < len ) + { + memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos ); + DC->setCVar( item->cvar, buff ); + } + + break; + + case K_RIGHTARROW: + case K_KP_RIGHTARROW: + if( item->cursorPos < len ) + item->cursorPos++; + + break; + + case K_LEFTARROW: + case K_KP_LEFTARROW: + if( item->cursorPos > 0 ) + item->cursorPos--; + + break; + + case K_HOME: + case K_KP_HOME: + item->cursorPos = 0; + + break; + + case K_END: + case K_KP_END: + item->cursorPos = len; + + break; + + case K_INS: + case K_KP_INS: + DC->setOverstrikeMode( !DC->getOverstrikeMode() ); + + break; + + case K_TAB: + case K_DOWNARROW: + case K_KP_DOWNARROW: + case K_UPARROW: + case K_KP_UPARROW: + // Ignore these keys from the say field + if( item->type == ITEM_TYPE_SAYFIELD ) + break; + + newItem = Menu_SetNextCursorItem( item->parent ); + + if( newItem && Item_IsEditField( newItem ) ) + { + g_editItem = newItem; + } + else + { + releaseFocus = qtrue; + goto exit; + } + + break; + + case K_MOUSE1: + case K_MOUSE2: + case K_MOUSE3: + case K_MOUSE4: + // Ignore these buttons from the say field + if( item->type == ITEM_TYPE_SAYFIELD ) + break; + // FALLTHROUGH + case K_ENTER: + case K_KP_ENTER: + case K_ESCAPE: + releaseFocus = qtrue; + goto exit; + + default: + break; + } + } + + releaseFocus = qfalse; + } + +exit: + Item_TextField_CalcPaintOffset( item, buff ); + + return !releaseFocus; +} + +static void _Scroll_ListBox_AutoFunc( scrollInfo_t *si ) +{ + if( DC->realTime > si->nextScrollTime ) + { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_ListBox_HandleKey( si->item, si->scrollKey, qtrue, qfalse ); + + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if( DC->realTime > si->nextAdjustTime ) + { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + + if( si->adjustValue > SCROLL_TIME_FLOOR ) + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } +} + +static void Scroll_ListBox_AutoFunc( void *p ) +{ + scrollInfo_t *si = ( scrollInfo_t* )p; + + qboolean cast = Item_ComboBox_MaybeCastToListBox( si->item ); + _Scroll_ListBox_AutoFunc( si ); + Item_ComboBox_MaybeUnCastFromListBox( si->item, cast ); +} + +static void _Scroll_ListBox_ThumbFunc( scrollInfo_t *si ) +{ + rectDef_t r; + int pos, max; + + if( DC->cursory != si->yStart ) + { + r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_ARROW_WIDTH - 1; + r.y = si->item->window.rect.y + SCROLLBAR_ARROW_HEIGHT + 1; + r.w = SCROLLBAR_ARROW_WIDTH; + r.h = si->item->window.rect.h - ( SCROLLBAR_ARROW_HEIGHT * 2 ) - 2; + max = Item_ListBox_MaxScroll( si->item ); + // + pos = ( DC->cursory - r.y - SCROLLBAR_ARROW_HEIGHT / 2 ) * max / ( r.h - SCROLLBAR_ARROW_HEIGHT ); + + if( pos < 0 ) + pos = 0; + else if( pos > max ) + pos = max; + + Item_ListBox_SetStartPos( si->item, pos ); + si->yStart = DC->cursory; + } + + if( DC->realTime > si->nextScrollTime ) + { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_ListBox_HandleKey( si->item, si->scrollKey, qtrue, qfalse ); + + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if( DC->realTime > si->nextAdjustTime ) + { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + + if( si->adjustValue > SCROLL_TIME_FLOOR ) + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } +} + +static void Scroll_ListBox_ThumbFunc( void *p ) +{ + scrollInfo_t *si = ( scrollInfo_t* )p; + + qboolean cast = Item_ComboBox_MaybeCastToListBox( si->item ); + _Scroll_ListBox_ThumbFunc( si ); + Item_ComboBox_MaybeUnCastFromListBox( si->item, cast ); +} + +static void Scroll_Slider_ThumbFunc( void *p ) +{ + float x, value, cursorx; + scrollInfo_t *si = ( scrollInfo_t* )p; + + if( si->item->text ) + x = si->item->textRect.x + si->item->textRect.w + ITEM_VALUE_OFFSET; + else + x = si->item->window.rect.x; + + cursorx = DC->cursorx; + + if( cursorx < x ) + cursorx = x; + else if( cursorx > x + SLIDER_WIDTH ) + cursorx = x + SLIDER_WIDTH; + + value = cursorx - x; + value /= SLIDER_WIDTH; + value *= si->item->typeData.edit->maxVal - si->item->typeData.edit->minVal; + value += si->item->typeData.edit->minVal; + DC->setCVar( si->item->cvar, va( "%f", value ) ); +} + +void Item_StartCapture( itemDef_t *item, int key ) +{ + int flags; + + // Don't allow captureFunc to be overridden + + if( captureFunc != voidFunction ) + return; + + switch( item->type ) + { + case ITEM_TYPE_LISTBOX: + case ITEM_TYPE_COMBOBOX: + { + qboolean cast = Item_ComboBox_MaybeCastToListBox( item ); + flags = Item_ListBox_OverLB( item, DC->cursorx, DC->cursory ); + Item_ComboBox_MaybeUnCastFromListBox( item, cast ); + + if( flags & ( WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW ) ) + { + scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START; + scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + scrollInfo.adjustValue = SCROLL_TIME_START; + scrollInfo.scrollKey = key; + scrollInfo.scrollDir = ( flags & WINDOW_LB_UPARROW ) ? qtrue : qfalse; + scrollInfo.item = item; + UI_InstallCaptureFunc( Scroll_ListBox_AutoFunc, &scrollInfo, 0 ); + itemCapture = item; + } + else if( flags & WINDOW_LB_THUMB ) + { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + UI_InstallCaptureFunc( Scroll_ListBox_ThumbFunc, &scrollInfo, 0 ); + itemCapture = item; + } + + break; + } + + case ITEM_TYPE_SLIDER: + { + flags = Item_Slider_OverSlider( item, DC->cursorx, DC->cursory ); + + if( flags & WINDOW_LB_THUMB ) + { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + UI_InstallCaptureFunc( Scroll_Slider_ThumbFunc, &scrollInfo, 0 ); + itemCapture = item; + } + + break; + } + } +} + +void Item_StopCapture( itemDef_t *item ) +{ +} + +qboolean Item_Slider_HandleKey( itemDef_t *item, int key, qboolean down ) +{ + float x, value, width; + + if( item->window.flags & WINDOW_HASFOCUS && item->cvar && + Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) ) + { + if( item->typeData.edit && ( key == K_ENTER || + key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) + { + rectDef_t testRect; + width = SLIDER_WIDTH; + + if( item->text ) + x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET; + else + x = item->window.rect.x; + + testRect = item->window.rect; + value = ( float )SLIDER_THUMB_WIDTH / 2; + testRect.x = x - value; + testRect.w = SLIDER_WIDTH + value; + + if( Rect_ContainsPoint( &testRect, DC->cursorx, DC->cursory ) ) + { + value = ( float )( DC->cursorx - x ) / width; + value *= ( item->typeData.edit->maxVal - item->typeData.edit->minVal ); + value += item->typeData.edit->minVal; + DC->setCVar( item->cvar, va( "%f", value ) ); + return qtrue; + } + } + } + + return qfalse; +} + + +qboolean Item_HandleKey( itemDef_t *item, int key, qboolean down ) +{ + if( itemCapture ) + { + Item_StopCapture( itemCapture ); + itemCapture = NULL; + UI_RemoveCaptureFunc( ); + } + else + { + if( down && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) + Item_StartCapture( item, key ); + } + + if( !down ) + return qfalse; + + // Edit fields are handled specially + if( Item_IsEditField( item ) ) + return qfalse; + + switch( item->type ) + { + case ITEM_TYPE_BUTTON: + return qfalse; + + case ITEM_TYPE_RADIOBUTTON: + return qfalse; + + case ITEM_TYPE_CHECKBOX: + return qfalse; + + case ITEM_TYPE_CYCLE: + return Item_Cycle_HandleKey( item, key ); + + case ITEM_TYPE_LISTBOX: + return Item_ListBox_HandleKey( item, key, down, qfalse ); + + case ITEM_TYPE_COMBOBOX: + return Item_ComboBox_HandleKey( item, key, down, qfalse ); + + case ITEM_TYPE_YESNO: + return Item_YesNo_HandleKey( item, key ); + + case ITEM_TYPE_MULTI: + return Item_Multi_HandleKey( item, key ); + + case ITEM_TYPE_OWNERDRAW: + return Item_OwnerDraw_HandleKey( item, key ); + + case ITEM_TYPE_BIND: + return Item_Bind_HandleKey( item, key, down ); + + case ITEM_TYPE_SLIDER: + return Item_Slider_HandleKey( item, key, down ); + + default: + return qfalse; + } +} + +void Item_Action( itemDef_t *item ) +{ + if( item ) + Item_RunScript( item, item->action ); +} + +itemDef_t *Menu_SetPrevCursorItem( menuDef_t *menu ) +{ + qboolean wrapped = qfalse; + int oldCursor = menu->cursorItem; + + if( menu->cursorItem < 0 ) + { + menu->cursorItem = menu->itemCount - 1; + wrapped = qtrue; + } + + while( menu->cursorItem > -1 ) + { + menu->cursorItem--; + + if( menu->cursorItem < 0 && !wrapped ) + { + wrapped = qtrue; + menu->cursorItem = menu->itemCount - 1; + } + + if( Item_SetFocus( menu->items[menu->cursorItem], DC->cursorx, DC->cursory ) ) + { + Menu_HandleMouseMove( menu, menu->items[menu->cursorItem]->window.rect.x + 1, + menu->items[menu->cursorItem]->window.rect.y + 1 ); + return menu->items[menu->cursorItem]; + } + } + + menu->cursorItem = oldCursor; + return NULL; + +} + +itemDef_t *Menu_SetNextCursorItem( menuDef_t *menu ) +{ + qboolean wrapped = qfalse; + int oldCursor = menu->cursorItem; + + + if( menu->cursorItem == -1 ) + { + menu->cursorItem = 0; + wrapped = qtrue; + } + + while( menu->cursorItem < menu->itemCount ) + { + menu->cursorItem++; + + if( menu->cursorItem >= menu->itemCount && !wrapped ) + { + wrapped = qtrue; + menu->cursorItem = 0; + } + + if( Item_SetFocus( menu->items[menu->cursorItem], DC->cursorx, DC->cursory ) ) + { + Menu_HandleMouseMove( menu, menu->items[menu->cursorItem]->window.rect.x + 1, + menu->items[menu->cursorItem]->window.rect.y + 1 ); + return menu->items[menu->cursorItem]; + } + + } + + menu->cursorItem = oldCursor; + return NULL; +} + +static void Window_CloseCinematic( windowDef_t *window ) +{ + if( window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0 ) + { + DC->stopCinematic( window->cinematic ); + window->cinematic = -1; + } +} + +static void Menu_CloseCinematics( menuDef_t *menu ) +{ + if( menu ) + { + int i; + Window_CloseCinematic( &menu->window ); + + for( i = 0; i < menu->itemCount; i++ ) + { + Window_CloseCinematic( &menu->items[i]->window ); + + if( menu->items[i]->type == ITEM_TYPE_OWNERDRAW ) + DC->stopCinematic( 0 - menu->items[i]->window.ownerDraw ); + } + } +} + +static void Display_CloseCinematics( void ) +{ + int i; + + for( i = 0; i < menuCount; i++ ) + Menu_CloseCinematics( &Menus[i] ); +} + +void Menus_Activate( menuDef_t *menu ) +{ + int i; + qboolean onTopOfMenuStack = qfalse; + + if( openMenuCount > 0 && menuStack[ openMenuCount - 1 ] == menu ) + onTopOfMenuStack = qtrue; + + menu->window.flags |= ( WINDOW_HASFOCUS | WINDOW_VISIBLE ); + + // If being opened for the first time + if( !onTopOfMenuStack ) + { + if( menu->onOpen ) + { + itemDef_t item; + item.parent = menu; + Item_RunScript( &item, menu->onOpen ); + } + + if( menu->soundName && *menu->soundName ) + DC->startBackgroundTrack( menu->soundName, menu->soundName ); + + Display_CloseCinematics( ); + + Menu_HandleMouseMove( menu, DC->cursorx, DC->cursory ); // force the item under the cursor to focus + + for( i = 0; i < menu->itemCount; i++ ) // reset selection in listboxes when opened + { + if( Item_IsListBox( menu->items[ i ] ) ) + { + menu->items[ i ]->cursorPos = DC->feederInitialise( menu->items[ i ]->feederID ); + Item_ListBox_SetStartPos( menu->items[ i ], 0 ); + DC->feederSelection( menu->items[ i ]->feederID, menu->items[ i ]->cursorPos ); + } + else if( menu->items[ i ]->type == ITEM_TYPE_CYCLE ) + { + menu->items[ i ]->typeData.cycle->cursorPos = + DC->feederInitialise( menu->items[ i ]->feederID ); + } + + } + + if( openMenuCount < MAX_OPEN_MENUS ) + menuStack[ openMenuCount++ ] = menu; + } +} + +qboolean Menus_ReplaceActive( menuDef_t *menu ) +{ + int i; + menuDef_t *active; + + if( openMenuCount < 1 ) + return qfalse; + + active = menuStack[ openMenuCount - 1 ]; + + if( !( active->window.flags & WINDOW_HASFOCUS ) || + !( active->window.flags & WINDOW_VISIBLE ) ) + { + return qfalse; + } + + if( menu == active ) + return qfalse; + + if( menu->itemCount != active->itemCount ) + { + Com_Printf( S_COLOR_YELLOW + "WARNING: Menus_ReplaceActive: expecting %i menu items, found %i\n", + menu->itemCount, active->itemCount); + return qfalse; + } + + for( i = 0; i < menu->itemCount; i++ ) + { + if( menu->items[ i ]->type != active->items[ i ]->type ) + { + Com_Printf( S_COLOR_YELLOW + "WARNING: Menus_ReplaceActive: type mismatch on item %i\n", i + 1 ); + return qfalse; + } + } + + active->window.flags &= ~( WINDOW_FADINGOUT | WINDOW_VISIBLE ); + menu->window.flags |= ( WINDOW_HASFOCUS | WINDOW_VISIBLE ); + + menuStack[ openMenuCount - 1 ] = menu; + if( menu->onOpen ) + { + itemDef_t item; + item.parent = menu; + Item_RunScript( &item, menu->onOpen ); + } + + // set the cursor position on the new menu to match the active one + for( i = 0; i < menu->itemCount; i++ ) + { + menu->items[ i ]->cursorPos = active->items[ i ]->cursorPos; + menu->items[ i ]->feederID = active->items[ i ]->feederID; + switch( Item_DataType( menu->items[ i ] ) ) + { + case TYPE_LIST: + menu->items[ i ]->typeData.list->startPos = + active->items[ i ]->typeData.list->startPos; + menu->items[ i ]->typeData.list->cursorPos = + active->items[ i ]->typeData.list->cursorPos; + break; + case TYPE_COMBO: + menu->items[ i ]->typeData.cycle->cursorPos = + active->items[ i ]->typeData.cycle->cursorPos; + break; + default: + break; + } + } + return qtrue; +} + +int Display_VisibleMenuCount( void ) +{ + int i, count; + count = 0; + + for( i = 0; i < menuCount; i++ ) + { + if( Menus[i].window.flags & ( WINDOW_FORCED | WINDOW_VISIBLE ) ) + count++; + } + + return count; +} + +void Menus_HandleOOBClick( menuDef_t *menu, int key, qboolean down ) +{ + if( menu ) + { + int i; + // basically the behaviour we are looking for is if there are windows in the stack.. see if + // the cursor is within any of them.. if not close them otherwise activate them and pass the + // key on.. force a mouse move to activate focus and script stuff + + if( down && menu->window.flags & WINDOW_OOB_CLICK ) + Menus_Close( menu ); + + for( i = 0; i < menuCount; i++ ) + { + if( Menu_OverActiveItem( &Menus[i], DC->cursorx, DC->cursory ) ) + { + Menus_Close( menu ); + Menus_Activate( &Menus[i] ); + Menu_HandleMouseMove( &Menus[i], DC->cursorx, DC->cursory ); + Menu_HandleKey( &Menus[i], key, down ); + } + } + + if( Display_VisibleMenuCount() == 0 ) + { + if( DC->Pause ) + DC->Pause( qfalse ); + } + + Display_CloseCinematics(); + } +} + +static rectDef_t *Item_CorrectedTextRect( itemDef_t *item ) +{ + static rectDef_t rect; + memset( &rect, 0, sizeof( rectDef_t ) ); + + if( item ) + { + rect = item->textRect; + + if( rect.w ) + rect.y -= rect.h; + } + + return ▭ +} + +void Menu_HandleKey( menuDef_t *menu, int key, qboolean down ) +{ + int i; + itemDef_t *item = NULL; + qboolean inHandler = qfalse; + + inHandler = qtrue; + + if( g_waitingForKey && down ) + { + Item_Bind_HandleKey( g_bindItem, key, down ); + inHandler = qfalse; + return; + } + + if( g_editingField && down ) + { + if( !Item_TextField_HandleKey( g_editItem, key ) ) + { + g_editingField = qfalse; + Item_RunScript( g_editItem, g_editItem->onTextEntry ); + g_editItem = NULL; + inHandler = qfalse; + return; + } + else + { + Item_RunScript( g_editItem, g_editItem->onCharEntry ); + } + } + + if( menu == NULL ) + { + inHandler = qfalse; + return; + } + + // see if the mouse is within the window bounds and if so is this a mouse click + if( down && !( menu->window.flags & WINDOW_POPUP ) && + !Rect_ContainsPoint( &menu->window.rect, DC->cursorx, DC->cursory ) ) + { + static qboolean inHandleKey = qfalse; + + if( !inHandleKey && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) + { + inHandleKey = qtrue; + Menus_HandleOOBClick( menu, key, down ); + inHandleKey = qfalse; + inHandler = qfalse; + return; + } + } + + if( g_comboBoxItem == NULL ) + { + // get the item with focus + for( i = 0; i < menu->itemCount; i++ ) + { + if( menu->items[i]->window.flags & WINDOW_HASFOCUS ) + item = menu->items[i]; + } + } + else + item = g_comboBoxItem; + + if( item != NULL ) + { + if( Item_HandleKey( item, key, down ) ) + { + Item_Action( item ); + inHandler = qfalse; + return; + } + } + + if( !down ) + { + inHandler = qfalse; + return; + } + + // default handling + switch( key ) + { + case K_F12: + if( DC->getCVarValue( "developer" ) ) + DC->executeText( EXEC_APPEND, "screenshot\n" ); + + break; + + case K_KP_UPARROW: + case K_UPARROW: + Menu_SetPrevCursorItem( menu ); + break; + + case K_ESCAPE: + if( !g_waitingForKey && menu->onESC ) + { + itemDef_t it; + it.parent = menu; + Item_RunScript( &it, menu->onESC ); + } + + break; + + case K_TAB: + case K_KP_DOWNARROW: + case K_DOWNARROW: + Menu_SetNextCursorItem( menu ); + break; + + case K_MOUSE1: + case K_MOUSE2: + if( item ) + { + if( item->type == ITEM_TYPE_TEXT ) + { + if( Rect_ContainsPoint( Item_CorrectedTextRect( item ), DC->cursorx, DC->cursory ) ) + Item_Action( item ); + } + else if( Item_IsEditField( item ) ) + { + if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) ) + { + char buffer[ MAX_STRING_CHARS ] = { 0 }; + + if( item->cvar ) + DC->getCVarString( item->cvar, buffer, sizeof( buffer ) ); + + item->cursorPos = strlen( buffer ); + + Item_TextField_CalcPaintOffset( item, buffer ); + + g_editingField = qtrue; + + g_editItem = item; + } + } + else + { + if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) ) + Item_Action( item ); + } + } + + break; + + case K_JOY1: + case K_JOY2: + case K_JOY3: + case K_JOY4: + case K_AUX1: + case K_AUX2: + case K_AUX3: + case K_AUX4: + case K_AUX5: + case K_AUX6: + case K_AUX7: + case K_AUX8: + case K_AUX9: + case K_AUX10: + case K_AUX11: + case K_AUX12: + case K_AUX13: + case K_AUX14: + case K_AUX15: + case K_AUX16: + break; + + case K_KP_ENTER: + case K_ENTER: + if( item ) + { + if( Item_IsEditField( item ) ) + { + char buffer[ MAX_STRING_CHARS ] = { 0 }; + + if( item->cvar ) + DC->getCVarString( item->cvar, buffer, sizeof( buffer ) ); + + item->cursorPos = strlen( buffer ); + + Item_TextField_CalcPaintOffset( item, buffer ); + + g_editingField = qtrue; + + g_editItem = item; + } + else + Item_Action( item ); + } + + break; + } + + inHandler = qfalse; +} + +void ToWindowCoords( float *x, float *y, windowDef_t *window ) +{ + if( window->border != 0 ) + { + *x += window->borderSize; + *y += window->borderSize; + } + + *x += window->rect.x; + *y += window->rect.y; +} + +void Rect_ToWindowCoords( rectDef_t *rect, windowDef_t *window ) +{ + ToWindowCoords( &rect->x, &rect->y, window ); +} + +void Item_SetTextExtents( itemDef_t *item, const char *text ) +{ + const char *textPtr = ( text ) ? text : item->text; + qboolean cvarContent; + + // It's hard to make a policy on what should be aligned statically and what + // should be aligned dynamically; there are reasonable cases for both. If + // it continues to be a problem then there should probably be an item keyword + // for it; but for the moment only adjusting the alignment of ITEM_TYPE_TEXT + // seems to suffice. + cvarContent = ( item->cvar && item->textalignment != ALIGN_LEFT && + item->type == ITEM_TYPE_TEXT ); + + if( textPtr == NULL ) + return; + + // as long as the item isn't dynamic content (ownerdraw or cvar), this + // keeps us from computing the widths and heights more than once + if( item->textRect.w == 0.0f || cvarContent || ( item->type == ITEM_TYPE_OWNERDRAW && + item->textalignment != ALIGN_LEFT ) ) + { + float originalWidth = 0.0f; + + if( item->textalignment == ALIGN_CENTER || item->textalignment == ALIGN_RIGHT ) + { + if( cvarContent ) + { + char buff[ MAX_CVAR_VALUE_STRING ]; + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + originalWidth = UI_Text_Width( item->text, item->textscale ) + + UI_Text_Width( buff, item->textscale ); + } + else + originalWidth = UI_Text_Width( item->text, item->textscale ); + } + + item->textRect.w = UI_Text_Width( textPtr, item->textscale ); + item->textRect.h = UI_Text_Height( textPtr, item->textscale ); + + if( item->textvalignment == VALIGN_BOTTOM ) + item->textRect.y = item->textaligny + item->window.rect.h; + else if( item->textvalignment == VALIGN_CENTER ) + item->textRect.y = item->textaligny + ( ( item->textRect.h + item->window.rect.h ) / 2.0f ); + else if( item->textvalignment == VALIGN_TOP ) + item->textRect.y = item->textaligny + item->textRect.h; + + if( item->textalignment == ALIGN_LEFT ) + item->textRect.x = item->textalignx; + else if( item->textalignment == ALIGN_CENTER ) + item->textRect.x = item->textalignx + ( ( item->window.rect.w - originalWidth ) / 2.0f ); + else if( item->textalignment == ALIGN_RIGHT ) + item->textRect.x = item->textalignx + item->window.rect.w - originalWidth; + + ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window ); + } +} + +void Item_TextColor( itemDef_t *item, vec4_t *newColor ) +{ + vec4_t lowLight; + menuDef_t *parent = ( menuDef_t* )item->parent; + + Fade( &item->window.flags, &item->window.foreColor[3], parent->fadeClamp, + &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount ); + + if( item->window.flags & WINDOW_HASFOCUS ) + memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) ); + else if( item->textStyle == ITEM_TEXTSTYLE_BLINK && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) ) + { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor( item->window.foreColor, lowLight, *newColor, 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } + else + { + memcpy( newColor, &item->window.foreColor, sizeof( vec4_t ) ); + // items can be enabled and disabled based on cvars + } + + if( item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest ) + { + if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) + memcpy( newColor, &parent->disableColor, sizeof( vec4_t ) ); + } +} + +static void SkipColorCodes( const char **text, char *lastColor ) +{ + while( Q_IsColorString( *text ) ) + { + lastColor[ 0 ] = (*text)[ 0 ]; + lastColor[ 1 ] = (*text)[ 1 ]; + (*text) += 2; + } +} + +static void SkipWhiteSpace( const char **text, char *lastColor ) +{ + while( **text ) + { + SkipColorCodes( text, lastColor ); + + if( **text != '\n' && isspace( **text ) ) + (*text)++; + else + break; + } +} + +const char *Item_Text_Wrap( const char *text, float scale, float width ) +{ + static char out[ 8192 ] = ""; + char *paint = out; + char c[ 3 ] = ""; + const char *p; + const char *eos; + float indentWidth = 0.0f; + + if( !text ) + return NULL; + + p = text; + eos = p + strlen( p ); + + if( ( eos - p ) >= sizeof( out ) ) + return NULL; + + *paint = '\0'; + + while( *p ) + { + float textWidth = 0.0f; + const char *eol = p; + const char *q = p; + float testWidth = width - indentWidth; + + SkipColorCodes( &q, c ); + + while( q && textWidth < testWidth ) + { + qboolean previousCharIsSpace = qfalse; + + // Remaining string is too short to wrap + if( q >= eos ) + { + eol = eos; + break; + } + + if( q > p && *q == INDENT_MARKER ) + { + indentWidth = textWidth; + eol = p; + } + + // Some color escapes might still be present + SkipColorCodes( &q, c ); + + // Manual line break + if( *q == '\n' ) + { + eol = q + 1; + break; + } + + if( !previousCharIsSpace && isspace( *q ) ) + eol = q; + + textWidth += UI_Char_Width( &q, scale ); + } + + // No split has taken place, so just split mid-word + if( eol == p ) + eol = q; + + // Note that usage of strcat and strlen is deliberately being + // avoided here as it becomes surprisingly expensive on larger + // blocks of text + + // Copy text + strncpy( paint, p, eol - p ); + paint += ( eol - p ); + *paint = '\0'; + + p = eol; + + if( paint - out > 0 && *( paint - 1 ) == '\n' ) + { + // The line is deliberately broken, clear the color and + // any current indent + c[ 0 ] = '\0'; + indentWidth = 0.0f; + } + else + { + // Add a \n if it's not there already + *paint++ = '\n'; + *paint = '\0'; + + // Insert a pixel indent on the next line + if( indentWidth > 0.0f ) + { + char *indentMarkerText = va( "%f%c", indentWidth, INDENT_MARKER ); + int indentMarkerTextLength = strlen( indentMarkerText ); + + strncpy( paint, indentMarkerText, indentMarkerTextLength ); + paint += indentMarkerTextLength; + *paint = '\0'; + } + + // Skip leading whitespace on next line and save the + // last color code + SkipWhiteSpace( &p, c ); + } + + if( c[ 0 ] ) + { + *paint++ = c[ 0 ]; + *paint++ = c[ 1 ]; + *paint = '\0'; + } + } + + return out; +} + +#define MAX_WRAP_CACHE 16 +#define MAX_WRAP_LINES 32 +#define MAX_WRAP_TEXT 512 + +typedef struct +{ + char text[ MAX_WRAP_TEXT * MAX_WRAP_LINES ]; //FIXME: augment with hash? + rectDef_t rect; + float scale; + char lines[ MAX_WRAP_LINES ][ MAX_WRAP_TEXT ]; + float lineCoords[ MAX_WRAP_LINES ][ 2 ]; + int numLines; +} + +wrapCache_t; + +static wrapCache_t wrapCache[ MAX_WRAP_CACHE ]; +static int cacheWriteIndex = 0; +static int cacheReadIndex = 0; +static int cacheReadLineNum = 0; + +static void UI_CreateCacheEntry( const char *text, rectDef_t *rect, float scale ) +{ + wrapCache_t *cacheEntry = &wrapCache[ cacheWriteIndex ]; + + Q_strncpyz( cacheEntry->text, text, sizeof( cacheEntry->text ) ); + cacheEntry->rect.x = rect->x; + cacheEntry->rect.y = rect->y; + cacheEntry->rect.w = rect->w; + cacheEntry->rect.h = rect->h; + cacheEntry->scale = scale; + cacheEntry->numLines = 0; +} + +static void UI_AddCacheEntryLine( const char *text, float x, float y ) +{ + wrapCache_t *cacheEntry = &wrapCache[ cacheWriteIndex ]; + + if( cacheEntry->numLines >= MAX_WRAP_LINES ) + return; + + Q_strncpyz( cacheEntry->lines[ cacheEntry->numLines ], text, + sizeof( cacheEntry->lines[ 0 ] ) ); + + cacheEntry->lineCoords[ cacheEntry->numLines ][ 0 ] = x; + + cacheEntry->lineCoords[ cacheEntry->numLines ][ 1 ] = y; + + cacheEntry->numLines++; +} + +static void UI_FinishCacheEntry( void ) +{ + cacheWriteIndex = ( cacheWriteIndex + 1 ) % MAX_WRAP_CACHE; +} + +static qboolean UI_CheckWrapCache( const char *text, rectDef_t *rect, float scale ) +{ + int i; + + for( i = 0; i < MAX_WRAP_CACHE; i++ ) + { + wrapCache_t *cacheEntry = &wrapCache[ i ]; + + if( strcmp( text, cacheEntry->text ) ) + continue; + + if( rect->x != cacheEntry->rect.x || + rect->y != cacheEntry->rect.y || + rect->w != cacheEntry->rect.w || + rect->h != cacheEntry->rect.h ) + continue; + + if( cacheEntry->scale != scale ) + continue; + + // This is a match + cacheReadIndex = i; + + cacheReadLineNum = 0; + + return qtrue; + } + + // No match - wrap isn't cached + return qfalse; +} + +static qboolean UI_NextWrapLine( const char **text, float *x, float *y ) +{ + wrapCache_t *cacheEntry = &wrapCache[ cacheReadIndex ]; + + if( cacheReadLineNum >= cacheEntry->numLines ) + return qfalse; + + *text = cacheEntry->lines[ cacheReadLineNum ]; + + *x = cacheEntry->lineCoords[ cacheReadLineNum ][ 0 ]; + + *y = cacheEntry->lineCoords[ cacheReadLineNum ][ 1 ]; + + cacheReadLineNum++; + + return qtrue; +} + +void Item_Text_Wrapped_Paint( itemDef_t *item ) +{ + char text[ 1024 ]; + const char *p, *textPtr; + float x, y, w, h; + vec4_t color; + qboolean useWrapCache = (qboolean)DC->getCVarValue( "ui_textWrapCache" ); + + if( item->text == NULL ) + { + if( item->cvar == NULL ) + return; + else + { + DC->getCVarString( item->cvar, text, sizeof( text ) ); + textPtr = text; + } + } + else + textPtr = item->text; + + if( *textPtr == '\0' ) + return; + + Item_TextColor( item, &color ); + + // Check if this block is cached + if( useWrapCache && UI_CheckWrapCache( textPtr, &item->window.rect, item->textscale ) ) + { + while( UI_NextWrapLine( &p, &x, &y ) ) + { + UI_Text_Paint( x, y, item->textscale, color, + p, 0, 0, item->textStyle ); + } + } + else + { + char buff[ 1024 ]; + float fontHeight = UI_Text_EmHeight( item->textscale ); + const float lineSpacing = fontHeight * 0.4f; + float lineHeight = fontHeight + lineSpacing; + float textHeight; + int textLength; + int paintLines, totalLines, lineNum = 0; + float paintY; + int i; + + if( useWrapCache ) + UI_CreateCacheEntry( textPtr, &item->window.rect, item->textscale ); + + x = item->window.rect.x + item->textalignx; + y = item->window.rect.y + item->textaligny; + w = item->window.rect.w - ( 2.0f * item->textalignx ); + h = item->window.rect.h - ( 2.0f * item->textaligny ); + + textPtr = Item_Text_Wrap( textPtr, item->textscale, w ); + textLength = strlen( textPtr ); + + // Count lines + totalLines = 0; + + for( i = 0; i < textLength; i++ ) + { + if( textPtr[ i ] == '\n' ) + totalLines++; + } + + paintLines = ( int )floor( ( h + lineSpacing ) / lineHeight ); + + if( paintLines > totalLines ) + paintLines = totalLines; + + textHeight = ( paintLines * lineHeight ) - lineSpacing; + + switch( item->textvalignment ) + { + default: + + case VALIGN_BOTTOM: + paintY = y + ( h - textHeight ); + break; + + case VALIGN_CENTER: + paintY = y + ( ( h - textHeight ) / 2.0f ); + break; + + case VALIGN_TOP: + paintY = y; + break; + } + + p = textPtr; + + for( i = 0, lineNum = 0; i < textLength && lineNum < paintLines; i++ ) + { + int lineLength = &textPtr[ i ] - p; + + if( lineLength >= sizeof( buff ) - 1 ) + break; + + if( textPtr[ i ] == '\n' || textPtr[ i ] == '\0' ) + { + itemDef_t lineItem; + + memset( &lineItem, 0, sizeof( itemDef_t ) ); + strncpy( buff, p, lineLength ); + buff[ lineLength ] = '\0'; + p = &textPtr[ i + 1 ]; + + lineItem.type = ITEM_TYPE_TEXT; + lineItem.textscale = item->textscale; + lineItem.textStyle = item->textStyle; + lineItem.text = buff; + lineItem.textalignment = item->textalignment; + lineItem.textvalignment = VALIGN_TOP; + lineItem.textalignx = 0.0f; + lineItem.textaligny = 0.0f; + + lineItem.textRect.w = 0.0f; + lineItem.textRect.h = 0.0f; + lineItem.window.rect.x = x; + lineItem.window.rect.y = paintY + ( lineNum * lineHeight ); + lineItem.window.rect.w = w; + lineItem.window.rect.h = lineHeight; + lineItem.window.border = item->window.border; + lineItem.window.borderSize = item->window.borderSize; + + if( DC->getCVarValue( "ui_developer" ) ) + { + vec4_t color; + color[ 0 ] = color[ 2 ] = color[ 3 ] = 1.0f; + color[ 1 ] = 0.0f; + DC->drawRect( lineItem.window.rect.x, lineItem.window.rect.y, + lineItem.window.rect.w, lineItem.window.rect.h, 1, color ); + } + + Item_SetTextExtents( &lineItem, buff ); + UI_Text_Paint( lineItem.textRect.x, lineItem.textRect.y, + lineItem.textscale, color, buff, 0, 0, + lineItem.textStyle ); + + if( useWrapCache ) + UI_AddCacheEntryLine( buff, lineItem.textRect.x, lineItem.textRect.y ); + + lineNum++; + } + } + + if( useWrapCache ) + UI_FinishCacheEntry( ); + } +} + +/* +============== +UI_DrawTextBlock +============== +*/ +void UI_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color, + float scale, int textalign, int textvalign, + int textStyle, const char *text ) +{ + static menuDef_t dummyParent; + static itemDef_t textItem; + + textItem.text = text; + + textItem.parent = &dummyParent; + memcpy( textItem.window.foreColor, color, sizeof( vec4_t ) ); + textItem.window.flags = 0; + + textItem.window.rect.x = rect->x; + textItem.window.rect.y = rect->y; + textItem.window.rect.w = rect->w; + textItem.window.rect.h = rect->h; + textItem.window.border = 0; + textItem.window.borderSize = 0.0f; + textItem.textRect.x = 0.0f; + textItem.textRect.y = 0.0f; + textItem.textRect.w = 0.0f; + textItem.textRect.h = 0.0f; + textItem.textalignment = textalign; + textItem.textvalignment = textvalign; + textItem.textalignx = text_x; + textItem.textaligny = text_y; + textItem.textscale = scale; + textItem.textStyle = textStyle; + + // Utilise existing wrap code + Item_Text_Wrapped_Paint( &textItem ); +} + +void Item_Text_Paint( itemDef_t *item ) +{ + char text[1024]; + const char *textPtr; + vec4_t color; + + if( item->window.flags & WINDOW_WRAPPED ) + { + Item_Text_Wrapped_Paint( item ); + return; + } + + if( item->text == NULL ) + { + if( item->cvar == NULL ) + return; + else + { + DC->getCVarString( item->cvar, text, sizeof( text ) ); + textPtr = text; + } + } + else + textPtr = item->text; + + // this needs to go here as it sets extents for cvar types as well + Item_SetTextExtents( item, textPtr ); + + if( *textPtr == '\0' ) + return; + + Item_TextColor( item, &color ); + + UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle ); +} + + + +void Item_TextField_Paint( itemDef_t *item ) +{ + char buff[1024]; + vec4_t newColor; + int offset = ( item->text && *item->text ) ? ITEM_VALUE_OFFSET : 0; + menuDef_t *parent = ( menuDef_t* )item->parent; + editFieldDef_t *editPtr = item->typeData.edit; + char cursor = DC->getOverstrikeMode() ? '|' : '_'; + qboolean editing = ( item->window.flags & WINDOW_HASFOCUS && g_editingField ); + const int cursorWidth = editing ? EDIT_CURSOR_WIDTH : 0; + + //FIXME: causes duplicate printing if item->text is not set (NULL) + Item_Text_Paint( item ); + + buff[0] = '\0'; + + if( item->cvar ) + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + + // maxFieldWidth hasn't been set, so use the item's rect + if( editPtr->maxFieldWidth == 0 ) + { + editPtr->maxFieldWidth = item->window.rect.w - + ( item->textRect.w + offset + ( item->textRect.x - item->window.rect.x ) ); + + if( editPtr->maxFieldWidth < MIN_FIELD_WIDTH ) + editPtr->maxFieldWidth = MIN_FIELD_WIDTH; + } + + if( !editing ) + editPtr->paintOffset = 0; + + // Shorten string to max viewable + while( UI_Text_Width( buff + editPtr->paintOffset, item->textscale ) > + ( editPtr->maxFieldWidth - cursorWidth ) && strlen( buff ) > 0 ) + buff[ strlen( buff ) - 1 ] = '\0'; + + parent = ( menuDef_t* )item->parent; + + if( item->window.flags & WINDOW_HASFOCUS ) + memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) ); + else + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + + if( editing ) + { + UI_Text_PaintWithCursor( item->textRect.x + item->textRect.w + offset, + item->textRect.y, item->textscale, newColor, + buff + editPtr->paintOffset, + item->cursorPos - editPtr->paintOffset, + cursor, editPtr->maxPaintChars, item->textStyle ); + } + else + { + UI_Text_Paint( item->textRect.x + item->textRect.w + offset, + item->textRect.y, item->textscale, newColor, + buff + editPtr->paintOffset, 0, + editPtr->maxPaintChars, item->textStyle ); + } + +} + +void Item_YesNo_Paint( itemDef_t *item ) +{ + vec4_t newColor; + float value; + int offset; + menuDef_t *parent = ( menuDef_t* )item->parent; + + value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0; + + if( item->window.flags & WINDOW_HASFOCUS ) + memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) ); + else + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + + offset = ( item->text && *item->text ) ? ITEM_VALUE_OFFSET : 0; + + if( item->text ) + { + Item_Text_Paint( item ); + UI_Text_Paint( item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, + newColor, ( value != 0 ) ? "Yes" : "No", 0, 0, item->textStyle ); + } + else + UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, ( value != 0 ) ? "Yes" : "No", 0, 0, item->textStyle ); +} + +void Item_Multi_Paint( itemDef_t *item ) +{ + vec4_t newColor; + const char *text = ""; + menuDef_t *parent = ( menuDef_t* )item->parent; + + if( item->window.flags & WINDOW_HASFOCUS ) + memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) ); + else + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + + text = Item_Multi_Setting( item ); + + if( item->text ) + { + Item_Text_Paint( item ); + UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, + item->textscale, newColor, text, 0, 0, item->textStyle ); + } + else + UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle ); +} + +void Item_Cycle_Paint( itemDef_t *item ) +{ + vec4_t newColor; + const char *text = ""; + menuDef_t *parent = (menuDef_t *)item->parent; + + if( item->window.flags & WINDOW_HASFOCUS ) + memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) ); + else + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + + if( item->typeData.cycle ) + text = DC->feederItemText( item->feederID, item->typeData.cycle->cursorPos, + 0, NULL ); + + if( item->text ) + { + Item_Text_Paint( item ); + UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, + item->textscale, newColor, text, 0, 0, item->textStyle ); + } + else + UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle ); +} + + +typedef struct +{ + char *command; + int id; + int defaultbind1; + int defaultbind2; + int bind1; + int bind2; +} +bind_t; + +typedef struct +{ + char *name; + float defaultvalue; + float value; +} +configcvar_t; + + +static bind_t g_bindings[] = + { + { "+scores", K_TAB, -1, -1, -1 }, + { "+button2", K_ENTER, -1, -1, -1 }, + { "+speed", K_SHIFT, -1, -1, -1 }, + { "+button6", 'z', -1, -1, -1 }, // human dodging + { "+button8", 'x', -1, -1, -1 }, + { "+forward", K_UPARROW, -1, -1, -1 }, + { "+back", K_DOWNARROW, -1, -1, -1 }, + { "+moveleft", ',', -1, -1, -1 }, + { "+moveright", '.', -1, -1, -1 }, + { "+moveup", K_SPACE, -1, -1, -1 }, + { "+movedown", 'c', -1, -1, -1 }, + { "+left", K_LEFTARROW, -1, -1, -1 }, + { "+right", K_RIGHTARROW, -1, -1, -1 }, + { "+strafe", K_ALT, -1, -1, -1 }, + { "+lookup", K_PGDN, -1, -1, -1 }, + { "+lookdown", K_DEL, -1, -1, -1 }, + { "+mlook", '/', -1, -1, -1 }, + { "centerview", K_END, -1, -1, -1 }, + { "+zoom", -1, -1, -1, -1 }, + { "weapon 1", '1', -1, -1, -1 }, + { "weapon 2", '2', -1, -1, -1 }, + { "weapon 3", '3', -1, -1, -1 }, + { "weapon 4", '4', -1, -1, -1 }, + { "weapon 5", '5', -1, -1, -1 }, + { "weapon 6", '6', -1, -1, -1 }, + { "weapon 7", '7', -1, -1, -1 }, + { "weapon 8", '8', -1, -1, -1 }, + { "weapon 9", '9', -1, -1, -1 }, + { "weapon 10", '0', -1, -1, -1 }, + { "weapon 11", -1, -1, -1, -1 }, + { "weapon 12", -1, -1, -1, -1 }, + { "weapon 13", -1, -1, -1, -1 }, + { "+attack", K_MOUSE1, -1, -1, -1 }, + { "+button5", K_MOUSE2, -1, -1, -1 }, // secondary attack + { "reload", 'r', -1, -1, -1 }, // reload + { "buy ammo", 'b', -1, -1, -1 }, // buy ammo + { "itemact medkit", 'm', -1, -1, -1 }, // use medkit + { "+button7", 'q', -1, -1, -1 }, // buildable use + { "deconstruct", 'e', -1, -1, -1 }, // buildable destroy + { "weapprev", '[', -1, -1, -1 }, + { "weapnext", ']', -1, -1, -1 }, + { "+button3", K_MOUSE3, -1, -1, -1 }, + { "+button4", K_MOUSE4, -1, -1, -1 }, + { "vote yes", K_F1, -1, -1, -1 }, + { "vote no", K_F2, -1, -1, -1 }, + { "teamvote yes", K_F3, -1, -1, -1 }, + { "teamvote no", K_F4, -1, -1, -1 }, + { "scoresUp", K_KP_PGUP, -1, -1, -1 }, + { "scoresDown", K_KP_PGDN, -1, -1, -1 }, + { "screenshotJPEG",-1, -1, -1, -1 }, + { "messagemode", -1, -1, -1, -1 }, + { "messagemode2", -1, -1, -1, -1 }, + { "cuboidAxis next", 'h', -1, -1, -1 }, + { "cuboidRotate", 'j', -1, -1, -1 } + }; + + +static const int g_bindCount = sizeof( g_bindings ) / sizeof( bind_t ); + +/* +================= +Controls_GetKeyAssignment +================= +*/ +static void Controls_GetKeyAssignment ( char *command, int *twokeys ) +{ + int count; + int j; + char b[256]; + + twokeys[0] = twokeys[1] = -1; + count = 0; + + for( j = 0; j < 256; j++ ) + { + DC->getBindingBuf( j, b, 256 ); + + if( *b == 0 ) + continue; + + if( !Q_stricmp( b, command ) ) + { + twokeys[count] = j; + count++; + + if( count == 2 ) + break; + } + } +} + +/* +================= +Controls_GetConfig +================= +*/ +void Controls_GetConfig( void ) +{ + int i; + int twokeys[ 2 ]; + + // iterate each command, get its numeric binding + + for( i = 0; i < g_bindCount; i++ ) + { + Controls_GetKeyAssignment( g_bindings[ i ].command, twokeys ); + + g_bindings[ i ].bind1 = twokeys[ 0 ]; + g_bindings[ i ].bind2 = twokeys[ 1 ]; + } +} + +/* +================= +Controls_SetConfig +================= +*/ +void Controls_SetConfig( qboolean restart ) +{ + int i; + + // iterate each command, get its numeric binding + + for( i = 0; i < g_bindCount; i++ ) + { + if( g_bindings[i].bind1 != -1 ) + { + DC->setBinding( g_bindings[i].bind1, g_bindings[i].command ); + + if( g_bindings[i].bind2 != -1 ) + DC->setBinding( g_bindings[i].bind2, g_bindings[i].command ); + } + } + + DC->executeText( EXEC_APPEND, "in_restart\n" ); +} + +/* +================= +Controls_SetDefaults +================= +*/ +void Controls_SetDefaults( void ) +{ + int i; + + // iterate each command, set its default binding + + for( i = 0; i < g_bindCount; i++ ) + { + g_bindings[i].bind1 = g_bindings[i].defaultbind1; + g_bindings[i].bind2 = g_bindings[i].defaultbind2; + } +} + +int BindingIDFromName( const char *name ) +{ + int i; + + for( i = 0; i < g_bindCount; i++ ) + { + if( Q_stricmp( name, g_bindings[i].command ) == 0 ) + return i; + } + + return -1; +} + +char g_nameBind1[32]; +char g_nameBind2[32]; + +void BindingFromName( const char *cvar ) +{ + int i, b1, b2; + + // iterate each command, set its default binding + + for( i = 0; i < g_bindCount; i++ ) + { + if( Q_stricmp( cvar, g_bindings[i].command ) == 0 ) + { + b1 = g_bindings[i].bind1; + + if( b1 == -1 ) + break; + + DC->keynumToStringBuf( b1, g_nameBind1, 32 ); + Q_strupr( g_nameBind1 ); + + b2 = g_bindings[i].bind2; + + if( b2 != -1 ) + { + DC->keynumToStringBuf( b2, g_nameBind2, 32 ); + Q_strupr( g_nameBind2 ); + strcat( g_nameBind1, " or " ); + strcat( g_nameBind1, g_nameBind2 ); + } + + return; + } + } + + strcpy( g_nameBind1, "???" ); +} + +void Item_Slider_Paint( itemDef_t *item ) +{ + vec4_t newColor; + float x, y, value; + menuDef_t *parent = ( menuDef_t* )item->parent; + float vScale = Item_Slider_VScale( item ); + + value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0; + + if( item->window.flags & WINDOW_HASFOCUS ) + memcpy( newColor, &parent->focusColor, sizeof( vec4_t ) ); + else + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + + if( item->text ) + { + Item_Text_Paint( item ); + x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET; + y = item->textRect.y - item->textRect.h + + ( ( item->textRect.h - ( SLIDER_HEIGHT * vScale ) ) / 2.0f ); + } + else + { + x = item->window.rect.x; + y = item->window.rect.y; + } + + DC->setColor( newColor ); + DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT * vScale, DC->Assets.sliderBar ); + + y = item->textRect.y - item->textRect.h + + ( ( item->textRect.h - ( SLIDER_THUMB_HEIGHT * vScale ) ) / 2.0f ); + + x = Item_Slider_ThumbPosition( item ); + DC->drawHandlePic( x - ( SLIDER_THUMB_WIDTH / 2 ), y, + SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT * vScale, DC->Assets.sliderThumb ); + +} + +void Item_Bind_Paint( itemDef_t *item ) +{ + vec4_t newColor, lowLight; + float value; + int maxChars = 0; + menuDef_t *parent = ( menuDef_t* )item->parent; + + if( item->typeData.edit ) + maxChars = item->typeData.edit->maxPaintChars; + + value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0; + + if( item->window.flags & WINDOW_HASFOCUS ) + { + if( g_bindItem == item ) + { + lowLight[0] = 0.8f * parent->focusColor[0]; + lowLight[1] = 0.8f * parent->focusColor[1]; + lowLight[2] = 0.8f * parent->focusColor[2]; + lowLight[3] = 0.8f * parent->focusColor[3]; + + LerpColor( parent->focusColor, lowLight, newColor, + 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } + else + memcpy( &newColor, &parent->focusColor, sizeof( vec4_t ) ); + } + else + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + + if( item->text ) + { + Item_Text_Paint( item ); + + if( g_bindItem == item && g_waitingForKey ) + { + UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, + item->textscale, newColor, "Press key", 0, maxChars, item->textStyle ); + } + else + { + BindingFromName( item->cvar ); + UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, + item->textscale, newColor, g_nameBind1, 0, maxChars, item->textStyle ); + } + } + else + UI_Text_Paint( item->textRect.x, item->textRect.y, item->textscale, newColor, + ( value != 0 ) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle ); +} + +qboolean Display_KeyBindPending( void ) +{ + return g_waitingForKey; +} + +qboolean Item_Bind_HandleKey( itemDef_t *item, int key, qboolean down ) +{ + int id; + int i; + + if( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && !g_waitingForKey ) + { + if( down && ( key == K_MOUSE1 || key == K_ENTER ) ) + { + g_waitingForKey = qtrue; + g_bindItem = item; + } + + return qtrue; + } + else + { + if( !g_waitingForKey || g_bindItem == NULL ) + return qtrue; + + if( key & K_CHAR_FLAG ) + return qtrue; + + switch( key ) + { + case K_ESCAPE: + g_waitingForKey = qfalse; + return qtrue; + + case K_BACKSPACE: + id = BindingIDFromName( item->cvar ); + + if( id != -1 ) + { + g_bindings[id].bind1 = -1; + g_bindings[id].bind2 = -1; + } + + Controls_SetConfig( qtrue ); + g_waitingForKey = qfalse; + g_bindItem = NULL; + return qtrue; + + case '`': + return qtrue; + } + } + + if( key != -1 ) + { + for( i = 0; i < g_bindCount; i++ ) + { + if( g_bindings[i].bind2 == key ) + g_bindings[i].bind2 = -1; + + if( g_bindings[i].bind1 == key ) + { + g_bindings[i].bind1 = g_bindings[i].bind2; + g_bindings[i].bind2 = -1; + } + } + } + + + id = BindingIDFromName( item->cvar ); + + if( id != -1 ) + { + if( key == -1 ) + { + if( g_bindings[id].bind1 != -1 ) + { + DC->setBinding( g_bindings[id].bind1, "" ); + g_bindings[id].bind1 = -1; + } + + if( g_bindings[id].bind2 != -1 ) + { + DC->setBinding( g_bindings[id].bind2, "" ); + g_bindings[id].bind2 = -1; + } + } + else if( g_bindings[id].bind1 == -1 ) + g_bindings[id].bind1 = key; + else if( g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1 ) + g_bindings[id].bind2 = key; + else + { + DC->setBinding( g_bindings[id].bind1, "" ); + DC->setBinding( g_bindings[id].bind2, "" ); + g_bindings[id].bind1 = key; + g_bindings[id].bind2 = -1; + } + } + + Controls_SetConfig( qtrue ); + g_waitingForKey = qfalse; + + return qtrue; +} + + +void Item_Model_Paint( itemDef_t *item ) +{ + float x, y, w, h; + refdef_t refdef; + refEntity_t ent; + vec3_t mins, maxs, origin; + vec3_t angles; + modelDef_t *modelPtr = item->typeData.model; + + if( modelPtr == NULL ) + return; + + // setup the refdef + memset( &refdef, 0, sizeof( refdef ) ); + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + w = item->window.rect.w - 2; + h = item->window.rect.h - 2; + + UI_AdjustFrom640( &x, &y, &w, &h ); + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + DC->modelBounds( item->asset, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the model nearly fills the box + if( qtrue ) + { + float len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + //origin[0] = len / tan(w/2); + } + else + origin[0] = item->textscale; + + refdef.fov_x = ( modelPtr->fov_x ) ? modelPtr->fov_x : w; + refdef.fov_y = ( modelPtr->fov_y ) ? modelPtr->fov_y : h; + + //refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f); + //xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); + //refdef.fov_y = atan2( refdef.height, xx ); + //refdef.fov_y *= ( 360 / M_PI ); + + DC->clearScene(); + + refdef.time = DC->realTime; + + // add the model + + memset( &ent, 0, sizeof( ent ) ); + + //adjust = 5.0 * sin( (float)uis.realtime / 500 ); + //adjust = 360 % (int)((float)uis.realtime / 1000); + //VectorSet( angles, 0, 0, 1 ); + + // use item storage to track + + if( modelPtr->rotationSpeed ) + { + if( DC->realTime > item->window.nextTime ) + { + item->window.nextTime = DC->realTime + modelPtr->rotationSpeed; + modelPtr->angle = ( int )( modelPtr->angle + 1 ) % 360; + } + } + + VectorSet( angles, 0, modelPtr->angle, 0 ); + AnglesToAxis( angles, ent.axis ); + + ent.hModel = item->asset; + VectorCopy( origin, ent.origin ); + VectorCopy( origin, ent.lightingOrigin ); + ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; + VectorCopy( ent.origin, ent.oldorigin ); + + DC->addRefEntityToScene( &ent ); + DC->renderScene( &refdef ); + +} + + +void Item_ListBoxRow_Paint( itemDef_t *item, int row, int renderPos, qboolean highlight, qboolean scrollbar ) +{ + float x, y, w; + listBoxDef_t *listPtr = item->typeData.list; + menuDef_t *menu = ( menuDef_t * )item->parent; + float one, two; + + one = 1.0f * DC->aspectScale; + two = 2.0f * DC->aspectScale; + + x = SCROLLBAR_X( item ); + y = SCROLLBAR_Y( item ) + ( listPtr->elementHeight * renderPos ); + w = item->window.rect.w - ( two * item->window.borderSize ); + + if( scrollbar ) + w -= SCROLLBAR_ARROW_WIDTH; + + if( listPtr->elementStyle == LISTBOX_IMAGE ) + { + qhandle_t image = DC->feederItemImage( item->feederID, row ); + + UI_SetClipRegion( x, y, listPtr->elementWidth, listPtr->elementHeight ); + + if( image ) + DC->drawHandlePic( x + one, y + 1.0f, listPtr->elementWidth - two, listPtr->elementHeight - 2.0f, image ); + + if( highlight && row == item->cursorPos ) + { + DC->drawRect( x, y, listPtr->elementWidth, listPtr->elementHeight, + item->window.borderSize, item->window.borderColor ); + } + + UI_ClearClipRegion( ); + } + else + { + const float m = UI_Text_EmHeight( item->textscale ); + char text[ MAX_STRING_CHARS ]; + qhandle_t optionalImage; + + if( listPtr->numColumns > 0 ) + { + int j; + + for( j = 0; j < listPtr->numColumns; j++ ) + { + float columnPos; + float width, height, yOffset; + + if( menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE ) + { + columnPos = ( listPtr->columnInfo[ j ].pos + 4.0f ) * DC->aspectScale; + width = listPtr->columnInfo[ j ].width * DC->aspectScale; + } + else + { + columnPos = ( listPtr->columnInfo[ j ].pos + 4.0f ); + width = listPtr->columnInfo[ j ].width; + } + + height = listPtr->columnInfo[ j ].width; + yOffset = y + ( ( listPtr->elementHeight - height ) / 2.0f ); + + Q_strncpyz( text, DC->feederItemText( item->feederID, row, j, &optionalImage ), sizeof( text ) ); + + UI_SetClipRegion( x + columnPos, yOffset, width, height ); + + if( optionalImage >= 0 ) + DC->drawHandlePic( x + columnPos, yOffset, width, height, optionalImage ); + else if( text[ 0 ] ) + { + float alignOffset = 0.0f, tw; + + tw = UI_Text_Width( text, item->textscale ); + + switch( listPtr->columnInfo[ j ].align ) + { + case ALIGN_LEFT: + alignOffset = 0.0f; + break; + + case ALIGN_RIGHT: + alignOffset = width - tw; + break; + + case ALIGN_CENTER: + alignOffset = ( width / 2.0f ) - ( tw / 2.0f ); + break; + + default: + alignOffset = 0.0f; + } + + UI_Text_Paint( x + columnPos + alignOffset, + y + m + ( ( listPtr->elementHeight - m ) / 2.0f ), + item->textscale, item->window.foreColor, text, 0, + 0, item->textStyle ); + } + + UI_ClearClipRegion( ); + } + } + else + { + float offset; + + if( menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE ) + offset = 4.0f * DC->aspectScale; + else + offset = 4.0f; + + Q_strncpyz( text, DC->feederItemText( item->feederID, row, 0, &optionalImage ), sizeof( text ) ); + + UI_SetClipRegion( x, y, w, listPtr->elementHeight ); + + if( optionalImage >= 0 ) + DC->drawHandlePic( x + offset, y, listPtr->elementHeight, listPtr->elementHeight, optionalImage ); + else if( text[ 0 ] ) + { + UI_Text_Paint( x + offset, y + m + ( ( listPtr->elementHeight - m ) / 2.0f ), + item->textscale, item->window.foreColor, text, 0, + 0, item->textStyle ); + } + + UI_ClearClipRegion( ); + } + + if( highlight && row == item->cursorPos ) + DC->fillRect( x, y, w, listPtr->elementHeight, item->window.outlineColor ); + } +} + +void Item_ListBox_Paint( itemDef_t *item ) +{ + float size; + int i; + listBoxDef_t *listPtr = item->typeData.list; + int count = DC->feederCount( item->feederID ); + qboolean scrollbar = !listPtr->noscrollbar && + count > Item_ListBox_NumItemsForItemHeight( item ); + + if( scrollbar ) + { + float x = SCROLLBAR_SLIDER_X( item ); + float y = SCROLLBAR_Y( item ); + float thumbY = Item_ListBox_ThumbDrawPosition( item ); + + // Up arrow + DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowUp ); + y = SCROLLBAR_SLIDER_Y( item ); + + // Scroll bar + size = SCROLLBAR_SLIDER_HEIGHT( item ); + DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, size, DC->Assets.scrollBar ); + y = SCROLLBAR_SLIDER_Y( item ) + size; + + // Down arrow + DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowDown ); + + // Thumb + DC->drawHandlePic( x, thumbY, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarThumb ); + } + + // Paint rows + for( i = listPtr->startPos; i < listPtr->endPos; i++ ) + Item_ListBoxRow_Paint( item, i, i - listPtr->startPos, qtrue, scrollbar ); +} + +void Item_Paint( itemDef_t *item ); + +void Item_ComboBox_Paint( itemDef_t *item ) +{ + float x, y, h; + + x = SCROLLBAR_SLIDER_X( item ); + y = SCROLLBAR_Y( item ); + h = item->window.rect.h - 2.0f; + + // Down arrow + DC->drawHandlePic( x, y, SCROLLBAR_ARROW_WIDTH, h, DC->Assets.scrollBarArrowDown ); + + Item_ListBoxRow_Paint( item, item->cursorPos, 0, qfalse, qtrue ); + + if( g_comboBoxItem != NULL ) + { + qboolean cast = Item_ComboBox_MaybeCastToListBox( item ); + Item_Paint( item ); + Item_ComboBox_MaybeUnCastFromListBox( item, cast ); + } +} + +void Item_ListBox_Update( itemDef_t *item ) +{ + listBoxDef_t *listPtr = item->typeData.list; + int feederCount = DC->feederCount( item->feederID ); + + if( listPtr->lastFeederCount != feederCount ) + { + if( listPtr->resetonfeederchange ) + { + item->cursorPos = DC->feederInitialise( item->feederID ); + Item_ListBox_SetStartPos( item, 0 ); + DC->feederSelection( item->feederID, item->cursorPos ); + } + else + { + // Make sure endPos is up-to-date + Item_ListBox_SetStartPos( item, listPtr->startPos ); + + // If the selection is off the end now, select the last element + if( item->cursorPos >= feederCount ) + item->cursorPos = feederCount - 1; + } + } + + listPtr->lastFeederCount = feederCount; +} + +void Item_OwnerDraw_Paint( itemDef_t *item ) +{ + menuDef_t *parent; + const char *text; + + if( item == NULL ) + return; + + parent = ( menuDef_t* )item->parent; + + if( DC->ownerDrawItem ) + { + vec4_t color, lowLight; + menuDef_t *parent = ( menuDef_t* )item->parent; + Fade( &item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, + parent->fadeCycle, qtrue, parent->fadeAmount ); + memcpy( &color, &item->window.foreColor, sizeof( color ) ); + + if( item->numColors > 0 && DC->getValue ) + { + // if the value is within one of the ranges then set color to that, otherwise leave at default + int i; + float f = DC->getValue( item->window.ownerDraw ); + + for( i = 0; i < item->numColors; i++ ) + { + if( f >= item->colorRanges[i].low && f <= item->colorRanges[i].high ) + { + memcpy( &color, &item->colorRanges[i].color, sizeof( color ) ); + break; + } + } + } + + if( item->window.flags & WINDOW_HASFOCUS ) + memcpy( color, &parent->focusColor, sizeof( vec4_t ) ); + else if( item->textStyle == ITEM_TEXTSTYLE_BLINK && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) ) + { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor( item->window.foreColor, lowLight, color, 0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } + + if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) + Com_Memcpy( color, parent->disableColor, sizeof( vec4_t ) ); + + if( DC->ownerDrawText && ( text = DC->ownerDrawText( item->window.ownerDraw ) ) ) + { + if( item->text && *item->text ) + { + Item_Text_Paint( item ); + + UI_Text_Paint( item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, + item->textRect.y, item->textscale, + color, text, 0, 0, item->textStyle ); + } + else + { + item->text = text; + Item_Text_Paint( item ); + item->text = NULL; + } + } + else + { + DC->ownerDrawItem( item->window.rect.x, item->window.rect.y, + item->window.rect.w, item->window.rect.h, + item->textalignx, item->textaligny, + item->window.ownerDraw, item->window.ownerDrawFlags, + item->alignment, item->textalignment, item->textvalignment, + item->window.borderSize, item->textscale, color, item->window.backColor, + item->window.background, item->textStyle ); + } + } +} + + +void Item_Update( itemDef_t *item ) +{ + if( item == NULL ) + return; + + if( Item_IsListBox( item ) ) + Item_ListBox_Update( item ); +} + +void Item_Paint( itemDef_t *item ) +{ + vec4_t red; + menuDef_t *parent = ( menuDef_t* )item->parent; + red[0] = red[3] = 1; + red[1] = red[2] = 0; + + if( item == NULL ) + return; + + if( item->window.flags & WINDOW_ORBITING ) + { + if( DC->realTime > item->window.nextTime ) + { + float rx, ry, a, c, s, w, h; + + item->window.nextTime = DC->realTime + item->window.offsetTime; + // translate + w = item->window.rectClient.w / 2; + h = item->window.rectClient.h / 2; + rx = item->window.rectClient.x + w - item->window.rectEffects.x; + ry = item->window.rectClient.y + h - item->window.rectEffects.y; + a = 3 * M_PI / 180; + c = cos( a ); + s = sin( a ); + item->window.rectClient.x = ( rx * c - ry * s ) + item->window.rectEffects.x - w; + item->window.rectClient.y = ( rx * s + ry * c ) + item->window.rectEffects.y - h; + Item_UpdatePosition( item ); + + } + } + + + if( item->window.flags & WINDOW_INTRANSITION ) + { + if( DC->realTime > item->window.nextTime ) + { + int done = 0; + item->window.nextTime = DC->realTime + item->window.offsetTime; + // transition the x,y + + if( item->window.rectClient.x == item->window.rectEffects.x ) + done++; + else + { + if( item->window.rectClient.x < item->window.rectEffects.x ) + { + item->window.rectClient.x += item->window.rectEffects2.x; + + if( item->window.rectClient.x > item->window.rectEffects.x ) + { + item->window.rectClient.x = item->window.rectEffects.x; + done++; + } + } + else + { + item->window.rectClient.x -= item->window.rectEffects2.x; + + if( item->window.rectClient.x < item->window.rectEffects.x ) + { + item->window.rectClient.x = item->window.rectEffects.x; + done++; + } + } + } + + if( item->window.rectClient.y == item->window.rectEffects.y ) + done++; + else + { + if( item->window.rectClient.y < item->window.rectEffects.y ) + { + item->window.rectClient.y += item->window.rectEffects2.y; + + if( item->window.rectClient.y > item->window.rectEffects.y ) + { + item->window.rectClient.y = item->window.rectEffects.y; + done++; + } + } + else + { + item->window.rectClient.y -= item->window.rectEffects2.y; + + if( item->window.rectClient.y < item->window.rectEffects.y ) + { + item->window.rectClient.y = item->window.rectEffects.y; + done++; + } + } + } + + if( item->window.rectClient.w == item->window.rectEffects.w ) + done++; + else + { + if( item->window.rectClient.w < item->window.rectEffects.w ) + { + item->window.rectClient.w += item->window.rectEffects2.w; + + if( item->window.rectClient.w > item->window.rectEffects.w ) + { + item->window.rectClient.w = item->window.rectEffects.w; + done++; + } + } + else + { + item->window.rectClient.w -= item->window.rectEffects2.w; + + if( item->window.rectClient.w < item->window.rectEffects.w ) + { + item->window.rectClient.w = item->window.rectEffects.w; + done++; + } + } + } + + if( item->window.rectClient.h == item->window.rectEffects.h ) + done++; + else + { + if( item->window.rectClient.h < item->window.rectEffects.h ) + { + item->window.rectClient.h += item->window.rectEffects2.h; + + if( item->window.rectClient.h > item->window.rectEffects.h ) + { + item->window.rectClient.h = item->window.rectEffects.h; + done++; + } + } + else + { + item->window.rectClient.h -= item->window.rectEffects2.h; + + if( item->window.rectClient.h < item->window.rectEffects.h ) + { + item->window.rectClient.h = item->window.rectEffects.h; + done++; + } + } + } + + Item_UpdatePosition( item ); + + if( done == 4 ) + item->window.flags &= ~WINDOW_INTRANSITION; + + } + } + + if( item->window.ownerDrawFlags && DC->ownerDrawVisible ) + { + if( !DC->ownerDrawVisible( item->window.ownerDrawFlags ) ) + item->window.flags &= ~WINDOW_VISIBLE; + else + item->window.flags |= WINDOW_VISIBLE; + } + + if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) ) + { + if( !Item_EnableShowViaCvar( item, CVAR_SHOW ) ) + return; + } + + if( item->window.flags & WINDOW_TIMEDVISIBLE ) + { + } + + if( !( item->window.flags & WINDOW_VISIBLE ) ) + return; + + Window_Paint( &item->window, parent->fadeAmount, parent->fadeClamp, parent->fadeCycle ); + + if( DC->getCVarValue( "ui_developer" ) ) + { + vec4_t color; + rectDef_t *r = Item_CorrectedTextRect( item ); + color[1] = color[3] = 1; + color[0] = color[2] = 0; + DC->drawRect( r->x, r->y, r->w, r->h, 1, color ); + } + + switch( item->type ) + { + case ITEM_TYPE_OWNERDRAW: + Item_OwnerDraw_Paint( item ); + break; + + case ITEM_TYPE_TEXT: + case ITEM_TYPE_BUTTON: + Item_Text_Paint( item ); + break; + + case ITEM_TYPE_RADIOBUTTON: + break; + + case ITEM_TYPE_CHECKBOX: + break; + + case ITEM_TYPE_CYCLE: + Item_Cycle_Paint( item ); + break; + + case ITEM_TYPE_LISTBOX: + Item_ListBox_Paint( item ); + break; + + case ITEM_TYPE_COMBOBOX: + Item_ComboBox_Paint( item ); + break; + + case ITEM_TYPE_MODEL: + Item_Model_Paint( item ); + break; + + case ITEM_TYPE_YESNO: + Item_YesNo_Paint( item ); + break; + + case ITEM_TYPE_MULTI: + Item_Multi_Paint( item ); + break; + + case ITEM_TYPE_BIND: + Item_Bind_Paint( item ); + break; + + case ITEM_TYPE_SLIDER: + Item_Slider_Paint( item ); + break; + + default: + if( Item_IsEditField( item ) ) + Item_TextField_Paint( item ); + + break; + } + + Border_Paint( &item->window ); +} + +void Menu_Init( menuDef_t *menu ) +{ + memset( menu, 0, sizeof( menuDef_t ) ); + menu->cursorItem = -1; + menu->fadeAmount = DC->Assets.fadeAmount; + menu->fadeClamp = DC->Assets.fadeClamp; + menu->fadeCycle = DC->Assets.fadeCycle; + Window_Init( &menu->window ); + menu->window.aspectBias = ALIGN_CENTER; +} + +itemDef_t *Menu_GetFocusedItem( menuDef_t *menu ) +{ + int i; + + if( menu ) + { + for( i = 0; i < menu->itemCount; i++ ) + { + if( menu->items[i]->window.flags & WINDOW_HASFOCUS ) + return menu->items[i]; + } + } + + return NULL; +} + +menuDef_t *Menu_GetFocused( void ) +{ + int i; + + for( i = 0; i < menuCount; i++ ) + { + if( Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE ) + return & Menus[i]; + } + + return NULL; +} + +void Menu_ScrollFeeder( menuDef_t *menu, int feeder, qboolean down ) +{ + if( menu ) + { + int i; + + for( i = 0; i < menu->itemCount; i++ ) + { + itemDef_t *item = menu->items[ i ]; + + if( item->feederID == feeder ) + { + qboolean cast = Item_ComboBox_MaybeCastToListBox( item ); + Item_ListBox_HandleKey( item, down ? K_DOWNARROW : K_UPARROW, qtrue, qtrue ); + Item_ComboBox_MaybeUnCastFromListBox( item, cast ); + + return; + } + } + } +} + + + +void Menu_SetFeederSelection( menuDef_t *menu, int feeder, int index, const char *name ) +{ + if( menu == NULL ) + { + if( name == NULL ) + menu = Menu_GetFocused(); + else + menu = Menus_FindByName( name ); + } + + if( menu ) + { + int i; + + for( i = 0; i < menu->itemCount; i++ ) + { + if( menu->items[i]->feederID == feeder ) + { + if( Item_IsListBox( menu->items[i] ) && index == 0 ) + { + menu->items[ i ]->typeData.list->cursorPos = 0; + Item_ListBox_SetStartPos( menu->items[ i ], 0 ); + } + + menu->items[i]->cursorPos = index; + DC->feederSelection( menu->items[i]->feederID, menu->items[i]->cursorPos ); + return; + } + } + } +} + +qboolean Menus_AnyFullScreenVisible( void ) +{ + int i; + + for( i = 0; i < menuCount; i++ ) + { + if( Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen ) + return qtrue; + } + + return qfalse; +} + +menuDef_t *Menus_ActivateByName( const char *p ) +{ + int i; + menuDef_t *m = NULL; + + // Activate one menu + + for( i = 0; i < menuCount; i++ ) + { + if( Q_stricmp( Menus[i].window.name, p ) == 0 ) + { + m = &Menus[i]; + Menus_Activate( m ); + break; + } + } + + // Defocus the others + for( i = 0; i < menuCount; i++ ) + { + if( Q_stricmp( Menus[i].window.name, p ) != 0 ) + Menus[i].window.flags &= ~WINDOW_HASFOCUS; + } + + return m; +} + +menuDef_t *Menus_ReplaceActiveByName( const char *p ) +{ + int i; + menuDef_t *m = NULL; + + // Activate one menu + + for( i = 0; i < menuCount; i++ ) + { + if( Q_stricmp( Menus[i].window.name, p ) == 0 ) + { + m = &Menus[i]; + if(! Menus_ReplaceActive( m ) ) + return NULL; + break; + } + } + return m; +} + + +void Item_Init( itemDef_t *item ) +{ + memset( item, 0, sizeof( itemDef_t ) ); + item->textscale = 0.55f; + Window_Init( &item->window ); + item->window.aspectBias = ASPECT_NONE; +} + +static qboolean Item_HandleMouseMove( itemDef_t *item, float x, float y, int pass, qboolean focusSet ) +{ + if( Rect_ContainsPoint( &item->window.rect, x, y ) ) + { + if( pass == 1 ) + { + if( item->type == ITEM_TYPE_TEXT && item->text ) + { + if( !Rect_ContainsPoint( Item_CorrectedTextRect( item ), x, y ) ) + return qtrue; + } + + // if we are over an item + if( IsVisible( item->window.flags ) ) + { + // different one + Item_MouseEnter( item, x, y ); + + if( !focusSet ) + focusSet = Item_SetFocus( item, x, y ); + } + } + + return qtrue; + } + + return qfalse; +} + +void Menu_HandleMouseMove( menuDef_t *menu, float x, float y ) +{ + int i, pass; + qboolean focusSet = qfalse; + qboolean result; + qboolean cast; + + if( menu == NULL ) + return; + + if( !( menu->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) ) + return; + + if( itemCapture ) + { + //Item_MouseMove(itemCapture, x, y); + return; + } + + if( g_waitingForKey || g_editingField ) + return; + + if( g_comboBoxItem != NULL ) + { + Item_SetFocus( g_comboBoxItem, x, y ); + focusSet = qtrue; + } + + // FIXME: this is the whole issue of focus vs. mouse over.. + // need a better overall solution as i don't like going through everything twice + for( pass = 0; pass < 2; pass++ ) + { + for( i = 0; i < menu->itemCount; i++ ) + { + itemDef_t *item = menu->items[ i ]; + + // turn off focus each item + // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + + if( !( item->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) ) + continue; + + // items can be enabled and disabled based on cvars + if( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && + !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) + continue; + + if( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && + !Item_EnableShowViaCvar( item, CVAR_SHOW ) ) + continue; + + cast = Item_ComboBox_MaybeCastToListBox( item ); + result = Item_HandleMouseMove( item, x, y, pass, focusSet ); + Item_ComboBox_MaybeUnCastFromListBox( item, cast ); + + if( !result && item->window.flags & WINDOW_MOUSEOVER ) + { + Item_MouseLeave( item ); + Item_SetMouseOver( item, qfalse ); + } + } + } + +} + +void Menu_Update( menuDef_t *menu ) +{ + int i; + + if( menu == NULL ) + return; + + for( i = 0; i < menu->itemCount; i++ ) + Item_Update( menu->items[ i ] ); +} + +void Menu_Paint( menuDef_t *menu, qboolean forcePaint ) +{ + int i; + + if( menu == NULL ) + return; + + if( !( menu->window.flags & WINDOW_VISIBLE ) && !forcePaint ) + return; + + if( menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible( menu->window.ownerDrawFlags ) ) + return; + + if( forcePaint ) + menu->window.flags |= WINDOW_FORCED; + + // draw the background if necessary + if( menu->fullScreen ) + { + // implies a background shader + // FIXME: make sure we have a default shader if fullscreen is set with no background + DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background ); + } + + // paint the background and or border + Window_Paint( &menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle ); + + Border_Paint( &menu->window ); + + for( i = 0; i < menu->itemCount; i++ ) + Item_Paint( menu->items[i] ); + + if( DC->getCVarValue( "ui_developer" ) ) + { + vec4_t color; + color[0] = color[2] = color[3] = 1; + color[1] = 0; + DC->drawRect( menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color ); + } +} + +/* +=============== +Keyword Hash +=============== +*/ + +#define KEYWORDHASH_SIZE 512 + +typedef struct keywordHash_s +{ + char *keyword; + qboolean ( *func )( itemDef_t *item, int handle ); + + int param; + + struct keywordHash_s *next; +} + +keywordHash_t; + +int KeywordHash_Key( char *keyword ) +{ + int register hash, i; + + hash = 0; + + for( i = 0; keyword[i] != '\0'; i++ ) + { + if( keyword[i] >= 'A' && keyword[i] <= 'Z' ) + hash += ( keyword[i] + ( 'a' - 'A' ) ) * ( 119 + i ); + else + hash += keyword[i] * ( 119 + i ); + } + + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ) & ( KEYWORDHASH_SIZE - 1 ); + return hash; +} + +void KeywordHash_Add( keywordHash_t *table[], keywordHash_t *key ) +{ + int hash; + + hash = KeywordHash_Key( key->keyword ); + /* + if(table[hash]) int collision = qtrue; + */ + key->next = table[hash]; + table[hash] = key; +} + +keywordHash_t *KeywordHash_Find( keywordHash_t *table[], char *keyword ) +{ + keywordHash_t *key; + int hash; + + hash = KeywordHash_Key( keyword ); + + for( key = table[hash]; key; key = key->next ) + { + if( !Q_stricmp( key->keyword, keyword ) ) + return key; + } + + return NULL; +} + +/* +=============== +Item_DataType + +Give a numeric representation of which typeData union element this item uses +=============== +*/ +itemDataType_t Item_DataType( itemDef_t *item ) +{ + switch( item->type ) + { + default: + case ITEM_TYPE_NONE: + return TYPE_NONE; + + case ITEM_TYPE_LISTBOX: + case ITEM_TYPE_COMBOBOX: + return TYPE_LIST; + + case ITEM_TYPE_CYCLE: + return TYPE_COMBO; + + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_SAYFIELD: + case ITEM_TYPE_YESNO: + case ITEM_TYPE_BIND: + case ITEM_TYPE_SLIDER: + case ITEM_TYPE_TEXT: + return TYPE_EDIT; + + case ITEM_TYPE_MULTI: + return TYPE_MULTI; + + case ITEM_TYPE_MODEL: + return TYPE_MODEL; + } +} + +/* +=============== +Item_IsEditField +=============== +*/ +static ID_INLINE qboolean Item_IsEditField( itemDef_t *item ) +{ + switch( item->type ) + { + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_SAYFIELD: + return qtrue; + + default: + return qfalse; + } +} + +/* +=============== +Item_IsListBox +=============== +*/ +static ID_INLINE qboolean Item_IsListBox( itemDef_t *item ) +{ + switch( item->type ) + { + case ITEM_TYPE_LISTBOX: + case ITEM_TYPE_COMBOBOX: + return qtrue; + + default: + return qfalse; + } +} + +/* +=============== +Item Keyword Parse functions +=============== +*/ + +// name +qboolean ItemParse_name( itemDef_t *item, int handle ) +{ + if( !PC_String_Parse( handle, &item->window.name ) ) + return qfalse; + + return qtrue; +} + +// name +qboolean ItemParse_focusSound( itemDef_t *item, int handle ) +{ + const char *temp; + + if( !PC_String_Parse( handle, &temp ) ) + return qfalse; + + item->focusSound = DC->registerSound( temp, qfalse ); + return qtrue; +} + + +// text +qboolean ItemParse_text( itemDef_t *item, int handle ) +{ + if( !PC_String_Parse( handle, &item->text ) ) + return qfalse; + + return qtrue; +} + +// group +qboolean ItemParse_group( itemDef_t *item, int handle ) +{ + if( !PC_String_Parse( handle, &item->window.group ) ) + return qfalse; + + return qtrue; +} + +// asset_model +qboolean ItemParse_asset_model( itemDef_t *item, int handle ) +{ + const char *temp; + + if( !PC_String_Parse( handle, &temp ) ) + return qfalse; + + item->asset = DC->registerModel( temp ); + item->typeData.model->angle = rand() % 360; + return qtrue; +} + +// asset_shader +qboolean ItemParse_asset_shader( itemDef_t *item, int handle ) +{ + const char *temp; + + if( !PC_String_Parse( handle, &temp ) ) + return qfalse; + + item->asset = DC->registerShaderNoMip( temp ); + return qtrue; +} + +// model_origin +qboolean ItemParse_model_origin( itemDef_t *item, int handle ) +{ + return ( PC_Float_Parse( handle, &item->typeData.model->origin[0] ) && + PC_Float_Parse( handle, &item->typeData.model->origin[1] ) && + PC_Float_Parse( handle, &item->typeData.model->origin[2] ) ); +} + +// model_fovx +qboolean ItemParse_model_fovx( itemDef_t *item, int handle ) +{ + return PC_Float_Parse( handle, &item->typeData.model->fov_x ); +} + +// model_fovy +qboolean ItemParse_model_fovy( itemDef_t *item, int handle ) +{ + return PC_Float_Parse( handle, &item->typeData.model->fov_y ); +} + +// model_rotation +qboolean ItemParse_model_rotation( itemDef_t *item, int handle ) +{ + return PC_Int_Parse( handle, &item->typeData.model->rotationSpeed ); +} + +// model_angle +qboolean ItemParse_model_angle( itemDef_t *item, int handle ) +{ + return PC_Int_Parse( handle, &item->typeData.model->angle ); +} + +// rect +qboolean ItemParse_rect( itemDef_t *item, int handle ) +{ + if( !PC_Rect_Parse( handle, &item->window.rectClient ) ) + return qfalse; + + return qtrue; +} + +// aspectBias +qboolean ItemParse_aspectBias( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->window.aspectBias ) ) + return qfalse; + + return qtrue; +} + +// style +qboolean ItemParse_style( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->window.style ) ) + return qfalse; + + return qtrue; +} + +// decoration +qboolean ItemParse_decoration( itemDef_t *item, int handle ) +{ + item->window.flags |= WINDOW_DECORATION; + return qtrue; +} + +// notselectable +qboolean ItemParse_notselectable( itemDef_t *item, int handle ) +{ + item->typeData.list->notselectable = qtrue; + return qtrue; +} + +// noscrollbar +qboolean ItemParse_noscrollbar( itemDef_t *item, int handle ) +{ + item->typeData.list->noscrollbar = qtrue; + return qtrue; +} + +// resetonfeederchange +qboolean ItemParse_resetonfeederchange( itemDef_t *item, int handle ) +{ + item->typeData.list->resetonfeederchange = qtrue; + return qtrue; +} + +// auto wrapped +qboolean ItemParse_wrapped( itemDef_t *item, int handle ) +{ + item->window.flags |= WINDOW_WRAPPED; + return qtrue; +} + + +// type +qboolean ItemParse_type( itemDef_t *item, int handle ) +{ + if( item->type != ITEM_TYPE_NONE ) + { + PC_SourceError( handle, "item already has a type" ); + return qfalse; + } + + if( !PC_Int_Parse( handle, &item->type ) ) + return qfalse; + + if( item->type == ITEM_TYPE_NONE ) + { + PC_SourceError( handle, "type must not be none" ); + return qfalse; + } + + // allocate the relevant type data + switch( item->type ) + { + case ITEM_TYPE_LISTBOX: + case ITEM_TYPE_COMBOBOX: + item->typeData.list = UI_Alloc( sizeof( listBoxDef_t ) ); + memset( item->typeData.list, 0, sizeof( listBoxDef_t ) ); + break; + + case ITEM_TYPE_CYCLE: + item->typeData.cycle = UI_Alloc( sizeof( cycleDef_t ) ); + memset( item->typeData.cycle, 0, sizeof( cycleDef_t ) ); + break; + + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_SAYFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_YESNO: + case ITEM_TYPE_BIND: + case ITEM_TYPE_SLIDER: + case ITEM_TYPE_TEXT: + item->typeData.edit = UI_Alloc( sizeof( editFieldDef_t ) ); + memset( item->typeData.edit, 0, sizeof( editFieldDef_t ) ); + + if( item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD ) + item->typeData.edit->maxPaintChars = MAX_EDITFIELD; + break; + + case ITEM_TYPE_MULTI: + item->typeData.multi = UI_Alloc( sizeof( multiDef_t ) ); + memset( item->typeData.multi, 0, sizeof( multiDef_t ) ); + break; + + case ITEM_TYPE_MODEL: + item->typeData.model = UI_Alloc( sizeof( modelDef_t ) ); + memset( item->typeData.model, 0, sizeof( modelDef_t ) ); + break; + + default: + break; + } + + return qtrue; +} + +// elementwidth, used for listbox image elements +qboolean ItemParse_elementwidth( itemDef_t *item, int handle ) +{ + return PC_Float_Parse( handle, &item->typeData.list->elementWidth ); +} + +// elementheight, used for listbox image elements +qboolean ItemParse_elementheight( itemDef_t *item, int handle ) +{ + return PC_Float_Parse( handle, &item->typeData.list->elementHeight ); +} + +// dropitems, number of items to drop from a combobox +qboolean ItemParse_dropitems( itemDef_t *item, int handle ) +{ + return PC_Int_Parse( handle, &item->typeData.list->dropItems ); +} + +// feeder +qboolean ItemParse_feeder( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->feederID ) ) + return qfalse; + + return qtrue; +} + +// elementtype, used to specify what type of elements a listbox contains +// uses textstyle for storage +qboolean ItemParse_elementtype( itemDef_t *item, int handle ) +{ + return PC_Int_Parse( handle, &item->typeData.list->elementStyle ); +} + +// columns sets a number of columns and an x pos and width per.. +qboolean ItemParse_columns( itemDef_t *item, int handle ) +{ + int i; + + if( !PC_Int_Parse( handle, &item->typeData.list->numColumns ) ) + return qfalse; + + if( item->typeData.list->numColumns > MAX_LB_COLUMNS ) + { + PC_SourceError( handle, "exceeded maximum allowed columns (%d)", + MAX_LB_COLUMNS ); + return qfalse; + } + + for( i = 0; i < item->typeData.list->numColumns; i++ ) + { + int pos, width, align; + + if( !PC_Int_Parse( handle, &pos ) || + !PC_Int_Parse( handle, &width ) || + !PC_Int_Parse( handle, &align ) ) + return qfalse; + + item->typeData.list->columnInfo[i].pos = pos; + item->typeData.list->columnInfo[i].width = width; + item->typeData.list->columnInfo[i].align = align; + } + + return qtrue; +} + +qboolean ItemParse_border( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->window.border ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_bordersize( itemDef_t *item, int handle ) +{ + if( !PC_Float_Parse( handle, &item->window.borderSize ) ) + return qfalse; + + return qtrue; +} + +// FIXME: why does this require a parameter? visible MENU_FALSE does nothing +qboolean ItemParse_visible( itemDef_t *item, int handle ) +{ + int i; + + if( !PC_Int_Parse( handle, &i ) ) + return qfalse; + + if( i ) + item->window.flags |= WINDOW_VISIBLE; + + return qtrue; +} + +// ownerdraw , implies ITEM_TYPE_OWNERDRAW +qboolean ItemParse_ownerdraw( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->window.ownerDraw ) ) + return qfalse; + + if( item->type != ITEM_TYPE_NONE && item->type != ITEM_TYPE_OWNERDRAW ) + { + PC_SourceError( handle, "ownerdraws cannot have an item type" ); + return qfalse; + } + + item->type = ITEM_TYPE_OWNERDRAW; + + return qtrue; +} + +qboolean ItemParse_align( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->alignment ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textalign( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->textalignment ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textvalign( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->textvalignment ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textalignx( itemDef_t *item, int handle ) +{ + if( !PC_Float_Parse( handle, &item->textalignx ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textaligny( itemDef_t *item, int handle ) +{ + if( !PC_Float_Parse( handle, &item->textaligny ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textscale( itemDef_t *item, int handle ) +{ + if( !PC_Float_Parse( handle, &item->textscale ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_textstyle( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->textStyle ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_backcolor( itemDef_t *item, int handle ) +{ + int i; + float f; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + item->window.backColor[i] = f; + } + + return qtrue; +} + +qboolean ItemParse_forecolor( itemDef_t *item, int handle ) +{ + int i; + float f; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + item->window.foreColor[i] = f; + item->window.flags |= WINDOW_FORECOLORSET; + } + + return qtrue; +} + +qboolean ItemParse_bordercolor( itemDef_t *item, int handle ) +{ + int i; + float f; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + item->window.borderColor[i] = f; + } + + return qtrue; +} + +qboolean ItemParse_outlinecolor( itemDef_t *item, int handle ) +{ + if( !PC_Color_Parse( handle, &item->window.outlineColor ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_background( itemDef_t *item, int handle ) +{ + const char *temp; + + if( !PC_String_Parse( handle, &temp ) ) + return qfalse; + + item->window.background = DC->registerShaderNoMip( temp ); + return qtrue; +} + +qboolean ItemParse_cinematic( itemDef_t *item, int handle ) +{ + if( !PC_String_Parse( handle, &item->window.cinematicName ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_doubleClick( itemDef_t *item, int handle ) +{ + return ( item->typeData.list && + PC_Script_Parse( handle, &item->typeData.list->doubleClick ) ); +} + +qboolean ItemParse_onFocus( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->onFocus ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_leaveFocus( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->leaveFocus ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_mouseEnter( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->mouseEnter ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_mouseExit( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->mouseExit ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->mouseEnterText ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_mouseExitText( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->mouseExitText ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_onTextEntry( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->onTextEntry ) ) + return qfalse; + + return qtrue; +} + + +qboolean ItemParse_onCharEntry( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->onCharEntry ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_action( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->action ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_cvarTest( itemDef_t *item, int handle ) +{ + if( !PC_String_Parse( handle, &item->cvarTest ) ) + return qfalse; + + return qtrue; +} + +qboolean ItemParse_cvar( itemDef_t *item, int handle ) +{ + if( !PC_String_Parse( handle, &item->cvar ) ) + return qfalse; + + if( Item_DataType( item ) == TYPE_EDIT ) + { + item->typeData.edit->minVal = -1; + item->typeData.edit->maxVal = -1; + item->typeData.edit->defVal = -1; + } + + return qtrue; +} + +qboolean ItemParse_maxChars( itemDef_t *item, int handle ) +{ + return PC_Int_Parse( handle, &item->typeData.edit->maxChars ); +} + +qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle ) +{ + return PC_Int_Parse( handle, &item->typeData.edit->maxPaintChars ); +} + +qboolean ItemParse_maxFieldWidth( itemDef_t *item, int handle ) +{ + if( !PC_Int_Parse( handle, &item->typeData.edit->maxFieldWidth ) ) + return qfalse; + + if( item->typeData.edit->maxFieldWidth < MIN_FIELD_WIDTH ) + { + PC_SourceError( handle, "max field width must be at least %d", + MIN_FIELD_WIDTH ); + return qfalse; + } + + return qtrue; +} + + + +qboolean ItemParse_cvarFloat( itemDef_t *item, int handle ) +{ + return ( PC_String_Parse( handle, &item->cvar ) && + PC_Float_Parse( handle, &item->typeData.edit->defVal ) && + PC_Float_Parse( handle, &item->typeData.edit->minVal ) && + PC_Float_Parse( handle, &item->typeData.edit->maxVal ) ); +} + +qboolean ItemParse_cvarStrList( itemDef_t *item, int handle ) +{ + pc_token_t token; + multiDef_t *multiPtr; + int pass; + + multiPtr = item->typeData.multi; + multiPtr->count = 0; + multiPtr->strDef = qtrue; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( *token.string != '{' ) + return qfalse; + + pass = 0; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + { + PC_SourceError( handle, "end of file inside menu item\n" ); + return qfalse; + } + + if( *token.string == '}' ) + return qtrue; + + if( *token.string == ',' || *token.string == ';' ) + continue; + + if( pass == 0 ) + { + multiPtr->cvarList[multiPtr->count] = String_Alloc( token.string ); + pass = 1; + } + else + { + multiPtr->cvarStr[multiPtr->count] = String_Alloc( token.string ); + pass = 0; + multiPtr->count++; + + if( multiPtr->count >= MAX_MULTI_CVARS ) + { + PC_SourceError( handle, "cvar string list may not exceed %d cvars", + MAX_MULTI_CVARS ); + return qfalse; + } + } + + } + + return qfalse; +} + +qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle ) +{ + pc_token_t token; + multiDef_t *multiPtr; + + multiPtr = item->typeData.multi; + multiPtr->count = 0; + multiPtr->strDef = qfalse; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( *token.string != '{' ) + return qfalse; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + { + PC_SourceError( handle, "end of file inside menu item\n" ); + return qfalse; + } + + if( *token.string == '}' ) + return qtrue; + + if( *token.string == ',' || *token.string == ';' ) + continue; + + multiPtr->cvarList[multiPtr->count] = String_Alloc( token.string ); + + if( !PC_Float_Parse( handle, &multiPtr->cvarValue[multiPtr->count] ) ) + return qfalse; + + multiPtr->count++; + + if( multiPtr->count >= MAX_MULTI_CVARS ) + { + PC_SourceError( handle, "cvar string list may not exceed %d cvars", + MAX_MULTI_CVARS ); + return qfalse; + } + } + + return qfalse; +} + + + +qboolean ItemParse_addColorRange( itemDef_t *item, int handle ) +{ + colorRangeDef_t color; + + if( PC_Float_Parse( handle, &color.low ) && + PC_Float_Parse( handle, &color.high ) && + PC_Color_Parse( handle, &color.color ) ) + { + if( item->numColors < MAX_COLOR_RANGES ) + { + memcpy( &item->colorRanges[item->numColors], &color, sizeof( color ) ); + item->numColors++; + } + else + { + PC_SourceError( handle, "may not exceed %d color ranges", + MAX_COLOR_RANGES ); + return qfalse; + } + + return qtrue; + } + + return qfalse; +} + +qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle ) +{ + int i; + + if( !PC_Int_Parse( handle, &i ) ) + return qfalse; + + item->window.ownerDrawFlags |= i; + return qtrue; +} + +qboolean ItemParse_enableCvar( itemDef_t *item, int handle ) +{ + if( PC_Script_Parse( handle, &item->enableCvar ) ) + { + item->cvarFlags = CVAR_ENABLE; + return qtrue; + } + + return qfalse; +} + +qboolean ItemParse_disableCvar( itemDef_t *item, int handle ) +{ + if( PC_Script_Parse( handle, &item->enableCvar ) ) + { + item->cvarFlags = CVAR_DISABLE; + return qtrue; + } + + return qfalse; +} + +qboolean ItemParse_showCvar( itemDef_t *item, int handle ) +{ + if( PC_Script_Parse( handle, &item->enableCvar ) ) + { + item->cvarFlags = CVAR_SHOW; + return qtrue; + } + + return qfalse; +} + +qboolean ItemParse_hideCvar( itemDef_t *item, int handle ) +{ + if( PC_Script_Parse( handle, &item->enableCvar ) ) + { + item->cvarFlags = CVAR_HIDE; + return qtrue; + } + + return qfalse; +} + + +keywordHash_t itemParseKeywords[] = { + {"name", ItemParse_name, TYPE_ANY}, + {"type", ItemParse_type, TYPE_ANY}, + {"text", ItemParse_text, TYPE_ANY}, + {"group", ItemParse_group, TYPE_ANY}, + {"asset_model", ItemParse_asset_model, TYPE_MODEL}, + {"asset_shader", ItemParse_asset_shader, TYPE_ANY}, // ? + {"model_origin", ItemParse_model_origin, TYPE_MODEL}, + {"model_fovx", ItemParse_model_fovx, TYPE_MODEL}, + {"model_fovy", ItemParse_model_fovy, TYPE_MODEL}, + {"model_rotation", ItemParse_model_rotation, TYPE_MODEL}, + {"model_angle", ItemParse_model_angle, TYPE_MODEL}, + {"rect", ItemParse_rect, TYPE_ANY}, + {"aspectBias", ItemParse_aspectBias, TYPE_ANY}, + {"style", ItemParse_style, TYPE_ANY}, + {"decoration", ItemParse_decoration, TYPE_ANY}, + {"notselectable", ItemParse_notselectable, TYPE_LIST}, + {"noscrollbar", ItemParse_noscrollbar, TYPE_LIST}, + {"resetonfeederchange", ItemParse_resetonfeederchange, TYPE_LIST}, + {"wrapped", ItemParse_wrapped, TYPE_ANY}, + {"elementwidth", ItemParse_elementwidth, TYPE_LIST}, + {"elementheight", ItemParse_elementheight, TYPE_LIST}, + {"dropitems", ItemParse_dropitems, TYPE_LIST}, + {"feeder", ItemParse_feeder, TYPE_ANY}, + {"elementtype", ItemParse_elementtype, TYPE_LIST}, + {"columns", ItemParse_columns, TYPE_LIST}, + {"border", ItemParse_border, TYPE_ANY}, + {"bordersize", ItemParse_bordersize, TYPE_ANY}, + {"visible", ItemParse_visible, TYPE_ANY}, + {"ownerdraw", ItemParse_ownerdraw, TYPE_ANY}, + {"align", ItemParse_align, TYPE_ANY}, + {"textalign", ItemParse_textalign, TYPE_ANY}, + {"textvalign", ItemParse_textvalign, TYPE_ANY}, + {"textalignx", ItemParse_textalignx, TYPE_ANY}, + {"textaligny", ItemParse_textaligny, TYPE_ANY}, + {"textscale", ItemParse_textscale, TYPE_ANY}, + {"textstyle", ItemParse_textstyle, TYPE_ANY}, + {"backcolor", ItemParse_backcolor, TYPE_ANY}, + {"forecolor", ItemParse_forecolor, TYPE_ANY}, + {"bordercolor", ItemParse_bordercolor, TYPE_ANY}, + {"outlinecolor", ItemParse_outlinecolor, TYPE_ANY}, + {"background", ItemParse_background, TYPE_ANY}, + {"onFocus", ItemParse_onFocus, TYPE_ANY}, + {"leaveFocus", ItemParse_leaveFocus, TYPE_ANY}, + {"mouseEnter", ItemParse_mouseEnter, TYPE_ANY}, + {"mouseExit", ItemParse_mouseExit, TYPE_ANY}, + {"mouseEnterText", ItemParse_mouseEnterText, TYPE_ANY}, + {"mouseExitText", ItemParse_mouseExitText, TYPE_ANY}, + {"onTextEntry", ItemParse_onTextEntry, TYPE_ANY}, + {"onCharEntry", ItemParse_onCharEntry, TYPE_ANY}, + {"action", ItemParse_action, TYPE_ANY}, + {"cvar", ItemParse_cvar, TYPE_ANY}, + {"maxChars", ItemParse_maxChars, TYPE_EDIT}, + {"maxPaintChars", ItemParse_maxPaintChars, TYPE_EDIT}, + {"maxFieldWidth", ItemParse_maxFieldWidth, TYPE_EDIT}, + {"focusSound", ItemParse_focusSound, TYPE_ANY}, + {"cvarFloat", ItemParse_cvarFloat, TYPE_EDIT}, + {"cvarStrList", ItemParse_cvarStrList, TYPE_MULTI}, + {"cvarFloatList", ItemParse_cvarFloatList, TYPE_MULTI}, + {"addColorRange", ItemParse_addColorRange, TYPE_ANY}, + {"ownerdrawFlag", ItemParse_ownerdrawFlag, TYPE_ANY}, // hm. + {"enableCvar", ItemParse_enableCvar, TYPE_ANY}, + {"cvarTest", ItemParse_cvarTest, TYPE_ANY}, + {"disableCvar", ItemParse_disableCvar, TYPE_ANY}, + {"showCvar", ItemParse_showCvar, TYPE_ANY}, + {"hideCvar", ItemParse_hideCvar, TYPE_ANY}, + {"cinematic", ItemParse_cinematic, TYPE_ANY}, + {"doubleclick", ItemParse_doubleClick, TYPE_LIST}, + {NULL, voidFunction2} +}; + +keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +Item_SetupKeywordHash +=============== +*/ +void Item_SetupKeywordHash( void ) +{ + int i; + + memset( itemParseKeywordHash, 0, sizeof( itemParseKeywordHash ) ); + + for( i = 0; itemParseKeywords[ i ].keyword; i++ ) + KeywordHash_Add( itemParseKeywordHash, &itemParseKeywords[ i ] ); +} + +/* +=============== +Item_Parse +=============== +*/ +qboolean Item_Parse( int handle, itemDef_t *item ) +{ + pc_token_t token; + keywordHash_t *key; + + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( *token.string != '{' ) + return qfalse; + + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + { + PC_SourceError( handle, "end of file inside menu item\n" ); + return qfalse; + } + + if( *token.string == '}' ) + return qtrue; + + key = KeywordHash_Find( itemParseKeywordHash, token.string ); + + if( !key ) + { + PC_SourceError( handle, "unknown menu item keyword %s", token.string ); + continue; + } + + // do type-checks + if( key->param != TYPE_ANY ) + { + itemDataType_t test = Item_DataType( item ); + + if( test != key->param ) + { + if( test == TYPE_NONE ) + PC_SourceError( handle, "menu item keyword %s requires " + "type specification", token.string ); + else + PC_SourceError( handle, "menu item keyword %s is incompatible with " + "specified item type", token.string ); + continue; + } + } + + if( !key->func( item, handle ) ) + { + PC_SourceError( handle, "couldn't parse menu item keyword %s", token.string ); + return qfalse; + } + } + + return qfalse; +} + + +// Item_InitControls +// init's special control types +void Item_InitControls( itemDef_t *item ) +{ + if( item == NULL ) + return; + + if( Item_IsListBox( item ) ) + { + item->cursorPos = 0; + + if( item->typeData.list ) + { + item->typeData.list->cursorPos = 0; + Item_ListBox_SetStartPos( item, 0 ); + item->typeData.list->cursorPos = 0; + } + } +} + +/* +=============== +Menu Keyword Parse functions +=============== +*/ + +qboolean MenuParse_font( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_String_Parse( handle, &menu->font ) ) + return qfalse; + + if( !DC->Assets.fontRegistered ) + { + DC->registerFont( menu->font, 48, &DC->Assets.textFont ); + DC->Assets.fontRegistered = qtrue; + } + + return qtrue; +} + +qboolean MenuParse_name( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_String_Parse( handle, &menu->window.name ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_fullscreen( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Int_Parse( handle, ( int* ) & menu->fullScreen ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_rect( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Rect_Parse( handle, &menu->window.rect ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_aspectBias( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Int_Parse( handle, &menu->window.aspectBias ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_style( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Int_Parse( handle, &menu->window.style ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_visible( itemDef_t *item, int handle ) +{ + int i; + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Int_Parse( handle, &i ) ) + return qfalse; + + if( i ) + menu->window.flags |= WINDOW_VISIBLE; + + return qtrue; +} + +qboolean MenuParse_onOpen( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Script_Parse( handle, &menu->onOpen ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_onClose( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Script_Parse( handle, &menu->onClose ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_onESC( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Script_Parse( handle, &menu->onESC ) ) + return qfalse; + + return qtrue; +} + + + +qboolean MenuParse_border( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Int_Parse( handle, &menu->window.border ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_borderSize( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Float_Parse( handle, &menu->window.borderSize ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_backcolor( itemDef_t *item, int handle ) +{ + int i; + float f; + menuDef_t *menu = ( menuDef_t* )item; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + menu->window.backColor[i] = f; + } + + return qtrue; +} + +qboolean MenuParse_forecolor( itemDef_t *item, int handle ) +{ + int i; + float f; + menuDef_t *menu = ( menuDef_t* )item; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + menu->window.foreColor[i] = f; + menu->window.flags |= WINDOW_FORECOLORSET; + } + + return qtrue; +} + +qboolean MenuParse_bordercolor( itemDef_t *item, int handle ) +{ + int i; + float f; + menuDef_t *menu = ( menuDef_t* )item; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + menu->window.borderColor[i] = f; + } + + return qtrue; +} + +qboolean MenuParse_focuscolor( itemDef_t *item, int handle ) +{ + int i; + float f; + menuDef_t *menu = ( menuDef_t* )item; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + menu->focusColor[i] = f; + } + + return qtrue; +} + +qboolean MenuParse_disablecolor( itemDef_t *item, int handle ) +{ + int i; + float f; + menuDef_t *menu = ( menuDef_t* )item; + + for( i = 0; i < 4; i++ ) + { + if( !PC_Float_Parse( handle, &f ) ) + return qfalse; + + menu->disableColor[i] = f; + } + + return qtrue; +} + + +qboolean MenuParse_outlinecolor( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Color_Parse( handle, &menu->window.outlineColor ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_background( itemDef_t *item, int handle ) +{ + const char *buff; + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_String_Parse( handle, &buff ) ) + return qfalse; + + menu->window.background = DC->registerShaderNoMip( buff ); + return qtrue; +} + +qboolean MenuParse_cinematic( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_String_Parse( handle, &menu->window.cinematicName ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle ) +{ + int i; + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Int_Parse( handle, &i ) ) + return qfalse; + + menu->window.ownerDrawFlags |= i; + return qtrue; +} + +qboolean MenuParse_ownerdraw( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Int_Parse( handle, &menu->window.ownerDraw ) ) + return qfalse; + + return qtrue; +} + + +// decoration +qboolean MenuParse_popup( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + menu->window.flags |= WINDOW_POPUP; + return qtrue; +} + + +qboolean MenuParse_outOfBounds( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + menu->window.flags |= WINDOW_OOB_CLICK; + return qtrue; +} + +qboolean MenuParse_soundLoop( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_String_Parse( handle, &menu->soundName ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_fadeClamp( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Float_Parse( handle, &menu->fadeClamp ) ) + return qfalse; + + return qtrue; +} + +qboolean MenuParse_fadeAmount( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Float_Parse( handle, &menu->fadeAmount ) ) + return qfalse; + + return qtrue; +} + + +qboolean MenuParse_fadeCycle( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( !PC_Int_Parse( handle, &menu->fadeCycle ) ) + return qfalse; + + return qtrue; +} + + +qboolean MenuParse_itemDef( itemDef_t *item, int handle ) +{ + menuDef_t *menu = ( menuDef_t* )item; + + if( menu->itemCount < MAX_MENUITEMS ) + { + menu->items[menu->itemCount] = UI_Alloc( sizeof( itemDef_t ) ); + Item_Init( menu->items[menu->itemCount] ); + + if( !Item_Parse( handle, menu->items[menu->itemCount] ) ) + return qfalse; + + Item_InitControls( menu->items[menu->itemCount] ); + menu->items[menu->itemCount++]->parent = menu; + } + else + { + PC_SourceError( handle, "itemDefs per menu may not exceed %d", + MAX_MENUITEMS ); + return qfalse; + } + + return qtrue; +} + +keywordHash_t menuParseKeywords[] = { + {"font", MenuParse_font}, + {"name", MenuParse_name}, + {"fullscreen", MenuParse_fullscreen}, + {"rect", MenuParse_rect}, + {"aspectBias", MenuParse_aspectBias}, + {"style", MenuParse_style}, + {"visible", MenuParse_visible}, + {"onOpen", MenuParse_onOpen}, + {"onClose", MenuParse_onClose}, + {"onESC", MenuParse_onESC}, + {"border", MenuParse_border}, + {"borderSize", MenuParse_borderSize}, + {"backcolor", MenuParse_backcolor}, + {"forecolor", MenuParse_forecolor}, + {"bordercolor", MenuParse_bordercolor}, + {"focuscolor", MenuParse_focuscolor}, + {"disablecolor", MenuParse_disablecolor}, + {"outlinecolor", MenuParse_outlinecolor}, + {"background", MenuParse_background}, + {"ownerdraw", MenuParse_ownerdraw}, + {"ownerdrawFlag", MenuParse_ownerdrawFlag}, + {"outOfBoundsClick", MenuParse_outOfBounds}, + {"soundLoop", MenuParse_soundLoop}, + {"itemDef", MenuParse_itemDef}, + {"cinematic", MenuParse_cinematic}, + {"popup", MenuParse_popup}, + {"fadeClamp", MenuParse_fadeClamp}, + {"fadeCycle", MenuParse_fadeCycle}, + {"fadeAmount", MenuParse_fadeAmount}, + {NULL, voidFunction2} +}; + +keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +Menu_SetupKeywordHash +=============== +*/ +void Menu_SetupKeywordHash( void ) +{ + int i; + + memset( menuParseKeywordHash, 0, sizeof( menuParseKeywordHash ) ); + + for( i = 0; menuParseKeywords[ i ].keyword; i++ ) + KeywordHash_Add( menuParseKeywordHash, &menuParseKeywords[ i ] ); +} + +/* +=============== +Menu_Parse +=============== +*/ +qboolean Menu_Parse( int handle, menuDef_t *menu ) +{ + pc_token_t token; + keywordHash_t *key; + + if( !trap_Parse_ReadToken( handle, &token ) ) + return qfalse; + + if( *token.string != '{' ) + return qfalse; + + while( 1 ) + { + memset( &token, 0, sizeof( pc_token_t ) ); + + if( !trap_Parse_ReadToken( handle, &token ) ) + { + PC_SourceError( handle, "end of file inside menu\n" ); + return qfalse; + } + + if( *token.string == '}' ) + return qtrue; + + key = KeywordHash_Find( menuParseKeywordHash, token.string ); + + if( !key ) + { + PC_SourceError( handle, "unknown menu keyword %s", token.string ); + continue; + } + + if( !key->func( ( itemDef_t* )menu, handle ) ) + { + PC_SourceError( handle, "couldn't parse menu keyword %s", token.string ); + return qfalse; + } + } + + return qfalse; +} + +/* +=============== +Menu_New +=============== +*/ +void Menu_New( int handle ) +{ + menuDef_t *menu = &Menus[menuCount]; + + if( menuCount < MAX_MENUS ) + { + Menu_Init( menu ); + + if( Menu_Parse( handle, menu ) ) + { + Menu_PostParse( menu ); + menuCount++; + } + } +} + +int Menu_Count( void ) +{ + return menuCount; +} + +void Menu_UpdateAll( void ) +{ + int i; + + for( i = 0; i < openMenuCount; i++ ) + Menu_Update( menuStack[ i ] ); +} + +void Menu_PaintAll( void ) +{ + int i; + + if( g_editingField || g_waitingForKey ) + DC->setCVar( "ui_hideCursor", "1" ); + else + DC->setCVar( "ui_hideCursor", "0" ); + + if( captureFunc != voidFunction ) + { + if( captureFuncExpiry > 0 && DC->realTime > captureFuncExpiry ) + UI_RemoveCaptureFunc( ); + else + captureFunc( captureData ); + } + + for( i = 0; i < openMenuCount; i++ ) + Menu_Paint( menuStack[ i ], qfalse ); + + if( DC->getCVarValue( "ui_developer" ) ) + { + vec4_t v = {1, 1, 1, 1}; + UI_Text_Paint( 5, 25, .5, v, va( "fps: %f", DC->FPS ), 0, 0, 0 ); + } +} + +void Menu_Reset( void ) +{ + menuCount = 0; +} + +displayContextDef_t *Display_GetContext( void ) +{ + return DC; +} + +void *Display_CaptureItem( int x, int y ) +{ + int i; + + for( i = 0; i < menuCount; i++ ) + { + if( Rect_ContainsPoint( &Menus[i].window.rect, x, y ) ) + return & Menus[i]; + } + + return NULL; +} + + +// FIXME: +qboolean Display_MouseMove( void *p, float x, float y ) +{ + int i; + menuDef_t *menu = p; + + if( menu == NULL ) + { + menu = Menu_GetFocused(); + + if( menu ) + { + if( menu->window.flags & WINDOW_POPUP ) + { + Menu_HandleMouseMove( menu, x, y ); + return qtrue; + } + } + + for( i = 0; i < menuCount; i++ ) + Menu_HandleMouseMove( &Menus[i], x, y ); + } + else + { + menu->window.rect.x += x; + menu->window.rect.y += y; + Menu_UpdatePosition( menu ); + } + + return qtrue; + +} + +int Display_CursorType( int x, int y ) +{ + int i; + + for( i = 0; i < menuCount; i++ ) + { + rectDef_t r2; + r2.x = Menus[i].window.rect.x - 3; + r2.y = Menus[i].window.rect.y - 3; + r2.w = r2.h = 7; + + if( Rect_ContainsPoint( &r2, x, y ) ) + return CURSOR_SIZER; + } + + return CURSOR_ARROW; +} + + +void Display_HandleKey( int key, qboolean down, int x, int y ) +{ + menuDef_t *menu = Display_CaptureItem( x, y ); + + if( menu == NULL ) + menu = Menu_GetFocused(); + + if( menu ) + Menu_HandleKey( menu, key, down ); +} + +static void Window_CacheContents( windowDef_t *window ) +{ + if( window ) + { + if( window->cinematicName ) + { + int cin = DC->playCinematic( window->cinematicName, 0, 0, 0, 0 ); + DC->stopCinematic( cin ); + } + } +} + + +static void Item_CacheContents( itemDef_t *item ) +{ + if( item ) + Window_CacheContents( &item->window ); + +} + +static void Menu_CacheContents( menuDef_t *menu ) +{ + if( menu ) + { + int i; + Window_CacheContents( &menu->window ); + + for( i = 0; i < menu->itemCount; i++ ) + Item_CacheContents( menu->items[i] ); + + if( menu->soundName && *menu->soundName ) + DC->registerSound( menu->soundName, qfalse ); + } + +} + +void Display_CacheAll( void ) +{ + int i; + + for( i = 0; i < menuCount; i++ ) + Menu_CacheContents( &Menus[i] ); +} + + +static qboolean Menu_OverActiveItem( menuDef_t *menu, float x, float y ) +{ + if( menu && menu->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) + { + if( Rect_ContainsPoint( &menu->window.rect, x, y ) ) + { + int i; + + for( i = 0; i < menu->itemCount; i++ ) + { + if( !( menu->items[i]->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) ) + continue; + + if( menu->items[i]->window.flags & WINDOW_DECORATION ) + continue; + + if( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) ) + { + itemDef_t * overItem = menu->items[i]; + + if( overItem->type == ITEM_TYPE_TEXT && overItem->text ) + { + if( Rect_ContainsPoint( Item_CorrectedTextRect( overItem ), x, y ) ) + return qtrue; + else + continue; + } + else + return qtrue; + } + } + + } + } + + return qfalse; +} diff --git a/src/ui/ui_shared.h b/src/ui/ui_shared.h new file mode 100644 index 0000000..649d51f --- /dev/null +++ b/src/ui/ui_shared.h @@ -0,0 +1,559 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef __UI_SHARED_H +#define __UI_SHARED_H + + +#include "../qcommon/q_shared.h" +#include "../renderer/tr_types.h" +#include "../client/keycodes.h" + +#include "../../assets/ui/menudef.h" + +#define MAX_MENUNAME 32 +#define MAX_ITEMTEXT 64 +#define MAX_ITEMACTION 64 +#define MAX_MENUDEFFILE 4096 +#define MAX_MENUFILE 32768 +#define MAX_MENUS 256 +#define MAX_MENUITEMS 128 +#define MAX_COLOR_RANGES 10 +#define MAX_OPEN_MENUS 16 + +#define WINDOW_MOUSEOVER 0x00000001 // mouse is over it, non exclusive +#define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive +#define WINDOW_VISIBLE 0x00000004 // is visible +#define WINDOW_GREY 0x00000008 // is visible but grey ( non-active ) +#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc.. +#define WINDOW_FADINGOUT 0x00000020 // fading out, non-active +#define WINDOW_FADINGIN 0x00000040 // fading in +#define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive +#define WINDOW_INTRANSITION 0x00000100 // window is in transition +#define WINDOW_FORECOLORSET 0x00000200 // forecolor was explicitly set ( used to color alpha images or not ) +#define WINDOW_HORIZONTAL 0x00000400 // for list boxes and sliders, vertical is default this is set of horizontal +#define WINDOW_LB_UPARROW 0x00000800 // mouse is over up arrow +#define WINDOW_LB_DOWNARROW 0x00001000 // mouse is over down arrow +#define WINDOW_LB_THUMB 0x00002000 // mouse is over thumb +#define WINDOW_LB_PGUP 0x00004000 // mouse is over page up +#define WINDOW_LB_PGDN 0x00008000 // mouse is over page down +#define WINDOW_ORBITING 0x00010000 // item is in orbit +#define WINDOW_OOB_CLICK 0x00020000 // close on out of bounds click +#define WINDOW_WRAPPED 0x00080000 // wrap text +#define WINDOW_FORCED 0x00100000 // forced open +#define WINDOW_POPUP 0x00200000 // popup +#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set +#define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented ) + + +// CGAME cursor type bits +#define CURSOR_NONE 0x00000001 +#define CURSOR_ARROW 0x00000002 +#define CURSOR_SIZER 0x00000004 + +#ifdef CGAME +#define STRING_POOL_SIZE 128*1024 +#else +#define STRING_POOL_SIZE 384*1024 +#endif +#define MAX_STRING_HANDLES 4096 + +#define MAX_SCRIPT_ARGS 12 +#define MAX_EDITFIELD 256 +#define ITEM_VALUE_OFFSET 8 + +#define ART_FX_BASE "menu/art/fx_base" +#define ART_FX_BLUE "menu/art/fx_blue" +#define ART_FX_CYAN "menu/art/fx_cyan" +#define ART_FX_GREEN "menu/art/fx_grn" +#define ART_FX_RED "menu/art/fx_red" +#define ART_FX_TEAL "menu/art/fx_teal" +#define ART_FX_WHITE "menu/art/fx_white" +#define ART_FX_YELLOW "menu/art/fx_yel" + +#define ASSET_GRADIENTBAR "ui/assets/gradientbar2.tga" +#define ASSET_SCROLLBAR "ui/assets/scrollbar.tga" +#define ASSET_SCROLLBAR_ARROWDOWN "ui/assets/scrollbar_arrow_dwn_a.tga" +#define ASSET_SCROLLBAR_ARROWUP "ui/assets/scrollbar_arrow_up_a.tga" +#define ASSET_SCROLLBAR_ARROWLEFT "ui/assets/scrollbar_arrow_left.tga" +#define ASSET_SCROLLBAR_ARROWRIGHT "ui/assets/scrollbar_arrow_right.tga" +#define ASSET_SCROLL_THUMB "ui/assets/scrollbar_thumb.tga" +#define ASSET_SLIDER_BAR "ui/assets/slider2.tga" +#define ASSET_SLIDER_THUMB "ui/assets/sliderbutt_1.tga" + +#define SCROLLBAR_ARROW_SIZE 16.0f +#define SCROLLBAR_ARROW_WIDTH (SCROLLBAR_ARROW_SIZE*DC->aspectScale) +#define SCROLLBAR_ARROW_HEIGHT SCROLLBAR_ARROW_SIZE +#define SCROLLBAR_SLIDER_X(_item) (_item->window.rect.x+_item->window.rect.w- \ + SCROLLBAR_ARROW_WIDTH-DC->aspectScale) +#define SCROLLBAR_SLIDER_Y(_item) (SCROLLBAR_Y(_item)+SCROLLBAR_ARROW_HEIGHT) +#define SCROLLBAR_SLIDER_HEIGHT(_item) (_item->window.rect.h-(SCROLLBAR_ARROW_HEIGHT*2.0f)-2.0f) +#define SCROLLBAR_X(_item) (_item->window.rect.x+DC->aspectScale) +#define SCROLLBAR_Y(_item) (_item->window.rect.y+1.0f) +#define SCROLLBAR_W(_item) (SCROLLBAR_SLIDER_X(_item)-SCROLLBAR_X(_item)) +#define SCROLLBAR_H(_item) (_item->window.rect.h-2.0f) + +#define SLIDER_WIDTH (96.0f*DC->aspectScale) +#define SLIDER_HEIGHT 16.0f +#define SLIDER_THUMB_WIDTH (12.0f*DC->aspectScale) +#define SLIDER_THUMB_HEIGHT 20.0f +#define NUM_CROSSHAIRS 10 + +typedef struct +{ + const char *command; + const char *args[MAX_SCRIPT_ARGS]; +} +scriptDef_t; + + +typedef struct +{ + float x; // horiz position + float y; // vert position + float w; // width + float h; // height; +} +rectDef_t; + +typedef rectDef_t Rectangle; + +// FIXME: do something to separate text vs window stuff + +typedef struct +{ + Rectangle rect; // client coord rectangle + int aspectBias; // direction in which to aspect compensate + Rectangle rectClient; // screen coord rectangle + const char *name; // + const char *group; // if it belongs to a group + const char *cinematicName; // cinematic name + int cinematic; // cinematic handle + int style; // + int border; // + int ownerDraw; // ownerDraw style + int ownerDrawFlags; // show flags for ownerdraw items + float borderSize; // + int flags; // visible, focus, mouseover, cursor + Rectangle rectEffects; // for various effects + Rectangle rectEffects2; // for various effects + int offsetTime; // time based value for various effects + int nextTime; // time next effect should cycle + vec4_t foreColor; // text color + vec4_t backColor; // border color + vec4_t borderColor; // border color + vec4_t outlineColor; // border color + qhandle_t background; // background asset +} +windowDef_t; + +typedef windowDef_t Window; + +typedef struct +{ + vec4_t color; + float low; + float high; +} +colorRangeDef_t; + +// FIXME: combine flags into bitfields to save space +// FIXME: consolidate all of the common stuff in one structure for menus and items +// THINKABOUTME: is there any compelling reason not to have items contain items +// and do away with a menu per say.. major issue is not being able to dynamically allocate +// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have +// the engine just allocate the pool for it based on a cvar +// many of the vars are re-used for different item types, as such they are not always named appropriately +// the benefits of c++ in DOOM will greatly help crap like this +// FIXME: need to put a type ptr that points to specific type info per type +// +#define MAX_LB_COLUMNS 16 + +typedef struct columnInfo_s +{ + int pos; + int width; + int align; +} +columnInfo_t; + +typedef struct listBoxDef_s +{ + int startPos; + int endPos; + int cursorPos; + + float elementWidth; + float elementHeight; + int elementStyle; + int dropItems; + + int numColumns; + columnInfo_t columnInfo[MAX_LB_COLUMNS]; + + const char *doubleClick; + + qboolean notselectable; + qboolean noscrollbar; + + qboolean resetonfeederchange; + int lastFeederCount; +} +listBoxDef_t; + +typedef struct cycleDef_s +{ + int cursorPos; +} +cycleDef_t; + +typedef struct editFieldDef_s +{ + float minVal; // edit field limits + float maxVal; // + float defVal; // + float range; // + int maxChars; // for edit fields + int maxPaintChars; // for edit fields + int maxFieldWidth; // for edit fields + int paintOffset; // +} +editFieldDef_t; + +#define MAX_MULTI_CVARS 32 + +typedef struct multiDef_s +{ + const char *cvarList[MAX_MULTI_CVARS]; + const char *cvarStr[MAX_MULTI_CVARS]; + float cvarValue[MAX_MULTI_CVARS]; + int count; + qboolean strDef; +} +multiDef_t; + +typedef struct modelDef_s +{ + int angle; + vec3_t origin; + float fov_x; + float fov_y; + int rotationSpeed; +} +modelDef_t; + +#define CVAR_ENABLE 0x00000001 +#define CVAR_DISABLE 0x00000002 +#define CVAR_SHOW 0x00000004 +#define CVAR_HIDE 0x00000008 + +typedef enum +{ + TYPE_ANY = -1, + TYPE_NONE, + TYPE_LIST, + TYPE_EDIT, + TYPE_MULTI, + TYPE_COMBO, + TYPE_MODEL +} itemDataType_t; + +typedef struct itemDef_s +{ + Window window; // common positional, border, style, layout info + Rectangle textRect; // rectangle the text ( if any ) consumes + int type; // text, button, radiobutton, checkbox, textfield, listbox, combo + int alignment; // left center right + int textalignment; // ( optional ) alignment for text within rect based on text width + int textvalignment; // ( optional ) alignment for text within rect based on text width + float textalignx; // ( optional ) text alignment x coord + float textaligny; // ( optional ) text alignment x coord + float textscale; // scale percentage from 72pts + int textStyle; // ( optional ) style, normal and shadowed are it for now + const char *text; // display text + void *parent; // menu owner + qhandle_t asset; // handle to asset + const char *mouseEnterText; // mouse enter script + const char *mouseExitText; // mouse exit script + const char *mouseEnter; // mouse enter script + const char *mouseExit; // mouse exit script + const char *action; // select script + const char *onFocus; // select script + const char *leaveFocus; // select script + const char *onTextEntry; // called when text entered + const char *onCharEntry; // called when text entered + const char *cvar; // associated cvar + const char *cvarTest; // associated cvar for enable actions + const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list + int cvarFlags; // what type of action to take on cvarenables + sfxHandle_t focusSound; + int numColors; // number of color ranges + colorRangeDef_t colorRanges[MAX_COLOR_RANGES]; + int feederID; // where to get data for this item + int cursorPos; // cursor position in characters + union + { + void *data; + listBoxDef_t *list; + editFieldDef_t *edit; + multiDef_t *multi; + cycleDef_t *cycle; + modelDef_t *model; + } typeData; // type specific data pointers +} +itemDef_t; + +typedef struct +{ + Window window; + const char *font; // font + qboolean fullScreen; // covers entire screen + int itemCount; // number of items; + int fontIndex; // + int cursorItem; // which item as the cursor + int fadeCycle; // + float fadeClamp; // + float fadeAmount; // + const char *onOpen; // run when the menu is first opened + const char *onClose; // run when the menu is closed + const char *onESC; // run when the menu is closed + const char *soundName; // background loop sound for menu + + vec4_t focusColor; // focus color for items + vec4_t disableColor; // focus color for items + itemDef_t *items[MAX_MENUITEMS]; // items this menu contains +} +menuDef_t; + +typedef struct +{ + const char *fontStr; + const char *cursorStr; + const char *gradientStr; + fontInfo_t textFont; + fontInfo_t smallFont; + fontInfo_t bigFont; + qhandle_t cursor; + qhandle_t gradientBar; + qhandle_t scrollBarArrowUp; + qhandle_t scrollBarArrowDown; + qhandle_t scrollBarArrowLeft; + qhandle_t scrollBarArrowRight; + qhandle_t scrollBar; + qhandle_t scrollBarThumb; + qhandle_t buttonMiddle; + qhandle_t buttonInside; + qhandle_t solidBox; + qhandle_t sliderBar; + qhandle_t sliderThumb; + sfxHandle_t menuEnterSound; + sfxHandle_t menuExitSound; + sfxHandle_t menuBuzzSound; + sfxHandle_t itemFocusSound; + float fadeClamp; + int fadeCycle; + float fadeAmount; + float shadowX; + float shadowY; + vec4_t shadowColor; + float shadowFadeClamp; + qboolean fontRegistered; + emoticon_t emoticons[ MAX_EMOTICONS ]; + int emoticonCount; +} +cachedAssets_t; + +typedef struct +{ + const char *name; + void ( *handler ) ( itemDef_t *item, char** args ); +} +commandDef_t; + +typedef struct +{ + qhandle_t ( *registerShaderNoMip ) ( const char *p ); + void ( *setColor ) ( const vec4_t v ); + void ( *drawHandlePic ) ( float x, float y, float w, float h, qhandle_t asset ); + void ( *drawStretchPic ) ( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); + qhandle_t ( *registerModel ) ( const char *p ); + void ( *modelBounds ) ( qhandle_t model, vec3_t min, vec3_t max ); + void ( *fillRect ) ( float x, float y, float w, float h, const vec4_t color ); + void ( *drawRect ) ( float x, float y, float w, float h, float size, const vec4_t color ); + void ( *drawSides ) ( float x, float y, float w, float h, float size ); + void ( *drawTopBottom ) ( float x, float y, float w, float h, float size ); + void ( *clearScene ) ( void ); + void ( *addRefEntityToScene ) ( const refEntity_t *re ); + void ( *renderScene ) ( const refdef_t *fd ); + void ( *registerFont ) ( const char *pFontname, int pointSize, fontInfo_t *font ); + void ( *ownerDrawItem ) ( float x, float y, float w, float h, float text_x, + float text_y, int ownerDraw, int ownerDrawFlags, + int align, int textalign, int textvalign, + float borderSize, float scale, vec4_t foreColor, + vec4_t backColor, qhandle_t shader, int textStyle ); + float ( *getValue ) ( int ownerDraw ); + qboolean ( *ownerDrawVisible ) ( int flags ); + void ( *runScript )( char **p ); + void ( *getCVarString )( const char *cvar, char *buffer, int bufsize ); + float ( *getCVarValue )( const char *cvar ); + void ( *setCVar )( const char *cvar, const char *value ); + void ( *drawTextWithCursor )( float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style ); + void ( *setOverstrikeMode )( qboolean b ); + qboolean ( *getOverstrikeMode )( void ); + void ( *startLocalSound )( sfxHandle_t sfx, int channelNum ); + qboolean ( *ownerDrawHandleKey )( int ownerDraw, int key ); + int ( *feederCount )( int feederID ); + const char *( *feederItemText )( int feederID, int index, int column, qhandle_t *handle ); + qhandle_t ( *feederItemImage )( int feederID, int index ); + void ( *feederSelection )( int feederID, int index ); + int ( *feederInitialise )( int feederID ); + void ( *keynumToStringBuf )( int keynum, char *buf, int buflen ); + void ( *getBindingBuf )( int keynum, char *buf, int buflen ); + void ( *setBinding )( int keynum, const char *binding ); + void ( *executeText )( int exec_when, const char *text ); + void ( *Error )( int level, const char *error, ... ); + void ( *Print )( const char *msg, ... ); + void ( *Pause )( qboolean b ); + int ( *ownerDrawWidth )( int ownerDraw, float scale ); + const char *( *ownerDrawText )( int ownerDraw ); + sfxHandle_t ( *registerSound )( const char *name, qboolean compressed ); + void ( *startBackgroundTrack )( const char *intro, const char *loop ); + void ( *stopBackgroundTrack )( void ); + int ( *playCinematic )( const char *name, float x, float y, float w, float h ); + void ( *stopCinematic )( int handle ); + void ( *drawCinematic )( int handle, float x, float y, float w, float h ); + void ( *runCinematicFrame )( int handle ); + + float yscale; + float xscale; + float aspectScale; + int realTime; + int frameTime; + float cursorx; + float cursory; + float smallFontScale; + float bigFontScale; + qboolean debug; + + cachedAssets_t Assets; + + glconfig_t glconfig; + qhandle_t whiteShader; + qhandle_t gradientImage; + qhandle_t cursor; + float FPS; + +} +displayContextDef_t; + +const char *String_Alloc( const char *p ); +void String_Init( void ); +void String_Report( void ); +void Init_Display( displayContextDef_t *dc ); +void Display_ExpandMacros( char * buff ); +void Menu_Init( menuDef_t *menu ); +void Item_Init( itemDef_t *item ); +void Menu_PostParse( menuDef_t *menu ); +menuDef_t *Menu_GetFocused( void ); +void Menu_HandleKey( menuDef_t *menu, int key, qboolean down ); +void Menu_HandleMouseMove( menuDef_t *menu, float x, float y ); +void Menu_ScrollFeeder( menuDef_t *menu, int feeder, qboolean down ); +qboolean Float_Parse( char **p, float *f ); +qboolean Color_Parse( char **p, vec4_t *c ); +qboolean Int_Parse( char **p, int *i ); +qboolean Rect_Parse( char **p, rectDef_t *r ); +qboolean String_Parse( char **p, const char **out ); +qboolean Script_Parse( char **p, const char **out ); +qboolean PC_Float_Parse( int handle, float *f ); +qboolean PC_Color_Parse( int handle, vec4_t *c ); +qboolean PC_Int_Parse( int handle, int *i ); +qboolean PC_Rect_Parse( int handle, rectDef_t *r ); +qboolean PC_String_Parse( int handle, const char **out ); +qboolean PC_Script_Parse( int handle, const char **out ); +int Menu_Count( void ); +void Menu_New( int handle ); +void Menu_UpdateAll( void ); +void Menu_PaintAll( void ); +menuDef_t *Menus_ActivateByName( const char *p ); +menuDef_t *Menus_ReplaceActiveByName( const char *p ); +void Menu_Reset( void ); +qboolean Menus_AnyFullScreenVisible( void ); +void Menus_Activate( menuDef_t *menu ); +qboolean Menus_ReplaceActive( menuDef_t *menu ); + +displayContextDef_t *Display_GetContext( void ); +void *Display_CaptureItem( int x, int y ); +qboolean Display_MouseMove( void *p, float x, float y ); +int Display_CursorType( int x, int y ); +qboolean Display_KeyBindPending( void ); +menuDef_t *Menus_FindByName( const char *p ); +void Menus_CloseByName( const char *p ); +void Display_HandleKey( int key, qboolean down, int x, int y ); +void LerpColor( vec4_t a, vec4_t b, vec4_t c, float t ); +void Menus_CloseAll( void ); +void Menu_Update( menuDef_t *menu ); +void Menu_Paint( menuDef_t *menu, qboolean forcePaint ); +void Menu_SetFeederSelection( menuDef_t *menu, int feeder, int index, const char *name ); +void Display_CacheAll( void ); + +typedef void ( CaptureFunc ) ( void *p ); + +void UI_InstallCaptureFunc( CaptureFunc *f, void *data, int timeout ); +void UI_RemoveCaptureFunc( void ); + +void *UI_Alloc( int size ); +void UI_InitMemory( void ); +qboolean UI_OutOfMemory( void ); + +void Controls_GetConfig( void ); +void Controls_SetConfig( qboolean restart ); +void Controls_SetDefaults( void ); + +void trap_R_SetClipRegion( const float *region ); + +//for cg_draw.c +void Item_Text_Wrapped_Paint( itemDef_t *item ); +const char *Item_Text_Wrap( const char *text, float scale, float width ); +void UI_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color, + float scale, int textalign, int textvalign, + int textStyle, const char *text ); +void UI_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ); +void UI_Text_Paint_Limit( float *maxX, float x, float y, float scale, + vec4_t color, const char* text, float adjust, int limit ); +float UI_Text_Width( const char *text, float scale ); +float UI_Text_Height( const char *text, float scale ); +float UI_Text_EmWidth( float scale ); +float UI_Text_EmHeight( float scale ); +qboolean UI_Text_IsEmoticon( const char *s, qboolean *escaped, int *length, qhandle_t *h, int *width ); +void UI_EscapeEmoticons( char *dest, const char *src, int destsize ); + +int trap_Parse_AddGlobalDefine( char *define ); +int trap_Parse_LoadSource( const char *filename ); +int trap_Parse_FreeSource( int handle ); +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ); +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ); + +void BindingFromName( const char *cvar ); +extern char g_nameBind1[ 32 ]; +extern char g_nameBind2[ 32 ]; +#endif diff --git a/src/ui/ui_syscalls.asm b/src/ui/ui_syscalls.asm new file mode 100644 index 0000000..1e797a9 --- /dev/null +++ b/src/ui/ui_syscalls.asm @@ -0,0 +1,102 @@ +code + +equ trap_Error -1 +equ trap_Print -2 +equ trap_Milliseconds -3 +equ trap_Cvar_Set -4 +equ trap_Cvar_VariableValue -5 +equ trap_Cvar_VariableStringBuffer -6 +equ trap_Cvar_SetValue -7 +equ trap_Cvar_Reset -8 +equ trap_Cvar_Create -9 +equ trap_Cvar_InfoStringBuffer -10 +equ trap_Argc -11 +equ trap_Argv -12 +equ trap_Cmd_ExecuteText -13 +equ trap_FS_FOpenFile -14 +equ trap_FS_Read -15 +equ trap_FS_Write -16 +equ trap_FS_FCloseFile -17 +equ trap_FS_GetFileList -18 +equ trap_R_RegisterModel -19 +equ trap_R_RegisterSkin -20 +equ trap_R_RegisterShaderNoMip -21 +equ trap_R_ClearScene -22 +equ trap_R_AddRefEntityToScene -23 +equ trap_R_AddPolyToScene -24 +equ trap_R_AddLightToScene -25 +equ trap_R_RenderScene -26 +equ trap_R_SetColor -27 +equ trap_R_SetClipRegion -28 +equ trap_R_DrawStretchPic -29 +equ trap_UpdateScreen -30 +equ trap_CM_LerpTag -31 +equ trap_CM_LoadModel -32 +equ trap_S_RegisterSound -33 +equ trap_S_StartLocalSound -34 +equ trap_Key_KeynumToStringBuf -35 +equ trap_Key_GetBindingBuf -36 +equ trap_Key_SetBinding -37 +equ trap_Key_IsDown -38 +equ trap_Key_GetOverstrikeMode -39 +equ trap_Key_SetOverstrikeMode -40 +equ trap_Key_ClearStates -41 +equ trap_Key_GetCatcher -42 +equ trap_Key_SetCatcher -43 +equ trap_GetClipboardData -44 +equ trap_GetGlconfig -45 +equ trap_GetClientState -46 +equ trap_GetConfigString -47 +equ trap_LAN_GetPingQueueCount -48 +equ trap_LAN_ClearPing -49 +equ trap_LAN_GetPing -50 +equ trap_LAN_GetPingInfo -51 +equ trap_Cvar_Register -52 +equ trap_Cvar_Update -53 +equ trap_MemoryRemaining -54 +equ trap_R_RegisterFont -55 +equ trap_R_ModelBounds -56 +equ trap_S_StopBackgroundTrack -57 +equ trap_S_StartBackgroundTrack -58 +equ trap_RealTime -59 +equ trap_LAN_GetServerCount -60 +equ trap_LAN_GetServerAddressString -61 +equ trap_LAN_GetServerInfo -62 +equ trap_LAN_MarkServerVisible -63 +equ trap_LAN_UpdateVisiblePings -64 +equ trap_LAN_ResetPings -65 +equ trap_LAN_LoadCachedServers -66 +equ trap_LAN_SaveCachedServers -67 +equ trap_LAN_AddServer -68 +equ trap_LAN_RemoveServer -69 +equ trap_CIN_PlayCinematic -70 +equ trap_CIN_StopCinematic -71 +equ trap_CIN_RunCinematic -72 +equ trap_CIN_DrawCinematic -73 +equ trap_CIN_SetExtents -74 +equ trap_R_RemapShader -75 +equ trap_LAN_ServerStatus -76 +equ trap_LAN_GetServerPing -77 +equ trap_LAN_ServerIsVisible -78 +equ trap_LAN_CompareServers -79 +equ trap_FS_Seek -80 +equ trap_SetPbClStatus -81 + +equ trap_Parse_AddGlobalDefine -82 +equ trap_Parse_LoadSource -83 +equ trap_Parse_FreeSource -84 +equ trap_Parse_ReadToken -85 +equ trap_Parse_SourceFileAndLine -86 + +equ trap_GetNews -87 + +equ memset -101 +equ memcpy -102 +equ strncpy -103 +equ sin -104 +equ cos -105 +equ atan2 -106 +equ sqrt -107 +equ floor -108 +equ ceil -109 + diff --git a/src/ui/ui_syscalls.c b/src/ui/ui_syscalls.c new file mode 100644 index 0000000..2187f18 --- /dev/null +++ b/src/ui/ui_syscalls.c @@ -0,0 +1,483 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "ui_local.h" + +// this file is only included when building a dll +// syscalls.asm is included instead when building a qvm + +static intptr_t ( QDECL *syscall )( intptr_t arg, ... ) = ( intptr_t ( QDECL * )( intptr_t, ... ) ) - 1; + +Q_EXPORT void dllEntry( intptr_t ( QDECL *syscallptr )( intptr_t arg, ... ) ) +{ + syscall = syscallptr; +} + +int PASSFLOAT( float x ) +{ + float floatTemp; + floatTemp = x; + return *( int * ) & floatTemp; +} + +void trap_Print( const char *string ) +{ + syscall( UI_PRINT, string ); +} + +void trap_Error( const char *string ) +{ + syscall( UI_ERROR, string ); +} + +int trap_Milliseconds( void ) +{ + return syscall( UI_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) +{ + syscall( UI_CVAR_REGISTER, cvar, var_name, value, flags ); +} + +void trap_Cvar_Update( vmCvar_t *cvar ) +{ + syscall( UI_CVAR_UPDATE, cvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) +{ + syscall( UI_CVAR_SET, var_name, value ); +} + +float trap_Cvar_VariableValue( const char *var_name ) +{ + int temp; + temp = syscall( UI_CVAR_VARIABLEVALUE, var_name ); + return ( *( float* ) & temp ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) +{ + syscall( UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +void trap_Cvar_SetValue( const char *var_name, float value ) +{ + syscall( UI_CVAR_SETVALUE, var_name, PASSFLOAT( value ) ); +} + +void trap_Cvar_Reset( const char *name ) +{ + syscall( UI_CVAR_RESET, name ); +} + +void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ) +{ + syscall( UI_CVAR_CREATE, var_name, var_value, flags ); +} + +void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ) +{ + syscall( UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize ); +} + +int trap_Argc( void ) +{ + return syscall( UI_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) +{ + syscall( UI_ARGV, n, buffer, bufferLength ); +} + +void trap_Cmd_ExecuteText( int exec_when, const char *text ) +{ + syscall( UI_CMD_EXECUTETEXT, exec_when, text ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) +{ + return syscall( UI_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) +{ + syscall( UI_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) +{ + syscall( UI_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) +{ + syscall( UI_FS_FCLOSEFILE, f ); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) +{ + return syscall( UI_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +int trap_FS_Seek( fileHandle_t f, long offset, int origin ) +{ + return syscall( UI_FS_SEEK, f, offset, origin ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) +{ + return syscall( UI_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) +{ + return syscall( UI_R_REGISTERSKIN, name ); +} + +void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ) +{ + syscall( UI_R_REGISTERFONT, fontName, pointSize, font ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) +{ + return syscall( UI_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_ClearScene( void ) +{ + syscall( UI_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) +{ + syscall( UI_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) +{ + syscall( UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) +{ + syscall( UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT( intensity ), PASSFLOAT( r ), PASSFLOAT( g ), PASSFLOAT( b ) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) +{ + syscall( UI_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) +{ + syscall( UI_R_SETCOLOR, rgba ); +} + +void trap_R_SetClipRegion( const float *region ) +{ + syscall( UI_R_SETCLIPREGION, region ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) +{ + syscall( UI_R_DRAWSTRETCHPIC, PASSFLOAT( x ), PASSFLOAT( y ), PASSFLOAT( w ), PASSFLOAT( h ), PASSFLOAT( s1 ), PASSFLOAT( t1 ), PASSFLOAT( s2 ), PASSFLOAT( t2 ), hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) +{ + syscall( UI_R_MODELBOUNDS, model, mins, maxs ); +} + +void trap_UpdateScreen( void ) +{ + syscall( UI_UPDATESCREEN ); +} + +int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ) +{ + return syscall( UI_CM_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT( frac ), tagName ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) +{ + syscall( UI_S_STARTLOCALSOUND, sfx, channelNum ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) +{ + return syscall( UI_S_REGISTERSOUND, sample, compressed ); +} + +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) +{ + syscall( UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen ); +} + +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) +{ + syscall( UI_KEY_GETBINDINGBUF, keynum, buf, buflen ); +} + +void trap_Key_SetBinding( int keynum, const char *binding ) +{ + syscall( UI_KEY_SETBINDING, keynum, binding ); +} + +qboolean trap_Key_IsDown( int keynum ) +{ + return syscall( UI_KEY_ISDOWN, keynum ); +} + +qboolean trap_Key_GetOverstrikeMode( void ) +{ + return syscall( UI_KEY_GETOVERSTRIKEMODE ); +} + +void trap_Key_SetOverstrikeMode( qboolean state ) +{ + syscall( UI_KEY_SETOVERSTRIKEMODE, state ); +} + +void trap_Key_ClearStates( void ) +{ + syscall( UI_KEY_CLEARSTATES ); +} + +int trap_Key_GetCatcher( void ) +{ + return syscall( UI_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) +{ + syscall( UI_KEY_SETCATCHER, catcher ); +} + +void trap_GetClipboardData( char *buf, int bufsize ) +{ + syscall( UI_GETCLIPBOARDDATA, buf, bufsize ); +} + +void trap_GetClientState( uiClientState_t *state ) +{ + syscall( UI_GETCLIENTSTATE, state ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) +{ + syscall( UI_GETGLCONFIG, glconfig ); +} + +int trap_GetConfigString( int index, char* buff, int buffsize ) +{ + return syscall( UI_GETCONFIGSTRING, index, buff, buffsize ); +} + +int trap_LAN_GetServerCount( int source ) +{ + return syscall( UI_LAN_GETSERVERCOUNT, source ); +} + +void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) +{ + syscall( UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen ); +} + +void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ) +{ + syscall( UI_LAN_GETSERVERINFO, source, n, buf, buflen ); +} + +int trap_LAN_GetServerPing( int source, int n ) +{ + return syscall( UI_LAN_GETSERVERPING, source, n ); +} + +int trap_LAN_GetPingQueueCount( void ) +{ + return syscall( UI_LAN_GETPINGQUEUECOUNT ); +} + +int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ) +{ + return syscall( UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen ); +} + +qboolean trap_GetNews( qboolean force ) +{ + return syscall( UI_GETNEWS, force ); +} + +void trap_LAN_SaveCachedServers( void ) +{ + syscall( UI_LAN_SAVECACHEDSERVERS ); +} + +void trap_LAN_LoadCachedServers( void ) +{ + syscall( UI_LAN_LOADCACHEDSERVERS ); +} + +void trap_LAN_ResetPings( int n ) +{ + syscall( UI_LAN_RESETPINGS, n ); +} + +void trap_LAN_ClearPing( int n ) +{ + syscall( UI_LAN_CLEARPING, n ); +} + +void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) +{ + syscall( UI_LAN_GETPING, n, buf, buflen, pingtime ); +} + +void trap_LAN_GetPingInfo( int n, char *buf, int buflen ) +{ + syscall( UI_LAN_GETPINGINFO, n, buf, buflen ); +} + +void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ) +{ + syscall( UI_LAN_MARKSERVERVISIBLE, source, n, visible ); +} + +int trap_LAN_ServerIsVisible( int source, int n ) +{ + return syscall( UI_LAN_SERVERISVISIBLE, source, n ); +} + +qboolean trap_LAN_UpdateVisiblePings( int source ) +{ + return syscall( UI_LAN_UPDATEVISIBLEPINGS, source ); +} + +int trap_LAN_AddServer( int source, const char *name, const char *addr ) +{ + return syscall( UI_LAN_ADDSERVER, source, name, addr ); +} + +void trap_LAN_RemoveServer( int source, const char *addr ) +{ + syscall( UI_LAN_REMOVESERVER, source, addr ); +} + +int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) +{ + return syscall( UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2 ); +} + +int trap_MemoryRemaining( void ) +{ + return syscall( UI_MEMORY_REMAINING ); +} + +int trap_Parse_AddGlobalDefine( char *define ) +{ + return syscall( UI_PARSE_ADD_GLOBAL_DEFINE, define ); +} + +int trap_Parse_LoadSource( const char *filename ) +{ + return syscall( UI_PARSE_LOAD_SOURCE, filename ); +} + +int trap_Parse_FreeSource( int handle ) +{ + return syscall( UI_PARSE_FREE_SOURCE, handle ); +} + +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ) +{ + return syscall( UI_PARSE_READ_TOKEN, handle, pc_token ); +} + +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) +{ + return syscall( UI_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_S_StopBackgroundTrack( void ) +{ + syscall( UI_S_STOPBACKGROUNDTRACK ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) +{ + syscall( UI_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +int trap_RealTime( qtime_t *qtime ) +{ + return syscall( UI_REAL_TIME, qtime ); +} + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ) +{ + return syscall( UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits ); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic( int handle ) +{ + return syscall( UI_CIN_STOPCINEMATIC, handle ); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic ( int handle ) +{ + return syscall( UI_CIN_RUNCINEMATIC, handle ); +} + + +// draws the current frame +void trap_CIN_DrawCinematic ( int handle ) +{ + syscall( UI_CIN_DRAWCINEMATIC, handle ); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents ( int handle, int x, int y, int w, int h ) +{ + syscall( UI_CIN_SETEXTENTS, handle, x, y, w, h ); +} + + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) +{ + syscall( UI_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_SetPbClStatus( int status ) +{ + syscall( UI_SET_PBCLSTATUS, status ); +} diff --git a/src/zlib/adler32.c b/src/zlib/adler32.c new file mode 100644 index 0000000..007ba26 --- /dev/null +++ b/src/zlib/adler32.c @@ -0,0 +1,149 @@ +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +#define BASE 65521UL /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* use NO_DIVIDE if your processor does not do division in hardware */ +#ifdef NO_DIVIDE +# define MOD(a) \ + do { \ + if (a >= (BASE << 16)) a -= (BASE << 16); \ + if (a >= (BASE << 15)) a -= (BASE << 15); \ + if (a >= (BASE << 14)) a -= (BASE << 14); \ + if (a >= (BASE << 13)) a -= (BASE << 13); \ + if (a >= (BASE << 12)) a -= (BASE << 12); \ + if (a >= (BASE << 11)) a -= (BASE << 11); \ + if (a >= (BASE << 10)) a -= (BASE << 10); \ + if (a >= (BASE << 9)) a -= (BASE << 9); \ + if (a >= (BASE << 8)) a -= (BASE << 8); \ + if (a >= (BASE << 7)) a -= (BASE << 7); \ + if (a >= (BASE << 6)) a -= (BASE << 6); \ + if (a >= (BASE << 5)) a -= (BASE << 5); \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +# define MOD4(a) \ + do { \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +#else +# define MOD(a) a %= BASE +# define MOD4(a) a %= BASE +#endif + +/* ========================================================================= */ +uLong ZEXPORT adler32(adler, buf, len) + uLong adler; + const Bytef *buf; + uInt len; +{ + unsigned long sum2; + unsigned n; + + /* split Adler-32 into component sums */ + sum2 = (adler >> 16) & 0xffff; + adler &= 0xffff; + + /* in case user likes doing a byte at a time, keep it fast */ + if (len == 1) { + adler += buf[0]; + if (adler >= BASE) + adler -= BASE; + sum2 += adler; + if (sum2 >= BASE) + sum2 -= BASE; + return adler | (sum2 << 16); + } + + /* initial Adler-32 value (deferred check for len == 1 speed) */ + if (buf == Z_NULL) + return 1L; + + /* in case short lengths are provided, keep it somewhat fast */ + if (len < 16) { + while (len--) { + adler += *buf++; + sum2 += adler; + } + if (adler >= BASE) + adler -= BASE; + MOD4(sum2); /* only added so many BASE's */ + return adler | (sum2 << 16); + } + + /* do length NMAX blocks -- requires just one modulo operation */ + while (len >= NMAX) { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + do { + DO16(buf); /* 16 sums unrolled */ + buf += 16; + } while (--n); + MOD(adler); + MOD(sum2); + } + + /* do remaining bytes (less than NMAX, still just one modulo) */ + if (len) { /* avoid modulos if none remaining */ + while (len >= 16) { + len -= 16; + DO16(buf); + buf += 16; + } + while (len--) { + adler += *buf++; + sum2 += adler; + } + MOD(adler); + MOD(sum2); + } + + /* return recombined sums */ + return adler | (sum2 << 16); +} + +/* ========================================================================= */ +uLong ZEXPORT adler32_combine(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off_t len2; +{ + unsigned long sum1; + unsigned long sum2; + unsigned rem; + + /* the derivation of this formula is left as an exercise for the reader */ + rem = (unsigned)(len2 % BASE); + sum1 = adler1 & 0xffff; + sum2 = rem * sum1; + MOD(sum2); + sum1 += (adler2 & 0xffff) + BASE - 1; + sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem; + if (sum1 > BASE) sum1 -= BASE; + if (sum1 > BASE) sum1 -= BASE; + if (sum2 > (BASE << 1)) sum2 -= (BASE << 1); + if (sum2 > BASE) sum2 -= BASE; + return sum1 | (sum2 << 16); +} diff --git a/src/zlib/crc32.c b/src/zlib/crc32.c new file mode 100644 index 0000000..f658a9e --- /dev/null +++ b/src/zlib/crc32.c @@ -0,0 +1,423 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id$ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + */ + +#ifdef MAKECRCH +# include +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +#include "zutil.h" /* for STDC and FAR definitions */ + +#define local static + +/* Find a four-byte integer type for crc32_little() and crc32_big(). */ +#ifndef NOBYFOUR +# ifdef STDC /* need ANSI C limits.h to determine sizes */ +# include +# define BYFOUR +# if (UINT_MAX == 0xffffffffUL) + typedef unsigned int u4; +# else +# if (ULONG_MAX == 0xffffffffUL) + typedef unsigned long u4; +# else +# if (USHRT_MAX == 0xffffffffUL) + typedef unsigned short u4; +# else +# undef BYFOUR /* can't find a four-byte integer type! */ +# endif +# endif +# endif +# endif /* STDC */ +#endif /* !NOBYFOUR */ + +/* Definitions for doing the crc four data bytes at a time. */ +#ifdef BYFOUR +# define REV(w) (((w)>>24)+(((w)>>8)&0xff00)+ \ + (((w)&0xff00)<<8)+(((w)&0xff)<<24)) + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, unsigned)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, unsigned)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local unsigned long FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const unsigned long FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + unsigned long c; + int n, k; + unsigned long poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0UL; + for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++) + poly |= 1UL << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (unsigned long)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = REV(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = REV(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const unsigned long FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const unsigned long FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", table[n], + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +#include "crc32.h" +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const unsigned long FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const unsigned long FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + u4 endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +#ifdef BYFOUR + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register u4 c; + register const u4 FAR *buf4; + + c = (u4)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *++buf4; \ + c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register u4 c; + register const u4 FAR *buf4; + + c = REV((u4)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4--; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf4++; + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + } while (--len); + c = ~c; + return (unsigned long)(REV(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times(mat, vec) + unsigned long *mat; + unsigned long vec; +{ + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square(square, mat) + unsigned long *square; + unsigned long *mat; +{ + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off_t len2; +{ + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case */ + if (len2 == 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320L; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} diff --git a/src/zlib/crc32.h b/src/zlib/crc32.h new file mode 100644 index 0000000..8053b61 --- /dev/null +++ b/src/zlib/crc32.h @@ -0,0 +1,441 @@ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const unsigned long FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; diff --git a/src/zlib/inffast.c b/src/zlib/inffast.c new file mode 100644 index 0000000..bbee92e --- /dev/null +++ b/src/zlib/inffast.c @@ -0,0 +1,318 @@ +/* inffast.c -- fast decoding + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifndef ASMINF + +/* Allow machine dependent optimization for post-increment or pre-increment. + Based on testing to date, + Pre-increment preferred for: + - PowerPC G3 (Adler) + - MIPS R5000 (Randers-Pehrson) + Post-increment preferred for: + - none + No measurable difference: + - Pentium III (Anderson) + - M68060 (Nikl) + */ +#ifdef POSTINC +# define OFF 0 +# define PUP(a) *(a)++ +#else +# define OFF 1 +# define PUP(a) *++(a) +#endif + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + unsigned char FAR *in; /* local strm->next_in */ + unsigned char FAR *last; /* while in < last, enough input available */ + unsigned char FAR *out; /* local strm->next_out */ + unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ + unsigned char FAR *end; /* while out < end, enough space available */ +#ifdef INFLATE_STRICT + unsigned dmax; /* maximum distance from zlib header */ +#endif + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */ + unsigned long hold; /* local strm->hold */ + unsigned bits; /* local strm->bits */ + code const FAR *lcode; /* local strm->lencode */ + code const FAR *dcode; /* local strm->distcode */ + unsigned lmask; /* mask for first level of length codes */ + unsigned dmask; /* mask for first level of distance codes */ + code this; /* retrieved table entry */ + unsigned op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + unsigned len; /* match length, unused bytes */ + unsigned dist; /* match distance */ + unsigned char FAR *from; /* where to copy match from */ + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + in = strm->next_in - OFF; + last = in + (strm->avail_in - 5); + out = strm->next_out - OFF; + beg = out - (start - strm->avail_out); + end = out + (strm->avail_out - 257); +#ifdef INFLATE_STRICT + dmax = state->dmax; +#endif + wsize = state->wsize; + whave = state->whave; + write = state->write; + window = state->window; + hold = state->hold; + bits = state->bits; + lcode = state->lencode; + dcode = state->distcode; + lmask = (1U << state->lenbits) - 1; + dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + do { + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + this = lcode[hold & lmask]; + dolen: + op = (unsigned)(this.bits); + hold >>= op; + bits -= op; + op = (unsigned)(this.op); + if (op == 0) { /* literal */ + Tracevv((stderr, this.val >= 0x20 && this.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", this.val)); + PUP(out) = (unsigned char)(this.val); + } + else if (op & 16) { /* length base */ + len = (unsigned)(this.val); + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + len += (unsigned)hold & ((1U << op) - 1); + hold >>= op; + bits -= op; + } + Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + this = dcode[hold & dmask]; + dodist: + op = (unsigned)(this.bits); + hold >>= op; + bits -= op; + op = (unsigned)(this.op); + if (op & 16) { /* distance base */ + dist = (unsigned)(this.val); + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + } + dist += (unsigned)hold & ((1U << op) - 1); +#ifdef INFLATE_STRICT + if (dist > dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + hold >>= op; + bits -= op; + Tracevv((stderr, "inflate: distance %u\n", dist)); + op = (unsigned)(out - beg); /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + from = window - OFF; + if (write == 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + else if (write < op) { /* wrap around window */ + from += wsize + write - op; + op -= write; + if (op < len) { /* some from end of window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = window - OFF; + if (write < len) { /* some from start of window */ + op = write; + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + } + else { /* contiguous in window */ + from += write - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + while (len > 2) { + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + else { + from = out - dist; /* copy direct from output */ + do { /* minimum length is three */ + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } while (len > 2); + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + } + else if ((op & 64) == 0) { /* 2nd level distance code */ + this = dcode[this.val + (hold & ((1U << op) - 1))]; + goto dodist; + } + else { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + } + else if ((op & 64) == 0) { /* 2nd level length code */ + this = lcode[this.val + (hold & ((1U << op) - 1))]; + goto dolen; + } + else if (op & 32) { /* end-of-block */ + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + else { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + } while (in < last && out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + in -= len; + bits -= len << 3; + hold &= (1U << bits) - 1; + + /* update state and return */ + strm->next_in = in + OFF; + strm->next_out = out + OFF; + strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last)); + strm->avail_out = (unsigned)(out < end ? + 257 + (end - out) : 257 - (out - end)); + state->hold = hold; + state->bits = bits; + return; +} + +/* + inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe): + - Using bit fields for code structure + - Different op definition to avoid & for extra bits (do & for table bits) + - Three separate decoding do-loops for direct, window, and write == 0 + - Special case for distance > 1 copies to do overlapped load and store copy + - Explicit branch predictions (based on measured branch probabilities) + - Deferring match copy and interspersed it with decoding subsequent codes + - Swapping literal/length else + - Swapping window/direct else + - Larger unrolled copy loops (three is about right) + - Moving len -= 3 statement into middle of loop + */ + +#endif /* !ASMINF */ diff --git a/src/zlib/inffast.h b/src/zlib/inffast.h new file mode 100644 index 0000000..1e88d2d --- /dev/null +++ b/src/zlib/inffast.h @@ -0,0 +1,11 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +void inflate_fast OF((z_streamp strm, unsigned start)); diff --git a/src/zlib/inffixed.h b/src/zlib/inffixed.h new file mode 100644 index 0000000..75ed4b5 --- /dev/null +++ b/src/zlib/inffixed.h @@ -0,0 +1,94 @@ + /* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ + + /* WARNING: this file should *not* be used by applications. It + is part of the implementation of the compression library and + is subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} + }; + + static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} + }; diff --git a/src/zlib/inflate.c b/src/zlib/inflate.c new file mode 100644 index 0000000..792fdee --- /dev/null +++ b/src/zlib/inflate.c @@ -0,0 +1,1368 @@ +/* inflate.c -- zlib decompression + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * Change history: + * + * 1.2.beta0 24 Nov 2002 + * - First version -- complete rewrite of inflate to simplify code, avoid + * creation of window when not needed, minimize use of window when it is + * needed, make inffast.c even faster, implement gzip decoding, and to + * improve code readability and style over the previous zlib inflate code + * + * 1.2.beta1 25 Nov 2002 + * - Use pointers for available input and output checking in inffast.c + * - Remove input and output counters in inffast.c + * - Change inffast.c entry and loop from avail_in >= 7 to >= 6 + * - Remove unnecessary second byte pull from length extra in inffast.c + * - Unroll direct copy to three copies per loop in inffast.c + * + * 1.2.beta2 4 Dec 2002 + * - Change external routine names to reduce potential conflicts + * - Correct filename to inffixed.h for fixed tables in inflate.c + * - Make hbuf[] unsigned char to match parameter type in inflate.c + * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset) + * to avoid negation problem on Alphas (64 bit) in inflate.c + * + * 1.2.beta3 22 Dec 2002 + * - Add comments on state->bits assertion in inffast.c + * - Add comments on op field in inftrees.h + * - Fix bug in reuse of allocated window after inflateReset() + * - Remove bit fields--back to byte structure for speed + * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths + * - Change post-increments to pre-increments in inflate_fast(), PPC biased? + * - Add compile time option, POSTINC, to use post-increments instead (Intel?) + * - Make MATCH copy in inflate() much faster for when inflate_fast() not used + * - Use local copies of stream next and avail values, as well as local bit + * buffer and bit count in inflate()--for speed when inflate_fast() not used + * + * 1.2.beta4 1 Jan 2003 + * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings + * - Move a comment on output buffer sizes from inffast.c to inflate.c + * - Add comments in inffast.c to introduce the inflate_fast() routine + * - Rearrange window copies in inflate_fast() for speed and simplification + * - Unroll last copy for window match in inflate_fast() + * - Use local copies of window variables in inflate_fast() for speed + * - Pull out common write == 0 case for speed in inflate_fast() + * - Make op and len in inflate_fast() unsigned for consistency + * - Add FAR to lcode and dcode declarations in inflate_fast() + * - Simplified bad distance check in inflate_fast() + * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new + * source file infback.c to provide a call-back interface to inflate for + * programs like gzip and unzip -- uses window as output buffer to avoid + * window copying + * + * 1.2.beta5 1 Jan 2003 + * - Improved inflateBack() interface to allow the caller to provide initial + * input in strm. + * - Fixed stored blocks bug in inflateBack() + * + * 1.2.beta6 4 Jan 2003 + * - Added comments in inffast.c on effectiveness of POSTINC + * - Typecasting all around to reduce compiler warnings + * - Changed loops from while (1) or do {} while (1) to for (;;), again to + * make compilers happy + * - Changed type of window in inflateBackInit() to unsigned char * + * + * 1.2.beta7 27 Jan 2003 + * - Changed many types to unsigned or unsigned short to avoid warnings + * - Added inflateCopy() function + * + * 1.2.0 9 Mar 2003 + * - Changed inflateBack() interface to provide separate opaque descriptors + * for the in() and out() functions + * - Changed inflateBack() argument and in_func typedef to swap the length + * and buffer address return values for the input function + * - Check next_in and next_out for Z_NULL on entry to inflate() + * + * The history for versions after 1.2.0 are in ChangeLog in zlib distribution. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); +local int updatewindow OF((z_streamp strm, unsigned out)); +#ifdef BUILDFIXED + void makefixed OF((void)); +#endif +local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf, + unsigned len)); + +int ZEXPORT inflateReset(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + strm->total_in = strm->total_out = state->total = 0; + strm->msg = Z_NULL; + strm->adler = 1; /* to support ill-conceived Java test suite */ + state->mode = HEAD; + state->last = 0; + state->havedict = 0; + state->dmax = 32768U; + state->head = Z_NULL; + state->wsize = 0; + state->whave = 0; + state->write = 0; + state->hold = 0; + state->bits = 0; + state->lencode = state->distcode = state->next = state->codes; + Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +int ZEXPORT inflatePrime(strm, bits, value) +z_streamp strm; +int bits; +int value; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR; + value &= (1L << bits) - 1; + state->hold += value << state->bits; + state->bits += bits; + return Z_OK; +} + +int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size) +z_streamp strm; +int windowBits; +const char *version; +int stream_size; +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL) return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *) + ZALLOC(strm, 1, sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + if (windowBits < 0) { + state->wrap = 0; + windowBits = -windowBits; + } + else { + state->wrap = (windowBits >> 4) + 1; +#ifdef GUNZIP + if (windowBits < 48) windowBits &= 15; +#endif + } + if (windowBits < 8 || windowBits > 15) { + ZFREE(strm, state); + strm->state = Z_NULL; + return Z_STREAM_ERROR; + } + state->wbits = (unsigned)windowBits; + state->window = Z_NULL; + return inflateReset(strm); +} + +int ZEXPORT inflateInit_(strm, version, stream_size) +z_streamp strm; +const char *version; +int stream_size; +{ + return inflateInit2_(strm, DEF_WBITS, version, stream_size); +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables(state) +struct inflate_state FAR *state; +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +#include + +/* + Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also + defines BUILDFIXED, so the tables are built on the fly. makefixed() writes + those tables to stdout, which would be piped to inffixed.h. A small program + can simply call makefixed to do this: + + void makefixed(void); + + int main(void) + { + makefixed(); + return 0; + } + + Then that can be linked with zlib built with MAKEFIXED defined and run: + + a.out > inffixed.h + */ +void makefixed() +{ + unsigned low, size; + struct inflate_state state; + + fixedtables(&state); + puts(" /* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", state.lencode[low].op, state.lencode[low].bits, + state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +local int updatewindow(strm, out) +z_streamp strm; +unsigned out; +{ + struct inflate_state FAR *state; + unsigned copy, dist; + + state = (struct inflate_state FAR *)strm->state; + + /* if it hasn't been done already, allocate space for the window */ + if (state->window == Z_NULL) { + state->window = (unsigned char FAR *) + ZALLOC(strm, 1U << state->wbits, + sizeof(unsigned char)); + if (state->window == Z_NULL) return 1; + } + + /* if window not in use yet, initialize */ + if (state->wsize == 0) { + state->wsize = 1U << state->wbits; + state->write = 0; + state->whave = 0; + } + + /* copy state->wsize or less output bytes into the circular window */ + copy = out - strm->avail_out; + if (copy >= state->wsize) { + zmemcpy(state->window, strm->next_out - state->wsize, state->wsize); + state->write = 0; + state->whave = state->wsize; + } + else { + dist = state->wsize - state->write; + if (dist > copy) dist = copy; + zmemcpy(state->window + state->write, strm->next_out - copy, dist); + copy -= dist; + if (copy) { + zmemcpy(state->window, strm->next_out - copy, copy); + state->write = copy; + state->whave = state->wsize; + } + else { + state->write += dist; + if (state->write == state->wsize) state->write = 0; + if (state->whave < state->wsize) state->whave += dist; + } + } + return 0; +} + +/* Macros for inflate(): */ + +/* check function to use adler32() for zlib or crc32() for gzip */ +#ifdef GUNZIP +# define UPDATE(check, buf, len) \ + (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) +#else +# define UPDATE(check, buf, len) adler32(check, buf, len) +#endif + +/* check macros for header crc */ +#ifdef GUNZIP +# define CRC2(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + check = crc32(check, hbuf, 2); \ + } while (0) + +# define CRC4(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + hbuf[2] = (unsigned char)((word) >> 16); \ + hbuf[3] = (unsigned char)((word) >> 24); \ + check = crc32(check, hbuf, 4); \ + } while (0) +#endif + +/* Load registers with state in inflate() for speed */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Restore state from registers in inflate() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflate() + if there is no input available. */ +#define PULLBYTE() \ + do { \ + if (have == 0) goto inf_leave; \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflate(). */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Reverse the bytes in a 32-bit value */ +#define REVERSE(q) \ + ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + +/* + inflate() uses a state machine to process as much input data and generate as + much output data as possible before returning. The state machine is + structured roughly as follows: + + for (;;) switch (state) { + ... + case STATEn: + if (not enough input data or output space to make progress) + return; + ... make progress ... + state = STATEm; + break; + ... + } + + so when inflate() is called again, the same case is attempted again, and + if the appropriate resources are provided, the machine proceeds to the + next state. The NEEDBITS() macro is usually the way the state evaluates + whether it can proceed or should return. NEEDBITS() does the return if + the requested bits are not available. The typical use of the BITS macros + is: + + NEEDBITS(n); + ... do something with BITS(n) ... + DROPBITS(n); + + where NEEDBITS(n) either returns from inflate() if there isn't enough + input left to load n bits into the accumulator, or it continues. BITS(n) + gives the low n bits in the accumulator. When done, DROPBITS(n) drops + the low n bits off the accumulator. INITBITS() clears the accumulator + and sets the number of available bits to zero. BYTEBITS() discards just + enough bits to put the accumulator on a byte boundary. After BYTEBITS() + and a NEEDBITS(8), then BITS(8) would return the next byte in the stream. + + NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return + if there is no input available. The decoding of variable length codes uses + PULLBYTE() directly in order to pull just enough bytes to decode the next + code, and no more. + + Some states loop until they get enough input, making sure that enough + state information is maintained to continue the loop where it left off + if NEEDBITS() returns in the loop. For example, want, need, and keep + would all have to actually be part of the saved state in case NEEDBITS() + returns: + + case STATEw: + while (want < need) { + NEEDBITS(n); + keep[want++] = BITS(n); + DROPBITS(n); + } + state = STATEx; + case STATEx: + + As shown above, if the next state is also the next case, then the break + is omitted. + + A state may also return if there is not enough output space available to + complete that state. Those states are copying stored data, writing a + literal byte, and copying a matching string. + + When returning, a "goto inf_leave" is used to update the total counters, + update the check value, and determine whether any progress has been made + during that inflate() call in order to return the proper return code. + Progress is defined as a change in either strm->avail_in or strm->avail_out. + When there is a window, goto inf_leave will update the window with the last + output written. If a goto inf_leave occurs in the middle of decompression + and there is no window currently, goto inf_leave will create one and copy + output to the window for the next call of inflate(). + + In this implementation, the flush parameter of inflate() only affects the + return code (per zlib.h). inflate() always writes as much as possible to + strm->next_out, given the space available and the provided input--the effect + documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers + the allocation of and copying into a sliding window until necessary, which + provides the effect documented in zlib.h for Z_FINISH when the entire input + stream available. So the only thing the flush parameter actually does is: + when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it + will return Z_BUF_ERROR if it has not reached the end of the stream. + */ + +int ZEXPORT inflate(strm, flush) +z_streamp strm; +int flush; +{ + struct inflate_state FAR *state; + unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned in, out; /* save starting available input and output */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code this; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ +#ifdef GUNZIP + unsigned char hbuf[4]; /* buffer for gzip header crc calculation */ +#endif + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0)) + return Z_STREAM_ERROR; + + state = (struct inflate_state FAR *)strm->state; + if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */ + LOAD(); + in = have; + out = left; + ret = Z_OK; + for (;;) + switch (state->mode) { + case HEAD: + if (state->wrap == 0) { + state->mode = TYPEDO; + break; + } + NEEDBITS(16); +#ifdef GUNZIP + if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */ + state->check = crc32(0L, Z_NULL, 0); + CRC2(state->check, hold); + INITBITS(); + state->mode = FLAGS; + break; + } + state->flags = 0; /* expect zlib header */ + if (state->head != Z_NULL) + state->head->done = -1; + if (!(state->wrap & 1) || /* check if zlib header allowed */ +#else + if ( +#endif + ((BITS(8) << 8) + (hold >> 8)) % 31) { + strm->msg = (char *)"incorrect header check"; + state->mode = BAD; + break; + } + if (BITS(4) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + DROPBITS(4); + len = BITS(4) + 8; + if (len > state->wbits) { + strm->msg = (char *)"invalid window size"; + state->mode = BAD; + break; + } + state->dmax = 1U << len; + Tracev((stderr, "inflate: zlib header ok\n")); + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = hold & 0x200 ? DICTID : TYPE; + INITBITS(); + break; +#ifdef GUNZIP + case FLAGS: + NEEDBITS(16); + state->flags = (int)(hold); + if ((state->flags & 0xff) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + if (state->flags & 0xe000) { + strm->msg = (char *)"unknown header flags set"; + state->mode = BAD; + break; + } + if (state->head != Z_NULL) + state->head->text = (int)((hold >> 8) & 1); + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = TIME; + case TIME: + NEEDBITS(32); + if (state->head != Z_NULL) + state->head->time = hold; + if (state->flags & 0x0200) CRC4(state->check, hold); + INITBITS(); + state->mode = OS; + case OS: + NEEDBITS(16); + if (state->head != Z_NULL) { + state->head->xflags = (int)(hold & 0xff); + state->head->os = (int)(hold >> 8); + } + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = EXLEN; + case EXLEN: + if (state->flags & 0x0400) { + NEEDBITS(16); + state->length = (unsigned)(hold); + if (state->head != Z_NULL) + state->head->extra_len = (unsigned)hold; + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + } + else if (state->head != Z_NULL) + state->head->extra = Z_NULL; + state->mode = EXTRA; + case EXTRA: + if (state->flags & 0x0400) { + copy = state->length; + if (copy > have) copy = have; + if (copy) { + if (state->head != Z_NULL && + state->head->extra != Z_NULL) { + len = state->head->extra_len - state->length; + zmemcpy(state->head->extra + len, next, + len + copy > state->head->extra_max ? + state->head->extra_max - len : copy); + } + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + state->length -= copy; + } + if (state->length) goto inf_leave; + } + state->length = 0; + state->mode = NAME; + case NAME: + if (state->flags & 0x0800) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->name != Z_NULL && + state->length < state->head->name_max) + state->head->name[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->name = Z_NULL; + state->length = 0; + state->mode = COMMENT; + case COMMENT: + if (state->flags & 0x1000) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->comment != Z_NULL && + state->length < state->head->comm_max) + state->head->comment[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->comment = Z_NULL; + state->mode = HCRC; + case HCRC: + if (state->flags & 0x0200) { + NEEDBITS(16); + if (hold != (state->check & 0xffff)) { + strm->msg = (char *)"header crc mismatch"; + state->mode = BAD; + break; + } + INITBITS(); + } + if (state->head != Z_NULL) { + state->head->hcrc = (int)((state->flags >> 9) & 1); + state->head->done = 1; + } + strm->adler = state->check = crc32(0L, Z_NULL, 0); + state->mode = TYPE; + break; +#endif + case DICTID: + NEEDBITS(32); + strm->adler = state->check = REVERSE(hold); + INITBITS(); + state->mode = DICT; + case DICT: + if (state->havedict == 0) { + RESTORE(); + return Z_NEED_DICT; + } + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = TYPE; + case TYPE: + if (flush == Z_BLOCK) goto inf_leave; + case TYPEDO: + if (state->last) { + BYTEBITS(); + state->mode = CHECK; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + case STORED: + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + state->mode = COPY; + case COPY: + copy = state->length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy == 0) goto inf_leave; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + break; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + case TABLE: + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + state->have = 0; + state->mode = LENLENS; + case LENLENS: + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + state->have = 0; + state->mode = CODELENS; + case CODELENS: + while (state->have < state->nlen + state->ndist) { + for (;;) { + this = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if (this.val < 16) { + NEEDBITS(this.bits); + DROPBITS(this.bits); + state->lens[state->have++] = this.val; + } + else { + if (this.val == 16) { + NEEDBITS(this.bits + 2); + DROPBITS(this.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = state->lens[state->have - 1]; + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (this.val == 17) { + NEEDBITS(this.bits + 3); + DROPBITS(this.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(this.bits + 7); + DROPBITS(this.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* build code tables */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + case LEN: + if (have >= 6 && left >= 258) { + RESTORE(); + inflate_fast(strm, out); + LOAD(); + break; + } + for (;;) { + this = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if (this.op && (this.op & 0xf0) == 0) { + last = this; + for (;;) { + this = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + this.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(this.bits); + state->length = (unsigned)this.val; + if ((int)(this.op) == 0) { + Tracevv((stderr, this.val >= 0x20 && this.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", this.val)); + state->mode = LIT; + break; + } + if (this.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + if (this.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + state->extra = (unsigned)(this.op) & 15; + state->mode = LENEXT; + case LENEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + state->mode = DIST; + case DIST: + for (;;) { + this = state->distcode[BITS(state->distbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if ((this.op & 0xf0) == 0) { + last = this; + for (;;) { + this = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + this.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(this.bits); + if (this.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)this.val; + state->extra = (unsigned)(this.op) & 15; + state->mode = DISTEXT; + case DISTEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } +#ifdef INFLATE_STRICT + if (state->offset > state->dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + if (state->offset > state->whave + out - left) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + state->mode = MATCH; + case MATCH: + if (left == 0) goto inf_leave; + copy = out - left; + if (state->offset > copy) { /* copy from window */ + copy = state->offset - copy; + if (copy > state->write) { + copy -= state->write; + from = state->window + (state->wsize - copy); + } + else + from = state->window + (state->write - copy); + if (copy > state->length) copy = state->length; + } + else { /* copy from output */ + from = put - state->offset; + copy = state->length; + } + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = *from++; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; + case LIT: + if (left == 0) goto inf_leave; + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + case CHECK: + if (state->wrap) { + NEEDBITS(32); + out -= left; + strm->total_out += out; + state->total += out; + if (out) + strm->adler = state->check = + UPDATE(state->check, put - out, out); + out = left; + if (( +#ifdef GUNZIP + state->flags ? hold : +#endif + REVERSE(hold)) != state->check) { + strm->msg = (char *)"incorrect data check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: check matches trailer\n")); + } +#ifdef GUNZIP + state->mode = LENGTH; + case LENGTH: + if (state->wrap && state->flags) { + NEEDBITS(32); + if (hold != (state->total & 0xffffffffUL)) { + strm->msg = (char *)"incorrect length check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: length matches trailer\n")); + } +#endif + state->mode = DONE; + case DONE: + ret = Z_STREAM_END; + goto inf_leave; + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + default: + return Z_STREAM_ERROR; + } + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + inf_leave: + RESTORE(); + if (state->wsize || (state->mode < CHECK && out != strm->avail_out)) + if (updatewindow(strm, out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + in -= strm->avail_in; + out -= strm->avail_out; + strm->total_in += in; + strm->total_out += out; + state->total += out; + if (state->wrap && out) + strm->adler = state->check = + UPDATE(state->check, strm->next_out - out, out); + strm->data_type = state->bits + (state->last ? 64 : 0) + + (state->mode == TYPE ? 128 : 0); + if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK) + ret = Z_BUF_ERROR; + return ret; +} + +int ZEXPORT inflateEnd(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->window != Z_NULL) ZFREE(strm, state->window); + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} + +int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) +z_streamp strm; +const Bytef *dictionary; +uInt dictLength; +{ + struct inflate_state FAR *state; + unsigned long id; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->wrap != 0 && state->mode != DICT) + return Z_STREAM_ERROR; + + /* check for correct dictionary id */ + if (state->mode == DICT) { + id = adler32(0L, Z_NULL, 0); + id = adler32(id, dictionary, dictLength); + if (id != state->check) + return Z_DATA_ERROR; + } + + /* copy dictionary to window */ + if (updatewindow(strm, strm->avail_out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + if (dictLength > state->wsize) { + zmemcpy(state->window, dictionary + dictLength - state->wsize, + state->wsize); + state->whave = state->wsize; + } + else { + zmemcpy(state->window + state->wsize - dictLength, dictionary, + dictLength); + state->whave = dictLength; + } + state->havedict = 1; + Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +int ZEXPORT inflateGetHeader(strm, head) +z_streamp strm; +gz_headerp head; +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if ((state->wrap & 2) == 0) return Z_STREAM_ERROR; + + /* save header structure */ + state->head = head; + head->done = 0; + return Z_OK; +} + +/* + Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found + or when out of input. When called, *have is the number of pattern bytes + found in order so far, in 0..3. On return *have is updated to the new + state. If on return *have equals four, then the pattern was found and the + return value is how many bytes were read including the last byte of the + pattern. If *have is less than four, then the pattern has not been found + yet and the return value is len. In the latter case, syncsearch() can be + called again with more data and the *have state. *have is initialized to + zero for the first call. + */ +local unsigned syncsearch(have, buf, len) +unsigned FAR *have; +unsigned char FAR *buf; +unsigned len; +{ + unsigned got; + unsigned next; + + got = *have; + next = 0; + while (next < len && got < 4) { + if ((int)(buf[next]) == (got < 2 ? 0 : 0xff)) + got++; + else if (buf[next]) + got = 0; + else + got = 4 - got; + next++; + } + *have = got; + return next; +} + +int ZEXPORT inflateSync(strm) +z_streamp strm; +{ + unsigned len; /* number of bytes to look at or looked at */ + unsigned long in, out; /* temporary to save total_in and total_out */ + unsigned char buf[4]; /* to restore bit buffer to byte string */ + struct inflate_state FAR *state; + + /* check parameters */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR; + + /* if first time, start search in bit buffer */ + if (state->mode != SYNC) { + state->mode = SYNC; + state->hold <<= state->bits & 7; + state->bits -= state->bits & 7; + len = 0; + while (state->bits >= 8) { + buf[len++] = (unsigned char)(state->hold); + state->hold >>= 8; + state->bits -= 8; + } + state->have = 0; + syncsearch(&(state->have), buf, len); + } + + /* search available input */ + len = syncsearch(&(state->have), strm->next_in, strm->avail_in); + strm->avail_in -= len; + strm->next_in += len; + strm->total_in += len; + + /* return no joy or set up to restart inflate() on a new block */ + if (state->have != 4) return Z_DATA_ERROR; + in = strm->total_in; out = strm->total_out; + inflateReset(strm); + strm->total_in = in; strm->total_out = out; + state->mode = TYPE; + return Z_OK; +} + +/* + Returns true if inflate is currently at the end of a block generated by + Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + implementation to provide an additional safety check. PPP uses + Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored + block. When decompressing, PPP checks that at the end of input packet, + inflate is waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + return state->mode == STORED && state->bits == 0; +} + +int ZEXPORT inflateCopy(dest, source) +z_streamp dest; +z_streamp source; +{ + struct inflate_state FAR *state; + struct inflate_state FAR *copy; + unsigned char FAR *window; + unsigned wsize; + + /* check input */ + if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL || + source->zalloc == (alloc_func)0 || source->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)source->state; + + /* allocate space */ + copy = (struct inflate_state FAR *) + ZALLOC(source, 1, sizeof(struct inflate_state)); + if (copy == Z_NULL) return Z_MEM_ERROR; + window = Z_NULL; + if (state->window != Z_NULL) { + window = (unsigned char FAR *) + ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + if (window == Z_NULL) { + ZFREE(source, copy); + return Z_MEM_ERROR; + } + } + + /* copy state */ + zmemcpy(dest, source, sizeof(z_stream)); + zmemcpy(copy, state, sizeof(struct inflate_state)); + if (state->lencode >= state->codes && + state->lencode <= state->codes + ENOUGH - 1) { + copy->lencode = copy->codes + (state->lencode - state->codes); + copy->distcode = copy->codes + (state->distcode - state->codes); + } + copy->next = copy->codes + (state->next - state->codes); + if (window != Z_NULL) { + wsize = 1U << state->wbits; + zmemcpy(window, state->window, wsize); + } + copy->window = window; + dest->state = (struct internal_state FAR *)copy; + return Z_OK; +} diff --git a/src/zlib/inflate.h b/src/zlib/inflate.h new file mode 100644 index 0000000..07bd3e7 --- /dev/null +++ b/src/zlib/inflate.h @@ -0,0 +1,115 @@ +/* inflate.h -- internal inflate state definition + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer decoding by inflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip decoding + should be left enabled. */ +#ifndef NO_GZIP +# define GUNZIP +#endif + +/* Possible inflate modes between inflate() calls */ +typedef enum { + HEAD, /* i: waiting for magic header */ + FLAGS, /* i: waiting for method and flags (gzip) */ + TIME, /* i: waiting for modification time (gzip) */ + OS, /* i: waiting for extra flags and operating system (gzip) */ + EXLEN, /* i: waiting for extra length (gzip) */ + EXTRA, /* i: waiting for extra bytes (gzip) */ + NAME, /* i: waiting for end of file name (gzip) */ + COMMENT, /* i: waiting for end of comment (gzip) */ + HCRC, /* i: waiting for header crc (gzip) */ + DICTID, /* i: waiting for dictionary check value */ + DICT, /* waiting for inflateSetDictionary() call */ + TYPE, /* i: waiting for type bits, including last-flag bit */ + TYPEDO, /* i: same, but skip check to exit inflate on new block */ + STORED, /* i: waiting for stored size (length and complement) */ + COPY, /* i/o: waiting for input or output to copy stored block */ + TABLE, /* i: waiting for dynamic block table lengths */ + LENLENS, /* i: waiting for code length code lengths */ + CODELENS, /* i: waiting for length/lit and distance code lengths */ + LEN, /* i: waiting for length/lit code */ + LENEXT, /* i: waiting for length extra bits */ + DIST, /* i: waiting for distance code */ + DISTEXT, /* i: waiting for distance extra bits */ + MATCH, /* o: waiting for output space to copy string */ + LIT, /* o: waiting for output space to write literal */ + CHECK, /* i: waiting for 32-bit check value */ + LENGTH, /* i: waiting for 32-bit length (gzip) */ + DONE, /* finished check, done -- remain here until reset */ + BAD, /* got a data error -- remain here until reset */ + MEM, /* got an inflate() memory error -- remain here until reset */ + SYNC /* looking for synchronization bytes to restart inflate() */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to the BAD or MEM mode -- not shown for clarity) + + Process header: + HEAD -> (gzip) or (zlib) + (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME + NAME -> COMMENT -> HCRC -> TYPE + (zlib) -> DICTID or TYPE + DICTID -> DICT -> TYPE + Read deflate blocks: + TYPE -> STORED or TABLE or LEN or CHECK + STORED -> COPY -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN + Read deflate codes: + LEN -> LENEXT or LIT or TYPE + LENEXT -> DIST -> DISTEXT -> MATCH -> LEN + LIT -> LEN + Process trailer: + CHECK -> LENGTH -> DONE + */ + +/* state maintained between inflate() calls. Approximately 7K bytes. */ +struct inflate_state { + inflate_mode mode; /* current inflate mode */ + int last; /* true if processing last block */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + int havedict; /* true if dictionary provided */ + int flags; /* gzip header method and flags (0 if zlib) */ + unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ + unsigned long check; /* protected copy of check value */ + unsigned long total; /* protected copy of output count */ + gz_headerp head; /* where to save gzip header information */ + /* sliding window */ + unsigned wbits; /* log base 2 of requested window size */ + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* bit accumulator */ + unsigned long hold; /* input bit accumulator */ + unsigned bits; /* number of bits in "in" */ + /* for string and stored block copying */ + unsigned length; /* literal or length of data to copy */ + unsigned offset; /* distance back to copy string from */ + /* for table and code decoding */ + unsigned extra; /* extra bits needed */ + /* fixed and dynamic code tables */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ +}; diff --git a/src/zlib/inftrees.c b/src/zlib/inftrees.c new file mode 100644 index 0000000..8a9c13f --- /dev/null +++ b/src/zlib/inftrees.c @@ -0,0 +1,329 @@ +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" + +#define MAXBITS 15 + +const char inflate_copyright[] = + " inflate 1.2.3 Copyright 1995-2005 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int inflate_table(type, lens, codes, table, bits, work) +codetype type; +unsigned short FAR *lens; +unsigned codes; +code FAR * FAR *table; +unsigned FAR *bits; +unsigned short FAR *work; +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code this; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 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 unsigned short lext[31] = { /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 201, 196}; + static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ + 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 unsigned short dext[32] = { /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) { /* no symbols to code at all */ + this.op = (unsigned char)64; /* invalid code marker */ + this.bits = (unsigned char)1; + this.val = (unsigned short)0; + *(*table)++ = this; /* make a table to force an error */ + *(*table)++ = this; + *bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min <= MAXBITS; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked when a LENS table is being made + against the space in *table, ENOUGH, minus the maximum space needed by + the worst case distance code, MAXD. This should never happen, but the + sufficiency of ENOUGH has not been proven exhaustively, hence the check. + This assumes that when type == LENS, bits == 9. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + this.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + this.op = (unsigned char)0; + this.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + this.op = (unsigned char)(extra[work[sym]]); + this.val = base[work[sym]]; + } + else { + this.op = (unsigned char)(32 + 64); /* end of block */ + this.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + next[(huff >> drop) + fill] = this; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* + Fill in rest of table for incomplete codes. This loop is similar to the + loop above in incrementing huff for table indices. It is assumed that + len is equal to curr + drop, so there is no loop needed to increment + through high index bits. When the current sub-table is filled, the loop + drops back to the root table to fill in any remaining entries there. + */ + this.op = (unsigned char)64; /* invalid code marker */ + this.bits = (unsigned char)(len - drop); + this.val = (unsigned short)0; + while (huff != 0) { + /* when done with sub-table, drop back to root table */ + if (drop != 0 && (huff & mask) != low) { + drop = 0; + len = root; + next = *table; + this.bits = (unsigned char)len; + } + + /* put invalid code marker in table */ + next[huff >> drop] = this; + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} diff --git a/src/zlib/inftrees.h b/src/zlib/inftrees.h new file mode 100644 index 0000000..b1104c8 --- /dev/null +++ b/src/zlib/inftrees.h @@ -0,0 +1,55 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 0001eeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1444 code structures (852 for length/literals + and 592 for distances, the latter actually the result of an + exhaustive search). The true maximum is not known, but the value + below is more than safe. */ +#define ENOUGH 2048 +#define MAXD 592 + +/* Type of code to build for inftable() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +extern int inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); diff --git a/src/zlib/zconf.h b/src/zlib/zconf.h new file mode 100644 index 0000000..9514c03 --- /dev/null +++ b/src/zlib/zconf.h @@ -0,0 +1,332 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#ifndef _MSC_VER /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/src/zlib/zlib.h b/src/zlib/zlib.h new file mode 100644 index 0000000..0228179 --- /dev/null +++ b/src/zlib/zlib.h @@ -0,0 +1,1357 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.3, July 18th, 2005 + + Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + + 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. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.2.3" +#define ZLIB_VERNUM 0x1230 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumualte before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + the value returned by deflateBound (see below). If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, + Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() stop + if and when it gets to the next deflate block boundary. When decoding the + zlib or gzip format, this will cause inflate() to return immediately after + the header and before the first block. When doing a raw inflate, inflate() + will go ahead and process the first block, and will return when it gets to + the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 + if inflate() is currently decoding the last block in the deflate stream, + plus 128 if inflate() returned immediately after decoding an end-of-block + code or decoding the complete header up to just before the first byte of the + deflate stream. The end-of-block will not be indicated until all of the + uncompressed data from that block has been written to strm->next_out. The + number of unused bits may in general be greater than seven, except when + bit 7 of data_type is set, in which case the number of unused bits will be + less than eight. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster approach + may be used for the single inflate() call. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the only effect of the flush parameter in this implementation + is on the return value of inflate(), as noted below, or when it returns early + because Z_BLOCK is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the adler32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the adler32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() will decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically. Any information + contained in the gzip header is not retained, so applications that need that + information should instead use raw inflate, see inflateInit2() below, or + inflateBack() and perform their own processing of the gzip header and + trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may then + call inflateSync() to look for a good compression block if a partial recovery + of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), + no header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as + Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy + parameter only affects the compression ratio but not the correctness of the + compressed output even if it is not set appropriately. Z_FIXED prevents the + use of dynamic Huffman codes, allowing for a simpler decoder for special + applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. In addition, the + current implementation of deflate will use at most the window size minus + 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() + or deflateInit2(). This would be used to allocate an output buffer + for deflation in a single pass, and so would be called before deflate(). +*/ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the + bits leftover from a previous deflate stream when appending to it. As such, + this function can only be used for raw deflate, and must be used before the + first deflate() call after a deflateInit2() or deflateReset(). bits must be + less than or equal to 16, and that many of the least significant bits of + value will be inserted in the output. + + deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is + a crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg + is set to null if there is no error message. inflateInit2 does not perform + any decompression apart from reading the zlib header if present: this will + be done by inflate(). (So next_in and avail_in may be modified, but next_out + and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called + immediately after inflateInit2() or inflateReset() and before any call of + inflate() to set the dictionary. The application must insure that the + dictionary that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK can be used to + force inflate() to return immediately after header processing is complete + and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When + any of extra, name, or comment are not Z_NULL and the respective field is + not present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the paramaters are invalid, Z_MEM_ERROR if the internal state could not + be allocated, or Z_VERSION_ERROR if the version of the library does not + match the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is more efficient than inflate() for + file i/o applications in that it avoids copying between the output and the + sliding window by simply making the window itself the output buffer. This + function trusts the application to not change the output buffer passed by + the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free + the allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects + only the raw deflate stream to decompress. This is different from the + normal behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format + error in the deflate stream (in which case strm->msg is set to indicate the + nature of the error), or Z_STREAM_ERROR if the stream was not properly + initialized. In the case of Z_BUF_ERROR, an input or output error can be + distinguished using strm->next_in which will be Z_NULL only if in() returned + an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to + out() returning non-zero. (in() will always be called before out(), so + strm->next_in is assured to be defined if out() returns non-zero.) Note + that inflateBack() cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least the value returned + by compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before + a compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h", or 'R' for run-length encoding + as in "wb1R". (See the description of deflateInit2 for more information + about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). The number of + uncompressed bytes written is limited to 4095. The caller should assure that + this limit is not exceeded. If it is exceeded, then gzprintf() will return + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read again later. + Only one character of push-back is allowed. gzungetc() returns the + character pushed, or -1 on failure. gzungetc() will fail if a + character has been pushed but not read yet, or if c is -1. The pushed + character will be discarded if the stream is repositioned with gzseek() + or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns 1 if file is being read directly without decompression, otherwise + zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is NULL, this function returns the required initial + value for the for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +} +#endif + +#endif /* ZLIB_H */ diff --git a/src/zlib/zutil.c b/src/zlib/zutil.c new file mode 100644 index 0000000..d55f594 --- /dev/null +++ b/src/zlib/zutil.c @@ -0,0 +1,318 @@ +/* zutil.c -- target dependent utility functions for the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zutil.h" + +#ifndef NO_DUMMY_DECL +struct internal_state {int dummy;}; /* for buggy compilers */ +#endif + +const char * const z_errmsg[10] = { +"need dictionary", /* Z_NEED_DICT 2 */ +"stream end", /* Z_STREAM_END 1 */ +"", /* Z_OK 0 */ +"file error", /* Z_ERRNO (-1) */ +"stream error", /* Z_STREAM_ERROR (-2) */ +"data error", /* Z_DATA_ERROR (-3) */ +"insufficient memory", /* Z_MEM_ERROR (-4) */ +"buffer error", /* Z_BUF_ERROR (-5) */ +"incompatible version",/* Z_VERSION_ERROR (-6) */ +""}; + + +const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +uLong ZEXPORT zlibCompileFlags() +{ + uLong flags; + + flags = 0; + switch (sizeof(uInt)) { + case 2: break; + case 4: flags += 1; break; + case 8: flags += 2; break; + default: flags += 3; + } + switch (sizeof(uLong)) { + case 2: break; + case 4: flags += 1 << 2; break; + case 8: flags += 2 << 2; break; + default: flags += 3 << 2; + } + switch (sizeof(voidpf)) { + case 2: break; + case 4: flags += 1 << 4; break; + case 8: flags += 2 << 4; break; + default: flags += 3 << 4; + } + switch (sizeof(z_off_t)) { + case 2: break; + case 4: flags += 1 << 6; break; + case 8: flags += 2 << 6; break; + default: flags += 3 << 6; + } +#ifdef DEBUG + flags += 1 << 8; +#endif +#if defined(ASMV) || defined(ASMINF) + flags += 1 << 9; +#endif +#ifdef ZLIB_WINAPI + flags += 1 << 10; +#endif +#ifdef BUILDFIXED + flags += 1 << 12; +#endif +#ifdef DYNAMIC_CRC_TABLE + flags += 1 << 13; +#endif +#ifdef NO_GZCOMPRESS + flags += 1L << 16; +#endif +#ifdef NO_GZIP + flags += 1L << 17; +#endif +#ifdef PKZIP_BUG_WORKAROUND + flags += 1L << 20; +#endif +#ifdef FASTEST + flags += 1L << 21; +#endif +#ifdef STDC +# ifdef NO_vsnprintf + flags += 1L << 25; +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif +#else + flags += 1L << 24; +# ifdef NO_snprintf + flags += 1L << 25; +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif +#endif + return flags; +} + +#ifdef DEBUG + +# ifndef verbose +# define verbose 0 +# endif +int z_verbose = verbose; + +void z_error (m) + char *m; +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif + +/* exported to allow conversion of error code to string for compress() and + * uncompress() + */ +const char * ZEXPORT zError(err) + int err; +{ + return ERR_MSG(err); +} + +#if defined(_WIN32_WCE) + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. + */ + int errno = 0; +#endif + +#ifndef HAVE_MEMCPY + +void zmemcpy(dest, source, len) + Bytef* dest; + const Bytef* source; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = *source++; /* ??? to be unrolled */ + } while (--len != 0); +} + +int zmemcmp(s1, s2, len) + const Bytef* s1; + const Bytef* s2; + uInt len; +{ + uInt j; + + for (j = 0; j < len; j++) { + if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; + } + return 0; +} + +void zmemzero(dest, len) + Bytef* dest; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = 0; /* ??? to be unrolled */ + } while (--len != 0); +} +#endif + + +#ifdef SYS16BIT + +#ifdef __TURBOC__ +/* Turbo C in 16-bit mode */ + +# define MY_ZCALLOC + +/* Turbo C malloc() does not allow dynamic allocation of 64K bytes + * and farmalloc(64K) returns a pointer with an offset of 8, so we + * must fix the pointer. Warning: the pointer must be put back to its + * original form in order to free it, use zcfree(). + */ + +#define MAX_PTR 10 +/* 10*64K = 640K */ + +local int next_ptr = 0; + +typedef struct ptr_table_s { + voidpf org_ptr; + voidpf new_ptr; +} ptr_table; + +local ptr_table table[MAX_PTR]; +/* This table is used to remember the original form of pointers + * to large buffers (64K). Such pointers are normalized with a zero offset. + * Since MSDOS is not a preemptive multitasking OS, this table is not + * protected from concurrent access. This hack doesn't work anyway on + * a protected system like OS/2. Use Microsoft C instead. + */ + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + voidpf buf = opaque; /* just to make some compilers happy */ + ulg bsize = (ulg)items*size; + + /* If we allocate less than 65520 bytes, we assume that farmalloc + * will return a usable pointer which doesn't have to be normalized. + */ + if (bsize < 65520L) { + buf = farmalloc(bsize); + if (*(ush*)&buf != 0) return buf; + } else { + buf = farmalloc(bsize + 16L); + } + if (buf == NULL || next_ptr >= MAX_PTR) return NULL; + table[next_ptr].org_ptr = buf; + + /* Normalize the pointer to seg:0 */ + *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4; + *(ush*)&buf = 0; + table[next_ptr++].new_ptr = buf; + return buf; +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + int n; + if (*(ush*)&ptr != 0) { /* object < 64K */ + farfree(ptr); + return; + } + /* Find the original pointer */ + for (n = 0; n < next_ptr; n++) { + if (ptr != table[n].new_ptr) continue; + + farfree(table[n].org_ptr); + while (++n < next_ptr) { + table[n-1] = table[n]; + } + next_ptr--; + return; + } + ptr = opaque; /* just to make some compilers happy */ + Assert(0, "zcfree: ptr not found"); +} + +#endif /* __TURBOC__ */ + + +#ifdef M_I86 +/* Microsoft C in 16-bit mode */ + +# define MY_ZCALLOC + +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) +# define _halloc halloc +# define _hfree hfree +#endif + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + return _halloc((long)items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + _hfree(ptr); +} + +#endif /* M_I86 */ + +#endif /* SYS16BIT */ + + +#ifndef MY_ZCALLOC /* Any system without a special alloc function */ + +#ifndef STDC +extern voidp malloc OF((uInt size)); +extern voidp calloc OF((uInt items, uInt size)); +extern void free OF((voidpf ptr)); +#endif + +voidpf zcalloc (opaque, items, size) + voidpf opaque; + unsigned items; + unsigned size; +{ + if (opaque) items += size - size; /* make compiler happy */ + return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : + (voidpf)calloc(items, size); +} + +void zcfree (opaque, ptr) + voidpf opaque; + voidpf ptr; +{ + free(ptr); + if (opaque) return; /* make compiler happy */ +} + +#endif /* MY_ZCALLOC */ diff --git a/src/zlib/zutil.h b/src/zlib/zutil.h new file mode 100644 index 0000000..b7d5eff --- /dev/null +++ b/src/zlib/zutil.h @@ -0,0 +1,269 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef ZUTIL_H +#define ZUTIL_H + +#define ZLIB_INTERNAL +#include "zlib.h" + +#ifdef STDC +# ifndef _WIN32_WCE +# include +# endif +# include +# include +#endif +#ifdef NO_ERRNO_H +# ifdef _WIN32_WCE + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. We rename it to + * avoid conflict with other libraries that use the same workaround. + */ +# define errno z_errno +# endif + extern int errno; +#else +# ifndef _WIN32_WCE +# include +# endif +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if(__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +# ifdef M_I86 + #include +# endif +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 0x07 +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifdef WIN32 +# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */ +# define OS_CODE 0x0b +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0f +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) +# if defined(_WIN32_WCE) +# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef _PTRDIFF_T_DEFINED + typedef int ptrdiff_t; +# define _PTRDIFF_T_DEFINED +# endif +# else +# define fdopen(fd,type) _fdopen(fd,type) +# endif +#endif + + /* common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS + /* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 + /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# define vsnprintf _vsnprintf +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +#endif +#ifdef VMS +# define NO_vsnprintf +#endif + +#if defined(pyr) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + extern void zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + extern int zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + extern void zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#ifdef DEBUG +# include + extern int z_verbose; + extern void z_error OF((char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + + +voidpf zcalloc OF((voidpf opaque, unsigned items, unsigned size)); +void zcfree OF((voidpf opaque, voidpf ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +#endif /* ZUTIL_H */ -- cgit

    Christopher +W. Fraser and David R. Hanson, Microsoft Research